cdn-experiments/ccp/js/inputwidgetcontroller.js

354 lines
9.3 KiB
JavaScript

class CCPInputWidgetController extends HTMLElement {
#input = null;
#renderer = null;
constructor(){
super()
this.#input = JSON.parse(this.getAttribute("input"))
this.#renderer = Renderer.instance(this.#input)
}
connectedCallback(){
console.log("Widget connected")
this.innerHTML = this.render()
this.#renderer.connectedCallback(this)
}
render(){
return this.#renderer.render()
}
get name(){
return this.#renderer.name
}
get value(){
return this.#renderer.getValue(this)
}
}
window.customElements.define('d4s-ccp-input', CCPInputWidgetController);
class Renderer{
#input = null;
constructor(input){
this.#input = input
}
connectedCallback(controller){
}
get schema(){
return this.#input.schema
}
get name(){
return this.#input.id
}
get title(){
return this.#input.title
}
get description(){
return this.#input.description
}
get required(){
return this.#input.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)
}
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")
}
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")
}
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)
}
getValue(parent){
return parent.querySelector("input").value
}
render(){
let required = this.required ? 'required="required"' : ""
let readonly = this.readOnly ? 'readonly="readOnly"' : ""
this.#html = `
<div class="ccp-input-widget form-field">
<label>
${this.title}
<span class="ccp-help-icon" title="${this.description}" alt="${this.description}">?</span>
</label>
<input class="ccp-input-widget form-control" name="${this.name}" value="${this.schema.default}" ${required} ${readonly}></input>
<span style="user-select:none;position:relative;top:-1.6rem;left:95%;cursor:pointer" name="password_toggle" class="d-none">&#128065;</span>
</div>
`
return this.#html
}
}
class FileInputRenderer extends Renderer{
#html = null;
#content = null;
constructor(input){
super(input)
}
connectedCallback(controller){
controller.querySelector(`input[name=${this.name}]`).addEventListener("change", ev=>{
const tgt = ev.target
const ename = tgt.getAttribute("name")
if(ename === this.name){
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()
ev.preventDefault()
tgt.value = null
return false
}
const reader = new FileReader()
reader.addEventListener('load', ev=>{
let encoded = ev.target.result.toString().replace(/^data:(.*,)?/, '');
if ((encoded.length % 4) > 0) {
encoded += '='.repeat(4 - (encoded.length % 4));
}
this.#content = encoded
})
reader.readAsDataURL(file)
}
})
}
getValue(parent){
return this.#content
}
render(){
let required = this.required ? 'required="required"' : ""
let readonly = this.readOnly ? 'readonly="readOnly"' : ""
this.#html = `
<div class="ccp-input-widget form-field">
<label>
${this.title}
<span class="ccp-help-icon" title="${this.description}" alt="${this.description}">?</span>
</label>
<input type="file" class="ccp-input-widget form-control" name="${this.name}" value="${this.schema.default}" ${required} ${readonly}></input>
</div>
`
return this.#html
}
}
class SecretInputRenderer extends Renderer{
#html = null;
constructor(input){
super(input)
}
getValue(parent){
return parent.querySelector("input").value
}
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()
}
})
}
render(){
let required = this.required ? 'required="required"' : ""
let readonly = this.readOnly ? 'readonly="readOnly"' : ""
this.#html = `
<div class="ccp-input-widget ccp-input-widget form-field">
<label>
${this.title}
<span class="ccp-help-icon" title="${this.description}" alt="${this.description}">?</span>
</label>
<input type="password" class="ccp-input-widget form-control" name="${this.name}" value="${this.schema.default}" ${required} ${readonly}></input>
<span style="user-select:none;position:relative;top:-1.6rem;float:right;cursor:pointer" name="password_toggle">&#128065;</span>
</div>
`
return this.#html
}
}
class DateTimeInputRenderer extends Renderer{
#html = null;
constructor(input){
super(input)
}
getValue(parent){
return parent.querySelector("input").value
}
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 = `
<div class="ccp-input-widget form-field">
<label>
${this.title}
<span class="ccp-help-icon" title="${this.description}" alt="${this.description}">?</span>
</label>
<input type="${t}" class="ccp-input-widget form-control" name="${this.name}" value="${this.schema.default}" ${required} ${readonly}></input>
</div>
`
return this.#html
}
}
class EnumInputRenderer extends Renderer{
#html = null;
constructor(input){
super(input)
}
getValue(parent){
return parent.querySelector("select").value
}
render(){
let options = this.schema.enum.map(e => {
return e === this.schema.default ?
`<option name="${e}" value="${e}" selected="selected">${e}</option>` :
`<option name="${e}" value="${e}">${e}</option>`
})
let required = this.required ? 'required="required"' : ""
let readonly = this.schema.readOnly ? 'readonly="readOnly"' : ""
this.#html = `
<div class="ccp-input-widget form-field">
<label>
${this.title}
<span class="ccp-help-icon" title="${this.description}" alt="${this.description}">?</span>
</label>
<select class="ccp-input-widget form-control" name="${this.name}" value="${this.schema.default}" ${required}>
${options.join("")}
</select>
</div>
`
return this.#html
}
}
class CodeInputRenderer extends Renderer{
#html = null;
#codemirror = null;
constructor(input){
super(input)
}
getValue(parent){
return parent.querySelector("textarea").textContent
}
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 = `
<div class="ccp-input-widget form-field">
<label>
${this.title}
<span class="ccp-help-icon" title="${this.description}" alt="${this.description}">?</span>
</label>
<textarea class="ccp-input-widget form-control" ${required} ${readonly}>${this.schema.default}</textarea>
</div>
`
return this.#html
}
}