@numbers-framework/frontend-react
Version:
React Frontend for Numbers Framework
1,237 lines (1,118 loc) • 26.7 kB
JavaScript
import React, { createElement, Fragment, useEffect } from 'react';
import { NumbersSelect } from './NumbersSelect/Base.js';
import { renderToStaticMarkup } from 'react-dom/server';
const HTML = (props) => <>{props.children}</>;
HTML.Div = (props) => {
return createElement(
'div',
props,
props.children
);
};
HTML.Span = (props) => {
return createElement(
'span',
props,
props.children
);
};
HTML.P = (props) => {
return createElement(
'p',
props,
props.children
);
};
HTML.Br = (props) => {
return createElement(
'br',
props,
props.children
);
};
HTML.Hr = (props) => {
return createElement(
'hr',
props,
props.children
);
};
HTML.Address = (props) => {
return createElement(
'address',
props,
props.children
);
};
HTML.Article = (props) => {
return createElement(
'article',
props,
props.children
);
};
HTML.Aside = (props) => {
return createElement(
'aside',
props,
props.children
);
};
HTML.Footer = (props) => {
return createElement(
'footer',
props,
props.children
);
};
HTML.Header = (props) => {
return createElement(
'header',
props,
props.children
);
};
HTML.HGroup = (props) => {
return createElement(
'hgroup',
props,
props.children
);
};
HTML.Main = (props) => {
return createElement(
'main',
props,
props.children
);
};
HTML.Nav = (props) => {
return createElement(
'nav',
props,
props.children
);
};
HTML.Section = (props) => {
return createElement(
'section',
props,
props.children
);
};
HTML.Search = (props) => {
return createElement(
'search',
props,
props.children
);
};
HTML.H1 = (props) => {
return createElement(
'h1',
props,
props.children
);
};
HTML.H2 = (props) => {
return createElement(
'h2',
props,
props.children
);
};
HTML.H3 = (props) => {
return createElement(
'h3',
props,
props.children
);
};
HTML.H4 = (props) => {
return createElement(
'h4',
props,
props.children
);
};
HTML.H5 = (props) => {
return createElement(
'h5',
props,
props.children
);
};
HTML.H6 = (props) => {
return createElement(
'h6',
props,
props.children
);
};
HTML.BlockQuote = (props) => {
return createElement(
'blockquote',
props,
props.children
);
};
HTML.Dd = (props) => {
return createElement(
'dd',
props,
props.children
);
};
HTML.Dl = (props) => {
return createElement(
'dl',
props,
props.children
);
};
HTML.Dt = (props) => {
return createElement(
'dt',
props,
props.children
);
};
HTML.Figure = (props) => {
return createElement(
'figure',
props,
props.children
);
};
HTML.FigCaption = (props) => {
return createElement(
'figcaption',
props,
props.children
);
};
HTML.Ol = (props) => {
return createElement(
'ol',
props,
props.children
);
};
HTML.Ul = (props) => {
return createElement(
'ul',
props,
props.children
);
};
HTML.Menu = (props) => {
return createElement(
'menu',
props,
props.children
);
};
HTML.Li = (props) => {
return createElement(
'li',
props,
props.children
);
};
HTML.Pre = (props) => {
return createElement(
'pre',
props,
props.children
);
};
HTML.A = (props) => {
return createElement(
'a',
props,
props.children
);
};
HTML.Abbr = (props) => {
return createElement(
'abbr',
props,
props.children
);
};
HTML.B = (props) => {
return createElement(
'b',
props,
props.children
);
};
HTML.Bdi = (props) => {
return createElement(
'bdi',
props,
props.children
);
};
HTML.Bdo = (props) => {
return createElement(
'bdo',
props,
props.children
);
};
HTML.Cite = (props) => {
return createElement(
'cite',
props,
props.children
);
};
HTML.Code = (props) => {
return createElement(
'code',
props,
props.children
);
};
HTML.Data = (props) => {
return createElement(
'data',
props,
props.children
);
};
HTML.Dfn = (props) => {
return createElement(
'dfn',
props,
props.children
);
};
HTML.Em = (props) => {
return createElement(
'em',
props,
props.children
);
};
HTML.I = (props) => {
return createElement(
'i',
props,
props.children
);
};
HTML.Kbd = (props) => {
return createElement(
'kbd',
props,
props.children
);
};
HTML.Mark = (props) => {
return createElement(
'mark',
props,
props.children
);
};
HTML.Q = (props) => {
return createElement(
'q',
props,
props.children
);
};
HTML.Ruby = (props) => {
return createElement(
'ruby',
props,
props.children
);
};
HTML.Rt = (props) => {
return createElement(
'rt',
props,
props.children
);
};
HTML.Rp = (props) => {
return createElement(
'rp',
props,
props.children
);
};
HTML.S = (props) => {
return createElement(
's',
props,
props.children
);
};
HTML.Small = (props) => {
return createElement(
'small',
props,
props.children
);
};
HTML.Strong = (props) => {
return createElement(
'strong',
props,
props.children
);
};
HTML.Sub = (props) => {
return createElement(
'sub',
props,
props.children
);
};
HTML.Sup = (props) => {
return createElement(
'sup',
props,
props.children
);
};
HTML.Time = (props) => {
return createElement(
'time',
props,
props.children
);
};
HTML.U = (props) => {
return createElement(
'u',
props,
props.children
);
};
HTML.Var = (props) => {
return createElement(
'var',
props,
props.children
);
};
HTML.Wbr = (props) => {
return createElement(
'wbr',
props,
props.children
);
};
HTML.Area = (props) => {
return createElement(
'area',
props,
props.children
);
};
HTML.Audio = (props) => {
return createElement(
'audio',
props,
props.children
);
};
HTML.Img = (props) => {
return createElement(
'img',
props,
props.children
);
};
HTML.Map = (props) => {
return createElement(
'map',
props,
props.children
);
};
HTML.Track = (props) => {
return createElement(
'track',
props,
props.children
);
};
HTML.Video = (props) => {
return createElement(
'video',
props,
props.children
);
};
HTML.Embed = (props) => {
return createElement(
'embed',
props,
props.children
);
};
HTML.FencedFrame = (props) => {
return createElement(
'fencedframe',
props,
props.children
);
};
HTML.IFrame = (props) => {
return createElement(
'iframe',
props,
props.children
);
};
HTML.Object = (props) => {
return createElement(
'object',
props,
props.children
);
};
HTML.Picture = (props) => {
return createElement(
'picture',
props,
props.children
);
};
HTML.Portal = (props) => {
return createElement(
'portal',
props,
props.children
);
};
HTML.Source = (props) => {
return createElement(
'source',
props,
props.children
);
};
HTML.Svg = (props) => {
return createElement(
'svg',
props,
props.children
);
};
HTML.Math = (props) => {
return createElement(
'math',
props,
props.children
);
};
HTML.Canvas = (props) => {
return createElement(
'canvas',
props,
props.children
);
};
HTML.NoScript = (props) => {
return createElement(
'noscript',
props,
props.children
);
};
HTML.Script = (props) => {
return createElement(
'script',
props,
props.children
);
};
HTML.Del = (props) => {
return createElement(
'del',
props,
props.children
);
};
HTML.Ins = (props) => {
return createElement(
'ins',
props,
props.children
);
};
HTML.Table = (props) => {
return createElement(
'table',
props,
props.children
);
};
HTML.Caption = (props) => {
return createElement(
'caption',
props,
props.children
);
};
HTML.Col = (props) => {
return createElement(
'col',
props,
props.children
);
};
HTML.ColGroup = (props) => {
return createElement(
'colgroup',
props,
props.children
);
};
HTML.TBody = (props) => {
return createElement(
'tbody',
props,
props.children
);
};
HTML.Td = (props) => {
return createElement(
'td',
props,
props.children
);
};
HTML.TFoot = (props) => {
return createElement(
'tfoot',
props,
props.children
);
};
HTML.Th = (props) => {
return createElement(
'th',
props,
props.children
);
};
HTML.THead = (props) => {
return createElement(
'thead',
props,
props.children
);
};
HTML.Tr = (props) => {
return createElement(
'tr',
props,
props.children
);
};
HTML.Button = (props) => {
let options = {...props};
let type = options['type'] ?? 'secondary';
if (type === 'default') {
type = 'secondary';
}
options['type'] = options['input_type'] ?? options['type'] ?? 'button';
options['value'] = options['value'] ?? 'Submit';
options['class'] = (options['class'] || '') + ' btn btn-' + type;
return HTML.Input({tag: 'input', ...options});
};
HTML.Button2 = (props) => {
let options = {...props};
let type = options['type'] ?? 'secondary';
if (type === 'default') {
type = 'secondary';
}
options['type'] = options['input_type'] ?? options['type'] ?? 'submit';
let value = options['value'] ?? 'Submit';
options['value'] = 1;
if (!options.children) {
options.children = [];
}
options.children.push(value);
options['class'] = (options['class'] || '') + ' btn btn-' + type;
return HTML.Tag({tag: 'button', ...options});
};
HTML.DataList = (props) => {
return createElement(
'datalist',
props,
props.children
);
};
HTML.FieldSet = (props) => {
return createElement(
'fieldset',
props,
props.children
);
};
HTML.Form = (props) => {
return createElement(
'form',
props,
props.children
);
};
HTML.Input = (props) => {
return HTML.Tag({tag: "input", ...props, self_closing: true});
};
HTML.Label = (props) => {
return HTML.Tag({tag: "label", ...props});
};
HTML.Legend = (props) => {
return createElement(
'legend',
props,
props.children
);
};
HTML.Meter = (props) => {
return createElement(
'meter',
props,
props.children
);
};
HTML.Output = (props) => {
return createElement(
'output',
props,
props.children
);
};
HTML.Progress = (props) => {
return createElement(
'progress',
props,
props.children
);
};
HTML.TextArea = (props) => {
return createElement(
'textarea',
props,
props.children
);
};
HTML.Details = (props) => {
return createElement(
'details',
props,
props.children
);
};
HTML.Dialog = (props) => {
return createElement(
'dialog',
props,
props.children
);
};
HTML.Summary = (props) => {
return createElement(
'summary',
props,
props.children
);
};
const HTMLSelect = (props) => {
let children = props.children;
let settings = { ...props };
// determine ID
if (!settings.id && settings.name) {
settings.id = settings.name;
}
if (!settings.id) {
settings.id = 'numbers_select_random_generated_id_' + Math.round(Math.random() * 1000) + '_' + Math.round(Math.random() * 1000) + '_' + Math.round(Math.random() * 1000);
}
// mounting
useEffect(() => {
if (settings.numbers) {
NumbersSelect(settings);
}
}, []);
if (settings.options) {
children = [];
let options = settings.options;
for (let k in settings.options) {
options[k]['value'] = k;
// if its object we expect to have name in there
if (typeof options[k] === 'object') {
options[k]['children'] = options[k]['name'];
} else {
options[k]['children'] = options[k];
}
options[k]['key'] = k;
// todo: add loc
children.push(HTML.Option(options[k]));
}
}
return createElement(
'select',
settings,
children
);
};
const HTMLOption = (props) => {
return createElement(
'option',
props,
props.children
);
};
const HTMLOptGroup = (props) => {
return createElement(
'optgroup',
props,
props.children
);
};
HTML.Select = HTMLSelect;
HTML.Option = HTMLOption;
HTML.OptGroup = HTMLOptGroup;
const arrayReverseObj = obj => Object.keys(obj).reverse().map(key=> ({ ...obj[key] }));
const percentageToGridColumns = (field_sizes, options) => {
options = options || {};
options['grid_columns'] = options['grid_columns'] || 12;
let step = 100 / options['grid_columns'];
let total = 0;
let empty = 0;
let arr = {
percent: {},
final: {},
temp: {},
grouped: {}
};
for (let k in field_sizes) {
arr['percent'][k] = field_sizes[k] ? field_sizes[k] : 0;
if (field_sizes[k]) {
total+= field_sizes[k];
} else {
empty+= 1;
}
}
// if we have empty columns and percent is less than 100 we prepopulate
if (total < 100 && empty !== 0) {
let temp = (100 - total) / empty;
for (let k in arr['percent']) {
if (arr['percent'][k] == 0) {
arr['percent'][k] = temp;
total+= temp;
}
}
}
// we need to rescale if percent is more than 100
if (total > 100) {
let scale = 100 / total;
for (let k in arr['percent']) {
arr['percent'][k] = arr['percent'][k] * scale;
}
}
// sort in ascending order
arr['percent'] = Object.entries(arr['percent'])
.sort(([,a],[,b]) => a-b)
.reduce((r, [k, v]) => ({ ...r, [k]: v }), {});
let cells_left = options['grid_columns'];
for (let k in arr['percent']) {
if (arr['percent'][k] <= step) {
arr['percent'][k] = 1;
cells_left--;
} else {
let rounded = Math.floor(arr['percent'][k] / step);
let leftover = arr['percent'][k] - rounded * step;
arr['final'][k] = rounded;
cells_left-= rounded;
if (leftover > 0) {
arr['temp'][k] = leftover;
}
}
}
// if we have cells left we distribute
if (cells_left > 0) {
// if we got here we distribute on by one
if (cells_left > 0) {
arr['temp'] = Object.entries(arr['temp'])
.sort(([,a],[,b]) => a-b)
.reduce((r, [k, v]) => ({ ...r, [k]: v }), {});
for (let k in arr['temp']) {
arr['final'][k]++;
cells_left--;
delete arr['temp'][k];
if (cells_left == 0) {
break;
}
}
}
}
return {
success: true,
error: [],
data: arr['final'],
percent: arr['percent']
};
};
HTML.PercentageToGridColumns = percentageToGridColumns;
const Grid = (options) => {
const rtl = options['rtl'] || false;
const grid_columns = 12;
let rows = options['options'] || {};
delete options['options'];
options['cell_class'] = options['cell_class'] || 'col-sm-';
if (!options['cell_class'].endsWith('-')) {
options['cell_class']+= '-';
}
let result = '';
for (let k in rows) {
// we need to determine field sizes
let field_sizes = [];
let row_class = '';
let flag_have_label = false;
for (let k2 in rows[k]) {
for (let k3 in rows[k][k2]) {
if (rows[k][k2][k3]['options']['percent']) {
field_sizes.push(parseInt(rows[k][k2][k3]['options']['percent']));
} else {
field_sizes.push(null);
}
if (rows[k][k2][k3]['row_class']) {
row_class+= ' ' + rows[k][k2][k3]['row_class'];
}
if ((rows[k][k2][k3]['label'] ?? '') !== '') {
flag_have_label = true;
}
}
}
let field_new_sizes = HTML.PercentageToGridColumns(field_sizes);
// count number of fields
let count_fields = Object.values(rows[k]).length;
result+= '<div class="row' + row_class + '">';
// we need to fill up empty columns if rtl
if (rtl) {
let index = 0;
let current_grid_columns = 0;
for (let k2 in rows[k]) {
for (let k3 in rows[k][k2]) {
// if we are mannually set field sizes we skip
if (rows[k][k2][k3]['options']['field_size']) {
current_grid_columns = 12;
break;
}
current_grid_columns+= field_new_sizes['data'][index];
rows[k][k2][k3]['options']['field_size'] = options['cell_class'] + field_new_sizes['data'][index]; // a must
index++;
}
}
if (current_grid_columns != grid_columns) {
rows[k]['__empty_column_fill__']['__empty_column_fill__'] = {
value: ' ',
options: {
field_size: options['cell_class'] + (grid_columns - current_grid_columns) // a must
}
};
field_new_sizes['data'][index] = grid_columns - current_grid_columns;
}
rows[k] = arrayReverseObj(rows[k]);
}
// loop though each field and render it
let index = 0;
for (let k2 in rows[k]) {
let flag_first_field = true;
if (rtl) {
rows[k][k2] = arrayReverseObj(rows[k][k2]);
}
for (let k3 in rows[k][k2]) {
let error_class = '';
if (rows[k][k2][k3]['error'] && rows[k][k2][k3]['error']['type']) {
if (rows[k][k2][k3]['error']['type'] == 'danger') {
rows[k][k2][k3]['error']['type'] = 'error';
}
error_class = 'has-' + rows[k][k2][k3]['error']['type'];
}
// style
let style = '';
if (rows[k][k2][k3]['options']['style']) {
style = ' style="' + rows[k][k2][k3]['options']['style'] + '"';
}
let field_size = rows[k][k2][k3]['options']['field_size'] ?? (options['cell_class'] + field_new_sizes['data'][index]);
let element_class = rows[k][k2][k3]['class'] ?? '';
result+= '<div class="' + field_size + ' form-group ' + error_class + ' ' + element_class + '"' + style + '>';
// label
if (flag_first_field) {
if ((rows[k][k2][k3]['label'] ?? '') !== '') {
// if label is not wrapped into label we autowrap
const label = new String(rows[k][k2][k3]['label']);
if (label.search('<label') == -1) {
rows[k][k2][k3]['label'] = HTML.Label({value: rows[k][k2][k3]['label'], return_html: true});
}
result+= rows[k][k2][k3]['label'];
} else if (flag_have_label) {
result+= '<label> </label>';
}
flag_first_field = false;
} else {
if (flag_have_label) {
result+= '<label> </label>';
}
}
result+= rows[k][k2][k3]['value'] ?? '';
// error messages
if (rows[k][k2][k3]['error'] && rows[k][k2][k3]['error']['message']) {
result+= rows[k][k2][k3]['error']['message'];
}
// description after error message
if (rows[k][k2][k3]['description']) {
result+= HTML.Text({type: 'muted', value: rows[k][k2][k3]['description'], return_html: true});
}
result+= '</div>';
index++;
}
}
result+= '</div>';
}
let outer_class = ['container-fluid'];
if (options['class']) {
outer_class.push(options['class']);
}
return '<div class="' + outer_class.join(' ') + '">' + result + '</div>';
};
HTML.Grid = Grid;
const Text = (props) => {
let options = props;
if (!props.children) {
props.children = [];
}
options['tag'] = options['tag'] ?? 'p';
options['type'] = 'text-' + (options['type'] ?? 'primary');
options['class'] = (options['class'] ?? '') + ' ' + options['type'];
if (options['value']) {
props.children.push(<React.Fragment>{options['value']}</React.Fragment>);
}
return createElement(
options['tag'],
options,
props.children
);
};
HTML.Text = Text;
const Separator = (props) => {
let value = props.value ?? null;
let icon = props.icon ?? null;
let result = '';
result+= '<table width="100%">';
result+= '<tr><td width="50%"><hr/></td>';
if (value || icon) {
// todo add Name method
result+= '<td width="1%" nowrap><b>' + HTML.Name(value, icon) + '</b></td>';
}
result+= '<td width="50%"><hr/></td></tr>';
result+= '</table>';
return result;
};
HTML.Separator = Separator;
const Mandatory = (props) => {
let options = {...props};
let asterisk = '';
switch (options['type'] ?? '') {
case 'mandatory':
asterisk = '<b style="color: red;" title="' + 'Mandatory' + '">*</b>';
options['tag'] = 'b';
break;
case 'conditional':
asterisk = '<b style="color: green;" title="' + 'Conditional' + '">*</b>';
options['tag'] = 'b';
break;
default:
options['tag'] = 'span';
}
// if we are formatting value
if (options['value']) {
if (options['value']['value']) {
options['value']['value'] = options['value']['value'] + ' ' + asterisk + (options['prepend'] ?? '');
} else {
options['value'] = {
value: options['value'] + ' ' + asterisk + (options['prepend'] ?? '')
};
}
options['value']['tag'] = options['tag'];
return HTML.Tag(options['value']);
} else {
return asterisk;
}
};
HTML.Mandatory = Mandatory;
const Tag = (props) => {
let options = {...props};
let tag = options['tag'] || 'div';
delete options['tag'];
if (options['class']) {
options['className'] = options['class'];
}
delete options['class'];
let as_html = false;
if (options['return_html']) {
as_html = true;
}
if (options['self_closing']) {
delete options['self_closing'];
} else {
if (options['have_html']) {
delete options['have_html'];
options['dangerouslySetInnerHTML'] = {};
options['dangerouslySetInnerHTML']['__html'] = options['value'];
} else if (options['value'] && !options.children) {
if (!options.children) {
options.children = [];
}
options.children.push(<Fragment>{options['value']}</Fragment>);
delete options['value'];
}
}
delete options['return_html'];
let result = createElement(
tag,
options,
options.children
);
if (as_html) {
return renderToStaticMarkup(result);
} else {
return result;
}
};
HTML.Tag = Tag;
HTML.Component = (options) => {
const TagComponent = options['component'];
delete options['component'];
return <TagComponent {...options} />;
};
const Icon = (options) => {
options['class'] = options['class'] ?? '';
options['tag'] = options['tag'] ?? 'i';
if (options['type'].indexOf('material-symbols-outlined') != -1) {
let value = options['type'].replaceAll(/(material-symbols-outlined)|(light)|(regular)|(bold)|(fill)|(animated)|(dark)|(inactive)/gi, '').trim();
options['value'] = value;
options['class']+= ' ' + options['type'].replace(value, '');
} else {
options['class']+= ' ' +options['type'];
}
delete options['type'];
return HTML.Tag(options);
}
HTML.Icon = Icon;
export default HTML;
export {
HTML
};