sclass HCRUD extends HAbstractRenderable { HCRUD_Data data; int defaultTextFieldCols = 80; bool mutationRights = true; bool allowCreateOrDelete = true; bool singleton; bool cmdsLeft; // put commands on the left instead of the right S tableClass; // CSS class for table Set uneditableFields, unshownFields; bool needsJQuery; // unused *() {} *(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); } // also handles commands if withCmds=true // you probably want to call renderPage() instead to handle all commands S render(bool withCmds, SS params) { if (!withCmds) ret renderTable(false); try answer handleCommands(params); ret renderMsgs(params) pUnlessEmpty(makeNav()) + renderTable(withCmds); } swappable S makeNav() { ret singleton || !allowCreateOrDelete ? "" : ahref(newLink(), "New " + itemName()); } S handleCommands(SS params) { new LS msgs; if (eqGet(params, "action", "create")) { O id = data.createObject(subMapStartingWith_dropPrefix(params, "f_")); msgs.add(itemName() + " created (ID: " + id + ")"); } if (eqGet(params, "action", "update")) msgs.add(data.updateObject(params.get("id"), subMapStartingWith_dropPrefix(params, "f_"))); for (S toDelete : keysDeprefixNemptyValue(params, "delete_")) msgs.add(data.deleteObject(toDelete)); ret nempty(msgs) ? refreshWithMsgs(msgs) : ""; } S encodeField(S s) { ret or(data.fieldNameToHTML(s), s); } // in table S renderValue(S field, O value) { if (value cast HTML) ret value.html; ret htmlEncode_nlToBr_withIndents(strOrEmpty(value)); } S renderTable(bool withCmds) { ret renderTable(withCmds, data.list()); } S renderTable(bool withCmds, L l) { if (empty(l)) ret p("No entries"); //LS fields = data.fields(); //if (fields == null) fields = allKeysFromList_inOrder(); l = map(l, map -> { map = mapMinusKeys(map, unshownFields); MapSO map2 = postProcessTableRow(map, mapToMap( (key, value) -> pair(encodeField(key), renderValue(key, value)), map)); if (singleton) map2.remove(data.fieldNameToHTML(data.idField())); // don't show ID in table in singleton mode if (withCmds) map2 = addCmdsToTableRow(map, map2); ret map2; }); ret htmlTable2_noHtmlEncode(l, tableParams()); } MapSO addCmdsToTableRow(MapSO map, MapSO map2) { 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(Map map) { map = mapMinusKeys(map, joinSets(unshownFields, uneditableFields)); ret htableRaw_valignTop( map(map, (field, value) -> { S help = data.fieldHelp(field); ret ll( encodeField(field), renderInput(field, value) + (empty(help) ? "" : "
" + small(help)) ); }) , border := 1, cellpadding := 4); } S renderInput(S field, O value) { S name = "f_" + field; ret renderInput(name, data.getRenderer(field), value); } S renderInput(S name, HCRUD_Data.Renderer r, O value) { //print("Renderer for " + name + ": " + r); if (r != null) value = r.preprocessValue(value); // switch by renderer type if (r cast HCRUD_Data.TextArea) ret htextarea(strOrEmpty(value), +name, cols := r.cols, rows := r.rows); if (r cast HCRUD_Data.TextField) ret htextfield(name, strOrEmpty(value), size := r.cols, style := "font-family: monospace"); if (r cast HCRUD_Data.ComboBox) ret hselect_list(r.entries, r.valueToEntry(value), +name); if (r cast HCRUD_Data.CheckBox) //ret hcheckbox(name, isTrue(value)); ret htrickcheckboxWithText(name, "", isTrue(value)); if (r cast HCRUD_Data.FlexibleLengthList) { L list = cast value; new LS rows; int leeway = 5, n = l(list)+leeway; 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 htag table(lines(rows)); } ret renderInput_default(name, value); } S renderInput_default(S name, O value) { ret htextfield(name, strOrEmpty(value), size := defaultTextFieldCols); } S renderNewForm() { ret renderNewForm(data.emptyObject()); } S renderNewForm(MapSO map1) { //printStruct("renderNewForm", map1); Map map = mapWithoutKey(map1, data.idField()); //printStruct("renderNewForm", map); ret hpostform( hhidden("action", "create") + renderForm(map) + p(hsubmit("Create")), action := baseLink); } S renderEditForm(S id) { Map map = mapWithoutKey(data.getObject(id), data.idField()); if (map == null) ret htmlEncode2("Entry " + id + " not found"); ret hpostform( hhidden("action", "update") + hhidden(+id) + renderForm(map) + p(hsubmit("Save changes")), action := baseLink); } S renderPage(SS params) { if (eqGet(params, "cmd", "new")) ret frame("New " + itemName(), renderNewForm()); if (nempty(params.get("edit"))) ret frame("Edit " + itemName(), renderEditForm(params.get("edit"))); if (nempty(params.get("duplicate"))) ret frame("New " + itemName(), renderNewForm(data.getObject(params.get("duplicate")))); ret frame(ahref(baseLink, firstToUpper(singleton ? data.itemName() : data.itemNamePlural())), render(mutationRights, params)); } HCRUD makeFrame(MakeFrame makeFrame) { super.makeFrame(makeFrame); this; } S cmdsKey() { ret ""; } S itemName() { ret data.itemName(); } swappable MapSO postProcessTableRow(MapSO data, MapSO rendered) { ret rendered; } swappable S renderCmds(MapSO item) { O id = item.get(data.idField()); ret joinNemptiesWithVBar( ahref(editLink(id), "edit"), singleton || !allowCreateOrDelete ? null : ahrefWithConfirm( "Really delete item " + id + "?", deleteLink(id), htmlEncode2(unicode_DEL())), singleton || !allowCreateOrDelete ? null : ahref(duplicateLink(id), "dup", title := "duplicate") ); } }