sclass SimpleCRUD_v2 extends JConceptsTable { JPanel buttons, panel; 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; int formFixer = 12; // stupid value to make submit button appear bool showBackRefs; int maxRefsToShow = 3; bool showClassNameSelectors; bool allowNewFields; int newFieldsToShow = 3; bool emptyStringsToNull; // convert all empty string values to nulls (also means dropping the field if it's dynamic) int formLabelsWidth = 100; // just another bug fixer settable bool showSearchBar = true; settable bool showAddButton = true; settable bool showEditButton = true; settable bool showDeleteButton = true; settable bool showDuplicateButton = true; settable bool iconButtons; settable bool editWindowScrollable; settable bool scrollableButtons; // if you have a lot of them // put a component in here to fill the space left of the buttons SingleComponentPanel scpButtonFiller; JComponent wrappedButtonBar; // After user has submitted edit/create window event conceptSaved(A concept); *(Class conceptClass) { super(conceptClass); } *(Concepts concepts, Class conceptClass) { super(concepts, conceptClass); } SimpleCRUD_v2 show(S frameTitle) { make(); showFrame(frameTitle, panel); this; } SimpleCRUD_v2 show() { ret show(entityNamePlural()); } SimpleCRUD_v2 showMaximized() { show(); maximizeFrame(panel); ret this; } JPanel makePanel() { ret make(); } JPanel make() { db(); framesBot(); ret make_dontStartBots(); } swappable MapSO itemToMap_inner(A a) { ret super.itemToMap_base(a); } MapSO itemToMap_base(A a) { MapSO map = itemToMap_inner(a); if (map == null) null; ret putAll(map, moreSpecialFieldsForItem(a)); } // shown on the right (usually) swappable MapSO moreSpecialFieldsForItem(A a) { MapSO map = litorderedmap(); if (showBackRefs) { Cl refs = allBackRefs(a); if (nempty(refs)) { refs = sortedByConceptID(refs); int more = l(refs)-maxRefsToShow; map.put("Referenced by", joinWithComma(takeFirst(maxRefsToShow, refs)) + (more > 0 ? ", " + more + " more" : "")); } } ret map; } public JPanel make_dontStartBots() { ret (JPanel) visualize(); } cachedVisualize { dropFields = asList(unshownFields); makeTable(); swing { JButton deleteButton = !showDeleteButton ? null : tableDependButton(table, makeDeleteButton(r { L l = selectedConcepts(); withDBLock(concepts, r { for (A c : l) c.delete() }); })); buttons = jRightAlignedLine(flattenToList( !showAddButton ? null : makeAddButton(r newConcept), !showEditButton || !showDuplicateButton ? null : tableDependButton(table, makeDuplicateButton(r { duplicateConcept(selectedConcept()) })), !showEditButton ? null : tableDependButton(table, makeEditButton(r { editConcept(selectedConcept()) })), deleteButton, )); wrappedButtonBar = wrapButtonBar(centerAndEastWithMargin(scpButtonFiller = singleComponentPanel(), buttons)); if (showSearchBar) { tableSearcher = tableWithSearcher2(table, withMargin := true); panel = centerAndSouthWithMargin(tableSearcher.panel, wrappedButtonBar); } else panel = centerAndSouthWithMargin(table, wrappedButtonBar); var fEdit = voidfunc(int row) { editConcept(getItem(row)) }; tablePopupMenuItem(table, "Edit", fEdit); onDoubleClick(table, fEdit); tablePopupMenuFirst(table, (menu, row) -> { Concept c = getItem(row); if (c != null) addMenuItem(menu, "Delete " + quote(shorten(str(c))), rThread { deleteConcept(c); }); }); } // end of swing ret panel; } swappable JComponent wrapButtonBar(JComponent buttonBar) { if (scrollableButtons) ret jBorderlessHigherHorizontalScrollPane(vstack(buttonBar)); ret buttonBar; } swappable void newConcept() { duplicateConcept(null); } swappable A createDuplicate(A c) { A clone = (A) unlisted(_getClass(c)); ccopyFieldsExcept(c, clone, dontDuplicateFields); ret clone; } void duplicateConcept(A oldConcept) { new EditWindow ew; if (oldConcept == null) ew.item = unlisted(conceptClass); else ew.item = createDuplicate(oldConcept); csetAll(ew.item, filters); makeComponents(ew); // save action F0 r = func -> bool { try { selectAfterUpdate(ew.item); concepts.register(ew.item); saveData(ew); } catch print e { infoBox(e); false; } true; }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); S title = "New " + entityName(); title += appendBracketed(javaClassDescForItem(ew.item)); renameSubmitButton("Create", showAForm(title, toObjectArray(listPlus(ew.matrix, r)))); } void editConcept(A c) { if (c == null) ret; new EditWindow ew; ew.item = c; makeComponents(ew); F0 r = func -> bool { try { // concept class was changed, replace object if (ew.item != c) { print("Replacing object: " + c + " => " + ew.item); replaceConceptAndUpdateRefs(c, ew.item); } saveData(ew); } catch print e { infoBox(e); false; } true; }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); renameSubmitButton("Save", showAForm("Edit " + entityName() + " #" + c.id, toObjectArray(listPlus(ew.matrix, r)))); } JComponent fieldComponent(A c, S field) { Class type = getFieldType(c.getClass(), field); Type genericType = genericFieldType(conceptClass, field); if (type == Concept.Ref.class) type = getTypeArgumentAsClass(genericType); O value = cget(c, field); if (type == null) type = _getClass(value); //print("Field type: " + field + " => " + type); if (type == bool.class) ret jCenteredCheckBox(isTrue(value)); else if (contains(multiLineFields, field) || containsNewLines(optCast S(value))) ret makeTextArea((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 if (isUneditableFieldType(type)) ret jlabel(structureOrText_crud(value)); else try { ret autoComboBox(structureOrText_crud(value), new TreeSet(map structureOrText_crud(collect(list(concepts, conceptClass), field)))); } catch e { printException(e); ret jTextField(structureOrText_crud(value)); } } void saveComponent(A c, S field, JComponent comp) { comp = unwrap(comp); ifdef SimpleCRUD_v2_debug printVars_str("saveComponent", +field, +comp); endifdef Class type = fieldType(c, field); Type genericType = genericFieldType(conceptClass, field); if (type == Concept.Ref.class) type = getTypeArgumentAsClass(genericType); 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) { S text = trimIf(!comp instanceof JTextArea, getText((JTextComponent) comp)); O value = postProcessValue(text); O converted = convertToField(value, c.getClass(), field); ifdef SimpleCRUD_v2_debug printVars_str("saveComponent", +field, +text, +value, +converted); endifdef cset(c, field, converted); } else if (comp cast JComboBox) { S text = getTextTrim(comp); if (isSubtype(type, Concept)) cset(c, field, getConcept(concepts, parseFirstLong(text))); else { O value = postProcessValue(text); cset(c, field, convertToField(value, c.getClass(), 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 } swappable Cl editableFieldsForItem(A c) { if (excludeFieldsFromEditing != null && modifiedField != null) excludeFieldsFromEditing.add(modifiedField); ret listWithoutSet( conceptFieldsInOrder(c), joinSets(excludeFieldsFromEditing, unshownFields, keys(filters))); } void excludeFieldsFromEditing(S... fields) { excludeFieldsFromEditing = setPlus(excludeFieldsFromEditing, fields); } Cl> possibleClasses() { ret (Cl) moveItemFirst(conceptClass, dropTypeParameter(sortClassesByNameIC(myNonAbstractClassesImplementing(conceptClass)))); } JComboBox> classSelectorComponent(A c) { ret setComboBoxRenderer(jTypedComboBox(possibleClasses(), _getClass(c)), customToStringListCellRenderer shortClassName()); } class NewField { JTextField tfName = jtextfield(); JTextField tfValue = jtextfield(); JComboBox cbRef = jcomboboxFromConcepts_str(concepts, conceptClass); JComboBox cbType = jcombobox("String", "Reference"); SingleComponentPanel scpValue = singleComponentPanel(); JPanel panel() { onChangeAndNow(cbType, r updateSCP); ret jhgridWithSpacing( withToolTip("Name for new field", withLabel("Name", tfName)), withLabel("Value", westAndCenterWithMargin(cbType, scpValue))); } S typeStr() { ret getText(cbType); } void updateSCP { scpValue.setComponent(eqic(typeStr(), "Reference") ? cbRef : withToolTip("Contents of new field", tfValue)); } S field() { ret gtt(tfName); } O value() { ret eqic(typeStr(), "Reference") ? getConcept(concepts, parseFirstLong(getText(cbRef))) : gtt(tfValue); } } class EditWindow { A item; Map componentsByField = litorderedmap(); new L matrix; // label, component, label, component, ... JComboBox> classSelector; new L newFields; } // override the following two methods to customize edit window void makeComponents(EditWindow ew) { // class selector if (showClassNameSelectors) { addAll(ew.matrix, makeLabel("Java Class"), ew.classSelector = classSelectorComponent(ew.item)); onChange(ew.classSelector, r { Class oldClass = _getClass(ew.item); Class newClass = getSelectedItem_typed(ew.classSelector); if (oldClass == newClass) ret; A oldItem = ew.item; ew.item = unlisted(newClass); ccopyFields(oldItem, ew.item); }); } // regular fields for (S field : editableFieldsForItem(ew.item)) { JComponent c = fieldComponent(ew.item, field); ew.componentsByField.put(field, c); addAll(ew.matrix, makeLabel(field), c); } // new fields if (allowNewFields && newFieldsToShow > 0) { addAll(ew.matrix, " ", jlabel()); // spacing for i to newFieldsToShow: { new NewField nf; ew.newFields.add(nf); addAll(ew.matrix, makeLabel(""/*"New field"*/), nf.panel()); } } } void saveData(EditWindow ew) { // save regular fields for (S field, JComponent component : ew.componentsByField) if (isIdentifier(field)) saveComponent(ew.item, field, component); // save new fields for (NewField nf : ew.newFields) { S field = nf.field(); O value = nf.value(); if (nempty(field) && notNullOrEmptyString(value)) cset(ew.item, field, value); } if (modifiedField != null) cset(ew.item, modifiedField, now()); conceptSaved(ew.item); } O postProcessValue(O o) { if (emptyStringsToNull && eq(o, "")) null; ret o; } // labels on left hand side of form JComponent makeLabel(S label) { ret jMinWidthAtLeast(formLabelsWidth, jlabel(label)); } swappable JComponent showAForm(S title, O... parts) { JForm form = JForm(parts).disposeFrameOnSubmit(); var panel = form.visualize(); showFrame(title, editWindowScrollable ? jscroll_vertical(panel) : panel); ret panel; } bool isUneditableFieldType(Class type) { ret isSubclassOfAny(type, Map, Cl, Pair); } void hideField(S field) { hideFields(field); } void hideFields(S... fields) { unshownFields = createOrAddToSet(unshownFields, fields); } selfType entityName(S name) { entityName = if0_const(name); this; } swappable S entityName() { ret shortClassName(conceptClass); } swappable S entityNamePlural() { ret plural(entityName()); } swappable S entityNameLower() { ret firstToLower(entityName()); } swappable S entityNamePluralLower() { ret firstToLower(entityNamePlural()); } JPanel buttons() { visualize(); ret buttons; } void addButtonInFront(S text, Runnable r) { addButtonInFront(jbutton(text, r)); } void addButton(S text, Runnable r) { addButton(jbutton(text, r)); } void addButton(JComponent c) { buttons().add(c); } void addButtonInFront(JComponent c) { addComponentInFront(buttons(), c); } JButton makeAddButton(Runnable r) { ret makeButton( "Add...", "Create a new " + entityNameLower(), #1103069, r); } JButton makeDeleteButton(Runnable r) { ret makeButton( "Delete", "Delete selected " + entityNamePluralLower(), #1103067, r); } JButton makeEditButton(Runnable r) { ret makeButton( "Edit...", "Edit selected " + entityNameLower(), #1103068, r); } JButton makeDuplicateButton(Runnable r) { ret makeButton( "Duplicate...", "Duplicate selected " + entityNameLower(), #1103070, r); } JButton makeButton(S text, S toolTip default null, S iconID, Runnable r) { if (iconButtons) ret toolTip(or2(toolTip, text), jimageButtonScaledToWidth(16, iconID, text, r)); else ret toolTip(toolTip, jbutton(text, r)); } void multiLineField(S field) { multiLineFields = addOrCreate(multiLineFields, field); } swappable JComponent makeTextArea(S text) { ret typeWriterTextArea(text); } void setButtonFiller(JComponent c) { scpButtonFiller.set(c); } TableSearcher tableSearcher() { visualize(); ret tableSearcher; } } // end of SimpleCRUD_v2