From d6b8cfa6457f748a2ee1f442561df576e42dce1d Mon Sep 17 00:00:00 2001 From: Chris Angelico Date: Sat, 2 Nov 2024 08:17:01 +1100 Subject: [PATCH] Add checkbox element type --- httpstatic/chan_form.js | 22 ++++++++++++++++++---- modules/http/chan_form.pike | 36 +++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/httpstatic/chan_form.js b/httpstatic/chan_form.js index b72255e5..86807be9 100644 --- a/httpstatic/chan_form.js +++ b/httpstatic/chan_form.js @@ -1,5 +1,5 @@ import {choc, set_content, DOM, on} from "https://rosuav.github.io/choc/factory.js"; -const {BUTTON, DIV, INPUT, LABEL, LI, P, PRE, TD, TIME, TR} = choc; //autoimport +const {BUTTON, DIV, INPUT, LABEL, LI, P, PRE, TD, TIME, TR, UL} = choc; //autoimport import {simpleconfirm} from "$$static||utils.js$$"; function format_time(ts) { @@ -21,7 +21,7 @@ export const autorender = { ]));}, } -const render_element = { +const render_element = { //Matches _element_types (see Pike code) "": el => P("Unknown element type - something went wrong - " + el.type), //({"twitchid", "Twitch username"}), //If mandatory, will force user to be logged in to submit simple: el => [ //extcall @@ -33,10 +33,21 @@ const render_element = { P("Paragraph input"), LABEL(["Label: ", INPUT({name: "label", value: el.label || ""}), " - shown in the form"]), ], - //({"paragraph", "Paragraph input"}), //({"address", "Street address"}), //({"radio", "Selection (radio) buttons"}), - //({"checkbox", "Check box(es)"}), + checkbox: el => [ //extcall + P("Set of checkboxes"), + UL([ + (el.label || []).map((l, i) => + LI([ + LABEL(["Label " + (i+1) + ": ", INPUT({name: "label[" + i + "]", value: l || ""})]), + " ", + BUTTON({type: "button", class: "deletefield", "data-field": "label[" + i + "]"}, "x"), + ]) + ), + LI(LABEL(["Add label: ", INPUT({name: "label[" + (el.label || []).length + "]", value: ""})])), + ]), + ], }; let editing = null; @@ -47,6 +58,7 @@ function openform(f) { if (el.type === "checkbox") el.checked = !!f[key]; else el.value = f[key] || ""; }); + DOM("#viewform").href = "form?form=" + f.id; DOM("#viewresp").href = "form?responses=" + f.id; set_content("#formelements", (f.elements||[]).map((el, idx) => DIV({class: "element", "data-idx": idx}, [ DIV({class: "header"}, [ @@ -89,6 +101,8 @@ on("click", "#delete_form", simpleconfirm("Are you sure? This cannot be undone!" on("change", ".element input", e => ws_sync.send({cmd: "edit_element", id: editing, idx: +e.match.closest_data("idx"), field: e.match.name, value: e.match.value})); +on("click", ".element .deletefield", e => ws_sync.send({cmd: "edit_element", id: editing, idx: +e.match.closest_data("idx"), field: e.match.dataset.field, value: ""})); + on("click", ".showresponse", e => { const r = e.match.resp_data; ["permitted", "timestamp"].forEach(key => { diff --git a/modules/http/chan_form.pike b/modules/http/chan_form.pike index 8415facd..709fdb54 100644 --- a/modules/http/chan_form.pike +++ b/modules/http/chan_form.pike @@ -12,6 +12,7 @@ constant markdown = #"# Forms for $$channel$$ > ### Edit form > > $$formfields$$ +> [Link to form](form?form=FORMID :#viewform target=_blank) (only while form is open)
> [View form responses](form?responses=FORMID :#viewresp target=_blank) (opens in new window)
> [Delete form](:#delete_form) > @@ -94,18 +95,19 @@ array formfields = ({ ({"is_open", "type=checkbox", "Open form"}), }); -array _element_types = ({ +array _element_types = ({ //Search for _element_types in this and the JS to find places to add more ({"twitchid", "Twitch username"}), //If mandatory, will force user to be logged in to submit ({"simple", "Text input"}), ({"paragraph", "Paragraph input"}), ({"address", "Street address"}), - ({"radio", "Selection (radio) buttons"}), - ({"checkbox", "Check box(es)"}), + ({"radio", "Selection (radio) buttons"}), //Should generally be made mandatory + ({"checkbox", "Check box(es)"}), //If mandatory, at least one checkbox must be selected (but more than one may be) }); mapping element_types = (mapping)_element_types; -mapping element_attributes = ([ +mapping element_attributes = ([ //Matches _element_types "simple": (["label": type_string]), "paragraph": (["label": type_string]), + "checkbox": (["label[]": type_string]), ]); __async__ mapping(string:mixed) http_request(Protocols.HTTP.Server.Request req) { @@ -153,7 +155,7 @@ __async__ mapping(string:mixed) http_request(Protocols.HTTP.Server.Request req) string formdata = ""; foreach (form->elements, mapping el) { string|zero elem = 0; - switch (el->type) { + switch (el->type) { //Matches _element_types case "simple": elem = sprintf("", el->label, "field-" + el->name, @@ -164,6 +166,15 @@ __async__ mapping(string:mixed) http_request(Protocols.HTTP.Server.Request req) el->label, "field-" + el->name, ); break; + case "checkbox": { + elem = ""; + break; + } default: break; } if (elem) formdata += sprintf("
%s
\n", "field-" + el->name, elem); @@ -294,7 +305,7 @@ __async__ void wscmd_add_element(object channel, mapping(string:mixed) conn, map __async__ void wscmd_edit_element(object channel, mapping(string:mixed) conn, mapping(string:mixed) msg) { mapping|zero form_data; - if (!intp(msg->idx) || msg->idx < 0 || !stringp(msg->field) || !stringp(msg->value)) return; + if (!intp(msg->idx) || msg->idx < 0 || !stringp(msg->field) || !stringp(msg->value) || msg->field == "") return; await(G->G->DB->mutate_config(channel->userid, "forms") {mapping cfg = __ARGS__[0]; form_data = cfg->forms[?msg->id]; if (!form_data) return; if (msg->idx >= sizeof(form_data->elements)) return; @@ -309,13 +320,24 @@ __async__ void wscmd_edit_element(object channel, mapping(string:mixed) conn, ma el->name = msg->value; form_data = 0; return; //Signal acceptance of the edit } + else if (msg->field[-1] == ']') { + sscanf(msg->field, "%s[%d]", string basename, int idx); + if (msg->field != sprintf("%s[%d]", basename, idx)) return; //Strict formatting, no extra zeroes or anything + function validator = element_attributes[el->type][basename + "[]"]; + if (!validator || !validator(msg->value)) return; + if (!arrayp(el[basename])) el[basename] = ({ }); + while (sizeof(el[basename]) <= idx) el[basename] += ({""}); + el[basename][idx] = msg->value; + el[basename] -= ({""}); //Set to blank to delete an entry + form_data = 0; return; + } else if (function validator = element_attributes[el->type][msg->field]) { if (!validator(msg->value)) return; el[msg->field] = msg->value; form_data = 0; return; } }); - if (form_data) send_updates_all(channel, ""); + if (!form_data) send_updates_all(channel, ""); } __async__ void wscmd_delete_element(object channel, mapping(string:mixed) conn, mapping(string:mixed) msg) {