* Major CBI improvements
[project/luci.git] / src / ffluci / cbi.lua
index 7588a7fd6da28846d392bf20a7cb19a7f1bdb01b..844f6c0bb333430cdd037c4508cf50c8bbc768a5 100644 (file)
@@ -168,7 +168,9 @@ end
 
 -- UCI get (cached)
 function Map.get(self, section, option)
-       if option and self.ucidata[section] then
+       if not section then
+               return self.ucidata
+       elseif option and self.ucidata[section] then
                return self.ucidata[section][option]
        else
                return self.ucidata[section]
@@ -188,8 +190,8 @@ function AbstractSection.__init__(self, map, sectiontype, ...)
        self.config = map.config
        self.optionals = {}
        
-       self.addremove = true
        self.optional = true
+       self.addremove = false
        self.dynamic = false
 end
 
@@ -210,14 +212,15 @@ function AbstractSection.parse_optionals(self, section)
                return
        end
        
+       self.optionals[section] = {}
+       
        local field = ffluci.http.formvalue("cbi.opt."..self.config.."."..section)
        for k,v in ipairs(self.children) do
-               if v.optional and not v:ucivalue(section) then
+               if v.optional and not v:cfgvalue(section) then
                        if field == v.option then
-                               self.map:set(section, field, v.default)
                                field = nil
                        else
-                               table.insert(self.optionals, v)
+                               table.insert(self.optionals[section], v)
                        end
                end
        end
@@ -239,7 +242,7 @@ function AbstractSection.parse_dynamic(self, section)
                return
        end
        
-       local arr  = ffluci.util.clone(self:ucivalue(section))
+       local arr  = ffluci.util.clone(self:cfgvalue(section))
        local form = ffluci.http.formvalue("cbid."..self.config.."."..section)
        if type(form) == "table" then
                for k,v in pairs(form) do
@@ -263,10 +266,20 @@ function AbstractSection.parse_dynamic(self, section)
 end    
 
 -- Returns the section's UCI table
-function AbstractSection.ucivalue(self, section)
+function AbstractSection.cfgvalue(self, section)
        return self.map:get(section)
 end
 
+-- Removes the section
+function AbstractSection.remove(self, section)
+       return self.map:del(section)
+end
+
+-- Creates the section
+function AbstractSection.create(self, section)
+       return self.map:set(section, nil, self.sectiontype)
+end
+
 
 
 --[[
@@ -282,59 +295,62 @@ function NamedSection.__init__(self, map, section, ...)
        self.addremove = false
 end
 
-function NamedSection.parse(self)      
-       local active = self:ucivalue(self.section)
+function NamedSection.parse(self)
+       local s = self.section  
+       local active = self:cfgvalue(s)
+       
        
        if self.addremove then
-               local path = self.config.."."..self.section
+               local path = self.config.."."..s
                if active then -- Remove the section
-                       if ffluci.http.formvalue("cbi.rns."..path) and self:remove() then
+                       if ffluci.http.formvalue("cbi.rns."..path) and self:remove(s) then
                                return
                        end
                else           -- Create and apply default values
-                       if ffluci.http.formvalue("cbi.cns."..path) and self:create() then
+                       if ffluci.http.formvalue("cbi.cns."..path) and self:create(s) then
                                for k,v in pairs(self.children) do
-                                       v:write(self.section, v.default)
+                                       v:write(s, v.default)
                                end
                        end
                end
        end
        
        if active then
-               AbstractSection.parse_dynamic(self, self.section)
-               Node.parse(self, self.section)
-               AbstractSection.parse_optionals(self, self.section)
+               AbstractSection.parse_dynamic(self, s)
+               Node.parse(self, s)
+               AbstractSection.parse_optionals(self, s)
        end     
 end
 
--- Removes the section
-function NamedSection.remove(self)
-       return self.map:del(self.section)
-end
-
--- Creates the section
-function NamedSection.create(self)
-       return self.map:set(self.section, nil, self.sectiontype)
-end
-
-
 
 --[[
 TypedSection - A (set of) configuration section(s) defined by the type
        addremove:      Defines whether the user can add/remove sections of this type
        anonymous:  Allow creating anonymous sections
-       valid:          a list of names or a validation function for creating sections 
-       scope:          a list of names or a validation function for editing sections
+       validate:       a validation function returning nil if the section is invalid 
 ]]--
 TypedSection = class(AbstractSection)
 
 function TypedSection.__init__(self, ...)
        AbstractSection.__init__(self, ...)
        self.template  = "cbi/tsection"
+       self.deps = {}
+       self.excludes = {}
        
        self.anonymous   = false
-       self.valid       = nil
-       self.scope               = nil
+end
+
+-- Return all matching UCI sections for this TypedSection
+function TypedSection.cfgsections(self)
+       local sections = {}
+       for k, v in pairs(self.map:get()) do
+               if v[".type"] == self.sectiontype then
+                       if self:checkscope(k) then
+                               sections[k] = v
+                       end
+               end
+       end
+       return sections 
 end
 
 -- Creates a new section of this type with the given name (or anonymous)
@@ -352,6 +368,16 @@ function TypedSection.create(self, name)
        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})
