1 (function(window, document, undefined) {
9 /* URL construction helpers */
10 path: function(prefix, parts) {
11 var url = [ prefix || '' ];
13 for (var i = 0; i < parts.length; i++)
14 if (/^(?:[a-zA-Z0-9_.%,;-]+\/)*[a-zA-Z0-9_.%,;-]+$/.test(parts[i]))
15 url.push('/', parts[i]);
24 return this.path(this.env.scriptname, arguments);
27 resource: function() {
28 return this.path(this.env.resource, arguments);
31 location: function() {
32 return this.path(this.env.scriptname, this.env.requestpath);
36 /* HTTP resource fetching */
37 get: function(url, args, cb) {
38 return this.poll(0, url, args, cb, false);
41 post: function(url, args, cb) {
42 return this.poll(0, url, args, cb, true);
45 poll: function(interval, url, args, cb, post) {
46 var data = post ? { token: this.env.token } : null;
48 if (!/^(?:\/|\S+:\/\/)/.test(url))
51 if (typeof(args) === 'object' && args !== null) {
55 if (args.hasOwnProperty(key))
56 switch (typeof(args[key])) {
60 data[key] = args[key];
64 data[key] = JSON.stringify(args[key]);
70 return XHR.poll(interval, url, data, cb, post);
72 return XHR.post(url, data, cb);
74 return XHR.get(url, data, cb);
77 stop: function(entry) { XHR.stop(entry) },
78 halt: function() { XHR.halt() },
79 run: function() { XHR.run() },
83 showModal: function(title, children) {
84 var dlg = modalDiv.firstElementChild;
86 dlg.setAttribute('class', 'modal');
88 this.dom.content(dlg, this.dom.create('h4', {}, title));
89 this.dom.append(dlg, children);
91 document.body.classList.add('modal-overlay-active');
96 hideModal: function() {
97 document.body.classList.remove('modal-overlay-active');
102 showTooltip: function(ev) {
103 var target = findParent(ev.target, '[data-tooltip]');
108 if (tooltipTimeout !== null) {
109 window.clearTimeout(tooltipTimeout);
110 tooltipTimeout = null;
113 var rect = target.getBoundingClientRect(),
114 x = rect.left + window.pageXOffset,
115 y = rect.top + rect.height + window.pageYOffset;
117 tooltipDiv.className = 'cbi-tooltip';
118 tooltipDiv.innerHTML = '▲ ';
119 tooltipDiv.firstChild.data += target.getAttribute('data-tooltip');
121 if (target.hasAttribute('data-tooltip-style'))
122 tooltipDiv.classList.add(target.getAttribute('data-tooltip-style'));
124 if ((y + tooltipDiv.offsetHeight) > (window.innerHeight + window.pageYOffset)) {
125 y -= (tooltipDiv.offsetHeight + target.offsetHeight);
126 tooltipDiv.firstChild.data = '▼ ' + tooltipDiv.firstChild.data.substr(2);
129 tooltipDiv.style.top = y + 'px';
130 tooltipDiv.style.left = x + 'px';
131 tooltipDiv.style.opacity = 1;
134 hideTooltip: function(ev) {
135 if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv)
138 if (tooltipTimeout !== null) {
139 window.clearTimeout(tooltipTimeout);
140 tooltipTimeout = null;
143 tooltipDiv.style.opacity = 0;
144 tooltipTimeout = window.setTimeout(function() { tooltipDiv.removeAttribute('style'); }, 250);
149 itemlist: function(node, items, separators) {
152 if (!Array.isArray(separators))
153 separators = [ separators || E('br') ];
155 for (var i = 0; i < items.length; i += 2) {
156 if (items[i+1] !== null && items[i+1] !== undefined) {
157 var sep = separators[(i/2) % separators.length],
160 children.push(E('span', { class: 'nowrap' }, [
161 items[i] ? E('strong', items[i] + ': ') : '',
165 if ((i+2) < items.length)
166 children.push(this.dom.elem(sep) ? sep.cloneNode(true) : sep);
170 this.dom.content(node, children);
176 /* DOM manipulation */
177 LuCI.prototype.dom = {
179 return (typeof(e) === 'object' && e !== null && 'nodeType' in e);
186 domParser = domParser || new DOMParser();
187 elem = domParser.parseFromString(s, 'text/html').body.firstChild;
193 dummyElem = dummyElem || document.createElement('div');
194 dummyElem.innerHTML = s;
195 elem = dummyElem.firstChild;
203 matches: function(node, selector) {
204 var m = this.elem(node) ? node.matches || node.msMatchesSelector : null;
205 return m ? m.call(node, selector) : false;
208 parent: function(node, selector) {
209 if (this.elem(node) && node.closest)
210 return node.closest(selector);
212 while (this.elem(node))
213 if (this.matches(node, selector))
216 node = node.parentNode;
221 append: function(node, children) {
222 if (!this.elem(node))
225 if (Array.isArray(children)) {
226 for (var i = 0; i < children.length; i++)
227 if (this.elem(children[i]))
228 node.appendChild(children[i]);
229 else if (children !== null && children !== undefined)
230 node.appendChild(document.createTextNode('' + children[i]));
232 return node.lastChild;
234 else if (typeof(children) === 'function') {
235 return this.append(node, children(node));
237 else if (this.elem(children)) {
238 return node.appendChild(children);
240 else if (children !== null && children !== undefined) {
241 node.innerHTML = '' + children;
242 return node.lastChild;
248 content: function(node, children) {
249 if (!this.elem(node))
252 while (node.firstChild)
253 node.removeChild(node.firstChild);
255 return this.append(node, children);
258 attr: function(node, key, val) {
259 if (!this.elem(node))
264 if (typeof(key) === 'object' && key !== null)
266 else if (typeof(key) === 'string')
267 attr = {}, attr[key] = val;
270 if (!attr.hasOwnProperty(key) || attr[key] === null || attr[key] === undefined)
273 switch (typeof(attr[key])) {
275 node.addEventListener(key, attr[key]);
279 node.setAttribute(key, JSON.stringify(attr[key]));
283 node.setAttribute(key, attr[key]);
289 var html = arguments[0],
290 attr = (arguments[1] instanceof Object && !Array.isArray(arguments[1])) ? arguments[1] : null,
291 data = attr ? arguments[2] : arguments[1],
296 else if (html.charCodeAt(0) === 60)
297 elem = this.parse(html);
299 elem = document.createElement(html);
304 this.attr(elem, attr);
305 this.append(elem, data);
314 modalDiv = document.body.appendChild(
315 this.dom.create('div', { id: 'modal_overlay' },
316 this.dom.create('div', { class: 'modal', role: 'dialog', 'aria-modal': true })));
318 tooltipDiv = document.body.appendChild(this.dom.create('div', { class: 'cbi-tooltip' }));
320 document.addEventListener('mouseover', this.showTooltip.bind(this), true);
321 document.addEventListener('mouseout', this.hideTooltip.bind(this), true);
322 document.addEventListener('focus', this.showTooltip.bind(this), true);
323 document.addEventListener('blur', this.hideTooltip.bind(this), true);
327 })(window, document);