luci-0.9: backport CBI from trunk, adds tab support, auto-hiding of commit notifications and better layout control

This commit is contained in:
Jo-Philipp Wich 2010-03-09 02:05:49 +00:00
parent 2c59a118ed
commit 3441fe8c82
7 changed files with 272 additions and 27 deletions

View file

@ -2,7 +2,7 @@
LuCI - Lua Configuration Interface
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
Copyright 2008-2009 Jo-Philipp Wich <xm@subsignal.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,6 +14,8 @@
*/
var cbi_d = [];
var cbi_t = [];
var cbi_c = [];
function cbi_d_add(field, dep, next) {
var obj = document.getElementById(field);
@ -43,7 +45,18 @@ function cbi_d_checkvalue(target, ref) {
var t = document.getElementById(target);
var value;
if (!t || !t.value) {
if (!t) {
var tl = document.getElementsByName(target);
if( tl.length > 0 && tl[0].type == 'radio' )
for( var i = 0; i < tl.length; i++ )
if( tl[i].checked ) {
value = tl[i].value;
break;
}
value = value ? value : "";
} else if (!t.value) {
value = "";
} else {
value = t.value;
@ -57,15 +70,26 @@ function cbi_d_checkvalue(target, ref) {
}
function cbi_d_check(deps) {
var reverse;
var def = false;
for (var i=0; i<deps.length; i++) {
var istat = true
var istat = true;
reverse = false;
for (var j in deps[i]) {
istat = (istat && cbi_d_checkvalue(j, deps[i][j]))
if (j == "!reverse") {
reverse = true;
} else if (j == "!default") {
def = true;
istat = false;
} else {
istat = (istat && cbi_d_checkvalue(j, deps[i][j]))
}
}
if (istat) {
return true
return !reverse;
}
}
return def;
}
function cbi_d_update() {
@ -78,16 +102,25 @@ function cbi_d_update() {
if (node && node.parentNode && !cbi_d_check(entry.deps)) {
node.parentNode.removeChild(node);
state = (state || !node.parentNode)
state = true;
if( entry.parent )
cbi_c[entry.parent]--;
} else if ((!node || !node.parentNode) && cbi_d_check(entry.deps)) {
if (!next) {
parent.appendChild(entry.node);
} else {
next.parentNode.insertBefore(entry.node, next);
}
state = (state || (node && node.parentNode))
state = true;
if( entry.parent )
cbi_c[entry.parent]++;
}
}
if (entry.parent) {
cbi_t_update();
}
if (state) {
cbi_d_update();
}
@ -219,3 +252,50 @@ function cbi_hijack_forms(layer, win, fail, load) {
});
}
}
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.className = o2.tab.className.replace(/(^| )cbi-tab( |$)/, " cbi-tab-disabled ");
o2.container.style.display = 'none';
}
else {
if(h) h.value = tab;
o2.tab.className = o2.tab.className.replace(/(^| )cbi-tab-disabled( |$)/, " cbi-tab ");
o2.container.style.display = 'block';
}
}
}
return false
}
function cbi_t_update() {
for( var sid in cbi_t )
for( var tid in cbi_t[sid] )
if( cbi_c[cbi_t[sid][tid].cid] == 0 ) {
cbi_t[sid][tid].tab.style.display = 'none';
}
else if( cbi_t[sid][tid].tab && cbi_t[sid][tid].tab.style.display == 'none' ) {
cbi_t[sid][tid].tab.style.display = '';
var t = cbi_t[sid][tid].tab;
window.setTimeout(function() { t.className = t.className.replace(/ cbi-tab-highlighted/g, '') }, 750);
cbi_t[sid][tid].tab.className += ' cbi-tab-highlighted';
}
}

View file

