CREATE_PREFIX = "cbi.cts."
REMOVE_PREFIX = "cbi.rts."
+RESORT_PREFIX = "cbi.sts."
+FEXIST_PREFIX = "cbi.cbe."
-- Loads a CBI map from given file, creating an environment and returns it
function load(cbimap, ...)
-- Render the children
function Node.render_children(self, ...)
+ local k, node
for k, node in ipairs(self.children) do
+ node.last_child = (k == #self.children)
node:render(...)
end
end
Node.parse(self, ...)
if self.save then
+ self:_run_hooks("on_save", "on_before_save")
for i, config in ipairs(self.parsechain) do
self.uci:save(config)
end
+ self:_run_hooks("on_after_save")
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:apply(self.parsechain)
self:_run_hooks("on_apply", "on_after_apply")
else
- self._apply = function()
- local cmd = self.uci:apply(self.parsechain, true)
- return io.popen(cmd)
- end
+ -- This is evaluated by the dispatcher and delegated to the
+ -- template which in turn fires XHR to perform the actual
+ -- apply actions.
+ self.apply_needed = true
end
-- Reparse sections
function Map.render(self, ...)
self:_run_hooks("on_init")
Node.render(self, ...)
- if false and self._apply then
- local fp = self._apply()
- fp:read("*a")
- fp:close()
- self:_run_hooks("on_apply")
- end
end
-- Creates a child section
-- UCI set
function Map.set(self, section, option, value)
- if option then
- return self.uci:set(self.config, section, option, value)
+ if type(value) ~= "table" or #value > 0 then
+ if option then
+ return self.uci:set(self.config, section, option, value)
+ else
+ return self.uci:set(self.config, section, value)
+ end
else
- return self.uci:set(self.config, section, value)
+ return Map.del(self, section, option)
end
end
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
+ local k, node
+ for k, node in ipairs(self.tabs[tab].childs) do
+ node.last_child = (k == #self.tabs[tab].childs)
node:render(...)
end
end
-- Create
local created
local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
- local name = self.map:formvalue(crval)
+ local origin, name = next(self.map:formvaluetable(crval))
if self.anonymous then
if name then
- created = self:create()
+ created = self:create(nil, origin)
end
else
if name then
end
if name and #name > 0 then
- created = self:create(name) and name
+ created = self:create(name, origin) and name
if not created then
self.invalid_cts = true
end
end
end
+ if self.sortable then
+ local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
+ local order = self.map:formvalue(stval)
+ if order and #order > 0 then
+ local sid
+ local num = 0
+ for sid in util.imatch(order) do
+ self.map.uci:reorder(self.config, sid, num)
+ num = num + 1
+ end
+ self.changed = (num > 0)
+ end
+ end
+
if created or self.changed then
self:push_events()
end
self.rmempty = not value
end
+function AbstractValue.add_error(self, section, type, msg)
+ self.error = self.error or { }
+ self.error[section] = msg or type
+
+ self.section.error = self.section.error or { }
+ self.section.error[section] = self.section.error[section] or { }
+ table.insert(self.section.error[section], msg or type)
+
+ if type == "invalid" then
+ self.tag_invalid[section] = true
+ elseif type == "missing" then
+ self.tag_missing[section] = true
+ end
+
+ self.tag_error[section] = true
+ self.map.save = false
+end
+
function AbstractValue.parse(self, section, novld)
local fvalue = self:formvalue(section)
local cvalue = self:cfgvalue(section)
fvalue = self:transform(fvalue)
if not fvalue and not novld then
- val_err = val_err or "invalid"
-
- self.error = self.error or { }
- self.error[section] = val_err
-
- self.section.error = self.section.error or { }
- self.section.error[section] = self.section.error[section] or { }
- table.insert(self.section.error[section], val_err)
-
- self.map.save = false
+ self:add_error(section, "invalid", val_err)
end
- if fvalue and not (fvalue == cvalue) then
+
+ if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
if self:write(section, fvalue) then
-- Push events
self.section.changed = true
--luci.util.append(self.map.events, self.events)
end
elseif cvalue ~= fvalue and not novld then
- self:write(section, fvalue or "")
- if self.error then
- self.error[section] = "missing"
- else
- self.error = { [section] = "missing" }
- end
- self.map.save = false
+ -- trigger validator with nil value to get custom user error msg.
+ local _, val_err = self:validate(nil, section)
+ self:add_error(section, "missing", val_err)
end
end
end
function AbstractValue.render(self, s, scope)
if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
scope = scope or {}
- 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
- return string.format(
- ' %s="%s"', tostring(key),
- luci.util.pcdata(tostring( val
- or scope[key]
- or (type(self[key]) ~= "function" and self[key])
- or "" ))
- )
- else
- return ''
- end
- end
-
- scope.attr = function(...)
- return scope.ifattr( true, ... )
- end
-
+ scope.section = s
+ scope.cbid = self:cbid(s)
Node.render(self, scope)
end
end
-- Return the UCI value of this object
function AbstractValue.cfgvalue(self, section)
- local value = (self.error and self.error[section] == "invalid")
- and self:formvalue(section) or self.map:get(section, self.option)
+ local value
+ if self.tag_error[section] then
+ value = self:formvalue(section)
+ else
+ value = self.map:get(section, self.option)
+ end
+
if not value then
return nil
elseif not self.cast or self.cast == type(value) then
return value[1]
end
elseif self.cast == "table" then
- return luci.util.split(value, "%s+", nil, true)
+ return { value }
end
end
-- Validate the form value
function AbstractValue.validate(self, value)
- if self.datatype and value and datatypes[self.datatype] then
- if type(value) == "table" then
- local v
- for _, v in ipairs(value) do
- if v and #v > 0 and not datatypes[self.datatype](v) then
- return nil
- end
+ if self.datatype and value then
+ local args = { }
+ local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
+
+ if dt and ar then
+ local a
+ for a in ar:gmatch("[^%s,]+") do
+ args[#args+1] = a
end
else
- if not datatypes[self.datatype](value) then
- return nil
+ dt = self.datatype
+ end
+
+ if dt and datatypes[dt] then
+ if type(value) == "table" then
+ local v
+ for _, v in ipairs(value) do
+ if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
+ return nil
+ end
+ end
+ else
+ if not datatypes[dt](value, unpack(args)) then
+ return nil
+ end
end
end
end
+
return value
end
self.vallist = {}
end
+function Value.reset_values(self)
+ self.keylist = {}
+ self.vallist = {}
+end
+
function Value.value(self, key, val)
val = val or key
table.insert(self.keylist, tostring(key))
AbstractValue.__init__(self, ...)
self.template = "cbi/fvalue"
- self.enabled = "1"
+ self.enabled = "1"
self.disabled = "0"
+ self.default = self.disabled
end
-- A flag can only have two states: set or unset
function Flag.parse(self, section)
- local fvalue = self:formvalue(section)
-
- if fvalue then
- fvalue = self.enabled
- else
- fvalue = self.disabled
- end
+ local fexists = self.map:formvalue(
+ FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
- if fvalue == self.enabled or (not self.optional and not self.rmempty) then
- if not(fvalue == self:cfgvalue(section)) then
+ if fexists then
+ local fvalue = self:formvalue(section) and self.enabled or self.disabled
+ if fvalue ~= self.default or (not self.optional and not self.rmempty) then
self:write(section, fvalue)
+ else
+ self:remove(section)
end
else
self:remove(section)
end
end
+function Flag.cfgvalue(self, section)
+ return AbstractValue.cfgvalue(self, section) or self.default
+end
--[[
self.widget = "select"
end
+function ListValue.reset_values(self)
+ self.keylist = {}
+ self.vallist = {}
+end
+
function ListValue.value(self, key, val, ...)
if luci.util.contains(self.keylist, key) then
return
AbstractValue.render(self, ...)
end
+function MultiValue.reset_values(self)
+ self.keylist = {}
+ self.vallist = {}
+end
+
function MultiValue.value(self, key, val)
if luci.util.contains(self.keylist, key) then
return
self.vallist = {}
end
+function DynamicList.reset_values(self)
+ self.keylist = {}
+ self.vallist = {}
+end
+
function DynamicList.value(self, key, val)
val = val or key
table.insert(self.keylist, tostring(key))
table.insert(self.vallist, tostring(val))
end
-function DynamicList.write(self, ...)
- self.map.proceed = true
- return AbstractValue.write(self, ...)
+function DynamicList.write(self, section, value)
+ local t = { }
+
+ if type(value) == "table" then
+ local x
+ for _, x in ipairs(value) do
+ if x and #x > 0 then
+ t[#t+1] = x
+ end
+ end
+ else
+ t = { value }
+ end
+
+ if self.cast == "string" then
+ value = table.concat(t, " ")
+ else
+ value = t
+ end
+
+ return AbstractValue.write(self, section, value)
+end
+
+function DynamicList.cfgvalue(self, section)
+ local value = AbstractValue.cfgvalue(self, section)
+
+ if type(value) == "string" then
+ local x
+ local t = { }
+ for x in value:gmatch("%S+") do
+ if #x > 0 then
+ t[#t+1] = x
+ end
+ end
+ value = t
+ end
+
+ return value
end
function DynamicList.formvalue(self, section)
local value = AbstractValue.formvalue(self, section)
- value = (type(value) == "table") and value or {value}
- local valid = {}
- for i, v in ipairs(value) do
- if v and #v > 0
- and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
- and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
- table.insert(valid, v)
+ if type(value) == "string" then
+ if self.cast == "string" then
+ local x
+ local t = { }
+ for x in value:gmatch("%S+") do
+ t[#t+1] = x
+ end
+ value = t
+ else
+ value = { value }
end
end
- return valid
+ return value
end