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

445
LINES

< > BotCompany Repo | #1026002 - HCRUD_Concepts

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

Libraryless. Click here for Pure Java version (17108L/110K).

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;
  
  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 lambdaMap itemToMapForList(itemsForListing());
  }
  
  // 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));
    }
  }
  
  L<A> defaultSort(L<A> l) { ret l; }
  
  swappable MapSO emptyObject() {
    A c = unlisted(cClass);
    
    // 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
    
    for (Field f : nonStaticNonTransientFieldObjectsOfType(Concept.RefL.class, cClass)) {
      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, cClass))
      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, cClass)) {
      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, cClass))
      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));
  }
  
  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);
      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(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) {
    ret checkConceptFields(c, mapToParams(filters)) ? "" : "Object " + c.id + " not in view";
  }
  
  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;
  }
  
  Renderer getRenderer(S field) {
    Class type = fieldType(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 (eq(type, Concept.Ref.class)) {
      Class<? extends Concept> c = getTypeArgumentAsClass(genericFieldType(cClass, field));
      ret makeConceptsComboBox(field, c);
    }
    
    // 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 = getTypeArgumentAsClass(genericFieldType(cClass, field));
      ret new FlexibleLengthList(makeConceptsComboBox(field, c));
    }
    
    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 (useDynamicComboBoxes || useDynamicComboBoxesForField != null && useDynamicComboBoxesForField.get(field))
      ret makeDynamicComboBox(field, c);
    
    LS entries = comboBoxItemsForConceptClass(c);
    if (l(entries) >= dynamicComboBoxesThreshold)
      ret makeDynamicComboBox(field, c);

    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!
  LS comboBoxItemsForConceptClass(Class<? extends Concept> c) {
    ret comboBoxItems(cc.list(c));
  }
  
  LS comboBoxItems(Cl<? extends Concept> l) {
    ret itemPlus("", lmap comboBoxItem(l));
  }
  
  S comboBoxItem(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)); }
  
  LS comboBoxSearch(S info, S query) {
    // info is the field name (Ref or RefL). get concept class
    S field = info;
    Class<? extends Concept> c = getTypeArgumentAsClass(genericFieldType(cClass, field));
    //if (c == null) null;
    
    // simple hack to show list for dynamic/new fields
    // assuming it's the same as the main class
    // could improve by providing better comboSearchInfo
    if (c == null) c = cClass;
    
    LS items = comboBoxItemsForConceptClass(c);
    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);
  }
}

download  show line numbers  debug dex  old transpilations   

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

No comments. add comment

Snippet ID: #1026002
Snippet name: HCRUD_Concepts
Eternal ID of this version: #1026002/160
Text MD5: 3d9e824ac0dfa056b072946e31d32c7e
Transpilation MD5: 5c3395f6a60d19782c03f442e10cb490
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2021-04-09 16:11:45
Source code size: 14043 bytes / 445 lines
Pitched / IR pitched: No / No
Views / Downloads: 531 / 1362
Version history: 159 change(s)
Referenced in: [show references]

Formerly at http://tinybrain.de/1026002 & http://1026002.tinybrain.de