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 halt: function() { XHR.halt() },
78 run: function() { XHR.run() },
82 showModal: function(title, children) {
83 var dlg = modalDiv.firstElementChild;
85 dlg.setAttribute('class', 'modal');
87 this.dom.content(dlg, this.dom.create('h4', {}, title));
88 this.dom.append(dlg, children);
90 document.body.classList.add('modal-overlay-active');
95 hideModal: function() {
96 document.body.classList.remove('modal-overlay-active');
101 showTooltip: function(ev) {
102 var target = findParent(ev.target, '[data-tooltip]');
107 if (tooltipTimeout !== null) {
108 window.clearTimeout(tooltipTimeout);
109 tooltipTimeout = null;
112 var rect = target.getBoundingClientRect(),
113 x = rect.left + window.pageXOffset,
114 y = rect.top + rect.height + window.pageYOffset;
116 tooltipDiv.className = 'cbi-tooltip';
117 tooltipDiv.innerHTML = '▲ ';
118 tooltipDiv.firstChild.data += target.getAttribute('data-tooltip');
120 if (target.hasAttribute('data-tooltip-style'))
121 tooltipDiv.classList.add(target.getAttribute('data-tooltip-style'));
123 if ((y + tooltipDiv.offsetHeight) > (window.innerHeight + window.pageYOffset)) {
124 y -= (tooltipDiv.offsetHeight + target.offsetHeight);
125 tooltipDiv.firstChild.data = '▼ ' + tooltipDiv.firstChild.data.substr(2);
128 tooltipDiv.style.top = y + 'px';
129 tooltipDiv.style.left = x + 'px';
130 tooltipDiv.style.opacity = 1;
133 hideTooltip: function(ev) {
134 if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv)
137 if (tooltipTimeout !== null) {
138 window.clearTimeout(tooltipTimeout);
139 tooltipTimeout = null;
142 tooltipDiv.style.opacity = 0;
143 tooltipTimeout = window.setTimeout(function() { tooltipDiv.removeAttribute('style'); }, 250);
148 itemlist: function(node, items, separators) {
151 if (!Array.isArray(separators))
152 separators = [ separators || E('br') ];
154 for (var i = 0; i < items.length; i += 2) {
155 if (items[i+1] !== null && items[i+1] !== undefined) {
156 var sep = separators[(i/2) % separators.length],
159 children.push(E('span', { class: 'nowrap' }, [
160 items[i] ? E('strong', items[i] + ': ') : '',
164 if ((i+2) < items.length)
165 children.push(this.dom.elem(sep) ? sep.cloneNode(true) : sep);
169 this.dom.content(node, children);
175 /* DOM manipulation */
176 LuCI.prototype.dom = {
178 return (typeof(e) === 'object' && e !== null && 'nodeType' in e);
185 domParser = domParser || new DOMParser();
186 elem = domParser.parseFromString(s, 'text/html').body.firstChild;
192 dummyElem = dummyElem || document.createElement('div');
193 dummyElem.innerHTML = s;
194 elem = dummyElem.firstChild;
202 matches: function(node, selector) {
203 var m = this.elem(node) ? node.matches || node.msMatchesSelector : null;
204 return m ? m.call(node, selector) : false;
207 parent: function(node, selector) {
208 if (this.elem(node) && node.closest)
209 return node.closest(selector);
211 while (this.elem(node))
212 if (this.matches(node, selector))
215 node = node.parentNode;
220 append: function(node, children) {
221 if (!this.elem(node))
224 if (Array.isArray(children)) {
225 for (var i = 0; i < children.length; i++)
226 if (this.elem(children[i]))
227 node.appendChild(children[i]);
228 else if (children !== null && children !== undefined)
229 node.appendChild(document.createTextNode('' + children[i]));
231 return node.lastChild;
233 else if (typeof(children) === 'function') {
234 return this.append(node, children(node));
236 else if (this.elem(children)) {
237 return node.appendChild(children);
239 else if (children !== null && children !== undefined) {
240 node.innerHTML = '' + children;
241 return node.lastChild;
247 content: function(node, children) {
248 if (!this.elem(node))
251 while (node.firstChild)
252 node.removeChild(node.firstChild);
254 return this.append(node, children);
257 attr: function(node, key, val) {
258 if (!this.elem(node))
263 if (typeof(key) === 'object' && key !== null)
265 else if (typeof(key) === 'string')
266 attr = {}, attr[key] = val;
269 if (!attr.hasOwnProperty(key) || attr[key] === null || attr[key] === undefined)
272 switch (typeof(attr[key])) {
274 node.addEventListener(key, attr[key]);
278 node.setAttribute(key, JSON.stringify(attr[key]));
282 node.setAttribute(key, attr[key]);
288 var html = arguments[0],
289 attr = (arguments[1] instanceof Object && !Array.isArray(arguments[1])) ? arguments[1] : null,
290 data = attr ? arguments[2] : arguments[1],
295 else if (html.charCodeAt(0) === 60)
296 elem = this.parse(html);
298 elem = document.createElement(html);
303 this.attr(elem, attr);
304 this.append(elem, data);
313 modalDiv = document.body.appendChild(this.dom.create('div', { id: 'modal_overlay' }, this.dom.create('div', { class: 'modal' })));
314 tooltipDiv = document.body.appendChild(this.dom.create('div', { class: 'cbi-tooltip' }));
316 document.addEventListener('mouseover', this.showTooltip.bind(this), true);
317 document.addEventListener('mouseout', this.hideTooltip.bind(this), true);
318 document.addEventListener('focus', this.showTooltip.bind(this), true);
319 document.addEventListener('blur', this.hideTooltip.bind(this), true);
323 })(window, document);