Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

747
LINES

< > BotCompany Repo | #1026001 // HCRUD - CRUD in HTML with pluggable data handler

JavaX fragment (include) [tags: use-pretranspiled]

Transpiled version (24071L) is out of date.

1  
// one instance should only be used for one page at a time
2  
sclass HCRUD extends HAbstractRenderable {
3  
  HCRUD_Data data; // normally an instance of HCRUD_Concepts (#1026002)
4  
  
5  
  // yeah these fields are a mess...
6  
  bool mutationRights = true;
7  
  bool allowCreateOrDelete = true;
8  
  bool allowCreate = true;
9  
  bool allowEdit = true;
10  
  bool singleton;
11  
  
12  
  bool cmdsLeft; // put commands on the left instead of the right
13  
  bool showEntryCountInTitle;
14  
  bool allowFieldRenaming;
15  
  int defaultTextFieldCols = 80;
16  
  int valueDisplayLength = 1000;
17  
  S tableClass; // CSS class for table
18  
  S formTableClass; // CSS class for form
19  
  S checkBoxClass = "crud_chkbox";
20  
  S customTitle;
21  
  bool showTextFieldsAsAutoExpandingTextAreas;
22  
  Set<S> unshownFields; // not shown in table or form
23  
  Set<S> uneditableFields; // not shown in form
24  
  Set<S> unlistedFields; // not shown in table
25  
  bool showCheckBoxes;
26  
  bool cleanItemIDs;
27  
  
28  
  bool haveJQuery, haveSelectizeJS, haveSelectizeClickable;
29  
  bool needsJQuery; // unused
30  
  bool paginate;
31  
  bool sortable;
32  
  bool buttonsOnTop, buttonsOnBottom = true;
33  
  bool duplicateInNewTab;
34  
  S formID = "crudForm";
35  
  bool showQuickSaveButton; // needs hnotificationPopups()
36  
  bool enableMultiSelect = true; // enables shift+click on check boxes. needs haveJQuery
37  
  bool cellColumnToolTips; // give each table cell a tooltip showing its column name
38  
  bool showSearchField;
39  
  S searchQuery;
40  
  
41  
  SS params;
42  
  new HTMLPaginator paginator;
43  
  
44  
  // sort options
45  
  S sortByField = "id";
46  
  S sortParameter = "sort";
47  
  bool descending;
48  
  
49  
  int flexibleLengthListLeeway = 5; // how many empty rows to display at the end of each flexible length list
50  
  
51  
  // set internally after update/create or from "selectObj" param to highlight an item in the list (or show only that item)
52  
  O objectIDToHighlight;
53  
  bool showOnlySelected;
54  
  
55  
  S fieldPrefix = "f_";
56  
  
57  
  int entryCount;
58  
  
59  
  *() {}
60  
  *(HCRUD_Data *data) {}
61  
  *(S *baseLink, HCRUD_Data *data) {}
62  
  
63  
  S newLink() { ret appendQueryToURL(baseLink, cmd := "new"); }
64  
  S deleteLink(O id) { ret appendQueryToURL(baseLink, "delete_" + id, 1); }
65  
  S editLink(O id) { ret appendQueryToURL(baseLink, edit := id); }
66  
  S duplicateLink(O id) { ret appendQueryToURL(baseLink, duplicate := id); }
67  
  
68  
  void setParams(SS params) {
69  
    this.params = params;
70  
    
71  
    if (objectIDToHighlight == null) objectIDToHighlight = params.get("selectObj");
72  
    if (eq("1", params.get("showOnlySelected")))
73  
      set showOnlySelected;
74  
  }
75  
  
76  
  // also handles commands if withCmds=true
77  
  // you probably want to call renderPage() instead to handle all commands
78  
  S render(bool withCmds, SS params) {
79  
    //print("HCRUD render");
80  
    setParams(params);
81  
    
82  
    if (!withCmds) ret renderTable(false);
83  
    
84  
    try answer handleCommands(params);
85  
    
86  
    ret renderMsgs(params)
87  
      + divUnlessEmpty(nav())
88  
      + renderTable(withCmds);
89  
  }
90  
  
91  
  swappable S nav() {
92  
    new LS l;
93  
    if (actuallyAllowCreate())
94  
      l.add(ahref(newLink(), "New " + itemName()));
95  
    if (showSearchField)
96  
      l.add(hInlineSearchForm("search", searchQuery, ""));
97  
    ret joinWithVBar(l);
98  
  }
99  
100  
  S handleCommands(SS params) {
101  
    new LS msgs;
102  
    
103  
    if (eqGet(params, "action", "create")) {
104  
      if (!actuallyAllowCreate()) fail("Creating objects not allowed");
105  
      processRenames(params);
106  
      O id = data.createObject(preprocessUpdateParams(params), fieldPrefix);
107  
      msgs.add(itemName() + " created (ID: " + id + ")");
108  
      objectIDToHighlight = id;
109  
    }
110  
    
111  
    if (eqGet(params, "action", "update")) {
112  
      if (!actuallyAllowEdit()) fail("Editing objects not allowed");
113  
      S id = params.get("id");
114  
      processRenames(params);
115  
      msgs.add(data.updateObject(id, preprocessUpdateParams(params), fieldPrefix));
116  
      objectIDToHighlight = id;
117  
    }
118  
119  
    LS toDeleteList = keysDeprefixNemptyValue(params, "delete_");
120  
    if (eq(params.get("bulkAction"), "deleteSelected"))
121  
      toDeleteList.addAll(keysDeprefixNemptyValue(params, "obj_"));
122  
      
123  
    for (S toDelete : toDeleteList) {
124  
      if (!actuallyAllowDelete()) fail("Deleting objects not allowed");
125  
      msgs.add(data.deleteObject(toDelete));
126  
    }
127  
128  
    ret empty(msgs) ? "" : refreshAfterCommand(params, msgs);
129  
  }
130  
  
131  
  swappable S refreshAfterCommand(SS params, LS msgs) {
132  
    S redirectAfterSave = mapGet(params, "redirectAfterSave");
133  
    if (nempty(redirectAfterSave)) ret hrefresh(redirectAfterSave);
134  
    
135  
    ret refreshWithMsgs(msgs,
136  
      anchor := objectIDToHighlight == null ? null : "obj" + objectIDToHighlight,
137  
      params := objectIDToHighlight == null ? null : litmap(selectObj := objectIDToHighlight));
138  
  }
139  
  
140  
  S encodeField(S s) {
141  
    ret or(data.fieldNameToHTML(s), s);
142  
  }
143  
  
144  
  // in table
145  
  swappable S renderValue(S field, O value) {
146  
    if (value cast HTML) ret value.html;
147  
    value = deref(value);
148  
    if (value cast SecretValue)
149  
      ret hhiddenStuff(renderValue_inner(value!));
150  
    ret renderValue_inner(value);
151  
  }
152  
153  
  swappable S renderValue_inner(O value) {  
154  
    if (value cast Bool)
155  
      ret yesNo_short(value);
156  
    ret htmlEncode_nlToBr_withIndents(shorten(valueDisplayLength, strOrEmpty(value)));
157  
  }
158  
  
159  
  S renderTable(bool withCmds) {
160  
    ret renderTable(withCmds, data.list());
161  
  }
162  
  
163  
  S valueToSortable(O value) {
164  
    if (value cast HTML)
165  
      ret value!;
166  
    ret strOrNull(value);
167  
  }
168  
  
169  
  // l = list of maps as it comes from data object
170  
  S renderTable(bool withCmds, L<MapSO> l) {
171  
    //print("HCRUD renderTable");
172  
    entryCount = l(l);
173  
    if (empty(l)) ret p("No entries");
174  
    if (!eq(data.defaultSortField(), pair(sortByField, descending))) {
175  
      if (nempty(sortByField)) {
176  
        print("Sorting " + nEntries(l) + " by " + sortByField);
177  
        l = sortByTransformedMapKey_alphaNum valueToSortable(l, sortByField);
178  
      }
179  
      if (descending) l = reversed(l);
180  
    }
181  
    
182  
    // l2 is the rendered map. use keyEncoding to get from l keys to l2 keys
183  
    new SS keyEncoding;
184  
    L<MapSO> l2 = lazyMap(l, _map -> {
185  
      O id = itemID(_map);
186  
      ret data.new Item(id) {
187  
        public MapSO calcFullMap() {
188  
          MapSO map = _map;
189  
          map = mapMinusKeys(map, joinSets(unshownFields, unlistedFields));
190  
          MapSO map2 = postProcessTableRow(map, mapToMap(
191  
            (key, value) -> pair(mapPut_returnValue(keyEncoding, key, encodeField(key)), renderValue(key, value)),
192  
            map));
193  
          if (singleton)
194  
            map2.remove(encodeField(data.idField())); // don't show ID in table in singleton mode
195  
          if (withCmds)
196  
            map2 = addCmdsToTableRow(map, map2);
197  
          // add anchor to row
198  
          map2.put(firstKey(map2), aname("obj" + id, firstValue(map2)));
199  
          ret map2;
200  
        }
201  
      };
202  
    });
203  
    
204  
    new LS out;
205  
    
206  
    if (paginate) {
207  
      paginator.processParams(params);
208  
      paginator.baseLink = addParamsToURL(baseLink,
209  
        filterKeys keepParamInPagination(params));
210  
      paginator.max = l(l2);
211  
      out.add(divUnlessEmpty(paginator.renderNav()));
212  
      
213  
      L<MapSO> l3 = subListOrFull(l2, paginator.visibleRange());
214  
      
215  
      //printVars_str(+objectIDToHighlight, visible := l(l3), first := first(l3), firstID := mapToID(first(l3)));
216  
217  
      // if highlighted object is not on page or user wants to see only this object, show only this object
218  
      if (objectIDToHighlight != null && (showOnlySelected || !any isHighlighted(l3))) {
219  
        l3 = llNonNulls(firstThat isHighlighted(l2));
220  
        //printVars_str(+objectIDToHighlight, total := l(l2), found := l(l3));
221  
      }
222  
223  
      l2 = l3;
224  
    }
225  
    
226  
    new SS replaceHeaders;
227  
    if (sortable && !singleton)
228  
      for (S key, html : keyEncoding) {
229  
        bool sortedByField = eq(sortByField, key);
230  
        bool showDescendingLink = sortedByField && !descending;
231  
        S htmlOld = html;
232  
        if (sortedByField) {
233  
          S title = showDescendingLink ? "Click here to sort descending" : "Click here to sort ascending";
234  
          S titleSorted = "Sorted by this field ("
235  
            + (descending ? "descending" : "ascending") + ")";
236  
          title = titleSorted + ". " + title;
237  
          html = span_title(title, unicode_downOrUpPointingTriangle(descending)) + " " + html;
238  
        }
239  
        S sortLink = appendQueryToURL(baseLink, sortParameter, showDescendingLink ? "-" + key : key);
240  
        replaceHeaders.put(htmlOld,
241  
          /*html + " " + ahref(sortLink,
242  
            unicode_smallDownOrUpPointingTriangle(showDescendingLink), +title)*/
243  
          ahref(sortLink, html));
244  
      }
245  
    
246  
    Map<S, O[]> paramsByColName = null;
247  
    if (cellColumnToolTips) {
248  
      paramsByColName = new Map;
249  
      for (MapSO map : l2)
250  
        for (S key : keys(map))
251  
          if (!paramsByColName.containsKey(key))
252  
            paramsByColName.put(key, litobjectarray(title := nullIfEmpty(htmldecode_dropTagsAndComments(key))));
253  
    }
254  
255  
    out.add(hpostform(
256  
      htmlTable2_noHtmlEncode(l2, paramsPlus(tableParams(), +replaceHeaders, +paramsByColName))
257  
        + (!withCmds || !showCheckBoxes ? "" : "\n" + divUnlessEmpty(renderBulkCmds())),
258  
      action := baseLink));
259  
      
260  
    if (showCheckBoxes && haveJQuery && enableMultiSelect)
261  
      out.add(hCheckBoxMultiSelect_v2());
262  
      
263  
    ret lines_rtrim(out);
264  
  }
265  
  
266  
  O mapToID(MapSO item) {
267  
    ret item == null ?: dropAllTags(strOrNull(item.get(encodeField(idField()))));
268  
  }
269  
  
270  
  // item is after encodeField
271  
  bool isHighlighted(MapSO item) {
272  
    O id = mapToID(item);
273  
    //print("isHighlighted ID: " + toStringWithClass(id) + " / " + toStringWithClass(objectIDToHighlight));
274  
    ret eq(id, objectIDToHighlight);
275  
  }
276  
  
277  
  swappable S renderBulkCmds() {
278  
    ret "Bulk action: " + hselect("bulkAction", litorderedmap("" := "", "deleteSelected" := "Delete selected"))
279  
      + " " + hsubmit("OK", onclick := "return confirm('Are you sure?')");
280  
  }
281  
  
282  
  MapSO addCmdsToTableRow(MapSO map, MapSO map2) {
283  
    if (showCheckBoxes) {
284  
      O id = itemID(map);
285  
      map2.put(checkBoxKey(), hcheckbox("obj_" + id, false, title := "Select this object for a bulk action", class := checkBoxClass));
286  
      map2 = putKeysFirst(map2, checkBoxKey());
287  
    }
288  
    map2.put(cmdsKey(), renderCmds(map));
289  
    if (cmdsLeft)
290  
      map2 = putKeysFirst(map2, cmdsKey());
291  
    ret map2;
292  
  }
293  
  
294  
  /*swappable*/ O[] tableParams() {
295  
    ret litparams(
296  
      tdParams := litparams(valign := "top"),
297  
      tableParams := litparams(class := tableClass));
298  
  }
299  
  
300  
  S renderForm(MapSO map) {
301  
    temp tempSetTL(htmlencode_forParams_useV2, true);
302  
    //print("renderForm: filteredFields=" + data.filteredFields());
303  
    map = mapMinusKeys(map, joinSets(unshownFields, uneditableFields, data.filteredFields()));
304  
    MapSO mapWithoutID = mapWithoutKey(map, data.idField());
305  
306  
    LLS matrix = map(mapWithoutID, (field, value) -> {
307  
      S help = data.fieldHelp(field);
308  
      ret ll(
309  
        allowFieldRenaming ? hinputfield("rename_" + field, field, class := "field-rename", style := "border: none; text-align: right", title := "Edit this to rename field " + quote(field) + " or clear to delete field") : encodeField(field),
310  
        addHelpText(help, renderInput(field, value))
311  
      );
312  
    });
313  
    massageFormMatrix(map, matrix);
314  
    
315  
    ret htableRaw_valignTop(matrix
316  
      , empty(formTableClass) ? litparams(border := 1, cellpadding := 4) : litparams(class := formTableClass));
317  
  }
318  
  
319  
  S renderInput(S field, O value) {
320  
    S name = fieldPrefix + field;
321  
    ret renderInput(name, data.getRenderer(field, value), value);
322  
  }
323  
  
324  
  S renderInput(S name, HCRUD_Data.Renderer r, O value) {
325  
    //print("Renderer for " + name + ": " + r);
326  
    if (r != null) value = r.preprocessValue(value);
327  
    
328  
    S meta = r == null ? "" : renderMetaInfo(r.metaInfo, name);
329  
    
330  
    // switch by renderer type
331  
332  
    if (r cast HCRUD_Data.AceEditor) {
333  
      //ret meta + hAceEditor(strOrEmpty(value), style := "width: " + r.cols + "ch; height: " + r.rows + "em", +name);
334  
      HTMLAceEditor ace = new(strOrEmpty(value));
335  
      ace.name = name;
336  
      ace.divParams.put(style := "width: " + r.cols + "ch; height: " + r.rows + "em");
337  
      customizeACEEditor(ace);
338  
      ret meta + ace.headStuff() + ace.html();
339  
    }
340  
341  
    if (r cast HCRUD_Data.TextArea)
342  
      ret meta + htextarea(strOrEmpty(value), +name, cols := r.cols, rows := r.rows);
343  
      
344  
    if (r cast HCRUD_Data.TextField)
345  
      ret meta + renderTextField(name, strOrEmpty(value), r.cols);
346  
347  
    if (r cast HCRUD_Data.ComboBox)
348  
      ret meta
349  
        + renderComboBox(name, r.valueToEntry(value), r.entries, r.editable);
350  
      
351  
    if (r cast HCRUD_Data.DynamicComboBox)
352  
      ret meta
353  
        + renderDynamicComboBox(name, r.valueToEntry(value), r.info, r.editable, r.url);
354  
      
355  
    if (r cast HCRUD_Data.CheckBox)
356  
      //ret hcheckbox(name, isTrue(value));
357  
      ret meta + htrickcheckboxWithText(name, "", isTrue(value));
358  
      
359  
    if (r cast HCRUD_Data.FlexibleLengthList) {
360  
      L list = cast value;
361  
      new LS rows;
362  
      
363  
      int n = l(list)+flexibleLengthListLeeway;
364  
      for i to n: {
365  
        O item = _get(list, i);
366  
        //print("Item: " + item);
367  
        rows.add(tr(td(i+1 + ".", align := "right")
368  
          + td(renderInput(name + "_" + i, r.itemRenderer, item))));
369  
      }
370  
      ret meta + htag table(lines(rows));
371  
    }
372  
    
373  
    if (r instanceof HCRUD_Data.NotEditable)
374  
      ret "Not editable";
375  
      
376  
    ret renderInput_default(name, value);
377  
  }
378  
  
379  
  swappable void customizeACEEditor(HTMLAceEditor ace) {}
380  
  
381  
  S renderMetaInfo(S metaInfo, S name) {
382  
    if (empty(metaInfo)) ret "";
383  
    ret hhidden("metaInfo_" + dropPrefix(fieldPrefix, name), metaInfo);
384  
  }
385  
  
386  
  S renderInput_default(S name, O value) {
387  
    ret renderTextField(name, strOrEmpty(value), defaultTextFieldCols);
388  
  }
389  
  
390  
  S renderTextField(S name, S value, int cols) {
391  
    if (showTextFieldsAsAutoExpandingTextAreas) {
392  
      ret htextarea(value, +name, class := "auto-expand",
393  
        style := "width: " + cols + "ch",
394  
        autofocus := eq(mapGet(params, "autofocus"), name) ? html_valueLessParam() : null,
395  
        onkeydown := jquery_submitFormOnCtrlEnter());
396  
    }
397  
398  
    ret htextfield(name, value, size := cols, style := "font-family: monospace");
399  
  }
400  
  
401  
  S renderNewForm() {
402  
    ret renderNewForm(data.emptyObject());
403  
  }
404  
  
405  
  // pre-populate fields from request parameters
406  
  S renderNewFormWithParams(SS params) {
407  
    SS filteredMap = subMapStartingWith_dropPrefix(params, fieldPrefix);
408  
    MapSO map = joinMaps(data.emptyObject(), (Map) filteredMap);
409  
    data.rawFormValues = params;
410  
    
411  
    // pre-populate list fields  
412  
    for (S key, value : filteredMap) {
413  
      LS l = splitAt(key, "_");
414  
      if (l(l) == 2) {
415  
        S field = first(l);
416  
        int idx = parseInt(second(l));
417  
        LS list = cast map.get(field);
418  
        if (!list instanceof ArrayList) map.put(field, list = new L);
419  
        listPut(list, idx, value);
420  
      }
421  
    }
422  
423  
    ret renderNewForm(map);
424  
  }
425  
  
426  
  S renderNewForm(MapSO map) {
427  
    //printStruct("renderNewForm", map);
428  
    S buttons = p(hsubmit("Create"));
429  
    ret hpostform(
430  
      hhidden("action", "create")
431  
      + formExtraHiddens()
432  
      + stringIf(buttonsOnTop, buttons)
433  
      + renderForm(map)
434  
      + stringIf(buttonsOnBottom, buttons),
435  
      paramsPlus(formParameters(), action := baseLink));
436  
  }
437  
  
438  
  swappable O[] formParameters() { ret litparams(id := formID); }
439  
  
440  
  S idField() { ret data.idField(); }
441  
  
442  
  S renderEditForm(S id) {
443  
    if (!actuallyAllowEdit())
444  
      ret "Can't edit objects in this table";
445  
      
446  
    if (!data.objectCanBeEdited(id))
447  
      ret htmlEncode2("Object " + id + " can't be edited");
448  
      
449  
    MapSO map = data.getObjectForEdit(id);
450  
    if (map == null) ret htmlEncode2("Entry " + id + " not found");
451  
    
452  
    S onlyFields = mapGet(params, "onlyFields");
453  
    if (nempty(onlyFields))
454  
      map = onlyKeys(map, itemPlus(idField(), tok_identifiersOnly(onlyFields)));
455  
456  
    S buttons = p_vbar(
457  
      hsubmit("Save changes"),
458  
      !showQuickSaveButton ? "" : 
459  
        hbuttonOnClick_noSubmit("Save & keep editing", [[
460  
          $.ajax({
461  
            type: 'POST',
462  
            url: $('#crudForm').attr('action'),
463  
            data: $('#crudForm').serialize(), 
464  
            success: function(response) { successNotification("Saved"); },
465  
          }).error(function() { errorNotification("Couldn't save"); });
466  
        ]]),
467  
      deleteObjectHTML(id));
468  
    ret hpostform(
469  
      hhidden("action", "update") +
470  
      formExtraHiddens() +
471  
      hhidden(+id) +
472  
      p("Object ID: " + htmlEncode2(id)) +
473  
      + stringIf(buttonsOnTop, buttons)
474  
      + renderForm(map)
475  
      + stringIf(buttonsOnBottom, buttons),
476  
      paramsPlus(formParameters(), action := baseLink + "#obj" + id));
477  
  }
478  
  
479  
  S renderPage(SS params) {
480  
    //print("HCRUD renderPage");
481  
    setParams(params);
482  
    
483  
    try answer handleComboSearch(params);
484  
    
485  
    if (eqGet(params, "cmd", "new")) {
486  
      if (!actuallyAllowCreate())
487  
        ret "Can't create objects in ths table";
488  
      ret frame(customTitleOr("New " + itemName()), renderNewFormWithParams(params));
489  
    }
490  
    
491  
    if (nempty(params.get("edit")))
492  
      ret frame("Edit " + itemName(), renderEditForm(params.get("edit")));
493  
      
494  
    if (nempty(params.get("duplicate")))
495  
      ret frame("New " + itemName(), renderNewForm(data.getObjectForDuplication(params.get("duplicate"))));
496  
      
497  
    S rendered = render(mutationRights, params);
498  
      
499  
    // handle commands, render list
500  
    S title = null;
501  
    if (singleton)
502  
      title = ahref(baseLink, firstToUpper(data.itemName()));
503  
    else {
504  
      if (objectIDToHighlight != null)
505  
        title = data.titleForObjectID(objectIDToHighlight);
506  
      if (empty(title))
507  
        title = (showEntryCountInTitle ? n2(entryCount) + " " : "")
508  
          + ahref(baseLink, firstToUpper(data.itemNamePlural()));
509  
    }
510  
511  
    ret frame(customTitleOr(title), rendered);
512  
  }
513  
  
514  
  HCRUD makeFrame(MakeFrame makeFrame) { super.makeFrame(makeFrame); this; }
515  
  
516  
  S cmdsKey() { ret "<!-- cmds -->"; }
517  
  S checkBoxKey() { ret "<!-- checkbox -->"; }
518  
  
519  
  S itemName() { ret data.itemName(); }
520  
  
521  
  swappable MapSO postProcessTableRow(MapSO data, MapSO rendered) { ret rendered; }
522  
  
523  
  O itemID(MapSO item) {
524  
    O id = mapGet(item, data.idField());
525  
    // getVarOpt decodes HTML record
526  
    if (cleanItemIDs) id = htmlDecode_dropTags(strOrNull(getVarOpt(id)));
527  
    ret id;
528  
  }
529  
  
530  
  long itemIDAsLong(MapSO item) {
531  
    ret parseLong(itemID(item));
532  
  }
533  
  
534  
  // return list of HTMLs for commands in pop down button
535  
  swappable LS additionalCmds(MapSO item) { null; }
536  
  
537  
  swappable S renderCmds(MapSO item) {
538  
    O id = itemID(item);
539  
    LS additionalCmds = additionalCmds(item);
540  
    ret joinNemptiesWithVBar(
541  
      !actuallyAllowEdit() || !data.objectCanBeEdited(id) ? null : ahref(editLink(id), "EDIT"),
542  
      deleteObjectHTML(id),
543  
      !actuallyAllowCreate() ? null : targetBlankIf(duplicateInNewTab, duplicateLink(id), "dup", title := "duplicate"),
544  
      empty(additionalCmds) ? null : hPopDownButton(additionalCmds)
545  
    );
546  
  }
547  
  
548  
  bool actuallyAllowCreate() {
549  
    ret !singleton && allowCreateOrDelete && allowCreate;
550  
  }
551  
  
552  
  bool actuallyAllowEdit() {
553  
    ret allowCreateOrDelete && allowEdit;
554  
  }
555  
  
556  
  bool actuallyAllowDelete() {
557  
    ret !singleton && allowCreateOrDelete;
558  
  }
559  
  
560  
  // e.g. for adding rows to edit/create form
561  
  swappable void massageFormMatrix(MapSO map, LLS matrix) {
562  
  }
563  
  
564  
  S renderComboBox(S name, S value, LS entries, bool editable) {
565  
    if (haveSelectizeJS) {
566  
      // coolest option - use selectize.js
567  
      S id = aGlobalID();
568  
      ret hselect_list(entries, value, +name, +id)
569  
        + hjs("$('#" + id + "').selectize" + [[
570  
          ({
571  
            searchField: 'text',
572  
            openOnFocus: true,
573  
            dropdownParent: 'body',
574  
            create: ]] + jsBool(editable) + [[
575  
            /*allowEmptyOption: true*/
576  
            ]]
577  
            + unnull(moreSelectizeOptions2(name)) + [[
578  
          });
579  
        ]])
580  
        + selectizeLayoutFix();
581  
    }
582  
      
583  
    if (haveJQuery) {
584  
      // make searchable list if JQuery is available
585  
      // (functional, but looks quite poor on Firefox)
586  
      // TODO: this seems to always be editable
587  
      S id = aGlobalID();
588  
      ret tag datalist(mapToLines hoption(entries), +id)
589  
        + tag input("", +name, list := id);
590  
    }
591  
    
592  
    // standard non-searchable list
593  
    if (editable)
594  
      ret hinputfield(name, value); // no editable combo box possible without selectize.js
595  
    ret hselect_list(entries, value, +name);
596  
  }
597  
  
598  
  S renderDynamicComboBox(S name, S value, S info, bool editable, S url default null) {
599  
    assertTrue(+haveSelectizeJS);
600  
    S id = aGlobalID();
601  
    S ajaxURL = or2(url, baseLink);
602  
    ret hselect_list(llNempties(value), value, +name, +id)
603  
      + hjs("$('#" + id + "').selectize" + [[
604  
        ({
605  
          searchField: 'text',
606  
          valueField: 'text',
607  
          labelField: 'text',
608  
          openOnFocus: true,
609  
          dropdownParent: 'body',
610  
          create: ]] + jsBool(editable) + [[,
611  
          load: function(query, callback) {
612  
            if (!query.length) return callback();
613  
            var data = {
614  
              comboSearchInfo: ]] + jsQuote(info) + [[,
615  
              comboSearchQuery: query
616  
            };
617  
            console.log("Loading " + ]] + jsQuote(baseLink) + [[ + " with " + JSON.stringify(data));
618  
            $.ajax({
619  
              url: ]] + jsQuote(ajaxURL) + [[,
620  
              type: 'GET',
621  
              dataType: 'json',
622  
              data: data,
623  
              error: function() {
624  
                console.log("Got error");
625  
                callback();
626  
              },
627  
              success: function(res) {
628  
                //console.log("Got data: " + res);
629  
                var converted = res.map(x => { return {text: x}; });
630  
                //console.log("Converted: " + converted);
631  
                callback(converted);
632  
              }
633  
            });
634  
          }
635  
          /*allowEmptyOption: true*/
636  
          ]] + moreSelectizeOptions2(name) + [[
637  
        });
638  
      ]])
639  
      + selectizeLayoutFix();
640  
  }
641  
      
642  
  void processSortParameter(SS params) {
643  
    S sort = mapGet(params, sortParameter);
644  
    //sortByField = null;
645  
    if (nempty(sort))
646  
      if (startsWith(sort, "-")) {
647  
        descending = true;
648  
        sortByField = substring(sort, 1);
649  
      } else {
650  
        descending = false;
651  
        sortByField = sort;
652  
      }
653  
  }
654  
  
655  
  S deleteObjectHTML(O id) {
656  
    ret !actuallyAllowDelete() ? null : 
657  
      !data.objectCanBeDeleted(id) ?
658  
        // TODO: custom msg!
659  
        span_title("Object can't be deleted, either there are references to it or you are not authorized", htmlEncode2(unicode_DEL()))
660  
        : ahrefWithConfirm(
661  
          "Really delete item " + id + "?", deleteLink(id), htmlEncode2(unicode_DEL()), title := "delete");
662  
  }
663  
  
664  
  // helpText is also HTML
665  
  S addHelpText(S helpText, S html) {
666  
    ret empty(helpText) ? html : html + p(small(helpText), style := "text-align: right");
667  
  }
668  
  
669  
  S moreSelectizeOptions2(S name) {
670  
    ret unnull(moreSelectizeOptions(name))
671  
      + (!haveSelectizeClickable ? "" : [[
672  
        , plugins: ['clickable']
673  
        , render: {
674  
            option: function(item) {
675  
              var id = item.text.match(/\d+/)[0];
676  
              return '<div><span>'+item.text+'</span>'
677  
                  + '<div style="float: right">'
678  
                  + '<a title="Go to object" class="clickable" href="/' + id + '" target="_blank">&#8599;</a>'
679  
                  + '</div></div>';
680  
            }
681  
          }
682  
        ]]);
683  
  }
684  
  
685  
  swappable S moreSelectizeOptions(S name) { ret ""; }
686  
687  
  // deliver content for dynamic search in combo boxes
688  
  S handleComboSearch(SS params) {
689  
    S query = params.get("comboSearchQuery");
690  
    if (nempty(query)) {
691  
      S info = params.get("comboSearchInfo");
692  
      ret jsonEncode_shallowLineBreaks(data.comboBoxSearch(info, query));
693  
    }
694  
    null;
695  
  }
696  
  
697  
  // for update or create
698  
  swappable SS preprocessUpdateParams(SS params) { ret params; }
699  
  
700  
  void disableAllMutationRights() {
701  
    mutationRights = allowCreateOrDelete = allowCreate
702  
      = allowEdit = false;
703  
  }
704  
  
705  
  swappable bool keepParamInPagination(S name) {
706  
    ret eq(name, "search");
707  
  }
708  
  
709  
  S customTitleOr(S title) {
710  
    ret or2(customTitle, or2(mapGet(params, "title"), title));
711  
  }
712  
  
713  
  swappable S selectizeLayoutFix() {
714  
    // dirty CSS quick-fix (TODO: send only once)
715  
    ret hcss(".selectize-input, .selectize-control { min-width: 300px }");
716  
  }
717  
  
718  
  S formExtraHiddens() {
719  
    S redirectAfterSave = mapGet(params, "redirectAfterSave");
720  
    ret empty(redirectAfterSave) ? "" : hhidden(+redirectAfterSave);
721  
  }
722  
  
723  
  void processRenames(SS params) {
724  
    if (!allowFieldRenaming) ret;
725  
    for (S key1 : keysList(params)) {
726  
      S field = dropPrefixOrNull("rename_", key1);
727  
      if (field == null) continue;
728  
      
729  
      S newName = trim(params.get(key1));
730  
      if (newName == null || eq(field, newName)) continue;
731  
      print("Renaming " + field + " to " + or2(newName, "<deleted>"));
732  
      params.remove("rename_" + field);
733  
      
734  
      // Try to catch f_myField, metaInfo_myField, f_myField_1...
735  
      S re = "^([^_]+_)" + regexpQuote(field) + "(_[^_]+)?$";
736  
      for (S key : keysList(params)) {
737  
        LS groups = regexpGroups(re, key);
738  
        if (groups != null) {
739  
          S newKey = empty(newName) ? null : first(groups) + newName + unnull(second(groups));
740  
          mapPut(params, newKey, params.get(key));
741  
          params.put(key, ""); // this should delete stuff (if empty strings are converted to null)
742  
          print("Renaming key: " + key + " => " + newKey);
743  
        }
744  
      }
745  
    }
746  
  }
747  
}

download  show line numbers  debug dex  old transpilations   

Travelled to 8 computer(s): bhatertpkbcr, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv

No comments. add comment

Snippet ID: #1026001
Snippet name: HCRUD - CRUD in HTML with pluggable data handler
Eternal ID of this version: #1026001/318
Text MD5: 5c56764e06575b9f3977bee2c9449b9c
Author: stefan
Category: javax / html
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2021-10-06 02:58:04
Source code size: 26208 bytes / 747 lines
Pitched / IR pitched: No / No
Views / Downloads: 1249 / 2778
Version history: 317 change(s)
Referenced in: [show references]