Files
david/src/core/Render.js
T
2026-07-03 20:45:03 +03:00

1090 lines
23 KiB
JavaScript

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: 'sp-top-bar' }))
.appendChild(El('div', { className: 'sp-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 (key === 'maxlength') {
key = 'maxLength';
val = parseInt(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 = val;
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