Transpiled version (35238L) is out of date.
1 | sclass SimpleCRUD_v2<A extends Concept> extends JConceptsTable<A> { |
2 | JPanel buttons, panel; |
3 | Set<S> unshownFields; // not shown in table or form |
4 | Set<S> excludeFieldsFromEditing; |
5 | S modifiedField; // field to hold last-modified timestamp |
6 | TableSearcher tableSearcher; |
7 | Set<S> multiLineFields; // string fields that should be shown as text areas |
8 | Set<S> dontDuplicateFields; |
9 | int formFixer = 12; // stupid value to make submit button appear |
10 | bool showBackRefs; |
11 | int maxRefsToShow = 3; |
12 | bool showClassNameSelectors; |
13 | bool allowNewFields; |
14 | int newFieldsToShow = 3; |
15 | bool emptyStringsToNull; // convert all empty string values to nulls (also means dropping the field if it's dynamic) |
16 | int formLabelsWidth = 100; // just another bug fixer |
17 | settable bool showSearchBar = true; |
18 | settable bool showAddButton = true; |
19 | settable bool showEditButton = true; |
20 | settable bool showDeleteButton = true; |
21 | settable bool showDuplicateButton = true; |
22 | settable bool iconButtons; |
23 | settable bool editWindowScrollable; |
24 | settable bool scrollableButtons; // if you have a lot of them |
25 | |
26 | // put a component in here to fill the space left of the buttons |
27 | SingleComponentPanel scpButtonFiller; |
28 | |
29 | JComponent wrappedButtonBar; |
30 | |
31 | // After user has submitted edit/create window |
32 | event conceptSaved(A concept); |
33 | |
34 | *(Class<A> conceptClass) { super(conceptClass); } |
35 | *(Concepts concepts, Class<A> conceptClass) { super(concepts, conceptClass); } |
36 | |
37 | SimpleCRUD_v2<A> show(S frameTitle) { |
38 | make(); |
39 | showFrame(frameTitle, panel); |
40 | this; |
41 | } |
42 | |
43 | SimpleCRUD_v2<A> show() { |
44 | ret show(entityNamePlural()); |
45 | } |
46 | |
47 | SimpleCRUD_v2<A> showMaximized() { show(); maximizeFrame(panel); ret this; } |
48 | |
49 | JPanel makePanel() { ret make(); } |
50 | JPanel make() { |
51 | db(); |
52 | framesBot(); |
53 | ret make_dontStartBots(); |
54 | } |
55 | |
56 | swappable MapSO itemToMap_inner(A a) { |
57 | ret super.itemToMap_base(a); |
58 | } |
59 | |
60 | MapSO itemToMap_base(A a) { |
61 | MapSO map = itemToMap_inner(a); |
62 | if (map == null) null; |
63 | ret putAll(map, moreSpecialFieldsForItem(a)); |
64 | } |
65 | |
66 | // shown on the right (usually) |
67 | swappable MapSO moreSpecialFieldsForItem(A a) { |
68 | MapSO map = litorderedmap(); |
69 | if (showBackRefs) { |
70 | Cl<Concept> refs = allBackRefs(a); |
71 | if (nempty(refs)) { |
72 | refs = sortedByConceptID(refs); |
73 | int more = l(refs)-maxRefsToShow; |
74 | map.put("Referenced by", |
75 | joinWithComma(takeFirst(maxRefsToShow, refs)) |
76 | + (more > 0 ? ", " + more + " more" : "")); |
77 | } |
78 | } |
79 | ret map; |
80 | } |
81 | |
82 | public JPanel make_dontStartBots() { ret (JPanel) visualize(); } |
83 | |
84 | cachedVisualize { |
85 | dropFields = asList(unshownFields); |
86 | makeTable(); |
87 | swing { |
88 | JButton deleteButton = !showDeleteButton ? null |
89 | : tableDependButton(table, makeDeleteButton(r { |
90 | L<A> l = selectedConcepts(); |
91 | withDBLock(concepts, r { for (A c : l) c.delete() }); |
92 | })); |
93 | |
94 | buttons = jRightAlignedLine(flattenToList( |
95 | !showAddButton ? null : makeAddButton(r newConcept), |
96 | !showEditButton || !showDuplicateButton ? null : tableDependButton(table, makeDuplicateButton(r { |
97 | duplicateConcept(selectedConcept()) |
98 | })), |
99 | !showEditButton ? null : tableDependButton(table, makeEditButton(r { |
100 | editConcept(selectedConcept()) |
101 | })), |
102 | deleteButton, |
103 | )); |
104 | |
105 | wrappedButtonBar = wrapButtonBar(centerAndEastWithMargin(scpButtonFiller = singleComponentPanel(), buttons)); |
106 | |
107 | if (showSearchBar) { |
108 | tableSearcher = tableWithSearcher2(table, withMargin := true); |
109 | panel = centerAndSouthWithMargin(tableSearcher.panel, wrappedButtonBar); |
110 | } else |
111 | panel = centerAndSouthWithMargin(table, wrappedButtonBar); |
112 | |
113 | var fEdit = voidfunc(int row) { |
114 | editConcept(getItem(row)) |
115 | }; |
116 | tablePopupMenuItem(table, "Edit", fEdit); |
117 | onDoubleClick(table, fEdit); |
118 | tablePopupMenuFirst(table, (menu, row) -> { |
119 | Concept c = getItem(row); |
120 | if (c != null) |
121 | addMenuItem(menu, "Delete " + quote(shorten(str(c))), rThread { |
122 | deleteConcept(c); |
123 | }); |
124 | }); |
125 | } // end of swing |
126 | ret panel; |
127 | } |
128 | |
129 | swappable JComponent wrapButtonBar(JComponent buttonBar) { |
130 | if (scrollableButtons) |
131 | ret jBorderlessHigherHorizontalScrollPane(vstack(buttonBar)); |
132 | ret buttonBar; |
133 | } |
134 | |
135 | swappable void newConcept() { |
136 | duplicateConcept(null); |
137 | } |
138 | |
139 | swappable A createDuplicate(A c) { |
140 | A clone = (A) unlisted(_getClass(c)); |
141 | ccopyFieldsExcept(c, clone, dontDuplicateFields); |
142 | ret clone; |
143 | } |
144 | |
145 | void duplicateConcept(A oldConcept) { |
146 | new EditWindow ew; |
147 | if (oldConcept == null) |
148 | ew.item = unlisted(conceptClass); |
149 | else |
150 | ew.item = createDuplicate(oldConcept); |
151 | |
152 | csetAll(ew.item, filters); |
153 | makeComponents(ew); |
154 | |
155 | // save action |
156 | F0<Bool> r = func -> bool { |
157 | try { |
158 | selectAfterUpdate(ew.item); |
159 | concepts.register(ew.item); |
160 | saveData(ew); |
161 | } catch print e { |
162 | infoBox(e); |
163 | false; |
164 | } |
165 | true; |
166 | }; |
167 | temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); |
168 | S title = "New " + entityName(); |
169 | title += appendBracketed(javaClassDescForItem(ew.item)); |
170 | |
171 | renameSubmitButton("Create", showAForm(title, |
172 | toObjectArray(listPlus(ew.matrix, r)))); |
173 | } |
174 | |
175 | void editConcept(A c) { |
176 | if (c == null) ret; |
177 | new EditWindow ew; |
178 | ew.item = c; |
179 | makeComponents(ew); |
180 | F0<Bool> r = func -> bool { |
181 | try { |
182 | // concept class was changed, replace object |
183 | if (ew.item != c) { |
184 | print("Replacing object: " + c + " => " + ew.item); |
185 | replaceConceptAndUpdateRefs(c, ew.item); |
186 | } |
187 | saveData(ew); |
188 | } catch print e { |
189 | infoBox(e); |
190 | false; |
191 | } |
192 | true; |
193 | }; |
194 | temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); |
195 | renameSubmitButton("Save", showAForm("Edit " + entityName() + " #" + c.id, |
196 | toObjectArray(listPlus(ew.matrix, r)))); |
197 | } |
198 | |
199 | JComponent fieldComponent(A c, S field) { |
200 | Class type = getFieldType(c.getClass(), field); |
201 | Type genericType = genericFieldType(conceptClass, field); |
202 | if (type == Concept.Ref.class) |
203 | type = getTypeArgumentAsClass(genericType); |
204 | |
205 | O value = cget(c, field); |
206 | if (type == null) type = _getClass(value); |
207 | //print("Field type: " + field + " => " + type); |
208 | if (type == bool.class) |
209 | ret jCenteredCheckBox(isTrue(value)); |
210 | else if (contains(multiLineFields, field) || containsNewLines(optCast S(value))) |
211 | ret makeTextArea((S) value); |
212 | else if (isSubtype(type, Concept)) |
213 | ret jcomboboxFromConcepts_str(concepts, type, (Concept) value); |
214 | ifclass SecretValue |
215 | else if (type == SecretValue.class) |
216 | ret jpassword(strOrEmpty(getVar(value/SecretValue))); |
217 | endif |
218 | else if (isUneditableFieldType(type)) |
219 | ret jlabel(structureOrText_crud(value)); |
220 | else try { |
221 | ret autoComboBox(structureOrText_crud(value), new TreeSet<S>(map structureOrText_crud(collect(list(concepts, conceptClass), field)))); |
222 | } catch e { |
223 | printException(e); |
224 | ret jTextField(structureOrText_crud(value)); |
225 | } |
226 | } |
227 | |
228 | void saveComponent(A c, S field, JComponent comp) { |
229 | comp = unwrap(comp); |
230 | ifdef SimpleCRUD_v2_debug |
231 | printVars_str("saveComponent", +field, +comp); |
232 | endifdef |
233 | |
234 | Class type = fieldType(c, field); |
235 | Type genericType = genericFieldType(conceptClass, field); |
236 | if (type == Concept.Ref.class) |
237 | type = getTypeArgumentAsClass(genericType); |
238 | |
239 | ifclass SecretValue |
240 | if (type == SecretValue.class) { |
241 | S text = getTextTrim((JPasswordField) comp); |
242 | cset(c, field, empty(text) ? null : SecretValue(text)); |
243 | } else |
244 | endif |
245 | if (comp instanceof JTextComponent) { |
246 | S text = trimIf(!comp instanceof JTextArea, getText((JTextComponent) comp)); |
247 | O value = postProcessValue(text); |
248 | O converted = convertToField(value, c.getClass(), field); |
249 | ifdef SimpleCRUD_v2_debug |
250 | printVars_str("saveComponent", +field, +text, +value, +converted); |
251 | endifdef |
252 | cset(c, field, converted); |
253 | } |
254 | else if (comp cast JComboBox) { |
255 | S text = getTextTrim(comp); |
256 | if (isSubtype(type, Concept)) |
257 | cset(c, field, getConcept(concepts, parseFirstLong(text))); |
258 | else { |
259 | O value = postProcessValue(text); |
260 | cset(c, field, convertToField(value, c.getClass(), field)); |
261 | } |
262 | } else if (comp instanceof JCheckBox) |
263 | cset(c, field, isChecked((JCheckBox) comp)); |
264 | ifclass ImageChooser |
265 | else if (comp instanceof ImageChooser) |
266 | cUpdatePNGFile(c, field, comp/ImageChooser.getImage(), false); |
267 | endif |
268 | } |
269 | |
270 | swappable Cl<S> editableFieldsForItem(A c) { |
271 | if (excludeFieldsFromEditing != null && modifiedField != null) excludeFieldsFromEditing.add(modifiedField); |
272 | ret listWithoutSet( |
273 | conceptFieldsInOrder(c), |
274 | joinSets(excludeFieldsFromEditing, unshownFields, keys(filters))); |
275 | } |
276 | |
277 | void excludeFieldsFromEditing(S... fields) { |
278 | excludeFieldsFromEditing = setPlus(excludeFieldsFromEditing, fields); |
279 | } |
280 | |
281 | |
282 | Cl<Class<? extends A>> possibleClasses() { |
283 | ret (Cl) moveItemFirst(conceptClass, dropTypeParameter(sortClassesByNameIC(myNonAbstractClassesImplementing(conceptClass)))); |
284 | } |
285 | |
286 | JComboBox<Class<? extends A>> classSelectorComponent(A c) { |
287 | ret setComboBoxRenderer(jTypedComboBox(possibleClasses(), _getClass(c)), |
288 | customToStringListCellRenderer shortClassName()); |
289 | } |
290 | |
291 | class NewField { |
292 | JTextField tfName = jtextfield(); |
293 | JTextField tfValue = jtextfield(); |
294 | JComboBox cbRef = jcomboboxFromConcepts_str(concepts, conceptClass); |
295 | JComboBox cbType = jcombobox("String", "Reference"); |
296 | SingleComponentPanel scpValue = singleComponentPanel(); |
297 | |
298 | JPanel panel() { |
299 | onChangeAndNow(cbType, r updateSCP); |
300 | ret jhgridWithSpacing( |
301 | withToolTip("Name for new field", withLabel("Name", tfName)), |
302 | withLabel("Value", westAndCenterWithMargin(cbType, scpValue))); |
303 | } |
304 | |
305 | S typeStr() { ret getText(cbType); } |
306 | |
307 | void updateSCP { |
308 | scpValue.setComponent(eqic(typeStr(), "Reference") |
309 | ? cbRef |
310 | : withToolTip("Contents of new field", tfValue)); |
311 | } |
312 | |
313 | S field() { ret gtt(tfName); } |
314 | O value() { |
315 | ret eqic(typeStr(), "Reference") |
316 | ? getConcept(concepts, parseFirstLong(getText(cbRef))) |
317 | : gtt(tfValue); |
318 | } |
319 | } |
320 | |
321 | class EditWindow { |
322 | A item; |
323 | Map<S, JComponent> componentsByField = litorderedmap(); |
324 | new L matrix; // label, component, label, component, ... |
325 | JComboBox<Class<? extends A>> classSelector; |
326 | new L<NewField> newFields; |
327 | } |
328 | |
329 | // override the following two methods to customize edit window |
330 | |
331 | void makeComponents(EditWindow ew) { |
332 | // class selector |
333 | |
334 | if (showClassNameSelectors) { |
335 | addAll(ew.matrix, makeLabel("Java Class"), ew.classSelector = classSelectorComponent(ew.item)); |
336 | onChange(ew.classSelector, r { |
337 | Class<? extends A> oldClass = _getClass(ew.item); |
338 | Class<? extends A> newClass = getSelectedItem_typed(ew.classSelector); |
339 | if (oldClass == newClass) ret; |
340 | A oldItem = ew.item; |
341 | ew.item = unlisted(newClass); |
342 | ccopyFields(oldItem, ew.item); |
343 | }); |
344 | } |
345 | |
346 | // regular fields |
347 | |
348 | for (S field : editableFieldsForItem(ew.item)) { |
349 | JComponent c = fieldComponent(ew.item, field); |
350 | ew.componentsByField.put(field, c); |
351 | addAll(ew.matrix, makeLabel(field), c); |
352 | } |
353 | |
354 | // new fields |
355 | |
356 | if (allowNewFields && newFieldsToShow > 0) { |
357 | addAll(ew.matrix, " ", jlabel()); // spacing |
358 | for i to newFieldsToShow: { |
359 | new NewField nf; |
360 | ew.newFields.add(nf); |
361 | addAll(ew.matrix, makeLabel(""/*"New field"*/), nf.panel()); |
362 | } |
363 | } |
364 | } |
365 | |
366 | void saveData(EditWindow ew) { |
367 | // save regular fields |
368 | |
369 | for (S field, JComponent component : ew.componentsByField) |
370 | if (isIdentifier(field)) |
371 | saveComponent(ew.item, field, component); |
372 | |
373 | // save new fields |
374 | |
375 | for (NewField nf : ew.newFields) { |
376 | S field = nf.field(); |
377 | O value = nf.value(); |
378 | if (nempty(field) && notNullOrEmptyString(value)) |
379 | cset(ew.item, field, value); |
380 | } |
381 | |
382 | if (modifiedField != null) cset(ew.item, modifiedField, now()); |
383 | |
384 | conceptSaved(ew.item); |
385 | } |
386 | |
387 | O postProcessValue(O o) { |
388 | if (emptyStringsToNull && eq(o, "")) null; |
389 | ret o; |
390 | } |
391 | |
392 | // labels on left hand side of form |
393 | JComponent makeLabel(S label) { |
394 | ret jMinWidthAtLeast(formLabelsWidth, jlabel(label)); |
395 | } |
396 | |
397 | swappable JComponent showAForm(S title, O... parts) { |
398 | JForm form = JForm(parts).disposeFrameOnSubmit(); |
399 | var panel = form.visualize(); |
400 | showFrame(title, editWindowScrollable ? jscroll_vertical(panel) : panel); |
401 | ret panel; |
402 | } |
403 | |
404 | bool isUneditableFieldType(Class type) { |
405 | ret isSubclassOfAny(type, Map, Cl, Pair); |
406 | } |
407 | |
408 | void hideField(S field) { hideFields(field); } |
409 | void hideFields(S... fields) { |
410 | unshownFields = createOrAddToSet(unshownFields, fields); |
411 | } |
412 | |
413 | selfType entityName(S name) { entityName = if0_const(name); this; } |
414 | |
415 | swappable S entityName() { ret shortClassName(conceptClass); } |
416 | swappable S entityNamePlural() { ret plural(entityName()); } |
417 | swappable S entityNameLower() { ret firstToLower(entityName()); } |
418 | swappable S entityNamePluralLower() { ret firstToLower(entityNamePlural()); } |
419 | |
420 | JPanel buttons() { visualize(); ret buttons; } |
421 | |
422 | void addButtonInFront(S text, Runnable r) { |
423 | addButtonInFront(jbutton(text, r)); |
424 | } |
425 | |
426 | void addButton(S text, Runnable r) { |
427 | addButton(jbutton(text, r)); |
428 | } |
429 | |
430 | void addButton(JComponent c) { |
431 | buttons().add(c); |
432 | } |
433 | |
434 | void addButtonInFront(JComponent c) { |
435 | addComponentInFront(buttons(), c); |
436 | } |
437 | |
438 | JButton makeAddButton(Runnable r) { ret makeButton( |
439 | "Add...", "Create a new " + entityNameLower(), #1103069, r); } |
440 | JButton makeDeleteButton(Runnable r) { ret makeButton( |
441 | "Delete", "Delete selected " + entityNamePluralLower(), #1103067, r); } |
442 | JButton makeEditButton(Runnable r) { ret makeButton( |
443 | "Edit...", "Edit selected " + entityNameLower(), #1103068, r); } |
444 | JButton makeDuplicateButton(Runnable r) { ret makeButton( |
445 | "Duplicate...", "Duplicate selected " + entityNameLower(), #1103070, r); } |
446 | |
447 | JButton makeButton(S text, S toolTip default null, S iconID, Runnable r) { |
448 | if (iconButtons) |
449 | ret toolTip(or2(toolTip, text), jimageButtonScaledToWidth(16, iconID, text, r)); |
450 | else |
451 | ret toolTip(toolTip, jbutton(text, r)); |
452 | } |
453 | |
454 | void multiLineField(S field) { |
455 | multiLineFields = addOrCreate(multiLineFields, field); |
456 | } |
457 | |
458 | swappable JComponent makeTextArea(S text) { |
459 | ret typeWriterTextArea(text); |
460 | } |
461 | |
462 | void setButtonFiller(JComponent c) { |
463 | scpButtonFiller.set(c); |
464 | } |
465 | |
466 | TableSearcher tableSearcher() { |
467 | visualize(); |
468 | ret tableSearcher; |
469 | } |
470 | } // end of SimpleCRUD_v2 |
Began life as a copy of #1006540
download show line numbers debug dex old transpilations
Travelled to 5 computer(s): bhatertpkbcr, ekrmjmnbrukm, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt
No comments. add comment
Snippet ID: | #1030726 |
Snippet name: | SimpleCRUD_v2 - create/read/update/delete for a concept class with simple fields [Swing] |
Eternal ID of this version: | #1030726/134 |
Text MD5: | 6b0e0bbefd12dcfc2bd0c6b01ade872e |
Author: | stefan |
Category: | javax / concepts / gui |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2023-10-15 13:29:11 |
Source code size: | 15416 bytes / 470 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 664 / 1587 |
Version history: | 133 change(s) |
Referenced in: | [show references] |