sclass HCRUD_Concepts extends HCRUD_Data { Concepts cc = db_mainConcepts(); Class cClass; new L> onCreateOrUpdate; MapSO filters; // fields to filter by/add to new objects ValueConverterForField valueConverter; bool referencesBlockDeletion; bool trimAllSingleLineValues; *(Class *cClass) {} // XXX - breaking change from just shortName() swappable S itemName() { ret humanizeShortName(cClass); } //LS fields() { ret conceptFields(cClass); } L itemsForListing() { ret defaultSort(asList(listConcepts())); } @Override L 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 list(IntRange range) { ret lambdaMap itemToMapForList(subListOrFull(itemsForListing(), range)); } Cl listConcepts() { ret conceptsWhere(cc, cClass, mapToParams(filters)); } L defaultSort(L l) { ret l; } MapSO emptyObject() { A c = unlisted(cClass); print("fieldOrder", getFieldOrder(c)); ret printStruct("emptyObject", itemToMap(c)); } 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 values; new Matches m; for (S key, O value : cloneMap(map)) if (startsWith(key, f.getName() + "_", m) && isInteger(m.rest())) { long conceptID = parseFirstLong((S) value); Concept concept = getConcept(cc, conceptID); printVars_str("RefL magic", +key, +conceptID, +concept); mapPut(values, parseInt(m.rest()), concept); map.remove(key); } map.put(f.getName(), valuesAsList(values)); } // Concepts.Ref magic (look up concept) for (Field f : nonStaticNonTransientFieldObjectsOfType(Concept.Ref.class, cClass)) { O value = map.get(f.getName()); if (value cast S) { long conceptID = parseFirstLong(value); Concept concept = getConcept(cc, conceptID); map.put(f.getName(), concept); } } if (trimAllSingleLineValues) for (Map.Entry e : map.entrySet()) { S val = optCastString(e.getValue()); if (val != null && isSingleLine(val) && isUntrimmed(val)) e.setValue(trim(val)); } } MapSO getObject(O id) { ret itemToMap(conceptForID(id)); } O createObject(SS map) { A c = cnew(cc, cClass); // make sure filters override setValues(c, mapMinusKeys(map, filteredFields())); cset(c, mapToParams(filters)); pcallFAll(onCreateOrUpdate, c); ret c.id; } void setValues(A c, SS map) { MapSO map2 = (Map) cloneMap(map); massageItemMapForUpdate(c, map2); if (valueConverter == null) cSmartSet(c, mapToParams(map2)); else cSmartSet_withConverter(valueConverter, c, mapToParams(map2)); } A conceptForID(O id) { ret getConcept(cc, cClass, toLong(id)); } S updateObject(O id, SS map) { A c = conceptForID(id); if (c == null) ret "Object " + id + " not found"; try answer checkFilters(c); setValues(c, map); pcallFAll(onCreateOrUpdate, c); ret "Object " + id + " updated"; } S deleteObject(O id) { A c = conceptForID(id); if (c == null) ret "Object " + id + " not found"; try answer checkFilters(c); deleteConcept(c); ret "Object " + id + " deleted"; } S checkFilters(A c) { ret checkConceptFields(c, mapToParams(filters)) ? "" : "Object " + c.id + " not in view"; } HCRUD_Concepts addFilters(MapSO map) { fOr (S field, O value : map) addFilter(field, value); this; } HCRUD_Concepts addFilter(S field, O value) { filters = orderedMapPutOrCreate(filters, field, value); this; } Renderer getRenderer(S field) { Class type = fieldType(cClass, field); 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 c = getTypeArgumentAsClass(genericFieldType(cClass, field)); ret makeConceptsComboBox(comboBoxItemsForConceptClass(c)); } // show a RefL<> field as a list of combo boxes if (eq(type, Concept.RefL.class)) { Class c = getTypeArgumentAsClass(genericFieldType(cClass, field)); LS items = comboBoxItemsForConceptClass(c); ret new FlexibleLengthList(makeConceptsComboBox(items)); } ret super.getRenderer(field); } ComboBox makeConceptsComboBox(LS entries) { ComboBox cb = new(entries); cb.valueToEntry = value -> { value = deref(value); print("ComboBox: value type=" + _getClass(value); if (value cast Concept) { long id = conceptID(value); S entry = firstWhereFirstLongIs(entries, id); print("combobox selected: " + id + " / " + entry); ret entry; } null; }; ret cb; } LS comboBoxItemsForConceptClass(Class c) { ret itemPlus("", map(cc.list(c), val -> shorten(val.id + ": " + val))); } bool objectCanBeDeleted(O id) { ret !referencesBlockDeletion || !hasBackRefs(conceptForID(id)); } Set filteredFields() { ret keys(filters); } }