luci-base: rework ui tabbing code
- Instantiate tab menus on the client side - Simplify server side markup generation - Show error indicators in cbi tabs Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
parent
747e10bae6
commit
76e9c0305e
7 changed files with 208 additions and 110 deletions
|
@ -12,7 +12,6 @@
|
|||
*/
|
||||
|
||||
var cbi_d = [];
|
||||
var cbi_t = [];
|
||||
var cbi_strings = { path: {}, label: {} };
|
||||
|
||||
function s8(bytes, off) {
|
||||
|
@ -727,13 +726,13 @@ function cbi_d_update() {
|
|||
parent.parentNode.style.display = (parent.options.length <= 1) ? 'none' : '';
|
||||
}
|
||||
|
||||
if (entry && entry.parent) {
|
||||
if (!cbi_t_update())
|
||||
cbi_tag_last(parent);
|
||||
}
|
||||
if (entry && entry.parent)
|
||||
cbi_tag_last(parent);
|
||||
|
||||
if (state)
|
||||
cbi_d_update();
|
||||
else if (parent)
|
||||
parent.dispatchEvent(new CustomEvent('dependency-update', { bubbles: true }));
|
||||
}
|
||||
|
||||
function cbi_init() {
|
||||
|
@ -1045,75 +1044,6 @@ function cbi_dynlist_init(dl, datatype, optional, choices)
|
|||
cbi_dynlist_init.prototype = CBIDynamicList;
|
||||
|
||||
|
||||
function cbi_t_add(section, tab) {
|
||||
var t = document.getElementById('tab.' + section + '.' + tab);
|
||||
var c = document.getElementById('container.' + section + '.' + tab);
|
||||
|
||||
if (t && c) {
|
||||
cbi_t[section] = (cbi_t[section] || [ ]);
|
||||
cbi_t[section][tab] = { 'tab': t, 'container': c, 'cid': c.id };
|
||||
}
|
||||
}
|
||||
|
||||
function cbi_t_switch(section, tab) {
|
||||
if (cbi_t[section] && cbi_t[section][tab]) {
|
||||
var o = cbi_t[section][tab];
|
||||
var h = document.getElementById('tab.' + section);
|
||||
|
||||
for (var tid in cbi_t[section]) {
|
||||
var o2 = cbi_t[section][tid];
|
||||
|
||||
if (o.tab.id != o2.tab.id) {
|
||||
o2.tab.classList.remove('cbi-tab');
|
||||
o2.tab.classList.add('cbi-tab-disabled');
|
||||
o2.container.style.display = 'none';
|
||||
}
|
||||
else {
|
||||
if(h)
|
||||
h.value = tab;
|
||||
|
||||
o2.tab.classList.remove('cbi-tab-disabled');
|
||||
o2.tab.classList.add('cbi-tab');
|
||||
o2.container.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function cbi_t_update() {
|
||||
var hl_tabs = [ ];
|
||||
var updated = false;
|
||||
|
||||
for (var sid in cbi_t)
|
||||
for (var tid in cbi_t[sid]) {
|
||||
var t = cbi_t[sid][tid].tab;
|
||||
var c = cbi_t[sid][tid].container;
|
||||
|
||||
if (!c.firstElementChild) {
|
||||
t.style.display = 'none';
|
||||
}
|
||||
else if (t.style.display == 'none') {
|
||||
t.style.display = '';
|
||||
t.classList.add('cbi-tab-highlighted');
|
||||
hl_tabs.push(t);
|
||||
}
|
||||
|
||||
cbi_tag_last(c);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (hl_tabs.length > 0)
|
||||
window.setTimeout(function() {
|
||||
for (var i = 0; i < hl_tabs.length; i++)
|
||||
hl_tabs[i].classList.remove('cbi-tab-highlighted');
|
||||
}, 750);
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
||||
function cbi_validate_form(form, errmsg)
|
||||
{
|
||||
/* if triggered by a section removal or addition, don't validate */
|
||||
|
|
|
@ -181,6 +181,175 @@
|
|||
}
|
||||
};
|
||||
|
||||
/* 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) {
|
||||
|
@ -316,6 +485,11 @@
|
|||
}
|
||||
};
|
||||
|
||||
/* Setup */
|
||||
LuCI.prototype.setupDOM = function(ev) {
|
||||
this.tabs.init();
|
||||
};
|
||||
|
||||
function LuCI(env) {
|
||||
this.env = env;
|
||||
|
||||
|
@ -329,6 +503,8 @@
|
|||
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;
|
||||
|
|
|
@ -3,25 +3,25 @@
|
|||
<%- end end -%>
|
||||
|
||||
<div class="cbi-map" id="cbi-<%=self.config%>">
|
||||
<% if self.title and #self.title > 0 then %><h2 name="content"><%=self.title%></h2><% end %>
|
||||
<% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %>
|
||||
<% if self.title and #self.title > 0 then %>
|
||||
<h2 name="content"><%=self.title%></h2>
|
||||
<% end %>
|
||||
<% if self.description and #self.description > 0 then %>
|
||||
<div class="cbi-map-descr"><%=self.description%></div>
|
||||
<% end %>
|
||||
<% if self.tabbed then %>
|
||||
<ul class="cbi-tabmenu map">
|
||||
<%- self.selected_tab = luci.http.formvalue("tab.m-" .. self.config) %>
|
||||
<% for i, section in ipairs(self.children) do %>
|
||||
<%- if not self.selected_tab then self.selected_tab = section.sectiontype end %>
|
||||
<li id="tab.m-<%=self.config%>.<%=section.section or section.sectiontype%>" class="cbi-tab<%=(section.sectiontype == self.selected_tab) and '' or '-disabled'%>">
|
||||
<a onclick="this.blur(); return cbi_t_switch('m-<%=self.config%>', '<%=section.section or section.sectiontype%>')" href="<%=REQUEST_URI%>?tab.m-<%=self.config%>=<%=section.section or section.sectiontype%>"><%=section.title or section.section or section.sectiontype %></a>
|
||||
<% if section.sectiontype == self.selected_tab then %><input type="hidden" id="tab.m-<%=self.config%>" name="tab.m-<%=self.config%>" value="<%=section.section or section.sectiontype%>" /><% end %>
|
||||
</li>
|
||||
<div>
|
||||
<% for i, section in ipairs(self.children) do
|
||||
tab = section.section or section.sectiontype %>
|
||||
<div class="cbi-tabcontainer"<%=
|
||||
attr("id", "container.m-%s.%s" %{ self.config, tab }) ..
|
||||
attr("data-tab", tab) ..
|
||||
attr("data-tab-title", section.title or tab))
|
||||
%>>
|
||||
<% section:render() %>
|
||||
</div>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% for i, section in ipairs(self.children) do %>
|
||||
<div class="cbi-tabcontainer" id="container.m-<%=self.config%>.<%=section.section or section.sectiontype%>"<% if section.sectiontype ~= self.selected_tab then %> style="display:none"<% end %>>
|
||||
<% section:render() %>
|
||||
</div>
|
||||
<script type="text/javascript">cbi_t_add('m-<%=self.config%>', '<%=section.section or section.sectiontype%>')</script>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if not self.save then -%>
|
||||
<div class="cbi-section-error">
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
<input type="submit" class="cbi-button" name="cbi.rns.<%=self.config%>.<%=section%>" value="<%:Delete%>" />
|
||||
</div>
|
||||
<%- end %>
|
||||
<%+cbi/tabmenu%>
|
||||
<div class="cbi-section-node<% if self.tabs then %> cbi-section-node-tabbed<% end %>" id="cbi-<%=self.config%>-<%=section%>">
|
||||
<%+cbi/ucisection%>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
<% for tab, data in pairs(self.tabs) do %>
|
||||
<div class="cbi-tabcontainer" id="container.<%=self.config%>.<%=section%>.<%=tab%>"<% if tab ~= self.selected_tab then %> style="display:none"<% end %>>
|
||||
<% if data.description then %><div class="cbi-tab-descr"><%=data.description%></div><% end %>
|
||||
<% for _, tab in ipairs(self.tab_names) do data = self.tabs[tab] %>
|
||||
<div class="cbi-tabcontainer"<%=
|
||||
attr("id", "container.%s.%s.%s" %{ self.config, section, tab }) ..
|
||||
attr("data-tab", tab) ..
|
||||
attr("data-tab-title", data.title) ..
|
||||
attr("data-tab-active", tostring(tab == self.selected_tab))
|
||||
%>>
|
||||
<% if data.description then %>
|
||||
<div class="cbi-tab-descr"><%=data.description%></div>
|
||||
<% end %>
|
||||
|
||||
<% self:render_tab(tab, section, scope or {}) %>
|
||||
</div>
|
||||
<script type="text/javascript">cbi_t_add('<%=self.config%>.<%=section%>', '<%=tab%>')</script>
|
||||
<% end %>
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
<%- if self.tabs then %>
|
||||
<ul class="cbi-tabmenu">
|
||||
<%- self.selected_tab = luci.http.formvalue("tab." .. self.config .. "." .. section) %>
|
||||
<%- for _, tab in ipairs(self.tab_names) do if #self.tabs[tab].childs > 0 then %>
|
||||
<%- if not self.selected_tab then self.selected_tab = tab end %>
|
||||
<li id="tab.<%=self.config%>.<%=section%>.<%=tab%>" class="cbi-tab<%=(tab == self.selected_tab) and '' or '-disabled'%>">
|
||||
<a onclick="this.blur(); return cbi_t_switch('<%=self.config%>.<%=section%>', '<%=tab%>')" href="<%=REQUEST_URI%>?tab.<%=self.config%>.<%=section%>=<%=tab%>"><%=self.tabs[tab].title%></a>
|
||||
<% if tab == self.selected_tab then %><input type="hidden" id="tab.<%=self.config%>.<%=section%>" name="tab.<%=self.config%>.<%=section%>" value="<%=tab%>" /><% end %>
|
||||
</li>
|
||||
<% end end -%>
|
||||
</ul>
|
||||
<% end -%>
|
|
@ -18,8 +18,6 @@
|
|||
<h3><%=section:upper()%></h3>
|
||||
<%- end %>
|
||||
|
||||
<%+cbi/tabmenu%>
|
||||
|
||||
<div class="cbi-section-node<% if self.tabs then %> cbi-section-node-tabbed<% end %>" id="cbi-<%=self.config%>-<%=section%>">
|
||||
<%+cbi/ucisection%>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue