import './css/app-ui.css'; import { Ut } from '../utils/Ut'; import { El } from '../utils/dom'; import { Boot } from './Boot'; import { Cancelable } from '../widgets/cancelable/Cancelable'; import { DatePicker } from '../widgets/date-picker/DatePicker'; import { TwoDatePicker } from '../widgets/two-datepicker/TwoDatepicker'; import { IntervalPicker } from '../widgets/interval-picker/IntervalPicker'; import { Uploader } from '../widgets/uploader/Uploader'; import { PinBox } from '../widgets/pin-box/PinBox'; export class Render { /** * Make Card Element */ static makeCardElement(node, parent) { node.outline = Ut.isSet(node.outline) ? node.outline : 1; let card = El('div', { className: 'sp-card' }); if (node.name) { card.className += ' card-' + node.name; } if (node.cls) { card.dxAddClass(node.cls); } let content = card; if (node.outline == 1) { content = card.appendChild( El('div', { className: 'content-card' }) ); } if (node.children && node.children.tabmenu) { let tabMenu = content.appendChild(El('div', { className: 'tab-menu' })); let active = false; Ut.each(Ut.objVal(node.children.tabmenu)?.children?.item, dataItem => { if (!dataItem.text) { return; } let item = tabMenu.appendChild(El('a', {}, El('i', { className: 'fa' + (dataItem['icon-type'] || 's') + ' fa-'+ dataItem.icon }), El('span', {}, dataItem.text) )); if (dataItem.id) { item.dataset.id = dataItem.id; } if (!active) { item.classList.add('active'); } active = true; }); tabMenu.dxOn('click', 'a', (_, t) => { t.dxSiblings('.active', itm => itm.classList.remove('active') ); t.classList.add('active'); content?.dxFind('[data-group]:not(.hide)')?.dxFadeOut(_ => content?.dxFind('[data-group="'+ t.dataset.id +'"]')?.dxFadeIn()); }); } if (node.title || node.descr || node?.children?.toolbar) { const topBarInner = content.appendChild(El('div', { className: 'top-bar' })) .appendChild(El('div', { className: 'top-bar-inner'}) ); if (node.title || node.descr) { const titleWrapper = topBarInner.appendChild(El('div', { className: 'title-wrapper' })); node.title && titleWrapper.append( El('h2', { className: 'title' }, Ut.tplString(node.title, node.template) )); node.descr && titleWrapper.append( El('p', { className: 'descr' }, Ut.tplString(node.descr, node.template)) ); } if (node?.children?.toolbar) { Render.makeUiElements( Ut.objVal(node.children.toolbar).children, topBarInner.appendChild( El('div', { className: 'ml10 flex v-center no-shrink' }) ) ); } } let container = (node['hide-content'] == 1) ? content : content.appendChild(El('div', { className: 'container' })); if (parent) { parent.append(card); } if (node.children) { Render.makeUiElements(node.children, container, node.template); } container.dxFind('[data-group]', (el, i) => i && el.classList.add('hide')); return card; } /** * Make Block Element */ static makeBlockElement(node, parent) { let block = El('div', { className: 'sp-block block-'+ node.name }); if (node.cls) { block.className += ' ' + node.cls; } if (node.id) { block.id = node.id; } if (node.title) { block.dataset.title = Ut.tplString(node.title, node.template); } if (node.descr) { block.dataset.descr = Ut.tplString(node.descr, node.template); } if (node.type) { block.dataset.type = node.type; } if (node.label) { block.textContent = Ut.tplString(node.label, node.template); } if (node.group) { block.dataset.group = node.group; } if (parent) { parent.append(block); } if (node.children) { Render.makeUiElements(node.children, block, node.template); } return block; } /** * Make Form Element */ static makeFormElement(node, parent) { let form = El('form', { novalidate: true, className: 'sp-form form-'+ node.name + (node.cls ? ' ' + node.cls : ''), autocomplete: 'off', method: 'post' }); if (node.title) { form.dataset.title = Ut.tplString(node.title, node.template); } if (node.group) { form.dataset.group = node.group; } if (parent) { parent.append(form); } if (node.children) { Render.makeUiElements(node.children, form, node.template); } return form; } /** * Make List Element */ static makeListElement(node, parent) { let list = El('ul', { className: 'sp-list list-'+ node.name + (node.cls ? ' ' + node.cls : ''), id: node.id || null }); if (node.group) { list.dataset.group = node.group; } if (node.children && node.children.item) { Ut.each (node.children.item, (item, action) => { let itm = El('li', { className: 'item-'+ (item.type || 'text') + (item.type == 'link' ? ' cmd-' : ' item-') + action + (item.cls ? ' ' + item.cls : ''), 'data-id': item.id || null, }); if (item.icon) { let icon = item.icon.split(' '); itm.append(El('i', { className: 'fa-'+ (icon[1] || 'solid') +' fa-'+ icon[0] }) ); } if (item.label) { itm.append(El('span', { className: 'item-label' }, Ut.tplString(item.label, node.template) )); } list.append(itm); }); if (parent) { parent.append(list); } } return list; } /** * Make Title Element */ static makeTitleElement(node, parent) { let blockTitle = El('div', { className: 'sp-title mb'+ (node.mb || 30) +' title-'+ node.name }); if (node.mt) { blockTitle.classList.add('mt' + node.mt); } if (node.cls) { blockTitle.classList.add(node.cls); } if (node.icon) { blockTitle.appendChild(El('i', { className: 'fa' + (field['icon-type'] || 's') + ' fa-'+ field.icon }) ); } let title = blockTitle.appendChild(El('h2', { className: 'text-semibold fz' + (node.size || 22) })); if (node.label) { title.textContent = Ut.tplString(node.label, node.template); } if (parent) { parent.append(blockTitle); } return blockTitle; } /** * Make Caption Element */ static makeCaptionElement(node, parent) { let blockTitle = El('div', { className: 'sp-caption mb'+ (node.mb || 30) +' caption-'+ node.name }); if (node.mt) { blockTitle.classList.add('mt' + node.mt); } if (node.cls) { blockTitle.classList.add(node.cls); } if (node.icon) { blockTitle.appendChild(El('i', { className: 'fa' + (field['icon-type'] || 's') + ' fa-'+ field.icon }) ); } let title = blockTitle.appendChild(El('h3', { className: 'text-medium fz' + (node.size || 18) })); if (node.label) { title.textContent = Ut.tplString(node.label, node.template); } if (parent) { parent.append(blockTitle); } return blockTitle; } /** * Make Info Element */ static makeInfoElement(field, parent) { let labelWrapper = El('div', { className: 'form-field field-info' }); if (field.name) { labelWrapper.dataset.name = field.name; } if (field.color) { labelWrapper.className += ' color-' + field.color; } if (field.cls) { labelWrapper.className += ' ' + field.cls; } if (field.icon) { labelWrapper.append( El('i', { className: 'fa' + (field['icon-type'] || 's') + ' fa-'+ field.icon + ' ic-info fz' + (field['icon-size'] || 32) + ' color-' + (field['icon-color'] || 'black') }) ); } let group = El('div'); if (field.label) { group.append(El('label', { className: 'label-name' }, Ut.tplString(field.label, field.template))); } let labelValue = El('div', { className: 'label-value' }); if (field.wrap) { labelValue.className += ' text-wrap'; } if (field.id) { labelValue.id = field.id; } if (field.value) { labelValue.textContent = field.value; } group.append(labelValue); labelWrapper.append(group); if (parent) { parent.append(labelWrapper); } return labelWrapper; } /** * Make Link Element */ static makeLinkElement(node, parent) { let elem = El('a', { className: 'link' }); if (node.name) { elem.className += ' link-' + node.name; } if (node.href) { node.href = node.href; } if (node.target) { node.target = node.target; } if (node.icon) { elem.prepend(El('i', { className: 'fa' + (node['icon-type'] || 's') + ' fa-'+ node.icon }) ); } if (node.label) { elem.append(Ut.tplString(node.label, node.template)); } else { elem.className += ' no-text'; } if (node.size) { elem.className += ' fz' + node.size; } if (node.color) { elem.className += ' color-' + node.color; } if (node.tooltip) { elem.dataset.tooltip = node.tooltip; if (node['tooltip-color']) { elem.className += ' tooltip-color-' + node['tooltip-color']; } } if (node.cls) { elem.className += ' ' + node.cls; } if (parent) { parent.append(elem); } return elem; } /** * Make Card Menu */ static makeCardMenu(node, parent) { let cardMenu = El('div', { className: 'sp-card-menu' }); if (node?.children?.item) { Ut.each (node.children.item, item => { if (!item.title) { return; } const menuItem = cardMenu.appendChild( El('a', { className: 'card-menu-item' }, El('div', { className: 'card-icon-wrapper' }, El('i', { className: 'fa' + (item['icon-type'] || 's') + ' fa-'+ (item.icon || 'cog') + ' fz' + (item['icon-size'] || 36) }) ), El('div', { className: 'card-menu-title' }, item.title) ) ); if (item.id) { menuItem.dataset.id = item.id; } if (item.init) { menuItem.dataset.init = item.init; } if (item.descr) { menuItem.append(El('div', { className: 'card-menu-descr' }, item.descr)); } cardMenu.append(menuItem); }); } if (parent) { parent.append(cardMenu); } return cardMenu; } /** * Make Button Element */ static makeButtonElement(node, parent) { let elem = El('button', { type: node.type || 'button' }); if (node.disabled) { elem.disabled = true; } elem.className = 'sp-button'; if (node.color) { elem.className += ' ui-color-' + node.color; } if (!node.label && node.icon) { elem.className += ' icon-only'; } if (node.cls) { elem.className += ' ' + node.cls; } if (node.id) { elem.id = node.id; } if (node.init) { elem.dataset.init = node.init; } if (node.label) { elem.append( El('span', {}, Ut.tplString(node.label, node.template) ) ); } if (node.icon) { elem.append( El('i', { className: 'icon-' + (node['icon-align'] || 'left') + ' fa' + (node['icon-type'] || 's') + ' fa-' + node.icon + ' fz' + (node['icon-size'] || 14) + ' color-' + (node['icon-color'] || 'white') }) ); } if (node.tooltip) { elem.dataset.tooltip = node.tooltip; if (node['tooltip-color']) { elem.className += ' tooltip-color-' + node['tooltip-color']; } } if (parent) { parent.append(elem); } return elem; } /** * Set Input Default Attrs */ static setInputAttr(input, field) { Ut.each(field, (val, key) => { if (key == 'readonly') { key = 'readOnly'; val = Boolean(val); } if (field.maxlength) { key = 'maxLength'; val = Boolean(val); } if (key === 'required' || key === 'disabled') { val = Boolean(val); } if (input.constructor.prototype) { if (key === 'type' || key === 'value') { return; } if (key === 'dataid') { input.dataset.id = field.dataid; return; } input[ key ] = val; } }); } /** * Make Field Wrapper */ static makeFieldWrapper(field, parent) { let wrapper = El('div', { className: 'form-field field-' + field.type }); if (field.name) { wrapper.className += ' field-' + field.name; } if (field.cls) { wrapper.className += ' ' + field.cls; } if (field.label) { wrapper.append( El('label', { className: 'field-label' }, Ut.tplString(field.label, field.template)) ); } let inputWrapper = null; if (field.type === 'text' || field.type === 'number' || field.type === 'password' || field.type === 'select') { inputWrapper = El('div', { className: 'input-wrapper' }); wrapper.append(inputWrapper); if (field.icon) { let icon; if (field['icon-tooltip'] || field['icon-action']) { icon = El('a', { className: 'icon-' + (field['icon-align'] || 'left') }, El('i', { className: 'fa' + (field['icon-type'] || 's') + ' fa-' + field.icon + ' fz' + (field['icon-size'] || 14) + ' color-' + (field['icon-color'] || 'black') }) ); } else { icon = El('i', { className: 'icon-' + (field['icon-align'] || 'left') + ' fa' + (field['icon-type'] || 's') + ' fa-' + field.icon + ' fz' + (field['icon-size'] || 14) + ' color-' + (field['icon-color'] || 'black') }); } if (field['icon-tooltip']) { icon.dataset.tooltip = field['icon-tooltip']; icon.className += ' tooltip-color-' + (field['icon-color'] || black); } if (field['icon-display'] == 0 ) { icon.className += ' hide'; } if (field['icon-cls']) { icon.className += ' ' + field['icon-cls']; } inputWrapper.append( icon ); } } if (parent) { parent.append(wrapper); } return inputWrapper || wrapper; } /** * Make Input Element */ static makeInputElement(field, parent) { const input = El('input', { type: field.type, className: 'sp-form-control' }); if (field.value) { input.value = field.value; } if (field.type !== 'hidden' && field.placeholder) { input.placeholder = field.placeholder; } if (field.type !== 'hidden' && field.autocomplete) { input.setAttribute('autocomplete', field.autocomplete || 'off'); } Render.setInputAttr(input, field); Render.makeFieldWrapper(field, parent).append(input); if (field.cancelable === '1') { new Cancelable(input); } if (field['max-chars']) { input.Counter({ limit: field['max-chars'] }); } return input; } /** * Make Textarea Element */ static makeTextareaElement(field, parent) { let textarea = El('textarea', { className: 'sp-form-control' }); if (field.rows) { textarea.setAttribute('rows', field.rows); } if (field.placeholder) { textarea.placeholder = field.placeholder; } Render.setInputAttr(textarea, field); if (field.autoexpand == '1') { textarea.AutoExpand({ minRows: field['min-rows'] }); } if (field['max-chars']) { textarea.Counter({ limit: field['max-chars'] }); } Render.makeFieldWrapper(field, parent).append(textarea); return textarea; } /** * Make DatePicker Element */ static makeDatePickerElement(field, parent) { const input = Render.makeInputElement({... field, ... { type: 'text' }}, parent); new DatePicker(input, { style: field.style || 'normal', disabled: field.disabled || false, multiple: field.multiple || false, setTime: field['set-time'] || false, locales: field.locales || 'ro-RO', dateFormat : field['date-format'] || 'dd-mm-yyyy', valueFormat : field['value-format'] || 'dd-mm-yyyy', datesSep : field['dates-sep'] || ' / ', valuesSep : field['values-sep'] || '&' }); return input; } /** * Make TwoDatePicker Element */ static makeTwoDatePickerElement(field, parent) { const input = Render.makeInputElement({... field, ... { type: 'text' }}, parent); new TwoDatePicker(input, { style: field.style || 'normal', disabled: field.disabled || false, locales: field.locales || 'ro-RO', }); return input; } /** * Make IntervalPicker Element */ static makeIntervalPickerElement(field, parent) { const input = Render.makeInputElement({... field, ... { type: 'text' }}, parent); new IntervalPicker(input, { style : field.style || 'normal', disabled : field.disabled || false, locales : field.locales || 'ro-RO', dateFormat : field['date-format'] || 'dd-mm-yyyy', valueFormat : field['value-format'] || 'dd-mm-yyyy' }); } /** * Make Input Color Element */ static makeInputColorElement(field, parent) { const input = El('input', { type: 'color' }); let elem = El('div', { className: 'input-color-wrapper' }, input); Render.setInputAttr(input, field); Render.makeFieldWrapper(field, parent).append(elem); return input; } /** * Make Input File */ static makeInputFileElement(field, parent, opts) { const input = El('input', { type: 'file' }); Render.setInputAttr(input, field); Render.makeFieldWrapper(field, parent).append(input); new Uploader(input, opts?.vars?.id ? { id: opts.vars.id } : {}); return input; } /** * Make Default Input Element */ static makeDefaultInputElement(field, parent) { const input = El('input', { type: field.type, className: 'sp-form-control' }); Render.setInputAttr(input, field); if (Ut.isSet(field.value)) { input.value = field.value; } return Render.makeFieldWrapper(field, parent).appendChild(input); } /** * Make Select Element */ static makeSelectElement(field, parent) { let elem = El('select', { className: 'sp-form-control' }); if (field.multiple === '1') { elem.multiple = true; } if (field.option) { Ut.each (field.option, (text, val) => elem.append(new Option(text, val, val == field.value)) ); if (field.value) { elem.value = field.value; } } Render.setInputAttr(elem, field); Render.makeFieldWrapper(field, parent).append(elem); return elem; } /** * Make Radio Element */ static makeRadioElement(field, parent) { if (!field.option) { return false; } let elem = El('div', { className: 'sp-control ' + field.type +'-control' }); Ut.each (field.option, (text, val) => { const input = El('input', { type: field.type }); Render.setInputAttr(input, field); if (field.value) { input.checked = (val == field.value); } input.value = val; let label = El('label', {}, input, El('span', { className: 'ctrl-indicator' }), text); label.className = 'radio-label'; if (field.xcls) { label.className += ' ' + field.xcls; } elem.append(label); if (field.group == '1' && field.type == 'checkbox') { input.SelectBox(); } }); Render.makeFieldWrapper(field, parent).append(elem); return elem; } /** * Make Radio Box Element */ static makeRadioBoxElement(field, parent) { if (!field.option) { return false; } let colors = [], i = 0; if (field.color) { colors = field.color.split(',').map(function(color) { return color.trim(); }); } let elem = El('div', { className: 'sp-radio-box' }); Ut.each (field.option, (text, val) => { let label = El('label'); label.className = 'radiobox-label'; if (field.xcls) { label.className += ' ' + field.xcls; } const input = El('input', { type: 'radio' }); Render.setInputAttr(input, field); input.value = val; input.checked = (val == field.value); label.append(input); let indicator = El('span', { className: 'ctrl-indicator label-color-'+ (colors[i] ? colors[i] : 'default') }); indicator.textContent = text; label.append(indicator); elem.append(label); i++; }); Render.makeFieldWrapper(field, parent).append(elem); return elem; } /** * Make Number Picker Element */ static makeNumPickerElement(field, parent) { const input = El('input', { name: field.name, type: 'hidden' }); if (field.id) { input.id = field.id; } if (Ut.isSet(field.value)) { input.value = field.value; } Render.makeFieldWrapper(field, parent).append(input); input.NumberPicker({ minVal: field.min, maxVal: field.max }); return input; } /** * Make Radio Box Element */ static makePinBoxElement(field, parent) { const input = El('input', { type: 'hidden', name: field.name }); Render.makeFieldWrapper(field, parent).append(input); new PinBox(input, { digits: field.digits || 6 }); return input; } /** * Make Hidden Element */ static makeHiddenElement(field, parent) { if (field.label) { return Render.makeInputElement(field, parent); } const input = parent.appendChild(El('input', { type: 'hidden' })); Ut.each(field, (val, key) => (key in HTMLInputElement.prototype) && (input[ key ] = val)) return input; } /** * Make Element */ static makeElement(props, parent, opts) { switch (props._type) { case 'card': return Render.makeCardElement(props, parent); case 'block': return Render.makeBlockElement(props, parent); case 'form': return Render.makeFormElement(props, parent); case 'field': return Render.makeFormControl(props, parent, opts); case 'list': return Render.makeListElement(props, parent); case 'title': return Render.makeTitleElement(props, parent); case 'caption': return Render.makeCaptionElement(props, parent); case 'link': return Render.makeLinkElement(props, parent); case 'button': case 'submit': return Render.makeButtonElement(props, parent); case 'info': return Render.makeInfoElement(props, parent); case 'cardmenu': return Render.makeCardMenu(props, parent); } } /** * Make Form Control */ static makeFormControl(field, parent, opts) { if (!field.type || !field.name) { // Field type && field name are required return false; } switch (field.type) { case 'text': return Render.makeInputElement(field, parent); case 'password': return Render.makeDefaultInputElement(field, parent); case 'textarea': return Render.makeTextareaElement(field, parent); case 'number': return Render.makeDefaultInputElement(field, parent); case 'range': return Render.makeDefaultInputElement(field, parent); case 'color': return Render.makeInputColorElement(field, parent); case 'datepicker': return Render.makeDatePickerElement(field, parent); case 'twodatepicker': return Render.makeTwoDatePickerElement(field, parent); case 'intervalpicker': return Render.makeIntervalPickerElement(field, parent); case 'file': return Render.makeInputFileElement(field, parent, opts); case 'select': return Render.makeSelectElement(field, parent); case 'radio': case 'checkbox': return Render.makeRadioElement(field, parent); case 'radiobox': return Render.makeRadioBoxElement(field, parent); case 'numpicker': return Render.makeNumPickerElement(field, parent); case 'pinbox': return Render.makePinBoxElement(field, parent); case 'hidden': return Render.makeHiddenElement(field, parent); } } /** * Make UI Elements */ static makeUiElements(rootNode, parent, template) { if (!rootNode) { return; } template = template || {}; let nodesList = []; Ut.each (rootNode, (node, type) => { Ut.each(node, (prop, name) => { if (typeof prop == 'object') { nodesList[prop.order] = prop; prop._type = type; prop.name = name; prop.template = template; } }); }); // Reiterate nodesList.forEach(props => Render.makeElement(props, parent)); } /** * Render UI Component * * @param string id Component ID * @param object template Template Object Vars * */ static make(id, template) { const token = id.split('.'); if (token.length != 2) { if (Boot.DEBUG) { console.log('Invalid component ID!'); } return null; } let nodeObj = null; if (Boot._section) { let mainNode = Boot.intData?.section[Boot._section[0]]; nodeObj = mainNode?.children?.[token[0]]?.[token[1]]; } else { nodeObj = Boot.intData?.[token[0]]?.[token[1]]; } if (!nodeObj) { if (Boot.DEBUG) { console.log('This component ID doesn\'t exist!'); } return null; } nodeObj._type = token[0]; nodeObj.name = token[1]; nodeObj.template = template || {}; return Render.makeElement(nodeObj, null, { vars: Boot.vars || {} }); } } // End Class