5 return L.Class.extend({
7 modalDiv = document.body.appendChild(
8 L.dom.create('div', { id: 'modal_overlay' },
9 L.dom.create('div', { class: 'modal', role: 'dialog', 'aria-modal': true })));
11 tooltipDiv = document.body.appendChild(
12 L.dom.create('div', { class: 'cbi-tooltip' }));
14 /* setup old aliases */
15 L.showModal = this.showModal;
16 L.hideModal = this.hideModal;
17 L.showTooltip = this.showTooltip;
18 L.hideTooltip = this.hideTooltip;
19 L.itemlist = this.itemlist;
21 document.addEventListener('mouseover', this.showTooltip.bind(this), true);
22 document.addEventListener('mouseout', this.hideTooltip.bind(this), true);
23 document.addEventListener('focus', this.showTooltip.bind(this), true);
24 document.addEventListener('blur', this.hideTooltip.bind(this), true);
26 document.addEventListener('luci-loaded', this.tabs.init.bind(this.tabs));
30 showModal: function(title, children) {
31 var dlg = modalDiv.firstElementChild;
33 dlg.setAttribute('class', 'modal');
35 L.dom.content(dlg, L.dom.create('h4', {}, title));
36 L.dom.append(dlg, children);
38 document.body.classList.add('modal-overlay-active');
43 hideModal: function() {
44 document.body.classList.remove('modal-overlay-active');
48 showTooltip: function(ev) {
49 var target = findParent(ev.target, '[data-tooltip]');
54 if (tooltipTimeout !== null) {
55 window.clearTimeout(tooltipTimeout);
56 tooltipTimeout = null;
59 var rect = target.getBoundingClientRect(),
60 x = rect.left + window.pageXOffset,
61 y = rect.top + rect.height + window.pageYOffset;
63 tooltipDiv.className = 'cbi-tooltip';
64 tooltipDiv.innerHTML = '▲ ';
65 tooltipDiv.firstChild.data += target.getAttribute('data-tooltip');
67 if (target.hasAttribute('data-tooltip-style'))
68 tooltipDiv.classList.add(target.getAttribute('data-tooltip-style'));
70 if ((y + tooltipDiv.offsetHeight) > (window.innerHeight + window.pageYOffset)) {
71 y -= (tooltipDiv.offsetHeight + target.offsetHeight);
72 tooltipDiv.firstChild.data = '▼ ' + tooltipDiv.firstChild.data.substr(2);
75 tooltipDiv.style.top = y + 'px';
76 tooltipDiv.style.left = x + 'px';
77 tooltipDiv.style.opacity = 1;
79 tooltipDiv.dispatchEvent(new CustomEvent('tooltip-open', {
81 detail: { target: target }
85 hideTooltip: function(ev) {
86 if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv ||
87 tooltipDiv.contains(ev.target) || tooltipDiv.contains(ev.relatedTarget))
90 if (tooltipTimeout !== null) {
91 window.clearTimeout(tooltipTimeout);
92 tooltipTimeout = null;
95 tooltipDiv.style.opacity = 0;
96 tooltipTimeout = window.setTimeout(function() { tooltipDiv.removeAttribute('style'); }, 250);
98 tooltipDiv.dispatchEvent(new CustomEvent('tooltip-close', { bubbles: true }));
102 itemlist: function(node, items, separators) {
105 if (!Array.isArray(separators))
106 separators = [ separators || E('br') ];
108 for (var i = 0; i < items.length; i += 2) {
109 if (items[i+1] !== null && items[i+1] !== undefined) {
110 var sep = separators[(i/2) % separators.length],
113 children.push(E('span', { class: 'nowrap' }, [
114 items[i] ? E('strong', items[i] + ': ') : '',
118 if ((i+2) < items.length)
119 children.push(L.dom.elem(sep) ? sep.cloneNode(true) : sep);
123 L.dom.content(node, children);
129 tabs: L.Class.singleton({
131 var groups = [], prevGroup = null, currGroup = null;
133 document.querySelectorAll('[data-tab]').forEach(function(tab) {
134 var parent = tab.parentNode;
136 if (!parent.hasAttribute('data-tab-group'))
137 parent.setAttribute('data-tab-group', groups.length);
139 currGroup = +parent.getAttribute('data-tab-group');
141 if (currGroup !== prevGroup) {
142 prevGroup = currGroup;
144 if (!groups[currGroup])
145 groups[currGroup] = [];
148 groups[currGroup].push(tab);
151 for (var i = 0; i < groups.length; i++)
152 this.initTabGroup(groups[i]);
154 document.addEventListener('dependency-update', this.updateTabs.bind(this));
159 this.setActiveTabId(-1, -1);
162 initTabGroup: function(panes) {
163 if (typeof(panes) != 'object' || !('length' in panes) || panes.length === 0)
166 var menu = E('ul', { 'class': 'cbi-tabmenu' }),
167 group = panes[0].parentNode,
168 groupId = +group.getAttribute('data-tab-group'),
171 for (var i = 0, pane; pane = panes[i]; i++) {
172 var name = pane.getAttribute('data-tab'),
173 title = pane.getAttribute('data-tab-title'),
174 active = pane.getAttribute('data-tab-active') === 'true';
176 menu.appendChild(E('li', {
177 'class': active ? 'cbi-tab' : 'cbi-tab-disabled',
181 'click': this.switchTab.bind(this)
188 group.parentNode.insertBefore(menu, group);
190 if (selected === null) {
191 selected = this.getActiveTabId(groupId);
193 if (selected < 0 || selected >= panes.length)
196 menu.childNodes[selected].classList.add('cbi-tab');
197 menu.childNodes[selected].classList.remove('cbi-tab-disabled');
198 panes[selected].setAttribute('data-tab-active', 'true');
200 this.setActiveTabId(groupId, selected);
204 getActiveTabState: function() {
205 var page = document.body.getAttribute('data-page');
208 var val = JSON.parse(window.sessionStorage.getItem('tab'));
209 if (val.page === page && Array.isArray(val.groups))
214 window.sessionStorage.removeItem('tab');
215 return { page: page, groups: [] };
218 getActiveTabId: function(groupId) {
219 return +this.getActiveTabState().groups[groupId] || 0;
222 setActiveTabId: function(groupId, tabIndex) {
224 var state = this.getActiveTabState();
225 state.groups[groupId] = tabIndex;
227 window.sessionStorage.setItem('tab', JSON.stringify(state));
229 catch (e) { return false; }
234 updateTabs: function(ev) {
235 document.querySelectorAll('[data-tab-title]').forEach(function(pane) {
236 var menu = pane.parentNode.previousElementSibling,
237 tab = menu.querySelector('[data-tab="%s"]'.format(pane.getAttribute('data-tab'))),
238 n_errors = pane.querySelectorAll('.cbi-input-invalid').length;
240 if (!pane.firstElementChild) {
241 tab.style.display = 'none';
242 tab.classList.remove('flash');
244 else if (tab.style.display === 'none') {
245 tab.style.display = '';
246 requestAnimationFrame(function() { tab.classList.add('flash') });
250 tab.setAttribute('data-errors', n_errors);
251 tab.setAttribute('data-tooltip', _('%d invalid field(s)').format(n_errors));
252 tab.setAttribute('data-tooltip-style', 'error');
255 tab.removeAttribute('data-errors');
256 tab.removeAttribute('data-tooltip');
261 switchTab: function(ev) {
262 var tab = ev.target.parentNode,
263 name = tab.getAttribute('data-tab'),
264 menu = tab.parentNode,
265 group = menu.nextElementSibling,
266 groupId = +group.getAttribute('data-tab-group'),
271 if (!tab.classList.contains('cbi-tab-disabled'))
274 menu.querySelectorAll('[data-tab]').forEach(function(tab) {
275 tab.classList.remove('cbi-tab');
276 tab.classList.remove('cbi-tab-disabled');
278 tab.getAttribute('data-tab') === name ? 'cbi-tab' : 'cbi-tab-disabled');
281 group.childNodes.forEach(function(pane) {
282 if (L.dom.matches(pane, '[data-tab]')) {
283 if (pane.getAttribute('data-tab') === name) {
284 pane.setAttribute('data-tab-active', 'true');
285 L.ui.tabs.setActiveTabId(groupId, index);
288 pane.setAttribute('data-tab-active', 'false');