diff options
author | Jo-Philipp Wich <jo@mein.io> | 2018-12-05 08:48:35 +0100 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2018-12-10 13:41:34 +0100 |
commit | 76e9c0305ed65480e0d1e3e86d831ac24de98bcd (patch) | |
tree | b5059bb0b138d4acd48f54814205f43ea888d878 | |
parent | 747e10bae6e9ec45e7d10f962c8c25ae804e97e7 (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.js | 78 | ||||
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/luci.js | 176 | ||||
-rw-r--r-- | modules/luci-base/luasrc/view/cbi/map.htm | 34 | ||||
-rw-r--r-- | modules/luci-base/luasrc/view/cbi/nsection.htm | 1 | ||||
-rw-r--r-- | modules/luci-base/luasrc/view/cbi/tabcontainer.htm | 15 | ||||
-rw-r--r-- | modules/luci-base/luasrc/view/cbi/tabmenu.htm | 12 | ||||
-rw-r--r-- | modules/luci-base/luasrc/view/cbi/tsection.htm | 2 |
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> |