summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2019-01-07 15:26:08 +0100
committerJo-Philipp Wich <jo@mein.io>2019-07-07 15:25:49 +0200
commit344c4c511936062ec4521d42d1c1ba51577daee0 (patch)
tree9b7d2504765e4864f0313875e87b679e24c29a1c
parentec6d4094b988818faf6d5d06f6b26d3e1bcbcd6f (diff)
luci-base: luci.js: split ui helper functions into external ui.js
Use the new class loader infrastructure to move gui specific functionality out of the luci.js core and dispatch a new event 'luci-loaded' which is fired once all external classes have been fetched. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/luci.js353
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/ui.js296
2 files changed, 339 insertions, 310 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/luci.js b/modules/luci-base/htdocs/luci-static/resources/luci.js
index 610cbcb62..b86d499c6 100644
--- a/modules/luci-base/htdocs/luci-static/resources/luci.js
+++ b/modules/luci-base/htdocs/luci-static/resources/luci.js
@@ -423,10 +423,7 @@
});
- var modalDiv = null,
- tooltipDiv = null,
- tooltipTimeout = null,
- dummyElem = null,
+ var dummyElem = null,
domParser = null,
originalCBIInit = null,
classes = {};
@@ -436,17 +433,6 @@
__init__: function(env) {
Object.assign(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));
document.addEventListener('poll-start', function(ev) {
@@ -554,32 +540,50 @@
/* DOM setup */
setupDOM: function(ev) {
- this.tabs.init();
-
- Request.addInterceptor(function(res) {
- if (res.status != 403 || res.headers.get('X-LuCI-Login-Required') != 'yes')
- return;
-
- Request.poll.stop();
-
- L.showModal(_('Session expired'), [
- E('div', { class: 'alert-message warning' },
- _('A new login is required since the authentication session expired.')),
- E('div', { class: 'right' },
- E('div', {
- class: 'btn primary',
- click: function() {
- var loc = window.location;
- window.location = loc.protocol + '//' + loc.host + loc.pathname + loc.search;
- }
- }, _('To login…')))
- ]);
+ Promise.all([
+ L.require('ui')
+ ]).then(function() {
+ Request.addInterceptor(function(res) {
+ if (res.status != 403 || res.headers.get('X-LuCI-Login-Required') != 'yes')
+ return;
- return Promise.reject(new Error('Session expired'));
- });
+ Request.poll.stop();
+
+ L.ui.showModal(_('Session expired'), [
+ E('div', { class: 'alert-message warning' },
+ _('A new login is required since the authentication session expired.')),
+ E('div', { class: 'right' },
+ E('div', {
+ class: 'btn primary',
+ click: function() {
+ var loc = window.location;
+ window.location = loc.protocol + '//' + loc.host + loc.pathname + loc.search;
+ }
+ }, _('To login…')))
+ ]);
+
+ return Promise.reject(new Error('Session expired'));
+ });
+
+ originalCBIInit();
+ Request.poll.start();
- originalCBIInit();
- Request.poll.start();
+ document.dispatchEvent(new CustomEvent('luci-loaded'));
+ }).catch(function(error) {
+ var msg = (error.stack || '').replace(/(.+?)@(.+):(\d+):(\d+)/g,
+ ' at $1 ($2:$3:$4)');
+
+ if (msg.indexOf(error.message) == -1)
+ msg = error.message + '\n' + msg;
+
+ if (error.name && msg.indexOf(error.name) != 0)
+ msg = error.name + ': ' + msg;
+
+ alert('LuCI class loading error:\n' + msg);
+
+ if (window.console && console.debug)
+ console.debug(error);
+ });
},
env: {},
@@ -649,281 +653,10 @@
halt: function() { return Request.poll.stop() },
run: function() { return Request.poll.start() },
-
- /* 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;
- },
-
Class: Class,
Request: Request
});
- /* 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) {
diff --git a/modules/luci-base/htdocs/luci-static/resources/ui.js b/modules/luci-base/htdocs/luci-static/resources/ui.js
new file mode 100644
index 000000000..c69f1cb0a
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/ui.js
@@ -0,0 +1,296 @@
+var modalDiv = null,
+ tooltipDiv = null,
+ tooltipTimeout = null;
+
+return L.Class.extend({
+ __init__: function() {
+ modalDiv = document.body.appendChild(
+ L.dom.create('div', { id: 'modal_overlay' },
+ L.dom.create('div', { class: 'modal', role: 'dialog', 'aria-modal': true })));
+
+ tooltipDiv = document.body.appendChild(
+ L.dom.create('div', { class: 'cbi-tooltip' }));
+
+ /* setup old aliases */
+ L.showModal = this.showModal;
+ L.hideModal = this.hideModal;
+ L.showTooltip = this.showTooltip;
+ L.hideTooltip = this.hideTooltip;
+ L.itemlist = this.itemlist;
+
+ 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('luci-loaded', this.tabs.init.bind(this.tabs));
+ },
+
+ /* Modal dialog */
+ showModal: function(title, children) {
+ var dlg = modalDiv.firstElementChild;
+
+ dlg.setAttribute('class', 'modal');
+
+ L.dom.content(dlg, L.dom.create('h4', {}, title));
+ L.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(L.dom.elem(sep) ? sep.cloneNode(true) : sep);
+ }
+ }
+
+ L.dom.content(node, children);
+
+ return node;
+ },
+
+ /* Tabs */
+ tabs: L.Class.singleton({
+ 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 (typeof(panes) != 'object' || !('length' in 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.ui.tabs.setActiveTabId(groupId, index);
+ }
+ else {
+ pane.setAttribute('data-tab-active', 'false');
+ }
+
+ index++;
+ }
+ });
+ }
+ })
+});