+end
+
+-- Excludes several sections by name
+function TypedSection.exclude(self, field)
+       self.excludes[field] = true
+end
+
 function TypedSection.parse(self)
        if self.addremove then
                -- Create
@@ -363,10 +389,17 @@ function TypedSection.parse(self)
                        end
                else            
                        if name then
-                               name = ffluci.util.validate(name, self.valid)
+                               -- Ignore if it already exists
+                               if self:cfgvalue(name) then
+                                       name = nil;
+                               end
+                               
+                               name = self:checkscope(name)
+                               
                                if not name then
                                        self.err_invalid = true
                                end             
+                               
                                if name and name:len() > 0 then
                                        self:create(name)
                                end
@@ -378,25 +411,20 @@ function TypedSection.parse(self)
                name = ffluci.http.formvalue(crval)
                if type(name) == "table" then
                        for k,v in pairs(name) do
-                               if ffluci.util.validate(k, self.valid) then
+                               if self:cfgvalue(k) and self:checkscope(k) then
                                        self:remove(k)
                                end
                        end
                end             
        end
        
-       for k, v in pairs(self:ucisections()) do
+       for k, v in pairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
                Node.parse(self, k)
                AbstractSection.parse_optionals(self, k)
        end
 end
 
--- Remove a section
-function TypedSection.remove(self, name)
-       return self.map:del(name)
-end
-
 -- Render the children
 function TypedSection.render_children(self, section)
        for k, node in ipairs(self.children) do
@@ -404,20 +432,37 @@ function TypedSection.render_children(self, section)
        end
 end
 
--- Return all matching UCI sections for this TypedSection
-function TypedSection.ucisections(self)
-       local sections = {}
-       for k, v in pairs(self.map.ucidata) do
-               if v[".type"] == self.sectiontype then
-                       if ffluci.util.validate(k, self.scope) then
-                               sections[k] = v
+-- Verifies scope of sections
+function TypedSection.checkscope(self, section)
+       -- Check if we are not excluded
+       if self.excludes[section] then
+               return nil
+       end
+       
+       -- Check if at least one dependency is met
+       if #self.deps > 0 and self:cfgvalue(section) then
+               local stat = false
+               
+               for k, v in ipairs(self.deps) do
+                       if self:cfgvalue(section)[v.option] == v.value then
+                               stat = true
                        end
                end
+               
+               if not stat then
+                       return nil
+               end
        end
-       return sections 
+       
+       return self:validate(section)
 end
 
 
