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.

// one instance should only be used for one page at a time
sclass HCRUD extends HAbstractRenderable {
  HCRUD_Data data; // normally an instance of HCRUD_Concepts (#1026002)
  
  // yeah these fields are a mess...
  bool mutationRights = true;
  bool allowCreateOrDelete = true;
  bool allowCreate = true;
  bool allowEdit = true;
  bool singleton;
  
  bool cmdsLeft; // put commands on the left instead of the right
  bool showEntryCountInTitle;
  bool allowFieldRenaming;
  int defaultTextFieldCols = 80;
  int valueDisplayLength = 1000;
  S tableClass; // CSS class for table
  S formTableClass; // CSS class for form
  S checkBoxClass = "crud_chkbox";
  S customTitle;
  bool showTextFieldsAsAutoExpandingTextAreas;
  Set<S> unshownFields; // not shown in table or form
  Set<S> uneditableFields; // not shown in form
  Set<S> unlistedFields; // not shown in table
  bool showCheckBoxes;
  bool cleanItemIDs;
  
  bool haveJQuery, haveSelectizeJS, haveSelectizeClickable;
  bool needsJQuery; // unused
  bool paginate;
  bool sortable;
  bool buttonsOnTop, buttonsOnBottom = true;
  bool duplicateInNewTab;
  S formID = "crudForm";
  bool showQuickSaveButton; // needs hnotificationPopups()
  bool enableMultiSelect = true; // enables shift+click on check boxes. needs haveJQuery
  bool cellColumnToolTips; // give each table cell a tooltip showing its column name
  bool showSearchField;
  S searchQuery;
  
  SS params;
  new HTMLPaginator paginator;
  
  // sort options
  S sortByField = "id";
  S sortParameter = "sort";
  bool descending;
  
  int flexibleLengthListLeeway = 5; // how many empty rows to display at the end of each flexible length list
  
  // set internally after update/create or from "selectObj" param to highlight an item in the list (or show only that item)
  O objectIDToHighlight;
  bool showOnlySelected;
  
  S fieldPrefix = "f_";
  
  int entryCount;
  
  *() {}
  *(HCRUD_Data *data) {}
  *(S *baseLink, HCRUD_Data *data) {}
  
  S newLink() { ret appendQueryToURL(baseLink, cmd := "new"); }
  S deleteLink(O id) { ret appendQueryToURL(baseLink, "delete_" + id, 1); }
  S editLink(O id) { ret appendQueryToURL(baseLink, edit := id); }
  S duplicateLink(O id) { ret appendQueryToURL(baseLink, duplicate := id); }
  
  void setParams(SS params) {
    this.params = params;
    
    if (objectIDToHighlight == null) objectIDToHighlight = params.get("selectObj");
    if (eq("1", params.get("showOnlySelected")))
      set showOnlySelected;
  }
  
  // also handles commands if withCmds=true
  // you probably want to call renderPage() instead to handle all commands
  S render(bool withCmds, SS params) {
    //print("HCRUD render");
    setParams(params);
    
    if (!withCmds) ret renderTable(false);
    
    try answer handleCommands(params);
    
    ret renderMsgs(params)
      + divUnlessEmpty(nav())
      + renderTable(withCmds);
  }
  
  swappable S nav() {
    new LS l;
    if (actuallyAllowCreate())
      l.add(ahref(newLink(), "New " + itemName()));
    if (showSearchField)
      l.add(hInlineSearchForm("search", searchQuery, ""));
    ret joinWithVBar(l);
  }

  S handleCommands(SS params) {
    new LS msgs;
    
    if (eqGet(params, "action", "create")) {
      if (!actuallyAllowCreate()) fail("Creating objects not allowed");
      processRenames(params);
      O id = data.createObject(preprocessUpdateParams(params), fieldPrefix);
      msgs.add(itemName() + " created (ID: " + id + ")");
      objectIDToHighlight = id;
    }
    
    if (eqGet(params, "action", "update")) {
      if (!actuallyAllowEdit()) fail("Editing objects not allowed");
      S id = params.get("id");
      processRenames(params);
      msgs.add(data.updateObject(id, preprocessUpdateParams(params), fieldPrefix));
      objectIDToHighlight = id;
    }

    LS toDeleteList = keysDeprefixNemptyValue(params, "delete_");
    if (eq(params.get("bulkAction"), "deleteSelected"))
      toDeleteList.addAll(keysDeprefixNemptyValue(params, "obj_"));
      
    for (S toDelete : toDeleteList) {
      if (!actuallyAllowDelete()) fail("Deleting objects not allowed");
      msgs.add(data.deleteObject(toDelete));
    }

    ret empty(msgs) ? "" : refreshAfterCommand(params, msgs);
  }
  
  swappable S refreshAfterCommand(SS params, LS msgs) {
    S redirectAfterSave = mapGet(params, "redirectAfterSave");
    if (nempty(redirectAfterSave)) ret hrefresh(redirectAfterSave);
    
    ret refreshWithMsgs(msgs,
      anchor := objectIDToHighlight == null ? null : "obj" + objectIDToHighlight,
      params := objectIDToHighlight == null ? null : litmap(selectObj := objectIDToHighlight));
  }
  
  S encodeField(S s) {
    ret or(data.fieldNameToHTML(s), s);
  }
  
  // in table
  swappable S renderValue(S field, O value) {
    if (value cast HTML) ret value.html;
    value = deref(value);
    if (value cast SecretValue)
      ret hhiddenStuff(renderValue_inner(value!));
    ret renderValue_inner(value);
  }

  swappable S renderValue_inner(O value) {  
    if (value cast Bool)
      ret yesNo_short(value);
    ret htmlEncode_nlToBr_withIndents(shorten(valueDisplayLength, strOrEmpty(value)));
  }
  
  S renderTable(bool withCmds) {
    ret renderTable(withCmds, data.list());
  }
  
  S valueToSortable(O value) {
    if (value cast HTML)
      ret value!;
    ret strOrNull(value);
  }
  
  // l = list of maps as it comes from data object
  S renderTable(bool withCmds, L<MapSO> l) {
    //print("HCRUD renderTable");
    entryCount = l(l);
    if (empty(l)) ret p("No entries");
    if (!eq(data.defaultSortField(), pair(sortByField, descending))) {
      if (nempty(sortByField)) {
        print("Sorting " + nEntries(l) + " by " + sortByField);
        l = sortByTransformedMapKey_alphaNum valueToSortable(l, sortByField);
      }
      if (descending) l = reversed(l);
    }
    
    // l2 is the rendered map. use keyEncoding to get from l keys to l2 keys
    new SS keyEncoding;
    L<MapSO> l2 = lazyMap(l, _map -> {
      O id = itemID(_map);
      ret data.new Item(id) {
        public MapSO calcFullMap() {
          MapSO map = _map;
          map = mapMinusKeys(map, joinSets(unshownFields, unlistedFields));
          MapSO map2 = postProcessTableRow(map, mapToMap(
            (key, value) -> pair(mapPut_returnValue(keyEncoding, key, encodeField(key)), renderValue(key, value)),
            map));
          if (singleton)
            map2.remove(encodeField(data.idField())); // don't show ID in table in singleton mode
          if (withCmds)
            map2 = addCmdsToTableRow(map, map2);
          // add anchor to row
          map2.put(firstKey(map2), aname("obj" + id, firstValue(map2)));
          ret map2;
        }
      };
    });
    
    new LS out;
    
    if (paginate) {
      paginator.processParams(params);
      paginator.baseLink = addParamsToURL(baseLink,
        filterKeys keepParamInPagination(params));
      paginator.max = l(l2);
      out.add(divUnlessEmpty(paginator.renderNav()));
      
      L<MapSO> l3 = subListOrFull(l2, paginator.visibleRange());
      
      //printVars_str(+objectIDToHighlight, visible := l(l3), first := first(l3), firstID := mapToID(first(l3)));

      // if highlighted object is not on page or user wants to see only this object, show only this object
      if (objectIDToHighlight != null && (showOnlySelected || !any isHighlighted(l3))) {
        l3 = llNonNulls(firstThat isHighlighted(l2));
        //printVars_str(+objectIDToHighlight, total := l(l2), found := l(l3));
      }

      l2 = l3;
    }
    
    new SS replaceHeaders;
    if (sortable && !singleton)
      for (S key, html : keyEncoding) {
        bool sortedByField = eq(sortByField, key);
        bool showDescendingLink = sortedByField && !descending;
        S htmlOld = html;
        if (sortedByField) {
          S title = showDescendingLink ? "Click here to sort descending" : "Click here to sort ascending";
          S titleSorted = "Sorted by this field ("
            + (descending ? "descending" : "ascending") + ")";
          title = titleSorted + ". " + title;
          html = span_title(title, unicode_downOrUpPointingTriangle(descending)) + " " + html;
        }
        S sortLink = appendQueryToURL(baseLink, sortParameter, showDescendingLink ? "-" + key : key);
        replaceHeaders.put(htmlOld,
          /*html + " " + ahref(sortLink,
            unicode_smallDownOrUpPointingTriangle(showDescendingLink), +title)*/
          ahref(sortLink, html));
      }
    
    Map<S, O[]> paramsByColName = null;
    if (cellColumnToolTips) {
      paramsByColName = new Map;
      for (MapSO map : l2)
        for (S key : keys(map))
          if (!paramsByColName.containsKey(key))
            paramsByColName.put(key, litobjectarray(title := nullIfEmpty(htmldecode_dropTagsAndComments(key))));
    }

    out.add(hpostform(
      htmlTable2_noHtmlEncode(l2, paramsPlus(tableParams(), +replaceHeaders, +paramsByColName))
        + (!withCmds || !showCheckBoxes ? "" : "\n" + divUnlessEmpty(renderBulkCmds())),
      action := baseLink));
      
    if (showCheckBoxes && haveJQuery && enableMultiSelect)
      out.add(hCheckBoxMultiSelect_v2());
      
    ret lines_rtrim(out);
  }
  
  O mapToID(MapSO item) {
    ret item == null ?: dropAllTags(strOrNull(item.get(encodeField(idField()))));
  }
  
  // item is after encodeField
  bool isHighlighted(MapSO item) {
    O id = mapToID(item);
    //print("isHighlighted ID: " + toStringWithClass(id) + " / " + toStringWithClass(objectIDToHighlight));
    ret eq(id, objectIDToHighlight);
  }
  
  swappable S renderBulkCmds() {
    ret "Bulk action: " + hselect("bulkAction", litorderedmap("" := "", "deleteSelected" := "Delete selected"))
      + " " + hsubmit("OK", onclick := "return confirm('Are you sure?')");
  }
  
  MapSO addCmdsToTableRow(MapSO map, MapSO map2) {
    if (showCheckBoxes) {
      O id = itemID(map);
      map2.put(checkBoxKey(), hcheckbox("obj_" + id, false, title := "Select this object for a bulk action", class := checkBoxClass));
      map2 = putKeysFirst(map2, checkBoxKey());
    }
    map2.put(cmdsKey(), renderCmds(map));
    if (cmdsLeft)
      map2 = putKeysFirst(map2, cmdsKey());
    ret map2;
  }
  
  /*swappable*/ O[] tableParams() {
    ret litparams(
      tdParams := litparams(valign := "top"),
      tableParams := litparams(class := tableClass));
  }
  
  S renderForm(MapSO map) {
    temp tempSetTL(htmlencode_forParams_useV2, true);
    //print("renderForm: filteredFields=" + data.filteredFields());
    map = mapMinusKeys(map, joinSets(unshownFields, uneditableFields, data.filteredFields()));
    MapSO mapWithoutID = mapWithoutKey(map, data.idField());

    LLS matrix = map(mapWithoutID, (field, value) -> {
      S help = data.fieldHelp(field);
      ret ll(
        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),
        addHelpText(help, renderInput(field, value))
      );
    });
    massageFormMatrix(map, matrix);
    
    ret htableRaw_valignTop(matrix
      , empty(formTableClass) ? litparams(border := 1, cellpadding := 4) : litparams(class := formTableClass));
  }
  
  S renderInput(S field, O value) {
    S name = fieldPrefix + field;
    ret renderInput(name, data.getRenderer(field, value), value);
  }
  
  S renderInput(S name, HCRUD_Data.Renderer r, O value) {
    //print("Renderer for " + name + ": " + r);
    if (r != null) value = r.preprocessValue(value);
    
    S meta = r == null ? "" : renderMetaInfo(r.metaInfo, name);
    
    // switch by renderer type

    if (r cast HCRUD_Data.AceEditor) {
      //ret meta + hAceEditor(strOrEmpty(value), style := "width: " + r.cols + "ch; height: " + r.rows + "em", +name);
      HTMLAceEditor ace = new(strOrEmpty(value));
      ace.name = name;
      ace.divParams.put(style := "width: " + r.cols + "ch; height: " + r.rows + "em");
      customizeACEEditor(ace);
      ret meta + ace.headStuff() + ace.html();
    }

    if (r cast HCRUD_Data.TextArea)
      ret meta + htextarea(strOrEmpty(value), +name, cols := r.cols, rows := r.rows);
      
    if (r cast HCRUD_Data.TextField)
      ret meta + renderTextField(name, strOrEmpty(value), r.cols);

    if (r cast HCRUD_Data.ComboBox)
      ret meta
        + renderComboBox(name, r.valueToEntry(value), r.entries, r.editable);
      
    if (r cast HCRUD_Data.DynamicComboBox)
      ret meta
        + renderDynamicComboBox(name, r.valueToEntry(value), r.info, r.editable, r.url);
      
    if (r cast HCRUD_Data.CheckBox)
      //ret hcheckbox(name, isTrue(value));
      ret meta + htrickcheckboxWithText(name, "", isTrue(value));
      
    if (r cast HCRUD_Data.FlexibleLengthList) {
      L list = cast value;
      new LS rows;
      
      int n = l(list)+flexibleLengthListLeeway;
      for i to n: {
        O item = _get(list, i);
        //print("Item: " + item);
        rows.add(tr(td(i+1 + ".", align := "right")
          + td(renderInput(name + "_" + i, r.itemRenderer, item))));
      }
      ret meta + htag table(lines(rows));
    }
    
    if (r instanceof HCRUD_Data.NotEditable)
      ret "Not editable";
      
    ret renderInput_default(name, value);
  }
  
  swappable void customizeACEEditor(HTMLAceEditor ace) {}
  
  S renderMetaInfo(S metaInfo, S name) {
    if (empty(metaInfo)) ret "";
    ret hhidden("metaInfo_" + dropPrefix(fieldPrefix, name), metaInfo);
  }
  
  S renderInput_default(S name, O value) {
    ret renderTextField(name, strOrEmpty(value), defaultTextFieldCols);
  }
  
  S renderTextField(S name, S value, int cols) {
    if (showTextFieldsAsAutoExpandingTextAreas) {
      ret htextarea(value, +name, class := "auto-expand",
        style := "width: " + cols + "ch",
        autofocus := eq(mapGet(params, "autofocus"), name) ? html_valueLessParam() : null,
        onkeydown := jquery_submitFormOnCtrlEnter());
    }

    ret htextfield(name, value, size := cols, style := "font-family: monospace");
  }
  
  S renderNewForm() {
    ret renderNewForm(data.emptyObject());
  }
  
  // pre-populate fields from request parameters
  S renderNewFormWithParams(SS params) {
    SS filteredMap = subMapStartingWith_dropPrefix(params, fieldPrefix);
    MapSO map = joinMaps(data.emptyObject(), (Map) filteredMap);
    data.rawFormValues = params;
    
    // pre-populate list fields  
    for (S key, value : filteredMap) {
      LS l = splitAt(key, "_");
      if (l(l) == 2) {
        S field = first(l);
        int idx = parseInt(second(l));
        LS list = cast map.get(field);
        if (!list instanceof ArrayList) map.put(field, list = new L);
        listPut(list, idx, value);
      }
    }

    ret renderNewForm(map);
  }
  
  S renderNewForm(MapSO map) {
    //printStruct("renderNewForm", map);
    S buttons = p(hsubmit("Create"));
    ret hpostform(
      hhidden("action", "create")
      + formExtraHiddens()
      + stringIf(buttonsOnTop, buttons)
      + renderForm(map)
      + stringIf(buttonsOnBottom, buttons),
      paramsPlus(formParameters(), action := baseLink));
  }
  
  swappable O[] formParameters() { ret litparams(id := formID); }
  
  S idField() { ret data.idField(); }
  
  S renderEditForm(S id) {
    if (!actuallyAllowEdit())
      ret "Can't edit objects in this table";
      
    if (!data.objectCanBeEdited(id))
      ret htmlEncode2("Object " + id + " can't be edited");
      
    MapSO map = data.getObjectForEdit(id);
    if (map == null) ret htmlEncode2("Entry " + id + " not found");
    
    S onlyFields = mapGet(params, "onlyFields");
    if (nempty(onlyFields))
      map = onlyKeys(map, itemPlus(idField(), tok_identifiersOnly(onlyFields)));

    S buttons = p_vbar(
      hsubmit("Save changes"),
      !showQuickSaveButton ? "" : 
        hbuttonOnClick_noSubmit("Save & keep editing", [[
          $.ajax({
            type: 'POST',
            url: $('#crudForm').attr('action'),
            data: $('#crudForm').serialize(), 
            success: function(response) { successNotification("Saved"); },
          }).error(function() { errorNotification("Couldn't save"); });
        ]]),
      deleteObjectHTML(id));
    ret hpostform(
      hhidden("action", "update") +
      formExtraHiddens() +
      hhidden(+id) +
      p("Object ID: " + htmlEncode2(id)) +
      + stringIf(buttonsOnTop, buttons)
      + renderForm(map)
      + stringIf(buttonsOnBottom, buttons),
      paramsPlus(formParameters(), action := baseLink + "#obj" + id));
  }
  
  S renderPage(SS params) {
    //print("HCRUD renderPage");
    setParams(params);
    
    try answer handleComboSearch(params);
    
    if (eqGet(params, "cmd", "new")) {
      if (!actuallyAllowCreate())
        ret "Can't create objects in ths table";
      ret frame(customTitleOr("New " + itemName()), renderNewFormWithParams(params));
    }
    
    if (nempty(params.get("edit")))
      ret frame("Edit " + itemName(), renderEditForm(params.get("edit")));
      
    if (nempty(params.get("duplicate")))
      ret frame("New " + itemName(), renderNewForm(data.getObjectForDuplication(params.get("duplicate"))));
      
    S rendered = render(mutationRights, params);
      
    // handle commands, render list
    S title = null;
    if (singleton)
      title = ahref(baseLink, firstToUpper(data.itemName()));
    else {
      if (objectIDToHighlight != null)
        title = data.titleForObjectID(objectIDToHighlight);
      if (empty(title))
        title = (showEntryCountInTitle ? n2(entryCount) + " " : "")
          + ahref(baseLink, firstToUpper(data.itemNamePlural()));
    }

    ret frame(customTitleOr(title), rendered);
  }
  
  HCRUD makeFrame(MakeFrame makeFrame) { super.makeFrame(makeFrame); this; }
  
  S cmdsKey() { ret "<!-- cmds -->"; }
  S checkBoxKey() { ret "<!-- checkbox -->"; }
  
  S itemName() { ret data.itemName(); }
  
  swappable MapSO postProcessTableRow(MapSO data, MapSO rendered) { ret rendered; }
  
  O itemID(MapSO item) {
    O id = mapGet(item, data.idField());
    // getVarOpt decodes HTML record
    if (cleanItemIDs) id = htmlDecode_dropTags(strOrNull(getVarOpt(id)));
    ret id;
  }
  
  long itemIDAsLong(MapSO item) {
    ret parseLong(itemID(item));
  }
  
  // return list of HTMLs for commands in pop down button
  swappable LS additionalCmds(MapSO item) { null; }
  
  swappable S renderCmds(MapSO item) {
    O id = itemID(item);
    LS additionalCmds = additionalCmds(item);
    ret joinNemptiesWithVBar(
      !actuallyAllowEdit() || !data.objectCanBeEdited(id) ? null : ahref(editLink(id), "EDIT"),
      deleteObjectHTML(id),
      !actuallyAllowCreate() ? null : targetBlankIf(duplicateInNewTab, duplicateLink(id), "dup", title := "duplicate"),
      empty(additionalCmds) ? null : hPopDownButton(additionalCmds)
    );
  }
  
  bool actuallyAllowCreate() {
    ret !singleton && allowCreateOrDelete && allowCreate;
  }
  
  bool actuallyAllowEdit() {
    ret allowCreateOrDelete && allowEdit;
  }
  
  bool actuallyAllowDelete() {
    ret !singleton && allowCreateOrDelete;
  }
  
  // e.g. for adding rows to edit/create form
  swappable void massageFormMatrix(MapSO map, LLS matrix) {
  }
  
  S renderComboBox(S name, S value, LS entries, bool editable) {
    if (haveSelectizeJS) {
      // coolest option - use selectize.js
      S id = aGlobalID();
      ret hselect_list(entries, value, +name, +id)
        + hjs("$('#" + id + "').selectize" + [[
          ({
            searchField: 'text',
            openOnFocus: true,
            dropdownParent: 'body',
            create: ]] + jsBool(editable) + [[
            /*allowEmptyOption: true*/
            ]]
            + unnull(moreSelectizeOptions2(name)) + [[
          });
        ]])
        + selectizeLayoutFix();
    }
      
    if (haveJQuery) {
      // make searchable list if JQuery is available
      // (functional, but looks quite poor on Firefox)
      // TODO: this seems to always be editable
      S id = aGlobalID();
      ret tag datalist(mapToLines hoption(entries), +id)
        + tag input("", +name, list := id);
    }
    
    // standard non-searchable list
    if (editable)
      ret hinputfield(name, value); // no editable combo box possible without selectize.js
    ret hselect_list(entries, value, +name);
  }
  
  S renderDynamicComboBox(S name, S value, S info, bool editable, S url default null) {
    assertTrue(+haveSelectizeJS);
    S id = aGlobalID();
    S ajaxURL = or2(url, baseLink);
    ret hselect_list(llNempties(value), value, +name, +id)
      + hjs("$('#" + id + "').selectize" + [[
        ({
          searchField: 'text',
          valueField: 'text',
          labelField: 'text',
          openOnFocus: true,
          dropdownParent: 'body',
          create: ]] + jsBool(editable) + [[,
          load: function(query, callback) {
            if (!query.length) return callback();
            var data = {
              comboSearchInfo: ]] + jsQuote(info) + [[,
              comboSearchQuery: query
            };
            console.log("Loading " + ]] + jsQuote(baseLink) + [[ + " with " + JSON.stringify(data));
            $.ajax({
              url: ]] + jsQuote(ajaxURL) + [[,
              type: 'GET',
              dataType: 'json',
              data: data,
              error: function() {
                console.log("Got error");
                callback();
              },
              success: function(res) {
                //console.log("Got data: " + res);
                var converted = res.map(x => { return {text: x}; });
                //console.log("Converted: " + converted);
                callback(converted);
              }
            });
          }
          /*allowEmptyOption: true*/
          ]] + moreSelectizeOptions2(name) + [[
        });
      ]])
      + selectizeLayoutFix();
  }
      
  void processSortParameter(SS params) {
    S sort = mapGet(params, sortParameter);
    //sortByField = null;
    if (nempty(sort))
      if (startsWith(sort, "-")) {
        descending = true;
        sortByField = substring(sort, 1);
      } else {
        descending = false;
        sortByField = sort;
      }
  }
  
  S deleteObjectHTML(O id) {
    ret !actuallyAllowDelete() ? null : 
      !data.objectCanBeDeleted(id) ?
        // TODO: custom msg!
        span_title("Object can't be deleted, either there are references to it or you are not authorized", htmlEncode2(unicode_DEL()))
        : ahrefWithConfirm(
          "Really delete item " + id + "?", deleteLink(id), htmlEncode2(unicode_DEL()), title := "delete");
  }
  
  // helpText is also HTML
  S addHelpText(S helpText, S html) {
    ret empty(helpText) ? html : html + p(small(helpText), style := "text-align: right");
  }
  
  S moreSelectizeOptions2(S name) {
    ret unnull(moreSelectizeOptions(name))
      + (!haveSelectizeClickable ? "" : [[
        , plugins: ['clickable']
        , render: {
            option: function(item) {
              var id = item.text.match(/\d+/)[0];
              return '<div><span>'+item.text+'</span>'
                  + '<div style="float: right">'
                  + '<a title="Go to object" class="clickable" href="/' + id + '" target="_blank">&#8599;</a>'
                  + '</div></div>';
            }
          }
        ]]);
  }
  
  swappable S moreSelectizeOptions(S name) { ret ""; }

  // deliver content for dynamic search in combo boxes
  S handleComboSearch(SS params) {
    S query = params.get("comboSearchQuery");
    if (nempty(query)) {
      S info = params.get("comboSearchInfo");
      ret jsonEncode_shallowLineBreaks(data.comboBoxSearch(info, query));
    }
    null;
  }
  
  // for update or create
  swappable SS preprocessUpdateParams(SS params) { ret params; }
  
  void disableAllMutationRights() {
    mutationRights = allowCreateOrDelete = allowCreate
      = allowEdit = false;
  }
  
  swappable bool keepParamInPagination(S name) {
    ret eq(name, "search");
  }
  
  S customTitleOr(S title) {
    ret or2(customTitle, or2(mapGet(params, "title"), title));
  }
  
  swappable S selectizeLayoutFix() {
    // dirty CSS quick-fix (TODO: send only once)
    ret hcss(".selectize-input, .selectize-control { min-width: 300px }");
  }
  
  S formExtraHiddens() {
    S redirectAfterSave = mapGet(params, "redirectAfterSave");
    ret empty(redirectAfterSave) ? "" : hhidden(+redirectAfterSave);
  }
  
  void processRenames(SS params) {
    if (!allowFieldRenaming) ret;
    for (S key1 : keysList(params)) {
      S field = dropPrefixOrNull("rename_", key1);
      if (field == null) continue;
      
      S newName = trim(params.get(key1));
      if (newName == null || eq(field, newName)) continue;
      print("Renaming " + field + " to " + or2(newName, "<deleted>"));
      params.remove("rename_" + field);
      
      // Try to catch f_myField, metaInfo_myField, f_myField_1...
      S re = "^([^_]+_)" + regexpQuote(field) + "(_[^_]+)?$";
      for (S key : keysList(params)) {
        LS groups = regexpGroups(re, key);
        if (groups != null) {
          S newKey = empty(newName) ? null : first(groups) + newName + unnull(second(groups));
          mapPut(params, newKey, params.get(key));
          params.put(key, ""); // this should delete stuff (if empty strings are converted to null)
          print("Renaming key: " + key + " => " + newKey);
        }
      }
    }
  }
}

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: 1351 / 2899
Version history: 317 change(s)
Referenced in: [show references]