libs/cbi: Fixed CBI handling of "Save & Apply"
[project/luci.git] / libs / cbi / luasrc / cbi.lua
index fbd2355..fe99f02 100644 (file)
@@ -57,14 +57,16 @@ function load(cbimap)
        luci.util.extfenv(func, "translate", luci.i18n.translate)
        luci.util.extfenv(func, "translatef", luci.i18n.translatef)
 
-       local map = func()
+       local maps = {func()}
 
-       if not instanceof(map, Map) then
-               error("CBI map returns no valid map object!")
-               return nil
+       for i, map in ipairs(maps) do
+               if not instanceof(map, Map) then
+                       error("CBI map returns no valid map object!")
+                       return nil
+               end
        end
 
-       return map
+       return maps
 end
 
 -- Node pseudo abstract class
@@ -142,17 +144,44 @@ function Map.__init__(self, config, ...)
        Node._i18n(self, config, nil, nil, ...)
 
        self.config = config
+       self.parsechain = {self.config}
        self.template = "cbi/map"
        if not uci.load(self.config) then
                error("Unable to read UCI data: " .. self.config)
        end
 end
 
+
+-- Chain foreign config
+function Map.chain(self, config)
+       table.insert(self.parsechain, config)
+end
+
 -- Use optimized UCI writing
 function Map.parse(self, ...)
        Node.parse(self, ...)
-       uci.save(self.config)
-       uci.unload(self.config)
+       for i, config in ipairs(self.parsechain) do
+               uci.save(config)
+       end
+       if luci.http.formvalue("cbi.apply") then
+               for i, config in ipairs(self.parsechain) do
+                       uci.commit(config)
+                       if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
+                               luci.sys.exec(luci.config.uci_oncommit[config])
+                       end
+
+                       -- Refresh data because commit changes section names
+                       uci.unload(config)
+                       uci.load(config)
+               end
+
+               -- Reparse sections
+               Node.parse(self, ...)
+
+       end
+       for i, config in ipairs(self.parsechain) do
+               uci.unload(config)
+       end
 end
 
 -- Creates a child section
@@ -173,7 +202,11 @@ end
 
 -- UCI set
 function Map.set(self, section, option, value)
-       return uci.set(self.config, section, option or value, option and value)
+       if option then
+               return uci.set(self.config, section, option, value)
+       else
+               return uci.set(self.config, section, value)
+       end
 end
 
 -- UCI del
@@ -208,6 +241,7 @@ function AbstractSection.__init__(self, map, sectiontype, ...)
        self.map = map
        self.config = map.config
        self.optionals = {}
+       self.defaults = {}
 
        self.optional = true
        self.addremove = false
@@ -297,7 +331,28 @@ end
 
 -- Creates the section
 function AbstractSection.create(self, section)
-       return self.map:set(section, nil, self.sectiontype)
+       local stat
+       
+       if section then
+               stat = self.map:set(section, nil, self.sectiontype)
+       else
+               section = self.map:add(self.sectiontype)
+               stat = section
+       end
+
+       if stat then
+               for k,v in pairs(self.children) do
+                       if v.default then
+                               self.map:set(section, v.option, v.default)
+                       end
+               end
+
+               for k,v in pairs(self.defaults) do
+                       self.map:set(section, k, v)
+               end
+       end
+
+       return stat
 end
 
 
@@ -310,7 +365,7 @@ NamedSection = class(AbstractSection)
 function NamedSection.__init__(self, map, section, type, ...)
        AbstractSection.__init__(self, map, type, ...)
        Node._i18n(self, map.config, section, nil, ...)
-       
+
        self.template = "cbi/nsection"
        self.section = section
        self.addremove = false
@@ -328,10 +383,9 @@ function NamedSection.parse(self)
                                return
                        end
                else           -- Create and apply default values
-                       if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
-                               for k,v in pairs(self.children) do
-                                       v:write(s, v.default)
-                               end
+                       if luci.http.formvalue("cbi.cns."..path) then
+                               self:create(s)
+                               return
                        end
                end
        end
@@ -372,27 +426,12 @@ function TypedSection.cfgsections(self)
                function (section)
                        if self:checkscope(section[".name"]) then
                                table.insert(sections, section[".name"])
-                       end 
+                       end
                end)
 
        return sections
 end
 
--- Creates a new section of this type with the given name (or anonymous)
-function TypedSection.create(self, name)
-       if name then
-               self.map:set(name, nil, self.sectiontype)
-       else
-               name = self.map:add(self.sectiontype)
-       end
-
-       for k,v in pairs(self.children) do
-               if v.default then
-                       self.map:set(name, v.option, v.default)
-               end
-       end
-end
-
 -- Limits scope to sections that have certain option => value pairs
 function TypedSection.depends(self, option, value)
        table.insert(self.deps, {option=option, value=value})
@@ -548,7 +587,25 @@ function AbstractValue.render(self, s, scope)
        if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
                scope = scope or {}
                scope.section = s
-               
+               scope.cbid    = "cbid." .. self.config ..
+                               "."     .. s           ..
+                                               "."     .. self.option
+
+               scope.ifattr = function(cond,key,val)
+                       if cond then
+                               return string.format(
+                                       ' %s="%s"', tostring(key),
+                                       tostring( val or scope[key] or self[key] or "" )
+                               )
+                       else
+                               return ''
+                       end
+               end
+
+               scope.attr = function(...)
+                       return scope.ifattr( true, ... )
+               end
+
                Node.render(self, scope)
        end
 end
@@ -612,10 +669,6 @@ function DummyValue.parse(self)
 
 end
 
-function DummyValue.render(self, s)
-       luci.template.render(self.template, {self=self, section=s})
-end
-
 
 --[[
 Flag - A flag being enabled or disabled
@@ -725,21 +778,15 @@ function MultiValue.valuelist(self, section)
 end
 
 function MultiValue.validate(self, val)
-       if not(type(val) == "string") then
-               return nil
-       end
+       val = (type(val) == "table") and val or {val}
 
-       local result = ""
+       local result
 
-       for value in val:gmatch("[^\n]+") do
+       for i, value in ipairs(val) do
                if luci.util.contains(self.keylist, value) then
-                       result = result .. self.delimiter .. value
+                       result = result and (result .. self.delimiter .. value) or value
                end
        end
 
-       if result:len() > 0 then
-               return result:sub(self.delimiter:len() + 1)
-       else
-               return nil
-       end
+       return result
 end