+-- Dummy validate function
+function TypedSection.validate(self, section)
+       return section
+end
+
 
 --[[
 AbstractValue - An abstract Value Type
@@ -437,14 +482,25 @@ function AbstractValue.__init__(self, map, option, ...)
        self.map    = map
        self.config = map.config
        self.tag_invalid = {}
+       self.deps = {}
        
-       self.valid    = nil
-       self.depends  = nil
+       self.rmempty  = false
        self.default  = nil
        self.size     = nil
        self.optional = false
 end
 
+-- Add a dependencie to another section field
+function AbstractValue.depends(self, field, value)
+       table.insert(self.deps, {field=field, value=value})
+end
+
+-- Return whether this object should be created
+function AbstractValue.formcreated(self, section)
+       local key = "cbi.opt."..self.config.."."..section
+       return (ffluci.http.formvalue(key) == self.option)
+end
+
 -- Returns the formvalue for this object
 function AbstractValue.formvalue(self, section)
        local key = "cbid."..self.map.config.."."..section.."."..self.option
@@ -453,43 +509,37 @@ end
 
 function AbstractValue.parse(self, section)
        local fvalue = self:formvalue(section)
-       if fvalue == "" then
-               fvalue = nil
-       end
        
-       
-       if fvalue then -- If we have a form value, validate it and write it to UCI
+       if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
                fvalue = self:validate(fvalue)
                if not fvalue then
                        self.tag_invalid[section] = true
                end
-               if fvalue and not (fvalue == self:ucivalue(section)) then
+               if fvalue and not (fvalue == self:cfgvalue(section)) then
                        self:write(section, fvalue)
                end 
        elseif ffluci.http.formvalue("cbi.submit") then -- Unset the UCI or error
                if self.rmempty or self.optional then
                        self:remove(section)
-               else
-                       self.tag_invalid[section] = true
                end
        end
 end
 
 -- Render if this value exists or if it is mandatory
-function AbstractValue.render(self, section)
-       if not self.optional or self:ucivalue(section) then 
-               ffluci.template.render(self.template, {self=self, section=section})
+function AbstractValue.render(self, s)
+       if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
+               ffluci.template.render(self.template, {self=self, section=s})
        end
 end
 
 -- Return the UCI value of this object
-function AbstractValue.ucivalue(self, section)
+function AbstractValue.cfgvalue(self, section)
        return self.map:get(section, self.option)
 end
 
 -- Validate the form value
-function AbstractValue.validate(self, val)
-       return ffluci.util.validate(val, self.valid)
+function AbstractValue.validate(self, value)
+       return value
 end
 
 -- Write to UCI
@@ -529,7 +579,7 @@ function Value.validate(self, val)
                val = nil
        end
        
-       return ffluci.util.validate(val, self.valid, self.isnumber, self.isinteger)
+       return ffluci.util.validate(val, self.isnumber, self.isinteger)
 end
 
 
@@ -559,7 +609,7 @@ function Flag.parse(self, section)
        end     
        
        if fvalue == self.enabled or (not self.optional and not self.rmempty) then              
-               if not(fvalue == self:ucivalue(section)) then
+               if not(fvalue == self:cfgvalue(section)) then
                        self:write(section, fvalue)
                end 
        else
@@ -585,7 +635,7 @@ function ListValue.__init__(self, ...)
        self.widget = "select"
 end
 
-function ListValue.add_value(self, key, val)
+function ListValue.value(self, key, val)
        val = val or key
        table.insert(self.keylist, tostring(key))
        table.insert(self.vallist, tostring(val)) 
@@ -618,14 +668,14 @@ function MultiValue.__init__(self, ...)
        self.delimiter = " "
 end
 
-function MultiValue.add_value(self, key, val)
+function MultiValue.value(self, key, val)
        val = val or key
        table.insert(self.keylist, tostring(key))
        table.insert(self.vallist, tostring(val)) 
 end
 
 function MultiValue.valuelist(self, section)
-       local val = self:ucivalue(section)
+       local val = self:cfgvalue(section)
        
        if not(type(val) == "string") then
                return {}