@ -230,7 +230,7 @@ function Node._i18n(self, config, section, option, title, description)
local key = config and config:gsub("[^%w]+", "") or ""
if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
self.title = title or luci.i18n.translate( key, option or section or config )
@ -238,6 +238,25 @@ function Node._i18n(self, config, section, option, title, description)
end
end
-- hook helper
function Node._run_hook(self, hook)
if type(self[hook]) == "function" then
return self[hook](self)
end
end
function Node._run_hooks(self, ...)
local f
local r = false
for _, f in ipairs(arg) do
if type(self[f]) == "function" then
self[f](self)
r = true
end
end
return r
end
-- Prepare nodes
function Node.prepare(self, ...)
for k, child in ipairs(self.children) do
@ -357,6 +376,7 @@ end
-- Use optimized UCI writing
function Map.parse(self, readinput, ...)
self.readinput = (readinput ~= false)
self:_run_hooks("on_parse")
if self:formvalue("cbi.skip") then
self.state = FORM_SKIP
@ -370,14 +390,17 @@ function Map.parse(self, readinput, ...)
self.uci:save(config)
end
if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
self:_run_hooks("on_before_commit")
for i, config in ipairs(self.parsechain) do
self.uci:commit(config)
-- Refresh data because commit changes section names
self.uci:load(config)
end
self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
if self.apply_on_parse then
self.uci:apply(self.parsechain)
self:_run_hooks("on_apply", "on_after_apply")
else
self._apply = function()
local cmd = self.uci:apply(self.parsechain, true)
@ -413,11 +436,13 @@ function Map.parse(self, readinput, ...)
end
function Map.render(self, ...)
self:_run_hooks("on_init")
Node.render(self, ...)
if self._apply then
local fp = self._apply()
fp:read("*a")
fp:close()
self:_run_hooks("on_apply")
end
end
@ -506,16 +531,13 @@ function Delegator.__init__(self, ...)
self.pageaction = false
self.readinput = true
self.allow_reset = false
self.allow_cancel = false
self.allow_back = false
self.allow_finish = false
self.template = "cbi/delegator"
end
function Delegator.set(self, name, node)
if type(node) == "table" and getmetatable(node) == nil then
node = Compound(unpack(node))
end
assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
assert(not self.nodes[name], "Duplicate entry")
self.nodes[name] = node
@ -527,9 +549,9 @@ function Delegator.add(self, name, node)
end
function Delegator.insert_after(self, name, after)
local n = #self.chain
local n = #self.chain + 1
for k, v in ipairs(self.chain) do
if v == state then
if v == after then
n = k + 1
break
end
@ -555,10 +577,30 @@ function Delegator.set_route(self, ...)
end
function Delegator.get(self, name)
return self.nodes[name]
local node = self.nodes[name]
if type(node) == "string" then
node = load(node, name)
end
if type(node) == "table" and getmetatable(node) == nil then
node = Compound(unpack(node))
end
return node
end
function Delegator.parse(self, ...)
if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
if self:_run_hooks("on_cancel") then
return FORM_DONE
end
end
if not Map.formvalue(self, "cbi.delg.current") then
self:_run_hooks("on_init")
end
local newcurrent
self.chain = self.chain or self:get_chain()
self.current = self.current or self:get_active()
@ -586,14 +628,20 @@ function Delegator.parse(self, ...)
if not Map.formvalue(self, "cbi.submit") then
return FORM_NODATA
elseif not newcurrent or not self:get(newcurrent) then
return FORM_DONE
elseif stat > FORM_PROCEED
and (not newcurrent or not self:get(newcurrent)) then
return self:_run_hook("on_done") or FORM_DONE
else
self.current = newcurrent
self.current = newcurrent or self.current
self.active = self:get(self.current)
if type(self.active) ~= "function" then
self.active:parse(false)
return FROM_PROCEED
self.active:populate_delegator(self)
local stat = self.active:parse(false)
if stat == FORM_SKIP then
return self:parse(...)
else
return FORM_PROCEED
end
else
return self:parse(...)
end
@ -659,6 +707,10 @@ function SimpleForm.parse(self, readinput, ...)
return FORM_SKIP
end
if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
return FORM_DONE
end
if self:submitstate() then
Node.parse(self, 1, ...)
end
@ -781,6 +833,19 @@ function AbstractSection.__init__(self, map, sectiontype, ...)
self.dynamic = false
end
-- Define a tab for the section
function AbstractSection.tab(self, tab, title, desc)
self.tabs = self.tabs or { }
self.tab_names = self.tab_names or { }
self.tab_names[#self.tab_names+1] = tab
self.tabs[tab] = {
title = title,
description = desc,
childs = { }
}
end
-- Appends a new option
function AbstractSection.option(self, class, option, ...)
-- Autodetect from UVL
@ -812,6 +877,31 @@ function AbstractSection.option(self, class, option, ...)
end
end
-- Appends a new tabbed option
function AbstractSection.taboption(self, tab, ...)
assert(tab and self.tabs and self.tabs[tab],
"Cannot assign option to not existing tab %q" % tostring(tab))
local l = self.tabs[tab].childs
local o = AbstractSection.option(self, ...)
if o then l[#l+1] = o end
return o
end
-- Render a single tab
function AbstractSection.render_tab(self, tab, ...)
assert(tab and self.tabs and self.tabs[tab],
"Cannot render not existing tab %q" % tostring(tab))
for _, node in ipairs(self.tabs[tab].childs) do
node:render(...)
end
end
-- Parse optional options
function AbstractSection.parse_optionals(self, section)
if not self.optional then
@ -1215,6 +1305,7 @@ function AbstractValue.__init__(self, map, section, option, ...)
self.tag_reqerror = {}
self.tag_error = {}
self.deps = {}
self.subdeps = {}
--self.cast = "string"
self.track_missing = false
@ -1356,6 +1447,7 @@ function AbstractValue.render(self, s, scope)
scope.section = s
scope.cbid = self:cbid(s)
scope.striptags = luci.util.striptags
scope.pcdata = luci.util.pcdata
scope.ifattr = function(cond,key,val)
if cond then
@ -1544,7 +1636,7 @@ function ListValue.value(self, key, val, ...)
table.insert(self.vallist, tostring(val))
for i, deps in ipairs({...}) do
table.insert(self.deps, {add = "-"..key, deps=deps})
self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
end
end

View file

@ -14,10 +14,25 @@ $Id$
-%>
<div class="cbi-map" id="cbi-<%=self.config%>">
<h2><a id="content" name="content"><%=self.title%></a></h2>
<div class="cbi-map-descr"><%=self.description%></div>
<% if self.title and #self.title > 0 then %>
<h2>
<%
if self.breadcrumb then
local elem
for _, elem in ipairs(self.breadcrumb) do
-%>
<a href="<%=luci.util.pcdata(elem[1])%>"><%=luci.util.pcdata(elem[2])%></a> &raquo;
<%
end
end
-%>
<a id="content" name="content"><%=self.title%></a>
</h2>
<% end %>
<% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %>
<%- if self._apply then -%>
<fieldset class="cbi-section">
<fieldset class="cbi-section" id="cbi-apply-<%=self.config%>">
<legend><%:cbi_applying%></legend>
<ul class="cbi-apply"><%-
local fp = self._apply()
@ -30,6 +45,12 @@ $Id$
fp:close()
-%></ul>
</fieldset>
<script type="text/javascript">
window.setTimeout(function() {
var e = document.getElementById('cbi-apply-<%=self.config%>');
if(e) e.style.display = 'none';
}, 2000);
</script>
<%- end -%>
<%- self:render_children() %>
<br />

View file

@ -1,7 +1,7 @@
<%#
LuCI - Lua Configuration Interface
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
Copyright 2008-2009 Jo-Philipp Wich <xm@subsignal>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ $Id$
<input type="submit" name="cbi.rns.<%=self.config%>.<%=section%>" value="<%:cbi_del%>" />
</div>
<%- end %>
<%+cbi/tabmenu%>
<div class="cbi-section-node" id="cbi-<%=self.config%>-<%=section%>">
<%+cbi/ucisection%>
</div>

View file

@ -0,0 +1,21 @@
<%#
LuCI - Lua Configuration Interface
Copyright 2009 Jo-Philipp Wich <xm@subsignal.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
$Id: tabcontainer.htm 5269 2009-08-16 03:29:46Z jow $
-%>
<% for tab, data in pairs(self.tabs) do %>
<div 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 %>
<% self:render_tab(tab, section, scope or {}) %>
</div>
<script type="text/javascript">cbi_t_add('<%=self.config%>.<%=section%>', '<%=tab%>')</script>
<% end %>

View file

@ -0,0 +1,27 @@
<%#
LuCI - Lua Configuration Interface
Copyright 2009 Jo-Philipp Wich <xm@subsignal.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
$Id: tabmenu.htm 5398 2009-10-10 22:18:50Z jow $
-%>
<%- 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 %>
<script type="text/javascript">cbi_c['container.<%=self.config%>.<%=section%>.<%=tab%>'] = <%=#self.tabs[tab].childs%>;</script>
<%- 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 -%>

View file

@ -24,12 +24,15 @@ $Id$
<input type="submit" name="cbi.rts.<%=self.config%>.<%=k%>" value="<%:cbi_del%>" />
</div>
<%- end %>
<% section = k; isempty = false %>
<%- section = k; isempty = false -%>
<% if not self.anonymous then -%>
<h3><%=k:upper()%></h3>
<h3><%=section:upper()%></h3>
<%- end %>
<%+cbi/tabmenu%>
<fieldset class="cbi-section-node" id="cbi-<%=self.config%>-<%=section%>">
<%+cbi/ucisection%>
</fieldset>