From 50ee54a8ff831d20b77a1261790155ab3176f5fb Mon Sep 17 00:00:00 2001 From: dcore94 Date: Tue, 26 Sep 2023 17:14:47 +0200 Subject: [PATCH 1/6] rewritten input widget controller --- ccp/js/executionformcontroller.js | 2 +- ccp/js/inputwidgetcontroller.js | 616 +++++++++++++------------- ccp/js/inputwidgeteditorcontroller.js | 8 +- 3 files changed, 320 insertions(+), 306 deletions(-) diff --git a/ccp/js/executionformcontroller.js b/ccp/js/executionformcontroller.js index 6e53f7c..246decb 100644 --- a/ccp/js/executionformcontroller.js +++ b/ccp/js/executionformcontroller.js @@ -61,7 +61,7 @@ class CCPExecutionForm extends HTMLElement{
Inputs
-
+
diff --git a/ccp/js/inputwidgetcontroller.js b/ccp/js/inputwidgetcontroller.js index 12c98e3..33a82d9 100644 --- a/ccp/js/inputwidgetcontroller.js +++ b/ccp/js/inputwidgetcontroller.js @@ -1,181 +1,319 @@ class CCPInputWidgetController extends HTMLElement { - - #input = null; - #renderer = null; + + #data = null; constructor(){ - super() - this.#input = JSON.parse(this.getAttribute("input")) - this.#renderer = Renderer.instance(this.#input) + super() } connectedCallback(){ - console.log("Widget connected") - this.innerHTML = this.render() - this.#renderer.connectedCallback(this) + this.#data = JSON.parse(this.getAttribute("input")) + + if(this.isEnum()){ + const opts = this.#data.schema.enum.join(",") + this.innerHTML += `` + + }else if(this.isCode()){ + this.innerHTML += `` + + }else if(this.isDateTime()){ + const t = this.#data.schema.format.toLowerCase() === "datetime" ? + "datetime-local" : this.#data.schema.format.toLowerCase() + this.innerHTML += `` + + }else if(this.isFile()){ + this.innerHTML += `` + + }else if(this.isSecret()){ + this.innerHTML += `` + + }else{ + this.innerHTML += `` + } } - render(){ - return this.#renderer.render() - } - - get name(){ - return this.#renderer.name + set value(value){ + this.firstElementChild.value = value } get value(){ - return this.#renderer.getValue(this) + return this.firstElementChild.value.length === 1 ? + this.firstElementChild.value[0] : + this.firstElementChild.value } - set value(v){ - return this.#renderer.setValue(this, v) + get name(){ + return this.firstElementChild.name + } + + isEnum(input){ + return (this.#data.schema.type === "string") && ("enum" in this.#data.schema) + } + + isSecret(){ + return (this.#data.schema.type === "string") && + ("format" in this.#data.schema) && + (this.#data.schema.format != null) && + (this.#data.schema.format.toLowerCase() === "secret") + } + + isCode(){ + return (this.#data.schema.type === "string") && + ("format" in this.#data.schema) && + (this.#data.schema.format != null) && + (this.#data.schema.format.toLowerCase() === "code") + } + + isFile(){ + return (this.#data.schema.type === "string") && + ("format" in this.#data.schema) && + (this.#data.schema.format != null) && + (this.#data.schema.format.toLowerCase() === "file") + } + + isDateTime(){ + return (this.#data.schema.type === "string") && + ("format" in this.#data.schema) && + (this.#data.schema.format != null) && + (["date", "time", "datetime"].indexOf(this.#data.schema.format.toLowerCase()) !== -1) } } window.customElements.define('d4s-ccp-input', CCPInputWidgetController); -class Renderer{ +class CCPBaseInputWidgetController extends HTMLElement{ + #rootdoc = null; + #minOccurs = 1; + #maxOccurs = 1; + #description = "" + #title = "" + #name = "" + #default = null + #options = null + #value = null + #readonly = false - #input = null; + //useful to avoid having a custom element for every basic input type + #type = null - constructor(input){ - this.#input = input - } + #count = 1 - connectedCallback(controller){ + constructor(){ + super() + this.#rootdoc = this//this.attachShadow({ mode: "open" }); + this.#name = this.getAttribute("name") + this.#title = this.getAttribute("title") + this.#description = this.getAttribute("description") + this.#default = this.getAttribute("default") + this.#minOccurs = Number(this.getAttribute("minoccurs") ? this.getAttribute("minoccurs") : 1) + this.#maxOccurs = Number(this.getAttribute("maxoccurs") ? this.getAttribute("maxoccurs") : 1) + this.#readonly = this.getAttribute("readonly") === "true" + // coalesce all basic input types + this.#type = this.getAttribute("type") + + // Handle enum case + this.#options = (this.getAttribute("options") ? this.getAttribute("options").split(",") : null) + + this.value = Array(Math.max(this.#minOccurs,1)).fill(this.#default) } - get schema(){ - return this.#input.schema + set value(v){ + const actual = Array.isArray(v) ? v : [v] + if(actual.length < this.#minOccurs || actual.length > this.#maxOccurs){ + throw `Value with length ${v.length} does not respect bounds [${this.minOccurs},${this.maxOccurs}]` + } + this.#value = actual + this.render() } - get name(){ - return this.#input.id - } - - get title(){ - return this.#input.title + get value(){ + return this.#value } - get description(){ - return this.#input.description + get rootdoc(){ + return this.#rootdoc } get required(){ - return this.#input.minOccurs > 0 - } - - get readOnly(){ - return this.#input.schema.readOnly + return this.#minOccurs > 0 } - static instance(input){ - if(this.isEnum(input)){ - return new EnumInputRenderer(input) - } - if(this.isCode(input)){ - return new CodeInputRenderer(input) - } - if(this.isDateTime(input)){ - return new DateTimeInputRenderer(input) - } - if(this.isSecret(input)){ - return new SecretInputRenderer(input) - } - if(this.isFile(input)){ - return new FileInputRenderer(input) - } - return new SimpleInputRenderer(input) + get readonly(){ + return this.#readonly } - static isEnum(input){ - return (input.schema.type === "string") && ("enum" in input.schema) - } - - static isSecret(input){ - return (input.schema.type === "string") && - ("format" in input.schema) && - (input.schema.format != null) && - (input.schema.format.toLowerCase() === "secret") + isIncrementable(){ + return this.#value.length < this.#maxOccurs } - static isCode(input){ - return (input.schema.type === "string") && - ("format" in input.schema) && - (input.schema.format != null) && - (input.schema.format.toLowerCase() === "code") - } - - static isFile(input){ - return (input.schema.type === "string") && - ("format" in input.schema) && - (input.schema.format != null) && - (input.schema.format.toLowerCase() === "file") + isDecrementable(){ + return this.#value.length > this.#minOccurs && this.#value.length > 1 } - static isDateTime(input){ - return (input.schema.type === "string") && - ("format" in input.schema) && - (input.schema.format != null) && - (["date", "time", "datetime"].indexOf(input.schema.format.toLowerCase()) !== -1) - } -} - -class SimpleInputRenderer extends Renderer{ - - #html = null; - - constructor(input){ - super(input) + get count(){ + return this.#count } - getValue(parent){ - return parent.querySelector("input").value + get name(){ + return this.#name } - setValue(parent, v){ - parent.querySelector("input").value = v + get title(){ + return this.#title + } + + get description(){ + return this.#description + } + + get default(){ + return this.#default + } + + get options(){ + return this.#options + } + + set default(v){ + this.#default = v + } + + get type(){ + return this.#type } render(){ - let required = this.required ? 'required="required"' : "" - let readonly = this.readOnly ? 'readonly="readOnly"' : "" - this.#html = ` -
-
+ ` + this.#rootdoc.querySelector("div[name=root]").addEventListener("click", ev=>{ + const src = ev.target.getAttribute("name") + if(src === "plus"){ + this.#value.push(this.#default) + this.render() + }else if(src === "minus"){ + this.#value.pop() + this.render() + } + }) } } -class FileInputRenderer extends Renderer{ - - #html = null; - #content = null; - - constructor(input){ - super(input) +class CCPSimpleInputWidgetController extends CCPBaseInputWidgetController{ + + constructor(){ + super() } - connectedCallback(controller){ - controller.querySelector(`input[name=${this.name}]`).addEventListener("change", ev=>{ + connectedCallback(){ + this.rootdoc.addEventListener("input", ev=>{ + if(ev.target.getAttribute("name") === this.name){ + const index = Number(ev.target.getAttribute("data-index")) + this.value[index] = ev.target.value + } + }) + } + + content(){ + if(this.value.length <= 1){ + return `` + } + var out = + this.value.map((c,i)=>{ + return ` + + ` + }).join("\n") + return out + } +} +window.customElements.define('d4s-ccp-input-simple', CCPSimpleInputWidgetController); + +class CCPEnumInputWidgetController extends CCPBaseInputWidgetController{ + + constructor(){ + super() + } + + connectedCallback(){ + this.rootdoc.addEventListener("change", ev=>{ + if(ev.target.getAttribute("name") === this.name){ + const index = Number(ev.target.getAttribute("data-index")) + this.value[index] = ev.target.value + } + }) + } + + content(){ + const opts = this.options.map(o=>``).join("\n") + if(this.value.length <= 1){ + return `` + } + var out = + this.value.map((c,i)=>{ + return ` + + ` + }).join("\n") + return out + } +} +window.customElements.define('d4s-ccp-input-enum', CCPEnumInputWidgetController); + +class CCPTextAreaWidgetController extends CCPBaseInputWidgetController{ + + constructor(){ + super() + } + + connectedCallback(){ + this.rootdoc.addEventListener("input", ev=>{ + if(ev.target.getAttribute("name") === this.name){ + const index = Number(ev.target.getAttribute("data-index")) + this.value[index] = ev.target.value + } + }) + } + + content(){ + if(this.value.length <= 1){ + return `` + } + var out = + this.value.map((c,i)=>{ + return ` + + ` + }).join("\n") + return out + } +} +window.customElements.define('d4s-ccp-input-textarea', CCPTextAreaWidgetController); + +class CCPFileInputWidgetController extends CCPBaseInputWidgetController{ + + constructor(){ + super() + } + + connectedCallback(){ + this.rootdoc.addEventListener("change", ev=>{ const tgt = ev.target - const ename = tgt.getAttribute("name") - if(ename === this.name){ + if(tgt.getAttribute("name") === this.name){ + const index = Number(tgt.getAttribute("data-index")) const file = ev.target.files[0] - /*if(file.type !== this.schema.contentMediaType){ - alert("Unsupported media type. Must be " + this.schema.contentMediaType) - ev.stopPropagation() - ev.preventDefault() - tgt.value = null - return false - }*/ if(file.size > 100*1024){ alert("This input allows only small files (100K). Use references instead ") ev.stopPropagation() @@ -189,197 +327,73 @@ class FileInputRenderer extends Renderer{ if ((encoded.length % 4) > 0) { encoded += '='.repeat(4 - (encoded.length % 4)); } - this.#content = encoded + this.value[index] = encoded + this.querySelector("small[name=preview]").textContent = encoded.substr(0,5) + "..." + encoded.substr(encoded.length-5) }) reader.readAsDataURL(file) } }) } - getValue(parent){ - return this.#content - } - - setValue(parent, v){ - parent.querySelector("input").value = v - } - - render(){ - let required = this.required ? 'required="required"' : "" - let readonly = this.readOnly ? 'readonly="readOnly"' : "" - this.#html = ` -
- - -
- ` - return this.#html + content(){ + if(this.value.length <= 1){ + const v = this.value.length ? this.value[0] : '' + return ` + + ${v.substr(0,5) + "..." + v.substr(v.length-5)} + ` + } + var out = + this.value.map((c,i)=>{ + return ` + + ${c.substr(0,5) + "..." + c.substr(s.length-5)} + ` + }).join("\n") + return out } } +window.customElements.define('d4s-ccp-input-file', CCPFileInputWidgetController); -class SecretInputRenderer extends Renderer{ - - #html = null; - - constructor(input){ - super(input) +class CCPSecretInputWidgetController extends CCPBaseInputWidgetController{ + + constructor(){ + super() } - getValue(parent){ - return parent.querySelector("input").value - } - - setValue(parent, v){ - parent.querySelector("input").value = v - } - - connectedCallback(controller){ - controller.addEventListener("click", ev=>{ - const ename = ev.target.getAttribute("name") - if(ename === "password_toggle"){ - const w = controller.querySelector("div.ccp-input-widget input") - w.type = (w.type === "password" ? "" : "password") - ev.preventDefault() + connectedCallback(){ + this.rootdoc.addEventListener("input", ev=>{ + if(ev.target.getAttribute("name") === this.name){ + const index = Number(ev.target.getAttribute("data-index")) + this.value[index] = ev.target.value + } + }) + + this.rootdoc.addEventListener("click", ev=>{ + if(ev.target.getAttribute("name") === "password_toggle"){ + const index = Number(ev.target.getAttribute("data-index")) + const field = this.rootdoc.querySelector(`input[data-index='${index}']`) + if(field.type === "text") field.type="password"; + else field.type = "text"; } }) } - - render(){ - let required = this.required ? 'required="required"' : "" - let readonly = this.readOnly ? 'readonly="readOnly"' : "" - this.#html = ` -
- - - 👁 -
- ` - return this.#html - } -} - -class DateTimeInputRenderer extends Renderer{ - - #html = null; - - constructor(input){ - super(input) - } - - getValue(parent){ - return parent.querySelector("input").value - } - - setValue(parent, v){ - parent.querySelector("input").value = v - } - - render(){ - let required = this.required ? 'required="required"' : "" - let readonly = this.schema.readOnly ? 'readonly="readOnly"' : "" - let t = this.schema.format.toLowerCase() === "datetime" ? "datetime-local" : this.schema.format.toLowerCase() - this.#html = ` -
- - -
- ` - return this.#html - } -} - -class EnumInputRenderer extends Renderer{ - - #html = null; - - constructor(input){ - super(input) - } - - getValue(parent){ - return parent.querySelector("select").value - } - - setValue(parent, v){ - parent.querySelector("select").value = v - } - - render(){ - let options = this.schema.enum.map(e => { - return e === this.schema.default ? - `` : - `` - }) - let required = this.required ? 'required="required"' : "" - let readonly = this.schema.readOnly ? 'readonly="readOnly"' : "" - this.#html = ` -
- - -
- ` - return this.#html - } -} - -class CodeInputRenderer extends Renderer{ - - #html = null; - #codemirror = null; - - constructor(input){ - super(input) - } - - getValue(parent){ - return parent.querySelector("textarea").value - } - - setValue(parent, v){ - parent.querySelector("textarea").value = v - } - - connectedCallback(controller){ - /*const ta = controller.querySelector("textarea") - const opts = { - lineNumbers: true, - indentUnit: 4, - matchBrackets: true, - mode: this.schema.contentMediaType, - readOnly : this.schema.readOnly ? true : false - } - this.#codemirror = CodeMirror.fromTextArea(ta, opts) - this.#codemirror.setValue(this.schema.default) - this.#codemirror.refresh()*/ - } - - render(){ - let required = this.required ? 'required="required"' : "" - let readonly = this.schema.readOnly ? 'readonly="readOnly"' : "" - this.#html = ` -
- - -
- ` - return this.#html + content(){ + if(this.value.length <= 1){ + return ` + + 👁 + ` + } + var out = + this.value.map((c,i)=>{ + return ` + + 👁 + ` + }).join("\n") + return out } } +window.customElements.define('d4s-ccp-input-secret', CCPSecretInputWidgetController); \ No newline at end of file diff --git a/ccp/js/inputwidgeteditorcontroller.js b/ccp/js/inputwidgeteditorcontroller.js index 0408631..9da3f80 100644 --- a/ccp/js/inputwidgeteditorcontroller.js +++ b/ccp/js/inputwidgeteditorcontroller.js @@ -92,14 +92,14 @@ class CCPInputWidgetEditorController extends HTMLElement{
-
- +
+
-
- +
+
From 8c80ab6f761a5927da334066b331374f169e083d Mon Sep 17 00:00:00 2001 From: dcore94 Date: Wed, 27 Sep 2023 12:14:35 +0200 Subject: [PATCH 2/6] added support for mulitichoice enum --- ccp/js/inputwidgetcontroller.js | 81 ++++++++++++++++++++++++--- ccp/js/inputwidgeteditorcontroller.js | 42 ++++++++------ 2 files changed, 99 insertions(+), 24 deletions(-) diff --git a/ccp/js/inputwidgetcontroller.js b/ccp/js/inputwidgetcontroller.js index 33a82d9..3163288 100644 --- a/ccp/js/inputwidgetcontroller.js +++ b/ccp/js/inputwidgetcontroller.js @@ -9,10 +9,15 @@ class CCPInputWidgetController extends HTMLElement { connectedCallback(){ this.#data = JSON.parse(this.getAttribute("input")) - if(this.isEnum()){ + if(this.isChecklist()){ + const opts = this.#data.schema.enum.join(",") - this.innerHTML += `` + this.innerHTML += `` + } else if(this.isEnum()){ + + const opts = this.#data.schema.enum.join(",") + this.innerHTML += `` }else if(this.isCode()){ this.innerHTML += `` @@ -46,7 +51,11 @@ class CCPInputWidgetController extends HTMLElement { return this.firstElementChild.name } - isEnum(input){ + isChecklist(){ + return this.isEnum() && (this.#data.schema.format === "checklist") + } + + isEnum(){ return (this.#data.schema.type === "string") && ("enum" in this.#data.schema) } @@ -117,6 +126,11 @@ class CCPBaseInputWidgetController extends HTMLElement{ this.value = Array(Math.max(this.#minOccurs,1)).fill(this.#default) } + setValue(v){ + this.#value = v + this.render() + } + set value(v){ const actual = Array.isArray(v) ? v : [v] if(actual.length < this.#minOccurs || actual.length > this.#maxOccurs){ @@ -272,6 +286,55 @@ class CCPEnumInputWidgetController extends CCPBaseInputWidgetController{ } window.customElements.define('d4s-ccp-input-enum', CCPEnumInputWidgetController); +class CCPChecklistInputWidgetController extends CCPBaseInputWidgetController{ + + constructor(){ + super() + } + + connectedCallback(){ + this.rootdoc.addEventListener("change", ev=>{ + if(ev.target.getAttribute("name") === this.name){ + const index = Number(ev.target.getAttribute("data-index")) + + const elems = Array.prototype.slice.call(ev.currentTarget.querySelectorAll(`input[name='${this.name}'][data-index='${index}']`)) + + this.value[index] = elems.filter(e=>e.checked).map(e=>e.value).join(",") + } + }) + } + + buildOpts(index, selections){ + return this.options.map(o=>` +
+ + -1 ? 'checked' : ''}/>
+ `).join("\n") + } + + content(){ + if(this.value.length === 1){ + const opts = this.buildOpts(0, this.value.length ? this.value[0].split(",") : []) + return ` +
+ ${opts} +
+ ` + } + var out = + this.value.map((c,i)=>{ + const opts = this.buildOpts(i, c.split(",")) + return ` +
+ ${opts} +
+ ` + }).join("\n") + return out + } +} +window.customElements.define('d4s-ccp-input-checklist', CCPChecklistInputWidgetController); + class CCPTextAreaWidgetController extends CCPBaseInputWidgetController{ constructor(){ @@ -382,15 +445,19 @@ class CCPSecretInputWidgetController extends CCPBaseInputWidgetController{ content(){ if(this.value.length <= 1){ return ` - - 👁 +
+ + 👁 +
` } var out = this.value.map((c,i)=>{ return ` - - 👁 +
+ + 👁 +
` }).join("\n") return out diff --git a/ccp/js/inputwidgeteditorcontroller.js b/ccp/js/inputwidgeteditorcontroller.js index 9da3f80..2b8ea66 100644 --- a/ccp/js/inputwidgeteditorcontroller.js +++ b/ccp/js/inputwidgeteditorcontroller.js @@ -65,27 +65,27 @@ class CCPInputWidgetEditorController extends HTMLElement{ this.innerHTML = `
- + ${ input.id !== 'ccpimage' ? this.renderDeleteButton() : ''}
- +
- +
- @@ -94,13 +94,19 @@ class CCPInputWidgetEditorController extends HTMLElement{
- +
- + +
+
+
+ +
+
@@ -108,7 +114,7 @@ class CCPInputWidgetEditorController extends HTMLElement{
- @@ -116,32 +122,34 @@ class CCPInputWidgetEditorController extends HTMLElement{ +
- -
-
-
- -
- +
- + Comma separated list of options
+
+ + +
${this.renderDefaultByType()} - 👁 + 👁
@@ -176,7 +184,7 @@ class CCPInputWidgetEditorController extends HTMLElement{ } else if(ename === "format"){ this.#input.schema.format = val - this.render(this.#input, this.#index, true) + //this.render(this.#input, this.#index, true) /*this.querySelector("div[name=input-default] span[name=password_toggle]").classList.add("d-none") this.querySelector("div[name=input-default] input[name=default]").type = "" if(this.#input.schema.format === "secret"){ From b8c0ea42793fbe585ccf7e5c20f9d72c87571801 Mon Sep 17 00:00:00 2001 From: dcore94 Date: Wed, 27 Sep 2023 16:54:14 +0200 Subject: [PATCH 3/6] fixed type --- ccp/js/inputwidgeteditorcontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccp/js/inputwidgeteditorcontroller.js b/ccp/js/inputwidgeteditorcontroller.js index 2b8ea66..bff3b02 100644 --- a/ccp/js/inputwidgeteditorcontroller.js +++ b/ccp/js/inputwidgeteditorcontroller.js @@ -142,7 +142,7 @@ class CCPInputWidgetEditorController extends HTMLElement{
From 5e8129b440a1a6d404e04945a1e182674c0eab85 Mon Sep 17 00:00:00 2001 From: dcore94 Date: Wed, 27 Sep 2023 16:59:44 +0200 Subject: [PATCH 4/6] fixed type selector --- ccp/js/inputwidgeteditorcontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccp/js/inputwidgeteditorcontroller.js b/ccp/js/inputwidgeteditorcontroller.js index bff3b02..0b79c6c 100644 --- a/ccp/js/inputwidgeteditorcontroller.js +++ b/ccp/js/inputwidgeteditorcontroller.js @@ -85,7 +85,7 @@ class CCPInputWidgetEditorController extends HTMLElement{
- From 0f7bbacce37ac6632f998a0772e8ad93e06c60c1 Mon Sep 17 00:00:00 2001 From: dcore94 Date: Wed, 27 Sep 2023 17:03:00 +0200 Subject: [PATCH 5/6] fixed type selector --- ccp/js/inputwidgeteditorcontroller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ccp/js/inputwidgeteditorcontroller.js b/ccp/js/inputwidgeteditorcontroller.js index 0b79c6c..d2583a4 100644 --- a/ccp/js/inputwidgeteditorcontroller.js +++ b/ccp/js/inputwidgeteditorcontroller.js @@ -86,8 +86,8 @@ class CCPInputWidgetEditorController extends HTMLElement{
From 650cc75df01bb45f2bfe35d773442d854ff32d4b Mon Sep 17 00:00:00 2001 From: dcore94 Date: Tue, 3 Oct 2023 16:18:48 +0200 Subject: [PATCH 6/6] if value is set from outside mark as not required --- ccp/js/inputwidgetcontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccp/js/inputwidgetcontroller.js b/ccp/js/inputwidgetcontroller.js index 3163288..08dd748 100644 --- a/ccp/js/inputwidgetcontroller.js +++ b/ccp/js/inputwidgetcontroller.js @@ -402,7 +402,7 @@ class CCPFileInputWidgetController extends CCPBaseInputWidgetController{ if(this.value.length <= 1){ const v = this.value.length ? this.value[0] : '' return ` - + ${v.substr(0,5) + "..." + v.substr(v.length-5)} ` }