UNPKG

@es-labs/node

Version:
511 lines (470 loc) 16.4 kB
// TODO fix // onsubmit --> multi select // error messages on submit // attributes // - mode: add, edit // // properties // - record // - config (t4t config) // // methods // // events // // requires bwc-combobox, debounce, autocomplete import './bwc-combobox.js' import { debounce } from './util.js' import { autocomplete } from './t4t-fe.js' const bulma = { // the keys are from t4t cols.<col>.ui.tag input: { tag: 'div', className: 'field', children: [ { tag: 'label', className: 'label' }, { tag: 'div', className: 'control', children: [ { tag: 'input-placeholder', className: 'input' }, ] }, { tag: 'p', className: 'help is-danger', errorLabel: true } ] }, // end input textarea: { tag: 'div', className: 'field', children: [ { tag: 'label', className: 'label' }, { tag: 'div', className: 'control', children: [ { tag: 'input-placeholder', className: 'textarea' }, ] }, { tag: 'p', className: 'help is-danger', errorLabel: true } ] }, select: { // ugly multiple tag: 'div', className: 'field', children: [ { tag: 'div', className: 'control', children: [ { tag: 'label', className: 'label' }, { tag: 'div', className: 'select is-fullwidth', // need to add is-multiple for bulma children: [ { tag: 'input-placeholder' }, ] } ] } ] }, // end select 'bwc-combobox': { tag: 'div', className: 'field', children: [ { tag: 'label', className: 'label' }, { tag: 'div', className: 'control has-icons-left', children: [ { tag: 'input-placeholder' }, // TODO className = 'input' ] }, { tag: 'p', className: 'help is-danger', errorLabel: true } ] } } // end bulma // Bootstrap - TODO VERIFY const bootstrap = { input: { tag: 'div', children: [ { tag: 'label', className: 'form-label' }, { tag: 'input-placeholder', className: 'form-control' }, { tag: 'div', className: 'form-text', errorLabel: true } ] }, textarea: { tag: 'div', children: [ { tag: 'label', className: 'form-label' }, { tag: 'input-placeholder', className: 'form-control' }, { tag: 'div', className: 'form-text', errorLabel: true } ] }, select: { tag: 'div', children: [ { tag: 'label', className: 'form-label' }, { tag: 'input-placeholder', className: 'form-select' }, ] }, 'bwc-combobox': { } } // Mui CSS - TODO VERIFY const muicss = { input: { tag: 'div', className: 'mui-textfield', children: [ { tag: 'label', children: [ { tag: 'span', className: 'mui--text-danger', errorLabel: true } ] }, { tag: 'input-placeholder' }, ] }, textarea: { tag: 'div', className: 'mui-textfield', children: [ { tag: 'label', children: [ { tag: 'span', className: 'mui--text-danger', errorLabel: true } ] }, { tag: 'input-placeholder' }, ] }, select: { tag: 'div', className: 'mui-select', children: [ { tag: 'label' }, { tag: 'input-placeholder' }, ] }, 'bwc-combobox': { } } const framework = bulma // set as bulma first const template = document.createElement('template') template.innerHTML = /*html*/` <style> .input-widget { /* background-color: red; */ display: flex; flex-direction: row; justify-content: center; align-items: center; height: var(--bwc-t4t-form-height, calc(100vh - 100px)); } .form-area { align-self: center; /* background-color: lightgray; */ overflow: auto; height: 96%; width: 80%; } .top-area { display: flex; flex-direction: row; justify-content: center; align-items: center; padding: 8px; position: sticky; top: 0px; background-color: lightgray; z-index: 1; } .bottom-area { display: flex; flex-direction: row; justify-content: center; align-items: center; padding: 8px; position: sticky; bottom: 0px; background-color: lightgray; z-index: 1; } </style> <div class="input-widget"> <form class="form-area" onsubmit="return false;"> <div class="top-area"><h1>Hello</h1></div> <div class="content-area"> </div> <div class="bottom-area"> <div class="button-group"> <!-- create buttons here --> <button type="submit" class="btn-submit button is-link">Submit</button> <button type="button" class="btn-cancel button is-link is-light">Cancel</button> </div> </div> <form> </div> ` class BwcT4tForm extends HTMLElement { constructor() { super() } #config = [] // from table config property passed in #record = {} // from record property passed in #xcols = {} // extended column information - info on input element, event, etc... get config () { return this.#config } set config (val) { this.#config = val } get record () { return this.#record } set record (val) { this.#record = val if (this.#config && this.#record) { // console.log('do render - val (this.#record)', val) // console.log('do render - config', this.#config) this._render() } } connectedCallback() { // console.log('bwc-t4t-form', this.#config, this.#record) this.appendChild(template.content.cloneNode(true)) if (this.#config && this.#record) { this._render() } } static get observedAttributes() { return ['mode'] } // attributeChangedCallback(name, oldVal, newVal) { // switch (name) { // case 'mode': break // default: break // } // } get mode() { return this.getAttribute('mode') } set mode(val) { this.setAttribute('mode', val) } // node is current node in tree, k = column key, c = column object formEl (node, k, c) { const mode = this.mode if (c[mode] === 'hide') return null // console.log(k, c) const { tag, className, attrs, children, errorLabel } = node // console.log(tag, className, attrs) const elementTag = (tag === 'input-placeholder') ? c.ui.tag : tag // replace for this const el = document.createElement(elementTag) if (!this.#xcols[k]) this.#xcols[k] = { } if (tag === 'label') el.innerText = c.label // set the label const inputAttrs = c?.ui?.attrs // set col specific attributes for the input if (inputAttrs) { for (let key in inputAttrs) { el.setAttribute(key, inputAttrs[key]) } } // DONE: input - text, integer, decimal, date, time, datetime, file(upload) // DONE: select (single and multiple, limited options) // DONE: textarea // DONE: bwc-combobox (multiple with tags), TODO: need to test more if (['input', 'textarea', 'select', 'bwc-combobox'].includes(elementTag)) { // its an input if (c.mode === 'readonly') el.setAttribute('disabled', true) // select is disabled, as it applies to more html tags if (c.required) el.setAttribute('required', true) if (elementTag === 'select') { // set the options // console.log('select', el.value, k, this.#record[k], this.mode) const selectString = (this.mode === 'add') ? c.default || '' : this.#record[k] || '' const selected = !selectString ? [] : (c?.ui?.attrs?.multiple) ? selectString.split(',') : [selectString] const options = c?.ui?.options for (let option of options) { const optEl = document.createElement('option') optEl.value = option.key optEl.innerText = option.text if (selected.includes(option.key)) { optEl.selected = true // set selected } el.appendChild(optEl) } } else { // other input if (elementTag === 'bwc-combobox') { // console.log('bwc-combobox', this.#record) el.setAttribute('listid', 'list-'+k) el.setAttribute('object-key', 'key') el.setAttribute('object-text', 'text') if (c?.ui?.attrs?.multiple) el.setAttribute('multiple', '') if (c?.ui?.attrs?.repeat) el.setAttribute('repeat', '') if (c?.ui?.attrs?.allowCustomTag) el.setAttribute('allow-custom-tag', '') if (c?.ui?.attrs?.tagLimit) el.setAttribute('tag-limit', c.ui.attrs.tagLimit) // disbled and required already set // TODO set input class el.onload = (e) => { // need to wait for component to load before setting the values // console.log('bwc loaded') const valueType = c?.ui?.valueType // TODO transform value if (c?.ui?.attrs?.multiple) { // can be array in column or join table const val = this.mode === 'add' ? c.default : this.#record[k] if (valueType === '') { el.tags = val.split(',').map(item => ({ key: item, text: item })) || [] } else { // object el.tags = val || [] } } else { // single const val = this.mode === 'add' ? c.default : this.#record[k] if (valueType === '') { el.value = val || '' el.selected = val ? { key: val, text: val } : null } else { // object el.value = val.text || '' // key and text should be same el.selected = val || null } } } // onload end el.onsearch = debounce(async (e) => { // this.#xcols['state'].el.value // use this.#xcols to get latest values // console.log(e.target.value, k, this.#record) // this.#record does not change until validated and submit let parentVal = null if (c?.options?.parentCol) { const col = this.#xcols[c?.options?.parentCol] if (col && col.el) parentVal = col.el.value } const res = await autocomplete(e.target.value, k, this.#record, parentVal) el.items = res }, 500) el.onselect = (e) => { // onselect works (events handled by DOM), onselected need to use addEventListener // TODO reset child value - may cascade down further const childColName = c?.options?.childCol if (childColName) { const col = this.#xcols[childColName] const childColObj = this.#config.cols[childColName] if (col && col.el) { if (childColObj?.ui?.attrs?.multiple) { // multiple col.el.tags = [] } else { col.el.value = '' col.el.selected = null } } } console.log('t4t combobox onselect', e.detail) } } else { // input, textarea if (this.mode === 'add') { // set the value el.value = c.default || '' } else if (this.mode === 'edit') { // console.log('is FileList',this.#record[k] instanceof FileList, k, el.type === 'file') el.value = el.type === 'file' ? '' : (this.#record[k] || '') } } } this.#xcols[k].el = el // set input element } if (errorLabel) { this.#xcols[k].errorEl = el } if (className) el.className = className // set classes // Bulma Specific Note (TODO tmprove this): if className has 'select' - it is bulma need to set is-multiple here if is multi select if (node?.className && node.className.includes('select')) { if (c?.ui?.attrs?.multiple) { el.classList.add('is-multiple') } } if (attrs) { for (let key in attrs) { el.setAttribute(key, attrs[key]) } } if (children) { children.forEach(child => { const childEl = this.formEl(child, k, c) if (childEl) el.appendChild(childEl) }) } return el } _render() { try { // const el = this.querySelector('#form-wrapper') const el = this.querySelector('.content-area') if (!el) return // .content-area not found el.innerHTML = '' const { cols, auto, pk, required, multiKey } = this.#config // console.log('this.#record', this.#record) for (let col in cols) { if (!auto.includes(col)) { const c = cols[col] // console.log('nonauto', c, this.mode) if ((this.mode === 'add' && c.add !== 'hide') || (this.mode === 'edit' && c.edit !== 'hide')) { const tagKey = c?.ui?.tag if (tagKey) { const fieldEl = this.formEl(framework[tagKey], col, c) if (c?.ui?.attrs?.type === 'file' && this.mode === 'edit') { // field is file... this.#xcols[col].errorEl.innerText = this.#record[col] || 'No Files Found' // console.log(fieldEl, col) } el.appendChild(fieldEl) } } } else { // console.log('auto', col) } } const btnSubmit = this.querySelector('.btn-submit') // btnSubmit.classList.add('button') btnSubmit.onclick = (e) => { let error = false // console.log('submit clicked') e.preventDefault() // e.stopPropagation() // check validity for (let col in this.#xcols) { if (this.#xcols[col].el) { if (this.#xcols[col]?.el?.checkValidity) { const valid = this.#xcols[col].el.checkValidity() if (!valid) error = true if (this.#xcols[col].errorEl) this.#xcols[col].errorEl.innerText = valid ? '' : this.#xcols[col].el.validationMessage } } } // console.log(this.#record) if (!error) { for (let col in this.#xcols) { // console.log('this.#xcols', this.#xcols[col].el.tagName) const inputEl = this.#xcols[col].el if (inputEl) { if (inputEl.tagName.toLowerCase() === 'select') { // select options, [string] - done, [{ key, text }] - next const selected = [] for (let opt of inputEl.selectedOptions) { selected.push(opt.value) } this.#record[col] = selected.join(',') } else if (inputEl.tagName.toLowerCase() === 'bwc-combobox') { // TODO set the value const c = this.#config.cols[col] const val = c?.ui?.attrs?.multiple ? inputEl.tags : inputEl.selected const valueType = c?.ui?.valueType if (c?.ui?.attrs?.multiple) { // can be array in column or join table if (valueType === '') { this.#record[col] = val.map(item => item.text).join(',') } else { // object this.#record[col] = val } } else { // single if (valueType === '') { this.#record[col] = val.text } else { // object this.#record[col] = val } } } else { // input, textarea this.#record[col] = inputEl.value if (inputEl.files) { // console.log(inputEl.files instanceof FileList) this.#record[col] = inputEl.files } } } } // console.log('test submit', this.#record) this.dispatchEvent(new CustomEvent('submit', { detail: { data: this.#record } })) } else { this.dispatchEvent(new CustomEvent('submit', { detail: { error } })) } } const btnCancel = this.querySelector('.btn-cancel') // btnCancel.classList.add('button') btnCancel.onclick = (e) => { e.preventDefault() this.dispatchEvent(new CustomEvent('cancel')) } } catch (e) { console.log('bwc-t4t-form', e) } } } customElements.define('bwc-t4t-form', BwcT4tForm) // or bwc-form-t4t /* <template v-if="tableCfg.cols[col].input === 'link'"> <mwc-textfield @click="router.push('/' + tableCfg.cols[col].options.to + '?keyval=' + recordObj[showForm].key + '&keycol=' + tableCfg.cols[col].options.relatedCol)" disabled class="field-item" :key="col + index" :label="tableCfg.cols[col].label" outlined type="text" v-model="recordObj[showForm][col]" ></mwc-textfield> </template> */