luci-base: move DOM manipulation functions to luci.js
authorJo-Philipp Wich <[email protected]>
Thu, 22 Nov 2018 07:52:14 +0000 (08:52 +0100)
committerJo-Philipp Wich <[email protected]>
Thu, 22 Nov 2018 11:58:34 +0000 (12:58 +0100)
Introduce a new luci.dom class which groups the DOM manipulation helpers
such as E(), findParent(), matchesElem() etc.

Provide wrappers for the old functions to ease the transition to the new
functions.

Also add a new widget helper function L.itemlist() which consolidates
the item enumeration formatting code found on various pages.

Signed-off-by: Jo-Philipp Wich <[email protected]>
modules/luci-base/htdocs/luci-static/resources/cbi.js
modules/luci-base/htdocs/luci-static/resources/luci.js

index 19228a2ff928e2a9c4ae06cf21e9312d3a595df0..edf634ee742085d0696af884688c56585fb964de 100644 (file)
@@ -1478,107 +1478,11 @@ if (!window.requestAnimationFrame) {
 }
 
 
-var dummyElem, domParser;
-
-function isElem(e)
-{
-       return (typeof(e) === 'object' && e !== null && 'nodeType' in e);
-}
-
-function toElem(s)
-{
-       var elem;
-
-       try {
-               domParser = domParser || new DOMParser();
-               elem = domParser.parseFromString(s, 'text/html').body.firstChild;
-       }
-       catch(e) {}
-
-       if (!elem) {
-               try {
-                       dummyElem = dummyElem || document.createElement('div');
-                       dummyElem.innerHTML = s;
-                       elem = dummyElem.firstChild;
-               }
-               catch (e) {}
-       }
-
-       return elem || null;
-}
-
-function matchesElem(node, selector)
-{
-       return ((node.matches && node.matches(selector)) ||
-               (node.msMatchesSelector && node.msMatchesSelector(selector)));
-}
-
-function findParent(node, selector)
-{
-       if (node.closest)
-               return node.closest(selector);
-
-       while (node)
-               if (matchesElem(node, selector))
-                       return node;
-               else
-                       node = node.parentNode;
-
-       return null;
-}
-
-function E()
-{
-       var html = arguments[0],
-           attr = (arguments[1] instanceof Object && !Array.isArray(arguments[1])) ? arguments[1] : null,
-           data = attr ? arguments[2] : arguments[1],
-           elem;
-
-       if (isElem(html))
-               elem = html;
-       else if (html.charCodeAt(0) === 60)
-               elem = toElem(html);
-       else
-               elem = document.createElement(html);
-
-       if (!elem)
-               return null;
-
-       if (attr)
-               for (var key in attr)
-                       if (attr.hasOwnProperty(key) && attr[key] !== null && attr[key] !== undefined)
-                               switch (typeof(attr[key])) {
-                               case 'function':
-                                       elem.addEventListener(key, attr[key]);
-                                       break;
-
-                               case 'object':
-                                       elem.setAttribute(key, JSON.stringify(attr[key]));
-                                       break;
-
-                               default:
-                                       elem.setAttribute(key, attr[key]);
-                               }
-
-       if (typeof(data) === 'function')
-               data = data(elem);
-
-       if (isElem(data)) {
-               elem.appendChild(data);
-       }
-       else if (Array.isArray(data)) {
-               for (var i = 0; i < data.length; i++)
-                       if (isElem(data[i]))
-                               elem.appendChild(data[i]);
-                       else
-                               elem.appendChild(document.createTextNode('' + data[i]));
-       }
-       else if (data !== null && data !== undefined) {
-               elem.innerHTML = '' + data;
-       }
-
-       return elem;
-}
+function isElem(e) { return L.dom.elem(e) }
+function toElem(s) { return L.dom.parse(s) }
+function matchesElem(node, selector) { return L.dom.matches(node, selector) }
+function findParent(node, selector) { return L.dom.parent(node, selector) }
+function E() { return L.dom.create.apply(L.dom, arguments) }
 
 if (typeof(window.CustomEvent) !== 'function') {
        function CustomEvent(event, params) {
index cbf22460e496ec3ac01a197041b4e99c87f5177f..dcda941f7bfab8f84ff04ea5ff07bc03b5a2ee9d 100644 (file)
@@ -1,7 +1,9 @@
-(function(window, document) {
+(function(window, document, undefined) {
        var modalDiv = null,
            tooltipDiv = null,
-           tooltipTimeout = null;
+           tooltipTimeout = null,
+           dummyElem = null,
+           domParser = null;
 
        LuCI.prototype = {
                /* URL construction helpers */
                                return XHR.get(url, data, cb);
                },
 
+               halt: function() { XHR.halt() },
+               run: function() { XHR.run() },
+
 
                /* Modal dialog */
                showModal: function(title, children) {
                        var dlg = modalDiv.firstElementChild;
 
-                       while (dlg.firstChild)
-                               dlg.removeChild(dlg.firstChild);
-
                        dlg.setAttribute('class', 'modal');
-                       dlg.appendChild(E('h4', {}, title));
 
-                       if (!Array.isArray(children))
-                               children = [ children ];
-
-                       for (var i = 0; i < children.length; i++)
-                               if (isElem(children[i]))
-                                       dlg.appendChild(children[i]);
-                               else
-                                       dlg.appendChild(document.createTextNode('' + children[i]));
+                       this.dom.content(dlg, this.dom.create('h4', {}, title));
+                       this.dom.append(dlg, children);
 
                        document.body.classList.add('modal-overlay-active');
 
 
                        tooltipDiv.style.opacity = 0;
                        tooltipTimeout = window.setTimeout(function() { tooltipDiv.removeAttribute('style'); }, 250);
+               },
+
+
+               /* Widget helper */
+               itemlist: function(node, items, separators) {
+                       var children = [];
+
+                       if (!Array.isArray(separators))
+                               separators = [ separators || E('br') ];
+
+                       for (var i = 0; i < items.length; i += 2) {
+                               if (items[i+1] !== null && items[i+1] !== undefined) {
+                                       var sep = separators[(i/2) % separators.length],
+                                           cld = [];
+
+                                       children.push(E('span', { class: 'nowrap' }, [
+                                               items[i] ? E('strong', items[i] + ': ') : '',
+                                               items[i+1]
+                                       ]));
+
+                                       if ((i+2) < items.length)
+                                               children.push(this.dom.elem(sep) ? sep.cloneNode(true) : sep);
+                               }
+                       }
+
+                       this.dom.content(node, children);
+
+                       return node;
+               }
+       };
+
+       /* DOM manipulation */
+       LuCI.prototype.dom = {
+               elem: function(e) {
+                       return (typeof(e) === 'object' && e !== null && 'nodeType' in e);
+               },
+
+               parse: function(s) {
+                       var elem;
+
+                       try {
+                               domParser = domParser || new DOMParser();
+                               elem = domParser.parseFromString(s, 'text/html').body.firstChild;
+                       }
+                       catch(e) {}
+
+                       if (!elem) {
+                               try {
+                                       dummyElem = dummyElem || document.createElement('div');
+                                       dummyElem.innerHTML = s;
+                                       elem = dummyElem.firstChild;
+                               }
+                               catch (e) {}
+                       }
+
+                       return elem || null;
+               },
+
+               matches: function(node, selector) {
+                       var m = this.elem(node) ? node.matches || node.msMatchesSelector : null;
+                       return m ? m.call(node, selector) : false;
+               },
+
+               parent: function(node, selector) {
+                       if (this.elem(node) && node.closest)
+                               return node.closest(selector);
+
+                       while (this.elem(node))
+                               if (this.matches(node, selector))
+                                       return node;
+                               else
+                                       node = node.parentNode;
+
+                       return null;
+               },
+
+               append: function(node, children) {
+                       if (!this.elem(node))
+                               return null;
+
+                       if (Array.isArray(children)) {
+                               for (var i = 0; i < children.length; i++)
+                                       if (this.elem(children[i]))
+                                               node.appendChild(children[i]);
+                                       else if (children !== null && children !== undefined)
+                                               node.appendChild(document.createTextNode('' + children[i]));
+
+                               return node.lastChild;
+                       }
+                       else if (typeof(children) === 'function') {
+                               return this.append(node, children(node));
+                       }
+                       else if (this.elem(children)) {
+                               return node.appendChild(children);
+                       }
+                       else if (children !== null && children !== undefined) {
+                               node.innerHTML = '' + children;
+                               return node.lastChild;
+                       }
+
+                       return null;
+               },
+
+               content: function(node, children) {
+                       if (!this.elem(node))
+                               return null;
+
+                       while (node.firstChild)
+                               node.removeChild(node.firstChild);
+
+                       return this.append(node, children);
+               },
+
+               attr: function(node, key, val) {
+                       if (!this.elem(node))
+                               return null;
+
+                       var attr = null;
+
+                       if (typeof(key) === 'object' && key !== null)
+                               attr = key;
+                       else if (typeof(key) === 'string')
+                               attr = {}, attr[key] = val;
+
+                       for (key in attr) {
+                               if (!attr.hasOwnProperty(key) || attr[key] === null || attr[key] === undefined)
+                                       continue;
+
+                               switch (typeof(attr[key])) {
+                               case 'function':
+                                       node.addEventListener(key, attr[key]);
+                                       break;
+
+                               case 'object':
+                                       node.setAttribute(key, JSON.stringify(attr[key]));
+                                       break;
+
+                               default:
+                                       node.setAttribute(key, attr[key]);
+                               }
+                       }
+               },
+
+               create: function() {
+                       var html = arguments[0],
+                           attr = (arguments[1] instanceof Object && !Array.isArray(arguments[1])) ? arguments[1] : null,
+                           data = attr ? arguments[2] : arguments[1],
+                           elem;
+
+                       if (this.elem(html))
+                               elem = html;
+                       else if (html.charCodeAt(0) === 60)
+                               elem = this.parse(html);
+                       else
+                               elem = document.createElement(html);
+
+                       if (!elem)
+                               return null;
+
+                       this.attr(elem, attr);
+                       this.append(elem, data);
+
+                       return elem;
                }
        };
 
        function LuCI(env) {
                this.env = env;
 
-               modalDiv = document.body.appendChild(E('div', { id: 'modal_overlay' }, E('div', { class: 'modal' })));
-               tooltipDiv = document.body.appendChild(E('div', { 'class': 'cbi-tooltip' }));
+               modalDiv = document.body.appendChild(this.dom.create('div', { id: 'modal_overlay' }, this.dom.create('div', { class: 'modal' })));
+               tooltipDiv = document.body.appendChild(this.dom.create('div', { class: 'cbi-tooltip' }));
 
                document.addEventListener('mouseover', this.showTooltip.bind(this), true);
                document.addEventListener('mouseout', this.hideTooltip.bind(this), true);