luci-base: move DOM manipulation functions to luci.js
authorJo-Philipp Wich <jo@mein.io>
Thu, 22 Nov 2018 07:52:14 +0000 (08:52 +0100)
committerJo-Philipp Wich <jo@mein.io>
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 <jo@mein.io>
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);