Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

518
LINES

< > BotCompany Repo | #1026002 // HCRUD_Concepts

JavaX fragment (include) [tags: use-pretranspiled]

Libraryless. Click here for Pure Java version (21685L/133K).

sclass HCRUD_Concepts<A extends Concept> extends HCRUD_Data {
  Concepts cc = db_mainConcepts();
  Class<A> cClass;
  new L<IVF1<A>> onCreateOrUpdate;
  new L<IVF1<A>> onCreate;
  IVF2<A, MapSO> afterUpdate; // parameter 2: old values
  MapSO filters; // fields to filter by/add to new objects
  SS ciFilters; // case-insensitive filters
  IF1<Cl<A>> customFilter;
  ValueConverterForField valueConverter;
  bool referencesBlockDeletion;
  bool trimAllSingleLineValues;
  Set<S> fieldsToHideInCreationForm;
  bool lockDB; // lock DB while updating object
  bool verbose;
  bool dropEmptyListValues = true;
  bool lsMagic;
  bool convertConceptValuesToRefs;
  A currentConcept; // while editing
  
  bool useDynamicComboBoxes; // dynamically load concepts combo box entries for all fields
  IPred<S> useDynamicComboBoxesForField; // activate dynamic combo boxes for selected fields
  int dynamicComboBoxesThreshold = 1000; // if there are this many entries or more, use a dynamic combo box
  
  *(Class<A> *cClass) {}
  *(Concepts *cc, Class<A> *cClass) {}
  
  // XXX - breaking change from just shortName()
  swappable S itemName() { ret humanizeShortName(cClass); }
  swappable S itemNamePlural() { ret super.itemNamePlural(); }
  
  //LS fields() { ret conceptFields(cClass); }
  
  L<A> itemsForListing() {
    ret defaultSort(asList(listConcepts()));
  }
  
  @Override
  L<MapSO> list() {
    //ret lazyMap itemToMapForList(itemsForListing());
    ret lazyMap(itemsForListing(), a -> new Item(str(a.id)) {
      public MapSO calcFullMap() { ret itemToMapForList(a); }
    });
  }
  
  // more efficient version - convert items only after taking subList
  // TODO: this is never called by HCRUD; make lazy list instead?
  @Override
  L<MapSO> list(IntRange range) {
    ret lambdaMap itemToMapForList(subListOrFull(itemsForListing(), range));
  }
  
  Cl<A> listConcepts() {
    Cl<A> l = listConcepts_firstStep();
    ret postProcess(customFilter, l);
  }
    
  swappable Cl<A> listConcepts_firstStep() {
    if (empty(ciFilters))
      ret conceptsWhere(cc, cClass, mapToParams(filters));
    else if (empty(filters))
      ret conceptsWhereCI(cc, cClass, mapToParams(ciFilters));
    else {
      // TODO: choose best index
      Cl<A> l = conceptsWhere(cc, cClass, mapToParams(filters));
      ret filterConceptsIC(l, mapToParams(ciFilters));
    }
  }
  
  swappable Pair<S, Bool> defaultSortField() { ret pair("id", true); }
  
  swappable L<A> defaultSort(L<A> l) { ret sortedByConceptIDDesc(l); }
  
  swappable A emptyConcept() {
    ret unlisted(cClass);
  }
  
  swappable MapSO emptyObject() {
    A c = emptyConcept();
    
    // not actually necessary here, we do it on create
    /*cset(c, mapToParams(filters));
    cset(c, mapToParams(ciFilters));*/
    
    MapSO map = itemToMap(c);
    //printVars_str emptyObject(+cClass, +filters, +ciFilters, +map);
    
    ret mapMinusKeys(fieldsToHideInCreationForm, map);
  }
  
  MapSO itemToMap(A c) {
    if (c == null) null;
    ret putKeysFirst(getFieldOrder(c), conceptToMap_gen_withNullValues(c));
  }
  
  MapSO itemToMapForList(A c) {
    if (c == null) null;
    MapSO map = itemToMap(c);
    massageItemMapForList(c, map);
    ret map;
  }
  
  swappable void massageItemMapForList(A c, MapSO map) {
  }
  
  swappable void massageItemMapForUpdate(A c, MapSO map) {
    // Concepts.RefL magic
    
    Cl<Field> refLFields = nonStaticNonTransientFieldObjectsOfType(Concept.RefL.class, c);
    printVars_str(+refLFields, +c);
    for (Field f : refLFields) {
      new TreeMap<Int, O> values;
      new Matches m;
      for (S key, O value : cloneMap(map))
        if (startsWith(key, f.getName() + "_", m) && isInteger(m.rest())) {
          Concept concept = getConceptFromString((S) value);
          //printVars_str("RefL magic", +key, +conceptID, +concept);
          if (concept != null || !dropEmptyListValues)
            values.put(parseInt(m.rest()), concept);
          map.remove(key);
        }
        
      if (!dropEmptyListValues)
        while (nempty(values) && lastValue(values) == null) {
          if (verbose) print("Dropping value " + lastEntry(values));
          removeLastKey(values);
        }
        
      map.put(f.getName(), valuesAsList(values));
    }
    
    // process dynamic bool, concept fields
    
    for (S name : cloneKeys(map)) {
      S metaInfo = metaInfoFromForm(name);
      print("metaInfo for " + name + ": " + metaInfo);
      if (eqic(metaInfo, "concept"))
        replaceStringValueWithConcept(map, name);
      else if (eqic(metaInfo, "bool"))
        replaceStringValueWithBool(map, name);
    }
    
    // Concepts.Ref magic (look up concept)
    
    for (Field f : nonStaticNonTransientFieldObjectsOfType(Concept.Ref.class, c))
      replaceStringValueWithConcept(map, f.getName());

    // trim values
    
    if (trimAllSingleLineValues)
      for (Map.Entry<S, O> e : map.entrySet()) {
        S val = optCastString(e.getValue());
        if (val != null && isSingleLine(val) && isUntrimmed(val))
          e.setValue(trim(val));
      }

    // LS magic
    
    if (lsMagic)
     for (Field f : nonStaticNonTransientFieldObjectsOfType(L.class, c)) {
      if (eqOneOf(f.getName(), "refs", "backRefs")) continue;
      new TreeMap<Int, S> values;
      new Matches m;
      
      for (S key, O value : cloneMap(map)) {
        if (startsWith(key, f.getName() + "_", m) && isInteger(m.rest())) {
          if (!dropEmptyListValues || nempty((S) value)) {
            if (verbose) print("Adding value " + m.rest() + " / " + value);
            mapPut(values, parseInt(m.rest()), (S) value);
          }
          map.remove(key);
        }
      }
      
      if (!dropEmptyListValues)
        while (nempty(values) && empty(lastValue(values))) {
          if (verbose) print("Dropping value " + lastEntry(values));
          removeLastKey(values);
        }
        
      map.put(f.getName(), valuesAsList(values));
    }
    
    // don't set SecretValue fields
    
    for (Field f : nonStaticNonTransientFieldObjectsOfType(SecretValue, c))
      map.remove(f.getName());
  }
  
  void replaceStringValueWithConcept(MapSO map, S key) {
    O value = map.get(key);
    if (value cast S) {
      Concept concept = getConceptFromString(value);
      map.put(key, concept);
    }
  }
  
  void replaceStringValueWithBool(MapSO map, S key) {
    O value = map.get(key);
    if (value cast S) {
      map.put(key, englishStringToBool(value));
    }
  }
  
  swappable MapSO getObject(O id) {
    ret itemToMap(conceptForID(id));
  }
  
  MapSO getObjectForEdit(O id) {
    currentConcept = conceptForID(id);
    ret getObject(id);
  }
  
  O createObject(SS fullMap, S fieldPrefix) {
    rawFormValues = fullMap;
    try {
      SS map = extractFieldValues(fullMap, fieldPrefix);
      A c = cnew(cc, cClass);
      // make sure filters override
      setValues(c, mapMinusKeys(map, filteredFields()), true);
      cset(c, mapToParams(filters));
      cset(c, mapToParams(ciFilters));
      pcallFAll(onCreate, c);
      pcallFAll(onCreateOrUpdate, c);
      callOpt(c, "_onCreated"); // TODO: synchronize?
      ret c.id;
    } finally {
      rawFormValues = null;
    }
  }
  
  void setValues(A c, SS map, bool creating) {
    lock lockDB && !creating ? dbLock(cc) : null;
    MapSO map2 = (Map) cloneMap(map);
    massageItemMapForUpdate(c, map2);
    if (verbose) {
      print("setValues " + map);
      print("backRefs: " + c.backRefs);
    }
    MapSO oldValues = !creating && afterUpdate != null
      ? cgetAll_cloneLists(c, keys(map2)) : null;
    if (convertConceptValuesToRefs)
      convertAllConceptValuesToRefs(c, map2);
    if (valueConverter == null)
      cSmartSet(c, mapToParams(map2));
    else
      cSmartSet_withConverter_pcall(verbose, valueConverter, c, mapToParams(map2));
    if (oldValues != null)
      callF(afterUpdate, c, oldValues);
    if (verbose)
      print("backRefs: " + c.backRefs);
  }
  
  // for dynamic fields
  void convertAllConceptValuesToRefs(A c, MapSO map) {
    for (S key, O value : cloneMap(map)) {
      if (value cast Concept)
        if (!hasField(c, key)) {
          print("Converting value to ref: " + key);
          map.put(key, c.new Ref(value));
        }
    }
  }
  
  A conceptForID(O id) {
    ret _getConcept(cc, cClass, toLong(id));
  }
  
  S updateObject(O id, SS fullMap, S fieldPrefix) {
    rawFormValues = fullMap;
    try {
      SS map = extractFieldValues(fullMap, fieldPrefix);
      A c = conceptForID(id);
      if (c == null) ret "Object " + id + " not found";
      try answer checkFilters(c);
      setValues(c, map, false);
      pcallFAll(onCreateOrUpdate, c);
      ret "Object " + id + " updated";
    } finally {
      rawFormValues = null;
    }
  }
  
  S deleteObject(O id) {
    A c = conceptForID(id);
    if (c == null) ret "Object " + id + " not found";
    try answer checkFilters(c);
    actuallyDeleteConcept(c);
    ret "Object " + id + " deleted";
  }
  
  swappable void actuallyDeleteConcept(A c) {
    deleteConcept(c);
  }
  
  S checkFilters(A c) {
    if (!checkConceptFields(c, mapToParams(filters))
     || !checkConceptFieldsIC(c, mapToParams(ciFilters)))
      ret "Object " + c.id + " not in view";
    ret "";
  }
  
  HCRUD_Concepts<A> addFilters(MapSO map) {
    fOr (S field, O value : map)
      addFilter(field, value);
    this;
  }
  
  HCRUD_Concepts<A> addFilter(S field, O value) {
    filters = orderedMapPutOrCreate(filters, field, value);
    this;
  }
  
  // TODO: do this the other way around (check for editable types)
  swappable bool isEditableValue(O value) {
    //if (value instanceof Concept.RefL) true; // subsumed in next line
    if (value instanceof L) true;
    if (value instanceof Cl) false;
    true;
  }
  
  Renderer getRenderer(S field) {
    if (!isEditableValue(currentValue))
      ret new NotEditable;
      
    Class type = fieldType(or(currentConcept, cClass), field);
    S metaInfo = metaInfoFromForm(field);
    //printVars_str("getRenderer", +cClass, +field, +type, +rawFormValues);
    
    if (eq(type, bool.class))
      ret new CheckBox;
      
    if (eq(type, Bool.class))
      ret new ComboBox(ll("", "yes", "no"),
        b -> trueFalseNull((Bool) b, "yes", "no", ""));
    
    // show a Ref<> field as a combo box
    
    if (isSubtypeOf(type, Concept.Ref.class)) {
      Class<? extends Concept> c = fieldTypeArg(field);
      AbstractComboBox cb = makeConceptsComboBox(field, c);
      cb.metaInfo = "concept";
      ret cb;
    }
    
    // show dynamic field with concept/ref value as combo box
    
    O val = deref(currentValue);
    if (val cast Concept) {
      Class<? extends Concept> c = val.getClass();
      AbstractComboBox cb = makeConceptsComboBox(field, c);
      cb.metaInfo = "concept";
      ret cb;
    }
    
    // show dynamic ref field from URL parameters
    
    if (eqic(metaInfo, "concept")) {
      printVars_str("metaInfo value", +field, +val);
      AbstractComboBox cb = makeConceptsComboBox(field, Concept); // TODO
      cb.metaInfo = "concept";
      ret cb;
    }
    
    // show a RefL<> field as a list of combo boxes
    
    if (eq(type, Concept.RefL.class)) {
      Class<? extends Concept> c = fieldTypeArg(field);
      ret new FlexibleLengthList(makeConceptsComboBox(field, c));
    }
    
    if (eq(type, L.class)) {
      //Class c = fieldTypeArg(field);
      ret new FlexibleLengthList(new TextField(80));
    }
    
    if (val cast Bool)
      ret new CheckBox;
    
    ret super.getRenderer(field);
  }
  
  DynamicComboBox makeDynamicComboBox(S field, Class<? extends Concept> c) {
    DynamicComboBox cb = new(field);
    cb.valueToEntry = value -> {
      value = deref(value);
      //print("ComboBox: value type=" + _getClass(value);
      long id = 0;
      if (value cast Concept) id = conceptID(value);
      else if (value instanceof S && isInteger((S) value)) id = parseLong(value);
      if (id != 0)
        ret comboBoxItem(_getConcept(cc, id));
      null;
    };
    ret cb;
  }

  AbstractComboBox makeConceptsComboBox(S field, Class<? extends Concept> c) {
    if (c == null) fail("Null type for field \*field*/. currentConcept: " + currentConcept);

    if (useDynamicComboBoxes || useDynamicComboBoxesForField != null && useDynamicComboBoxesForField.get(field))
      ret makeDynamicComboBox(field, c);
    
    Cl concepts = listConceptClass(c);
    if (l(concepts) >= dynamicComboBoxesThreshold)
      ret makeDynamicComboBox(field, c);
    LS entries = comboBoxItems(concepts);

    ComboBox cb = new(entries);
    cb.valueToEntry = value -> {
      value = deref(value);
      //print("ComboBox: value type=" + _getClass(value);
      long id = 0;
      if (value cast Concept) id = conceptID(value);
      else if (value instanceof S && isInteger((S) value)) id = parseLong(value);
      if (id != 0) {
        S entry = firstWhereFirstLongIs(entries, id);
        //print("combobox selected: " + id + " / " + entry);
        ret entry;
      }
      null;
    };
    ret cb;
  }
  
  // TODO: filters?
  <A extends Concept> Cl<A> listConceptClass(Class<A> c) {
    ret cc.list(c);
  }
  
  LS comboBoxItemsForConceptClass(Class<? extends Concept> c) {
    ret comboBoxItems(listConceptClass(c));
  }
  
  LS comboBoxItems(Cl<? extends Concept> l) {
    ret comboBoxItems_static(l);
  }
  
  static LS comboBoxItems_static(Cl<? extends Concept> l) {
    ret itemPlus("", lmap comboBoxItem_static(l));
  }
  
  S comboBoxItem(Concept val) {
    ret comboBoxItem_static(val);
  }
  
  sS comboBoxItem_static(Concept val) {
    ret val == null ? null : shorten(val.id + ": " + val);
  }
  
  swappable bool objectCanBeDeleted(O id) {
    ret !referencesBlockDeletion || !hasBackRefs(conceptForID(id));
  }
  
  Set<S> filteredFields() { ret joinSets(keys(filters), keys(ciFilters)); }
  
  Class<? extends Concept> fieldTypeArg(S field) {
    ret getTypeArgumentAsClass(genericFieldType(or(currentConcept, cClass), field));
  }
  
  swappable Class<? extends Concept> conceptClassForComboBoxSearch(S info, S query) {
    // info is the field name (Ref or RefL). get concept class
    if (!isIdentifier(info)) ret cClass;
    
    S field = info;
    Class<? extends Concept> c = fieldTypeArg(field);

    // simple hack to show list for dynamic/new fields
    // assuming it's the same as the main class
    // clients can improve on this
    if (c == null) c = cClass;
    
    ret c;
  }
  
  swappable LS comboBoxSearchBaseItems(S info, S query) {
    var c = conceptClassForComboBoxSearch(info, query);
    if (c == null) ret emptyList();
    ret comboBoxItemsForConceptClass(c);
  }
  
  LS comboBoxSearch(S info, S query) {
    LS items = comboBoxSearchBaseItems(info, query);
    ret takeFirst(10, scoredSearch(query, items));
  }
  
  // to find the concept e.g. within massageFormMatrix
  A conceptForMap(MapSO map) {
    if (map == null) null;
    long id = toLong(map.get(idField()));
    ret id == 0 ? null : (A) _getConcept(cc, id);
  }
  
  A getConcept(MapSO map) {
    ret conceptForMap(map);
  }
  
  SS extractFieldValues(SS fullMap, S fieldPrefix) {
    ret subMapStartingWith_dropPrefix(fullMap, fieldPrefix);
  }
  
  Concept getConceptFromString(S s) {
    long conceptID = parseFirstLong(s);
    ret _getConcept(cc, conceptID);
  }
  
  S metaInfoFromForm(S field) {
    ret mapGet(rawFormValues, "metaInfo_" + field);
  }
  
  void addCIFilter(S field, S value) {
    ciFilters = putOrCreate(ciFilters, field, value);
  }
  
  swappable S titleForObjectID(O id) {
    ret htmlEncode2(strOrNull(conceptForID(id)));
  }
}

download  show line numbers  debug dex  old transpilations   

Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv

No comments. add comment

Snippet ID: #1026002
Snippet name: HCRUD_Concepts
Eternal ID of this version: #1026002/201
Text MD5: 54c548eee75fe00ee9b2422e9ba511c3
Transpilation MD5: c29b4ffe951e292ffb159455b14be011
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2021-11-15 23:08:51
Source code size: 16155 bytes / 518 lines
Pitched / IR pitched: No / No
Views / Downloads: 919 / 2136
Version history: 200 change(s)
Referenced in: #1026001 - HCRUD - CRUD in HTML with pluggable data handler
#1034167 - Standard Classes + Interfaces (LIVE, continuation of #1003674)