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 extends A> oldClass = _getClass(ew.item);
Class extends A> 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