123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- (function(window, document, undefined) {
- var modalDiv = null,
- tooltipDiv = null,
- tooltipTimeout = null,
- dummyElem = null,
- domParser = null;
- LuCI.prototype = {
- /* URL construction helpers */
- path: function(prefix, parts) {
- var url = [ prefix || '' ];
- for (var i = 0; i < parts.length; i++)
- if (/^(?:[a-zA-Z0-9_.%,;-]+\/)*[a-zA-Z0-9_.%,;-]+$/.test(parts[i]))
- url.push('/', parts[i]);
- if (url.length === 1)
- url.push('/');
- return url.join('');
- },
- url: function() {
- return this.path(this.env.scriptname, arguments);
- },
- resource: function() {
- return this.path(this.env.resource, arguments);
- },
- location: function() {
- return this.path(this.env.scriptname, this.env.requestpath);
- },
- /* HTTP resource fetching */
- get: function(url, args, cb) {
- return this.poll(0, url, args, cb, false);
- },
- post: function(url, args, cb) {
- return this.poll(0, url, args, cb, true);
- },
- poll: function(interval, url, args, cb, post) {
- var data = post ? { token: this.env.token } : null;
- if (!/^(?:\/|\S+:\/\/)/.test(url))
- url = this.url(url);
- if (typeof(args) === 'object' && args !== null) {
- data = data || {};
- for (var key in args)
- if (args.hasOwnProperty(key))
- switch (typeof(args[key])) {
- case 'string':
- case 'number':
- case 'boolean':
- data[key] = args[key];
- break;
- case 'object':
- data[key] = JSON.stringify(args[key]);
- break;
- }
- }
- if (interval > 0)
- return XHR.poll(interval, url, data, cb, post);
- else if (post)
- return XHR.post(url, data, cb);
- else
- return XHR.get(url, data, cb);
- },
- stop: function(entry) { XHR.stop(entry) },
- halt: function() { XHR.halt() },
- run: function() { XHR.run() },
- /* Modal dialog */
- showModal: function(title, children) {
- var dlg = modalDiv.firstElementChild;
- dlg.setAttribute('class', 'modal');
- this.dom.content(dlg, this.dom.create('h4', {}, title));
- this.dom.append(dlg, children);
- document.body.classList.add('modal-overlay-active');
- return dlg;
- },
- hideModal: function() {
- document.body.classList.remove('modal-overlay-active');
- },
- /* Tooltip */
- showTooltip: function(ev) {
- var target = findParent(ev.target, '[data-tooltip]');
- if (!target)
- return;
- if (tooltipTimeout !== null) {
- window.clearTimeout(tooltipTimeout);
- tooltipTimeout = null;
- }
- var rect = target.getBoundingClientRect(),
- x = rect.left + window.pageXOffset,
- y = rect.top + rect.height + window.pageYOffset;
- tooltipDiv.className = 'cbi-tooltip';
- tooltipDiv.innerHTML = '▲ ';
- tooltipDiv.firstChild.data += target.getAttribute('data-tooltip');
- if (target.hasAttribute('data-tooltip-style'))
- tooltipDiv.classList.add(target.getAttribute('data-tooltip-style'));
- if ((y + tooltipDiv.offsetHeight) > (window.innerHeight + window.pageYOffset)) {
- y -= (tooltipDiv.offsetHeight + target.offsetHeight);
- tooltipDiv.firstChild.data = '▼ ' + tooltipDiv.firstChild.data.substr(2);
- }
- tooltipDiv.style.top = y + 'px';
- tooltipDiv.style.left = x + 'px';
- tooltipDiv.style.opacity = 1;
- tooltipDiv.dispatchEvent(new CustomEvent('tooltip-open', {
- bubbles: true,
- detail: { target: target }
- }));
- },
- hideTooltip: function(ev) {
- if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv ||
- tooltipDiv.contains(ev.target) || tooltipDiv.contains(ev.relatedTarget))
- return;
- if (tooltipTimeout !== null) {
- window.clearTimeout(tooltipTimeout);
- tooltipTimeout = null;
- }
- tooltipDiv.style.opacity = 0;
- tooltipTimeout = window.setTimeout(function() { tooltipDiv.removeAttribute('style'); }, 250);
- tooltipDiv.dispatchEvent(new CustomEvent('tooltip-close', { bubbles: true }));
- },
- /* 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;
- }
- };
- /* Tabs */
- LuCI.prototype.tabs = {
- init: function() {
- var groups = [], prevGroup = null, currGroup = null;
- document.querySelectorAll('[data-tab]').forEach(function(tab) {
- var parent = tab.parentNode;
- if (!parent.hasAttribute('data-tab-group'))
- parent.setAttribute('data-tab-group', groups.length);
- currGroup = +parent.getAttribute('data-tab-group');
- if (currGroup !== prevGroup) {
- prevGroup = currGroup;
- if (!groups[currGroup])
- groups[currGroup] = [];
- }
- groups[currGroup].push(tab);
- });
- for (var i = 0; i < groups.length; i++)
- this.initTabGroup(groups[i]);
- document.addEventListener('dependency-update', this.updateTabs.bind(this));
- this.updateTabs();
- if (!groups.length)
- this.setActiveTabId(-1, -1);
- },
- initTabGroup: function(panes) {
- if (!Array.isArray(panes) || panes.length === 0)
- return;
- var menu = E('ul', { 'class': 'cbi-tabmenu' }),
- group = panes[0].parentNode,
- groupId = +group.getAttribute('data-tab-group'),
- selected = null;
- for (var i = 0, pane; pane = panes[i]; i++) {
- var name = pane.getAttribute('data-tab'),
- title = pane.getAttribute('data-tab-title'),
- active = pane.getAttribute('data-tab-active') === 'true';
- menu.appendChild(E('li', {
- 'class': active ? 'cbi-tab' : 'cbi-tab-disabled',
- 'data-tab': name
- }, E('a', {
- 'href': '#',
- 'click': this.switchTab.bind(this)
- }, title)));
- if (active)
- selected = i;
- }
- group.parentNode.insertBefore(menu, group);
- if (selected === null) {
- selected = this.getActiveTabId(groupId);
- if (selected < 0 || selected >= panes.length)
- selected = 0;
- menu.childNodes[selected].classList.add('cbi-tab');
- menu.childNodes[selected].classList.remove('cbi-tab-disabled');
- panes[selected].setAttribute('data-tab-active', 'true');
- this.setActiveTabId(groupId, selected);
- }
- },
- getActiveTabState: function() {
- var page = document.body.getAttribute('data-page');
- try {
- var val = JSON.parse(window.sessionStorage.getItem('tab'));
- if (val.page === page && Array.isArray(val.groups))
- return val;
- }
- catch(e) {}
- window.sessionStorage.removeItem('tab');
- return { page: page, groups: [] };
- },
- getActiveTabId: function(groupId) {
- return +this.getActiveTabState().groups[groupId] || 0;
- },
- setActiveTabId: function(groupId, tabIndex) {
- try {
- var state = this.getActiveTabState();
- state.groups[groupId] = tabIndex;
- window.sessionStorage.setItem('tab', JSON.stringify(state));
- }
- catch (e) { return false; }
- return true;
- },
- updateTabs: function(ev) {
- document.querySelectorAll('[data-tab-title]').forEach(function(pane) {
- var menu = pane.parentNode.previousElementSibling,
- tab = menu.querySelector('[data-tab="%s"]'.format(pane.getAttribute('data-tab'))),
- n_errors = pane.querySelectorAll('.cbi-input-invalid').length;
- if (!pane.firstElementChild) {
- tab.style.display = 'none';
- tab.classList.remove('flash');
- }
- else if (tab.style.display === 'none') {
- tab.style.display = '';
- requestAnimationFrame(function() { tab.classList.add('flash') });
- }
- if (n_errors) {
- tab.setAttribute('data-errors', n_errors);
- tab.setAttribute('data-tooltip', _('%d invalid field(s)').format(n_errors));
- tab.setAttribute('data-tooltip-style', 'error');
- }
- else {
- tab.removeAttribute('data-errors');
- tab.removeAttribute('data-tooltip');
- }
- });
- },
- switchTab: function(ev) {
- var tab = ev.target.parentNode,
- name = tab.getAttribute('data-tab'),
- menu = tab.parentNode,
- group = menu.nextElementSibling,
- groupId = +group.getAttribute('data-tab-group'),
- index = 0;
- ev.preventDefault();
- if (!tab.classList.contains('cbi-tab-disabled'))
- return;
- menu.querySelectorAll('[data-tab]').forEach(function(tab) {
- tab.classList.remove('cbi-tab');
- tab.classList.remove('cbi-tab-disabled');
- tab.classList.add(
- tab.getAttribute('data-tab') === name ? 'cbi-tab' : 'cbi-tab-disabled');
- });
- group.childNodes.forEach(function(pane) {
- if (L.dom.matches(pane, '[data-tab]')) {
- if (pane.getAttribute('data-tab') === name) {
- pane.setAttribute('data-tab-active', 'true');
- L.tabs.setActiveTabId(groupId, index);
- }
- else {
- pane.setAttribute('data-tab-active', 'false');
- }
- index++;
- }
- });
- }
- };
- /* 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;
- }
- };
- /* Setup */
- LuCI.prototype.setupDOM = function(ev) {
- this.tabs.init();
- };
- function LuCI(env) {
- this.env = env;
- modalDiv = document.body.appendChild(
- this.dom.create('div', { id: 'modal_overlay' },
- this.dom.create('div', { class: 'modal', role: 'dialog', 'aria-modal': true })));
- 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);
- document.addEventListener('focus', this.showTooltip.bind(this), true);
- document.addEventListener('blur', this.hideTooltip.bind(this), true);
- document.addEventListener('DOMContentLoaded', this.setupDOM.bind(this));
- }
- window.LuCI = LuCI;
- })(window, document);
|