luci-base: move DOM manipulation functions to luci.js

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>
This commit is contained in:
Jo-Philipp Wich 2018-11-22 08:52:14 +01:00
parent 14487071db
commit 7c16decdb4
2 changed files with 179 additions and 117 deletions

View 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) {

View 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 */
@ -72,25 +74,18 @@
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');
@ -146,14 +141,177 @@
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);