summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2018-12-05 08:48:35 +0100
committerJo-Philipp Wich <jo@mein.io>2018-12-10 13:41:34 +0100
commit76e9c0305ed65480e0d1e3e86d831ac24de98bcd (patch)
treeb5059bb0b138d4acd48f54814205f43ea888d878
parent747e10bae6e9ec45e7d10f962c8c25ae804e97e7 (diff)
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>
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi.js78
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/luci.js176
-rw-r--r--modules/luci-base/luasrc/view/cbi/map.htm34
-rw-r--r--modules/luci-base/luasrc/view/cbi/nsection.htm1
-rw-r--r--modules/luci-base/luasrc/view/cbi/tabcontainer.htm15
-rw-r--r--modules/luci-base/luasrc/view/cbi/tabmenu.htm12
-rw-r--r--modules/luci-base/luasrc/view/cbi/tsection.htm2
7 files changed, 208 insertions, 110 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi.js b/modules/luci-base/htdocs/luci-static/resources/cbi.js
index 635740a70..61b83e829 100644
--- a/modules/luci-base/htdocs/luci-static/resources/cbi.js
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi.js
@@ -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 */
diff --git a/modules/luci-base/htdocs/luci-static/resources/luci.js b/modules/luci-base/htdocs/luci-static/resources/luci.js
index c1c1b0dd3..4cb8bf4e5 100644
--- a/modules/luci-base/htdocs/luci-static/resources/luci.js
+++ b/modules/luci-base/htdocs/luci-static/resources/luci.js
@@ -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;
diff --git a/modules/luci-base/luasrc/view/cbi/map.htm b/modules/luci-base/luasrc/view/cbi/map.htm
index d65a16167..31997a1c0 100644
--- a/modules/luci-base/luasrc/view/cbi/map.htm
+++ b/modules/luci-base/luasrc/view/cbi/map.htm
@@ -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">
diff --git a/modules/luci-base/luasrc/view/cbi/nsection.htm b/modules/luci-base/luasrc/view/cbi/nsection.htm
index 63abc5773..14232e3d9 100644
--- a/modules/luci-base/luasrc/view/cbi/nsection.htm
+++ b/modules/luci-base/luasrc/view/cbi/nsection.htm
@@ -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>
diff --git a/modules/luci-base/luasrc/view/cbi/tabcontainer.htm b/modules/luci-base/luasrc/view/cbi/tabcontainer.htm
index 38c435d6a..7fcb83578 100644
--- a/modules/luci-base/luasrc/view/cbi/tabcontainer.htm
+++ b/modules/luci-base/luasrc/view/cbi/tabcontainer.htm
@@ -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 %>
diff --git a/modules/luci-base/luasrc/view/cbi/tabmenu.htm b/modules/luci-base/luasrc/view/cbi/tabmenu.htm
deleted file mode 100644
index 06c1414bf..000000000
--- a/modules/luci-base/luasrc/view/cbi/tabmenu.htm
+++ /dev/null
@@ -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 -%>
diff --git a/modules/luci-base/luasrc/view/cbi/tsection.htm b/modules/luci-base/luasrc/view/cbi/tsection.htm
index 1a13df0c0..547a79332 100644
--- a/modules/luci-base/luasrc/view/cbi/tsection.htm
+++ b/modules/luci-base/luasrc/view/cbi/tsection.htm
@@ -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>