1/*
2 * RapidContext <https://www.rapidcontext.com/>
3 * Copyright (c) 2007-2024 Per Cederberg. All rights reserved.
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the BSD license.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * See the RapidContext LICENSE for more details.
13 */
14
15/**
16 * Creates a DOM element with attributes and child content. Also supports
17 * building a UI recursively from either an XML document or an XML string.
18 *
19 * @param {String/Node} nodeOrName the element name or UI to create
20 * @param {Object} [attrs] the optional name-value attribute mappings
21 * @param {Node/String} [...content] the child nodes or text
22 *
23 * @return {Node} the DOM element node created
24 *
25 * @function RapidContext.UI.create
26 */
27export default function create(nodeOrName, attrs, ...content) {
28 if (RapidContext.Widget.Classes[nodeOrName]) {
29 return RapidContext.Widget.Classes[nodeOrName](attrs, ...content);
30 } else if (/^[a-z0-9_-]+$/i.test(nodeOrName)) {
31 let el = document.createElement(nodeOrName);
32 for (let k in (attrs || {})) {
33 el.setAttribute(k, attrs[k]);
34 }
35 el.append(...content.filter(isNotNull));
36 return el;
37 } else if (/^<[\s\S]+>$/.test(nodeOrName)) {
38 let node = new DOMParser().parseFromString(nodeOrName, 'text/xml');
39 return create(node.documentElement);
40 } else if (nodeOrName.nodeType === 1) { // Node.ELEMENT_NODE
41 return createElem(nodeOrName);
42 } else if (nodeOrName.nodeType === 3) { // Node.TEXT_NODE
43 let str = nodeOrName.nodeValue || '';
44 return str.trim() ? document.createTextNode(str) : null;
45 } else if (nodeOrName.nodeType === 4) { // Node.CDATA_SECTION_NODE
46 let str = nodeOrName.nodeValue || '';
47 return str ? document.createTextNode(str) : null;
48 } else if (nodeOrName.nodeType === 9) { // Node.DOCUMENT_NODE
49 return create(nodeOrName.documentElement);
50 } else {
51 return null;
52 }
53}
54
55// Creates a DOM element from a UI XML node
56function createElem(node) {
57 let name = node.nodeName;
58 if (name == 'style') {
59 document.head.append(createStyleElem(node.innerText));
60 node.parentNode.removeChild(node);
61 return null;
62 } else if (name == 'script') {
63 console.warn('script injection is unsupported in UI XML', node);
64 return null;
65 }
66 let attrs = Array.from(node.attributes).reduce((o, a) => ({ ...o, [a.name]: a.value }), {});
67 let children = Array.from(node.childNodes).map((o) => create(o)).filter(Boolean);
68 let el = create(name, attrs, ...children);
69 if ('id' in attrs) {
70 el.setAttribute('id', attrs.id);
71 }
72 if ('w' in attrs) {
73 el.style.width = toCssLength(attrs.w);
74 }
75 if ('h' in attrs) {
76 el.style.height = toCssLength(attrs.h);
77 }
78 return el;
79}
80
81// Creates a DOM style element with specified CSS rules
82function createStyleElem(css) {
83 let style = document.createElement('style');
84 style.setAttribute('type', 'text/css');
85 try {
86 style.innerHTML = css;
87 } catch (e) {
88 let parts = css.split(/\s*[{}]\s*/);
89 for (let i = 0; i < parts.length; i += 2) {
90 let rules = parts[i].split(/\s*,\s*/);
91 let styles = parts[i + 1];
92 for (let j = 0; j < rules.length; j++) {
93 let rule = rules[j].replace(/\s+/, ' ').trim();
94 style.styleSheet.addRule(rule, styles);
95 }
96 }
97 }
98}
99
100// Translates a short length into a CSS calc() expression
101function toCssLength(val) {
102 if (/[+-]/.test(val)) {
103 val = 'calc( ' + val.replace(/[+-]/g, ' $& ') + ' )';
104 }
105 val = val.replace(/(\d)( |$)/g, '$1px$2');
106 return val;
107}
108
109function isNotNull(o) {
110 return o != null;
111}
112
RapidContext
Access · Discovery · Insight
www.rapidcontext.com