From 50ee54a8ff831d20b77a1261790155ab3176f5fb Mon Sep 17 00:00:00 2001 From: dcore94 Date: Tue, 26 Sep 2023 17:14:47 +0200 Subject: [PATCH] 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{
-
- +
+
-
- +
+