sbool SimpleCRUD_searcher = true; sclass SimpleCRUD { Concepts concepts; Class cc; JTable table; JPanel buttons, panel; O renderer; // optional, func(A) -> Map O sorter; // optional, func(Cl) -> Cl S hID = "ID"; Set unshownFields; // not shown in table or form Set excludeFieldsFromEditing; S modifiedField; // field to hold last-modified timestamp TableSearcher tableSearcher; Set multiLineFields; // string fields that should be shown as text areas Set dontDuplicateFields; bool latestFirst; int formFixer = 12; // stupid value to make submit button appear bool editOnDoubleClick = true; bool ensureIndexed; bool showValuesAsStruct; // BREAKING CHANGE - used to be true *(Class cc) { this(db_mainConcepts(), cc); } *(Concepts *concepts, Class *cc) {} SimpleCRUD show(S frameTitle) { make(); showFrame(frameTitle, panel); this; } SimpleCRUD show() { ret show(plural(shortClassName(cc))); } SimpleCRUD showMaximized() { show(); maximizeFrame(panel); ret this; } JPanel makePanel() { ret make(); } JPanel make() { db(); framesBot(); ret make_dontStartBots(); } JPanel make_dontStartBots() { printVars("SimpleCRUD.make_dontStartBots", +concepts, +cc, count := countConcepts(cc)); if (ensureIndexed) indexConceptClass(concepts, cc); // next line not in swing part to allow passing arguments // from outside like showConceptsTable_afterUpdate temp tempSetTL(showConceptsTable_concepts, concepts); temp tempSetTL(showConceptsTable_latestFirst, latestFirst); temp tempSetTL(showConceptsTable_sorter, sorter); temp tempSetTL(showConceptsTable_dropFields, asList(unshownFields)); temp tempSetTL(dataToTable_useStruct, showValuesAsStruct); table = makeConceptsTable(cc, wrapRenderer(renderer)); swing { buttons = jRightAlignedLine( jbutton("Add...", r { newConcept() }), tableDependButton(table, jbutton("Edit", r { editConcept(selectedConcept()) })), tableDependButton(table, jbutton("Delete", r { final L l = selectedConcepts(); thread "Delete concepts" { withDBLock(concepts, r { for (A c : l) c.delete() }); } })), tableDependButton(table, jbutton("Duplicate...", r { duplicateConcept(selectedConcept()) }))); if (SimpleCRUD_searcher) { tableSearcher = tableWithSearcher2(table, withMargin := true); panel = centerAndSouthWithMargin(tableSearcher.panel, withBottomMargin(buttons)); } else panel = centerAndSouthWithMargin(table, withBottomMargin(buttons)); O fEdit = voidfunc(int row) { editConcept(conceptForRow(row)) }; tablePopupMenuItem(table, "Edit...", fEdit); if (editOnDoubleClick) onDoubleClick(table, fEdit); } ret panel; } O wrapRenderer(fO renderer) { ret renderer == null ? null : func(A a) { putAll(litorderedmap(hID, str(a.id)), (Map) callF(renderer, a)) }; } void newConcept { duplicateConcept(null); } void duplicateConcept(A oldConcept) { final A c = unlisted(cc); ccopyFieldsExcept(oldConcept, c, dontDuplicateFields); final Map map = makeComponents(c); Runnable r = r { concepts.register(c); saveData(c, map); }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); showFormTitled2("New " + shortClassName(cc), arrayPlus(mapToObjectArray(map), r)); } void editConcept(final A c) { if (c == null) ret; final Map map = makeComponents(c); Runnable r = r { saveData(c, map) }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); showFormTitled2("Edit " + shortClassName(cc) + " #" + c.id, arrayPlus(mapToObjectArray(map), r)); } A selected aka selectedConcept() { ret conceptForRow(selectedRow(table)); } A conceptForRow(int row) { ret (A) concepts.getConcept(toLong(getTableCell(table, row, 0))); } int indexOfConcept(final A c) { if (c == null) ret -1; ret swing(func -> int { int n = tableRowCount(table); for row to n: if (toLong(getTableCell(table, row, 0)) == c.id) ret row; ret -1; }); } L selectedConcepts() { ret swing(() -> { int[] rows = table?.getSelectedRows(); new L l; fOr (int row : rows) l.add(conceptForRow(row)); ret l; }); } Map makeComponents(A c) { Map map = litorderedmap(); makeComponents(c, map); ret map; } JComponent fieldComponent(A c, S field) { Class type = getFieldType(cc, field); O value = getOpt(c, field); //print("Field type: " + field + " => " + type); if (type == bool.class) ret jCenteredCheckBox(isTrue(value)); else if (contains(multiLineFields, field) || containsNewLines(optCast S(value))) ret typeWriterTextArea((S) value); else if (isSubtype(type, Concept)) ret jcomboboxFromConcepts_str(concepts, type, (Concept) value); ifclass SecretValue else if (type == SecretValue.class) ret jpassword(strOrEmpty(getVar(value/SecretValue))); endif else try { ret autoComboBox(valueToString(value), new TreeSet(map valueToString(collect(list(concepts, cc), field)))); } catch e { printException(e); ret jTextField(valueToString(value)); } } void saveComponent(A c, S field, JComponent comp) { comp = unwrap(comp); Class type = fieldType(c, field); ifclass SecretValue if (type == SecretValue.class) { S text = getTextTrim((JPasswordField) comp); cset(c, field, empty(text) ? null : SecretValue(text)); } else endif if (comp instanceof JTextComponent) cset(c, field, convertToField(trimIf(!comp instanceof JTextArea, getText((JTextComponent) comp)), cc, field)); else if (comp cast JComboBox) { S text = getTextTrim(comp); if (isSubtype(type, Concept)) cset(c, field, getConcept(concepts, parseFirstLong(text))); else cset(c, field, convertToField(text, cc, field)); } else if (comp instanceof JCheckBox) cset(c, field, isChecked((JCheckBox) comp)); ifclass ImageChooser else if (comp instanceof ImageChooser) cUpdatePNGFile(c, field, comp/ImageChooser.getImage(), false); endif } L fields() { if (excludeFieldsFromEditing != null && modifiedField != null) excludeFieldsFromEditing.add(modifiedField); ret listWithoutSet([S field : conceptFieldsInOrder(cc) | fieldType(cc, field) != Concept.Ref.class], joinSets(excludeFieldsFromEditing, unshownFields)); } void excludeFieldsFromEditing(S... fields) { excludeFieldsFromEditing = setPlus(excludeFieldsFromEditing, fields); } // override the following two methods to customize edit window void makeComponents(A c, Map map) { for (S field : fields()) map.put(field, fieldComponent(c, field)); } void saveData(A c, Map components) { for (S field : keys(components)) saveComponent(c, field, components.get(field)); if (modifiedField != null) cset(c, modifiedField, now()); } JTable table() { ret table; } swappable S valueToString(O o) { ret showValuesAsStruct ? structureOrText_crud(o) : strOrNull(o); } } // end of SimpleCRUD