// assumes there is a S name field in the concept // not serializable sclass NameBasedVoiceCRUD { transient Concepts cc = db_mainConcepts(); Class conceptClass; S concept, concepts; // singular & plural of what we are managing // e.g. +scenario. must be non-null // string fields in the scope are treated case-insensitive O[] scope = new O[0]; L onSelect; transient A selected; *() {} *(Class conceptClass, S conceptName, O... scope) { this(db_mainConcepts(), conceptClass, conceptName, scope); } *(Concepts *cc, Class *conceptClass, S conceptName, O... scope) { concept = conceptName; concepts = plural(conceptName); this.scope = unnull(scope); } S answer(S s) null { new Matches m; if (match("how many \*concepts*/", s)) ret str(countConceptsCI(cc, conceptClass, unnull(scope))); if (match("delete \*concept*/ *", s, m)) { S name = $1; select(null); A sc = findConceptWithName(name); if (sc == null) ret format("\*firstToUpper(concept)*/ * not found", name); unregisterConcept(sc); addUndo(new UndoDeleteConcept(sc)); ret format("Scenario * deleted", getName(sc)); } if (match("new \*concept*/ *", s, m)) { S name = $1; A sc = conceptWhereCI(cc, conceptClass, paramsPlus(scope, +name)); if (sc != null) ret format("\*firstToUpper(concept)*/ * exists", name); A a = uniqCI(cc, conceptClass, paramsPlus(scope, +name)); select(a); addUndo(new UndoCreateConcept(a.id)); ret format("\*firstToUpper(concept)*/ * created", name); } if (match("list \*concepts*/", s)) ret or2(joinWithComma(collect name(conceptsWhereCI(cc, conceptClass, unnull(scope)))), "No \*concepts*/ defined"); if (match("select \*concept*/ *", s, m)) { A sc = findConceptWithName($1); if (sc == null) ret format("\*firstToUpper(concept)*/ * not found", $1); select(sc); ret format("\*firstToUpper(concept)*/ * selected", getName(sc)); } if (match("unselect \*concept*/", s)) { if (selected == null) ret "OK"; select(null); ret "OK, scenario unselected"; } if (match("rename \*concept*/ * to *", s, m)) { S name1 = $1, name2 = $2; if (!eqic(name1, name2) && conceptWhereCI(cc, conceptClass, paramsPlus(scope, name := name2)) != null) ret format("A \*concept*/ named * exists", name2); A sc = findConceptWithName(name1); if (sc == null) ret format("\*firstToUpper(concept)*/ * not found", name1); addUndo(new UndoSetConceptField(sc.id, 'name, name1, name2)); cset(sc, name := name2); select(sc); ret format("\*firstToUpper(concept)*/ * renamed to *", name1, name2); } } // this is more of a search function, not an exact name lookup swappable A findConceptWithName(S name) { Cl l = conceptsWhereCI(cc, conceptClass, scope); A _a = objectWhereIC(l, +name); if (_a != null) ret _a; ScoredSearcher searcher = new(name); for (A a : l) searcher.put(a, getName(a)); ret searcher.best(); } swappable S getName(A a) { ret getString name(a); } swappable void addUndo(UndoableWithAnswer undo) {} swappable void select(A a) { selected = a; pcallFAll(onSelect, a); } void onSelect(IVF1 f) { onSelect = listCreateAndAdd(onSelect, f); } }