Libraryless. Click here for Pure Java version (21685L/133K).
1 | sclass HCRUD_Concepts<A extends Concept> extends HCRUD_Data {
|
2 | Concepts cc = db_mainConcepts(); |
3 | Class<A> cClass; |
4 | new L<IVF1<A>> onCreateOrUpdate; |
5 | new L<IVF1<A>> onCreate; |
6 | IVF2<A, MapSO> afterUpdate; // parameter 2: old values |
7 | MapSO filters; // fields to filter by/add to new objects |
8 | SS ciFilters; // case-insensitive filters |
9 | IF1<Cl<A>> customFilter; |
10 | ValueConverterForField valueConverter; |
11 | bool referencesBlockDeletion; |
12 | bool trimAllSingleLineValues; |
13 | Set<S> fieldsToHideInCreationForm; |
14 | bool lockDB; // lock DB while updating object |
15 | bool verbose; |
16 | bool dropEmptyListValues = true; |
17 | bool lsMagic; |
18 | bool convertConceptValuesToRefs; |
19 | A currentConcept; // while editing |
20 | |
21 | bool useDynamicComboBoxes; // dynamically load concepts combo box entries for all fields |
22 | IPred<S> useDynamicComboBoxesForField; // activate dynamic combo boxes for selected fields |
23 | int dynamicComboBoxesThreshold = 1000; // if there are this many entries or more, use a dynamic combo box |
24 | |
25 | *(Class<A> *cClass) {}
|
26 | *(Concepts *cc, Class<A> *cClass) {}
|
27 | |
28 | // XXX - breaking change from just shortName() |
29 | swappable S itemName() { ret humanizeShortName(cClass); }
|
30 | swappable S itemNamePlural() { ret super.itemNamePlural(); }
|
31 | |
32 | //LS fields() { ret conceptFields(cClass); }
|
33 | |
34 | L<A> itemsForListing() {
|
35 | ret defaultSort(asList(listConcepts())); |
36 | } |
37 | |
38 | @Override |
39 | L<MapSO> list() {
|
40 | //ret lazyMap itemToMapForList(itemsForListing()); |
41 | ret lazyMap(itemsForListing(), a -> new Item(str(a.id)) {
|
42 | public MapSO calcFullMap() { ret itemToMapForList(a); }
|
43 | }); |
44 | } |
45 | |
46 | // more efficient version - convert items only after taking subList |
47 | // TODO: this is never called by HCRUD; make lazy list instead? |
48 | @Override |
49 | L<MapSO> list(IntRange range) {
|
50 | ret lambdaMap itemToMapForList(subListOrFull(itemsForListing(), range)); |
51 | } |
52 | |
53 | Cl<A> listConcepts() {
|
54 | Cl<A> l = listConcepts_firstStep(); |
55 | ret postProcess(customFilter, l); |
56 | } |
57 | |
58 | swappable Cl<A> listConcepts_firstStep() {
|
59 | if (empty(ciFilters)) |
60 | ret conceptsWhere(cc, cClass, mapToParams(filters)); |
61 | else if (empty(filters)) |
62 | ret conceptsWhereCI(cc, cClass, mapToParams(ciFilters)); |
63 | else {
|
64 | // TODO: choose best index |
65 | Cl<A> l = conceptsWhere(cc, cClass, mapToParams(filters)); |
66 | ret filterConceptsIC(l, mapToParams(ciFilters)); |
67 | } |
68 | } |
69 | |
70 | swappable Pair<S, Bool> defaultSortField() { ret pair("id", true); }
|
71 | |
72 | swappable L<A> defaultSort(L<A> l) { ret sortedByConceptIDDesc(l); }
|
73 | |
74 | swappable A emptyConcept() {
|
75 | ret unlisted(cClass); |
76 | } |
77 | |
78 | swappable MapSO emptyObject() {
|
79 | A c = emptyConcept(); |
80 | |
81 | // not actually necessary here, we do it on create |
82 | /*cset(c, mapToParams(filters)); |
83 | cset(c, mapToParams(ciFilters));*/ |
84 | |
85 | MapSO map = itemToMap(c); |
86 | //printVars_str emptyObject(+cClass, +filters, +ciFilters, +map); |
87 | |
88 | ret mapMinusKeys(fieldsToHideInCreationForm, map); |
89 | } |
90 | |
91 | MapSO itemToMap(A c) {
|
92 | if (c == null) null; |
93 | ret putKeysFirst(getFieldOrder(c), conceptToMap_gen_withNullValues(c)); |
94 | } |
95 | |
96 | MapSO itemToMapForList(A c) {
|
97 | if (c == null) null; |
98 | MapSO map = itemToMap(c); |
99 | massageItemMapForList(c, map); |
100 | ret map; |
101 | } |
102 | |
103 | swappable void massageItemMapForList(A c, MapSO map) {
|
104 | } |
105 | |
106 | swappable void massageItemMapForUpdate(A c, MapSO map) {
|
107 | // Concepts.RefL magic |
108 | |
109 | Cl<Field> refLFields = nonStaticNonTransientFieldObjectsOfType(Concept.RefL.class, c); |
110 | printVars_str(+refLFields, +c); |
111 | for (Field f : refLFields) {
|
112 | new TreeMap<Int, O> values; |
113 | new Matches m; |
114 | for (S key, O value : cloneMap(map)) |
115 | if (startsWith(key, f.getName() + "_", m) && isInteger(m.rest())) {
|
116 | Concept concept = getConceptFromString((S) value); |
117 | //printVars_str("RefL magic", +key, +conceptID, +concept);
|
118 | if (concept != null || !dropEmptyListValues) |
119 | values.put(parseInt(m.rest()), concept); |
120 | map.remove(key); |
121 | } |
122 | |
123 | if (!dropEmptyListValues) |
124 | while (nempty(values) && lastValue(values) == null) {
|
125 | if (verbose) print("Dropping value " + lastEntry(values));
|
126 | removeLastKey(values); |
127 | } |
128 | |
129 | map.put(f.getName(), valuesAsList(values)); |
130 | } |
131 | |
132 | // process dynamic bool, concept fields |
133 | |
134 | for (S name : cloneKeys(map)) {
|
135 | S metaInfo = metaInfoFromForm(name); |
136 | print("metaInfo for " + name + ": " + metaInfo);
|
137 | if (eqic(metaInfo, "concept")) |
138 | replaceStringValueWithConcept(map, name); |
139 | else if (eqic(metaInfo, "bool")) |
140 | replaceStringValueWithBool(map, name); |
141 | } |
142 | |
143 | // Concepts.Ref magic (look up concept) |
144 | |
145 | for (Field f : nonStaticNonTransientFieldObjectsOfType(Concept.Ref.class, c)) |
146 | replaceStringValueWithConcept(map, f.getName()); |
147 | |
148 | // trim values |
149 | |
150 | if (trimAllSingleLineValues) |
151 | for (Map.Entry<S, O> e : map.entrySet()) {
|
152 | S val = optCastString(e.getValue()); |
153 | if (val != null && isSingleLine(val) && isUntrimmed(val)) |
154 | e.setValue(trim(val)); |
155 | } |
156 | |
157 | // LS magic |
158 | |
159 | if (lsMagic) |
160 | for (Field f : nonStaticNonTransientFieldObjectsOfType(L.class, c)) {
|
161 | if (eqOneOf(f.getName(), "refs", "backRefs")) continue; |
162 | new TreeMap<Int, S> values; |
163 | new Matches m; |
164 | |
165 | for (S key, O value : cloneMap(map)) {
|
166 | if (startsWith(key, f.getName() + "_", m) && isInteger(m.rest())) {
|
167 | if (!dropEmptyListValues || nempty((S) value)) {
|
168 | if (verbose) print("Adding value " + m.rest() + " / " + value);
|
169 | mapPut(values, parseInt(m.rest()), (S) value); |
170 | } |
171 | map.remove(key); |
172 | } |
173 | } |
174 | |
175 | if (!dropEmptyListValues) |
176 | while (nempty(values) && empty(lastValue(values))) {
|
177 | if (verbose) print("Dropping value " + lastEntry(values));
|
178 | removeLastKey(values); |
179 | } |
180 | |
181 | map.put(f.getName(), valuesAsList(values)); |
182 | } |
183 | |
184 | // don't set SecretValue fields |
185 | |
186 | for (Field f : nonStaticNonTransientFieldObjectsOfType(SecretValue, c)) |
187 | map.remove(f.getName()); |
188 | } |
189 | |
190 | void replaceStringValueWithConcept(MapSO map, S key) {
|
191 | O value = map.get(key); |
192 | if (value cast S) {
|
193 | Concept concept = getConceptFromString(value); |
194 | map.put(key, concept); |
195 | } |
196 | } |
197 | |
198 | void replaceStringValueWithBool(MapSO map, S key) {
|
199 | O value = map.get(key); |
200 | if (value cast S) {
|
201 | map.put(key, englishStringToBool(value)); |
202 | } |
203 | } |
204 | |
205 | swappable MapSO getObject(O id) {
|
206 | ret itemToMap(conceptForID(id)); |
207 | } |
208 | |
209 | MapSO getObjectForEdit(O id) {
|
210 | currentConcept = conceptForID(id); |
211 | ret getObject(id); |
212 | } |
213 | |
214 | O createObject(SS fullMap, S fieldPrefix) {
|
215 | rawFormValues = fullMap; |
216 | try {
|
217 | SS map = extractFieldValues(fullMap, fieldPrefix); |
218 | A c = cnew(cc, cClass); |
219 | // make sure filters override |
220 | setValues(c, mapMinusKeys(map, filteredFields()), true); |
221 | cset(c, mapToParams(filters)); |
222 | cset(c, mapToParams(ciFilters)); |
223 | pcallFAll(onCreate, c); |
224 | pcallFAll(onCreateOrUpdate, c); |
225 | callOpt(c, "_onCreated"); // TODO: synchronize? |
226 | ret c.id; |
227 | } finally {
|
228 | rawFormValues = null; |
229 | } |
230 | } |
231 | |
232 | void setValues(A c, SS map, bool creating) {
|
233 | lock lockDB && !creating ? dbLock(cc) : null; |
234 | MapSO map2 = (Map) cloneMap(map); |
235 | massageItemMapForUpdate(c, map2); |
236 | if (verbose) {
|
237 | print("setValues " + map);
|
238 | print("backRefs: " + c.backRefs);
|
239 | } |
240 | MapSO oldValues = !creating && afterUpdate != null |
241 | ? cgetAll_cloneLists(c, keys(map2)) : null; |
242 | if (convertConceptValuesToRefs) |
243 | convertAllConceptValuesToRefs(c, map2); |
244 | if (valueConverter == null) |
245 | cSmartSet(c, mapToParams(map2)); |
246 | else |
247 | cSmartSet_withConverter_pcall(verbose, valueConverter, c, mapToParams(map2)); |
248 | if (oldValues != null) |
249 | callF(afterUpdate, c, oldValues); |
250 | if (verbose) |
251 | print("backRefs: " + c.backRefs);
|
252 | } |
253 | |
254 | // for dynamic fields |
255 | void convertAllConceptValuesToRefs(A c, MapSO map) {
|
256 | for (S key, O value : cloneMap(map)) {
|
257 | if (value cast Concept) |
258 | if (!hasField(c, key)) {
|
259 | print("Converting value to ref: " + key);
|
260 | map.put(key, c.new Ref(value)); |
261 | } |
262 | } |
263 | } |
264 | |
265 | A conceptForID(O id) {
|
266 | ret _getConcept(cc, cClass, toLong(id)); |
267 | } |
268 | |
269 | S updateObject(O id, SS fullMap, S fieldPrefix) {
|
270 | rawFormValues = fullMap; |
271 | try {
|
272 | SS map = extractFieldValues(fullMap, fieldPrefix); |
273 | A c = conceptForID(id); |
274 | if (c == null) ret "Object " + id + " not found"; |
275 | try answer checkFilters(c); |
276 | setValues(c, map, false); |
277 | pcallFAll(onCreateOrUpdate, c); |
278 | ret "Object " + id + " updated"; |
279 | } finally {
|
280 | rawFormValues = null; |
281 | } |
282 | } |
283 | |
284 | S deleteObject(O id) {
|
285 | A c = conceptForID(id); |
286 | if (c == null) ret "Object " + id + " not found"; |
287 | try answer checkFilters(c); |
288 | actuallyDeleteConcept(c); |
289 | ret "Object " + id + " deleted"; |
290 | } |
291 | |
292 | swappable void actuallyDeleteConcept(A c) {
|
293 | deleteConcept(c); |
294 | } |
295 | |
296 | S checkFilters(A c) {
|
297 | if (!checkConceptFields(c, mapToParams(filters)) |
298 | || !checkConceptFieldsIC(c, mapToParams(ciFilters))) |
299 | ret "Object " + c.id + " not in view"; |
300 | ret ""; |
301 | } |
302 | |
303 | HCRUD_Concepts<A> addFilters(MapSO map) {
|
304 | fOr (S field, O value : map) |
305 | addFilter(field, value); |
306 | this; |
307 | } |
308 | |
309 | HCRUD_Concepts<A> addFilter(S field, O value) {
|
310 | filters = orderedMapPutOrCreate(filters, field, value); |
311 | this; |
312 | } |
313 | |
314 | // TODO: do this the other way around (check for editable types) |
315 | swappable bool isEditableValue(O value) {
|
316 | //if (value instanceof Concept.RefL) true; // subsumed in next line |
317 | if (value instanceof L) true; |
318 | if (value instanceof Cl) false; |
319 | true; |
320 | } |
321 | |
322 | Renderer getRenderer(S field) {
|
323 | if (!isEditableValue(currentValue)) |
324 | ret new NotEditable; |
325 | |
326 | Class type = fieldType(or(currentConcept, cClass), field); |
327 | S metaInfo = metaInfoFromForm(field); |
328 | //printVars_str("getRenderer", +cClass, +field, +type, +rawFormValues);
|
329 | |
330 | if (eq(type, bool.class)) |
331 | ret new CheckBox; |
332 | |
333 | if (eq(type, Bool.class)) |
334 | ret new ComboBox(ll("", "yes", "no"),
|
335 | b -> trueFalseNull((Bool) b, "yes", "no", "")); |
336 | |
337 | // show a Ref<> field as a combo box |
338 | |
339 | if (isSubtypeOf(type, Concept.Ref.class)) {
|
340 | Class<? extends Concept> c = fieldTypeArg(field); |
341 | AbstractComboBox cb = makeConceptsComboBox(field, c); |
342 | cb.metaInfo = "concept"; |
343 | ret cb; |
344 | } |
345 | |
346 | // show dynamic field with concept/ref value as combo box |
347 | |
348 | O val = deref(currentValue); |
349 | if (val cast Concept) {
|
350 | Class<? extends Concept> c = val.getClass(); |
351 | AbstractComboBox cb = makeConceptsComboBox(field, c); |
352 | cb.metaInfo = "concept"; |
353 | ret cb; |
354 | } |
355 | |
356 | // show dynamic ref field from URL parameters |
357 | |
358 | if (eqic(metaInfo, "concept")) {
|
359 | printVars_str("metaInfo value", +field, +val);
|
360 | AbstractComboBox cb = makeConceptsComboBox(field, Concept); // TODO |
361 | cb.metaInfo = "concept"; |
362 | ret cb; |
363 | } |
364 | |
365 | // show a RefL<> field as a list of combo boxes |
366 | |
367 | if (eq(type, Concept.RefL.class)) {
|
368 | Class<? extends Concept> c = fieldTypeArg(field); |
369 | ret new FlexibleLengthList(makeConceptsComboBox(field, c)); |
370 | } |
371 | |
372 | if (eq(type, L.class)) {
|
373 | //Class c = fieldTypeArg(field); |
374 | ret new FlexibleLengthList(new TextField(80)); |
375 | } |
376 | |
377 | if (val cast Bool) |
378 | ret new CheckBox; |
379 | |
380 | ret super.getRenderer(field); |
381 | } |
382 | |
383 | DynamicComboBox makeDynamicComboBox(S field, Class<? extends Concept> c) {
|
384 | DynamicComboBox cb = new(field); |
385 | cb.valueToEntry = value -> {
|
386 | value = deref(value); |
387 | //print("ComboBox: value type=" + _getClass(value);
|
388 | long id = 0; |
389 | if (value cast Concept) id = conceptID(value); |
390 | else if (value instanceof S && isInteger((S) value)) id = parseLong(value); |
391 | if (id != 0) |
392 | ret comboBoxItem(_getConcept(cc, id)); |
393 | null; |
394 | }; |
395 | ret cb; |
396 | } |
397 | |
398 | AbstractComboBox makeConceptsComboBox(S field, Class<? extends Concept> c) {
|
399 | if (c == null) fail("Null type for field \*field*/. currentConcept: " + currentConcept);
|
400 | |
401 | if (useDynamicComboBoxes || useDynamicComboBoxesForField != null && useDynamicComboBoxesForField.get(field)) |
402 | ret makeDynamicComboBox(field, c); |
403 | |
404 | Cl concepts = listConceptClass(c); |
405 | if (l(concepts) >= dynamicComboBoxesThreshold) |
406 | ret makeDynamicComboBox(field, c); |
407 | LS entries = comboBoxItems(concepts); |
408 | |
409 | ComboBox cb = new(entries); |
410 | cb.valueToEntry = value -> {
|
411 | value = deref(value); |
412 | //print("ComboBox: value type=" + _getClass(value);
|
413 | long id = 0; |
414 | if (value cast Concept) id = conceptID(value); |
415 | else if (value instanceof S && isInteger((S) value)) id = parseLong(value); |
416 | if (id != 0) {
|
417 | S entry = firstWhereFirstLongIs(entries, id); |
418 | //print("combobox selected: " + id + " / " + entry);
|
419 | ret entry; |
420 | } |
421 | null; |
422 | }; |
423 | ret cb; |
424 | } |
425 | |
426 | // TODO: filters? |
427 | <A extends Concept> Cl<A> listConceptClass(Class<A> c) {
|
428 | ret cc.list(c); |
429 | } |
430 | |
431 | LS comboBoxItemsForConceptClass(Class<? extends Concept> c) {
|
432 | ret comboBoxItems(listConceptClass(c)); |
433 | } |
434 | |
435 | LS comboBoxItems(Cl<? extends Concept> l) {
|
436 | ret comboBoxItems_static(l); |
437 | } |
438 | |
439 | static LS comboBoxItems_static(Cl<? extends Concept> l) {
|
440 | ret itemPlus("", lmap comboBoxItem_static(l));
|
441 | } |
442 | |
443 | S comboBoxItem(Concept val) {
|
444 | ret comboBoxItem_static(val); |
445 | } |
446 | |
447 | sS comboBoxItem_static(Concept val) {
|
448 | ret val == null ? null : shorten(val.id + ": " + val); |
449 | } |
450 | |
451 | swappable bool objectCanBeDeleted(O id) {
|
452 | ret !referencesBlockDeletion || !hasBackRefs(conceptForID(id)); |
453 | } |
454 | |
455 | Set<S> filteredFields() { ret joinSets(keys(filters), keys(ciFilters)); }
|
456 | |
457 | Class<? extends Concept> fieldTypeArg(S field) {
|
458 | ret getTypeArgumentAsClass(genericFieldType(or(currentConcept, cClass), field)); |
459 | } |
460 | |
461 | swappable Class<? extends Concept> conceptClassForComboBoxSearch(S info, S query) {
|
462 | // info is the field name (Ref or RefL). get concept class |
463 | if (!isIdentifier(info)) ret cClass; |
464 | |
465 | S field = info; |
466 | Class<? extends Concept> c = fieldTypeArg(field); |
467 | |
468 | // simple hack to show list for dynamic/new fields |
469 | // assuming it's the same as the main class |
470 | // clients can improve on this |
471 | if (c == null) c = cClass; |
472 | |
473 | ret c; |
474 | } |
475 | |
476 | swappable LS comboBoxSearchBaseItems(S info, S query) {
|
477 | var c = conceptClassForComboBoxSearch(info, query); |
478 | if (c == null) ret emptyList(); |
479 | ret comboBoxItemsForConceptClass(c); |
480 | } |
481 | |
482 | LS comboBoxSearch(S info, S query) {
|
483 | LS items = comboBoxSearchBaseItems(info, query); |
484 | ret takeFirst(10, scoredSearch(query, items)); |
485 | } |
486 | |
487 | // to find the concept e.g. within massageFormMatrix |
488 | A conceptForMap(MapSO map) {
|
489 | if (map == null) null; |
490 | long id = toLong(map.get(idField())); |
491 | ret id == 0 ? null : (A) _getConcept(cc, id); |
492 | } |
493 | |
494 | A getConcept(MapSO map) {
|
495 | ret conceptForMap(map); |
496 | } |
497 | |
498 | SS extractFieldValues(SS fullMap, S fieldPrefix) {
|
499 | ret subMapStartingWith_dropPrefix(fullMap, fieldPrefix); |
500 | } |
501 | |
502 | Concept getConceptFromString(S s) {
|
503 | long conceptID = parseFirstLong(s); |
504 | ret _getConcept(cc, conceptID); |
505 | } |
506 | |
507 | S metaInfoFromForm(S field) {
|
508 | ret mapGet(rawFormValues, "metaInfo_" + field); |
509 | } |
510 | |
511 | void addCIFilter(S field, S value) {
|
512 | ciFilters = putOrCreate(ciFilters, field, value); |
513 | } |
514 | |
515 | swappable S titleForObjectID(O id) {
|
516 | ret htmlEncode2(strOrNull(conceptForID(id))); |
517 | } |
518 | } |
download show line numbers debug dex old transpilations
Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
| Snippet ID: | #1026002 |
| Snippet name: | HCRUD_Concepts |
| Eternal ID of this version: | #1026002/201 |
| Text MD5: | 54c548eee75fe00ee9b2422e9ba511c3 |
| Transpilation MD5: | c29b4ffe951e292ffb159455b14be011 |
| Author: | stefan |
| Category: | javax |
| Type: | JavaX fragment (include) |
| Public (visible to everyone): | Yes |
| Archived (hidden from active list): | No |
| Created/modified: | 2021-11-15 23:08:51 |
| Source code size: | 16155 bytes / 518 lines |
| Pitched / IR pitched: | No / No |
| Views / Downloads: | 1173 / 2434 |
| Version history: | 200 change(s) |
| Referenced in: | [show references] |