libs/cbi: implement tabbing to split large sections and group options in tabs
[project/luci.git] / libs / cbi / luasrc / cbi.lua
index 8cfc695..33b0c7c 100644 (file)
@@ -27,17 +27,24 @@ limitations under the License.
 module("luci.cbi", package.seeall)
 
 require("luci.template")
 module("luci.cbi", package.seeall)
 
 require("luci.template")
-require("luci.util")
+local util = require("luci.util")
 require("luci.http")
 require("luci.uvl")
 
 require("luci.http")
 require("luci.uvl")
 
+
+--local event      = require "luci.sys.event"
+local fs         = require("nixio.fs")
 local uci        = require("luci.model.uci")
 local uci        = require("luci.model.uci")
-local class      = luci.util.class
-local instanceof = luci.util.instanceof
+local class      = util.class
+local instanceof = util.instanceof
 
 FORM_NODATA  =  0
 
 FORM_NODATA  =  0
+FORM_PROCEED =  0
 FORM_VALID   =  1
 FORM_VALID   =  1
+FORM_DONE       =  1
 FORM_INVALID = -1
 FORM_INVALID = -1
+FORM_CHANGED =  2
+FORM_SKIP    =  4
 
 AUTO = true
 
 
 AUTO = true
 
@@ -46,13 +53,25 @@ REMOVE_PREFIX = "cbi.rts."
 
 -- Loads a CBI map from given file, creating an environment and returns it
 function load(cbimap, ...)
 
 -- Loads a CBI map from given file, creating an environment and returns it
 function load(cbimap, ...)
-       require("luci.fs")
+       local fs   = require "nixio.fs"
        local i18n = require "luci.i18n"
        require("luci.config")
        require("luci.util")
 
        local i18n = require "luci.i18n"
        require("luci.config")
        require("luci.util")
 
+       local upldir = "/lib/uci/upload/"
        local cbidir = luci.util.libpath() .. "/model/cbi/"
        local cbidir = luci.util.libpath() .. "/model/cbi/"
-       local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua")
+       local func, err
+
+       if fs.access(cbimap) then
+               func, err = loadfile(cbimap)
+       elseif fs.access(cbidir..cbimap..".lua") then
+               func, err = loadfile(cbidir..cbimap..".lua")
+       elseif fs.access(cbidir..cbimap..".lua.gz") then
+               func, err = loadfile(cbidir..cbimap..".lua.gz")
+       else
+               func, err = nil, "Model '" .. cbimap .. "' not found!"
+       end
+
        assert(func, err)
 
        luci.i18n.loadc("cbi")
        assert(func, err)
 
        luci.i18n.loadc("cbi")
@@ -69,15 +88,68 @@ function load(cbimap, ...)
                        return rawget(tbl, key) or _M[key] or _G[key]
                end}))
 
                        return rawget(tbl, key) or _M[key] or _G[key]
                end}))
 
-       local maps = {func()}
+       local maps       = { func() }
+       local uploads    = { }
+       local has_upload = false
 
        for i, map in ipairs(maps) do
                if not instanceof(map, Node) then
                        error("CBI map returns no valid map object!")
                        return nil
 
        for i, map in ipairs(maps) do
                if not instanceof(map, Node) then
                        error("CBI map returns no valid map object!")
                        return nil
+               else
+                       map:prepare()
+                       if map.upload_fields then
+                               has_upload = true
+                               for _, field in ipairs(map.upload_fields) do
+                                       uploads[
+                                               field.config .. '.' ..
+                                               field.section.sectiontype .. '.' ..
+                                               field.option
+                                       ] = true
+                               end
+                       end
                end
        end
 
                end
        end
 
+       if has_upload then
+               local uci = luci.model.uci.cursor()
+               local prm = luci.http.context.request.message.params
+               local fd, cbid
+
+               luci.http.setfilehandler(
+                       function( field, chunk, eof )
+                               if not field then return end
+                               if field.name and not cbid then
+                                       local c, s, o = field.name:gmatch(
+                                               "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
+                                       )()
+
+                                       if c and s and o then
+                                               local t = uci:get( c, s )
+                                               if t and uploads[c.."."..t.."."..o] then
+                                                       local path = upldir .. field.name
+                                                       fd = io.open(path, "w")
+                                                       if fd then
+                                                               cbid = field.name
+                                                               prm[cbid] = path
+                                                       end
+                                               end
+                                       end
+                               end
+
+                               if field.name == cbid and fd then
+                                       fd:write(chunk)
+                               end
+
+                               if eof and fd then
+                                       fd:close()
+                                       fd   = nil
+                                       cbid = nil
+                               end
+                       end
+               )
+       end
+
        return maps
 end
 
        return maps
 end
 
@@ -101,7 +173,7 @@ local function _uvl_validate_section(node, name)
 
        local function tag_section(e)
                local s = { }
 
        local function tag_section(e)
                local s = { }
-               for _, c in ipairs(e.childs) do
+               for _, c in ipairs(e.childs or { e }) do
                        if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
                                table.insert( s, c.childs[1]:string() )
                        else
                        if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
                                table.insert( s, c.childs[1]:string() )
                        else
@@ -166,6 +238,13 @@ function Node._i18n(self, config, section, option, title, description)
        end
 end
 
        end
 end
 
+-- Prepare nodes
+function Node.prepare(self, ...)
+       for k, child in ipairs(self.children) do
+               child:prepare(...)
+       end
+end
+
 -- Append child nodes
 function Node.append(self, obj)
        table.insert(self.children, obj)
 -- Append child nodes
 function Node.append(self, obj)
        table.insert(self.children, obj)
@@ -208,6 +287,11 @@ function Template.render(self)
        luci.template.render(self.template, {self=self})
 end
 
        luci.template.render(self.template, {self=self})
 end
 
+function Template.parse(self, readinput)
+       self.readinput = (readinput ~= false)
+       return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
+end
+
 
 --[[
 Map - A map describing a configuration file
 
 --[[
 Map - A map describing a configuration file
@@ -222,8 +306,15 @@ function Map.__init__(self, config, ...)
        self.parsechain = {self.config}
        self.template = "cbi/map"
        self.apply_on_parse = nil
        self.parsechain = {self.config}
        self.template = "cbi/map"
        self.apply_on_parse = nil
+       self.readinput = true
+       self.proceed = false
+       self.flow = {}
+
        self.uci = uci.cursor()
        self.save = true
        self.uci = uci.cursor()
        self.save = true
+
+       self.changed = false
+
        if not self.uci:load(self.config) then
                error("Unable to read UCI data: " .. self.config)
        end
        if not self.uci:load(self.config) then
                error("Unable to read UCI data: " .. self.config)
        end
@@ -233,6 +324,14 @@ function Map.__init__(self, config, ...)
 
 end
 
 
 end
 
+function Map.formvalue(self, key)
+       return self.readinput and luci.http.formvalue(key)
+end
+
+function Map.formvaluetable(self, key)
+       return self.readinput and luci.http.formvaluetable(key) or {}
+end
+
 function Map.get_scheme(self, sectiontype, option)
        if not option then
                return self.scheme and self.scheme.sections[sectiontype]
 function Map.get_scheme(self, sectiontype, option)
        if not option then
                return self.scheme and self.scheme.sections[sectiontype]
@@ -242,21 +341,35 @@ function Map.get_scheme(self, sectiontype, option)
        end
 end
 
        end
 end
 
+function Map.submitstate(self)
+       return self:formvalue("cbi.submit")
+end
 
 -- Chain foreign config
 function Map.chain(self, config)
        table.insert(self.parsechain, config)
 end
 
 
 -- Chain foreign config
 function Map.chain(self, config)
        table.insert(self.parsechain, config)
 end
 
+function Map.state_handler(self, state)
+       return state
+end
+
 -- Use optimized UCI writing
 -- Use optimized UCI writing
-function Map.parse(self)
-       Node.parse(self)
+function Map.parse(self, readinput, ...)
+       self.readinput = (readinput ~= false)
+
+       if self:formvalue("cbi.skip") then
+               self.state = FORM_SKIP
+               return self:state_handler(self.state)
+       end
+
+       Node.parse(self, ...)
 
        if self.save then
                for i, config in ipairs(self.parsechain) do
                        self.uci:save(config)
                end
 
        if self.save then
                for i, config in ipairs(self.parsechain) do
                        self.uci:save(config)
                end
-               if luci.http.formvalue("cbi.apply") then
+               if self:submitstate() and not self.proceed and (self.flow.autoapply or luci.http.formvalue("cbi.apply")) then
                        for i, config in ipairs(self.parsechain) do
                                self.uci:commit(config)
 
                        for i, config in ipairs(self.parsechain) do
                                self.uci:commit(config)
 
@@ -279,7 +392,24 @@ function Map.parse(self)
                for i, config in ipairs(self.parsechain) do
                        self.uci:unload(config)
                end
                for i, config in ipairs(self.parsechain) do
                        self.uci:unload(config)
                end
+               if type(self.commit_handler) == "function" then
+                       self:commit_handler(self:submitstate())
+               end
        end
        end
+
+       if self:submitstate() then
+               if not self.save then
+                       self.state = FORM_INVALID
+               elseif self.proceed then
+                       self.state = FORM_PROCEED
+               else
+                       self.state = self.changed and FORM_CHANGED or FORM_VALID
+               end
+       else
+               self.state = FORM_NODATA
+       end
+
+       return self:state_handler(self.state)
 end
 
 function Map.render(self, ...)
 end
 
 function Map.render(self, ...)
@@ -336,6 +466,164 @@ function Map.get(self, section, option)
        end
 end
 
        end
 end
 
+--[[
+Compound - Container
+]]--
+Compound = class(Node)
+
+function Compound.__init__(self, ...)
+       Node.__init__(self)
+       self.template = "cbi/compound"
+       self.children = {...}
+end
+
+function Compound.populate_delegator(self, delegator)
+       for _, v in ipairs(self.children) do
+               v.delegator = delegator
+       end
+end
+
+function Compound.parse(self, ...)
+       local cstate, state = 0
+
+       for k, child in ipairs(self.children) do
+               cstate = child:parse(...)
+               state = (not state or cstate < state) and cstate or state
+       end
+
+       return state
+end
+
+
+--[[
+Delegator - Node controller
+]]--
+Delegator = class(Node)
+function Delegator.__init__(self, ...)
+       Node.__init__(self, ...)
+       self.nodes = {}
+       self.defaultpath = {}
+       self.pageaction = false
+       self.readinput = true
+       self.allow_reset = 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
+end
+
+function Delegator.add(self, name, node)
+       node = self:set(name, node)
+       self.defaultpath[#self.defaultpath+1] = name
+end
+
+function Delegator.insert_after(self, name, after)
+       local n = #self.chain
+       for k, v in ipairs(self.chain) do
+               if v == state then
+                       n = k + 1
+                       break
+               end
+       end
+       table.insert(self.chain, n, name)
+end
+
+function Delegator.set_route(self, ...)
+       local n, chain, route = 0, self.chain, {...}
+       for i = 1, #chain do
+               if chain[i] == self.current then
+                       n = i
+                       break
+               end
+       end
+       for i = 1, #route do
+               n = n + 1
+               chain[n] = route[i]
+       end
+       for i = n + 1, #chain do
+               chain[i] = nil
+       end
+end
+
+function Delegator.get(self, name)
+       return self.nodes[name]
+end
+
+function Delegator.parse(self, ...)
+       local newcurrent
+       self.chain = self.chain or self:get_chain()
+       self.current = self.current or self:get_active()
+       self.active = self.active or self:get(self.current)
+       assert(self.active, "Invalid state")
+       
+       local stat = FORM_DONE
+       if type(self.active) ~= "function" then
+               self.active:populate_delegator(self)
+               stat = self.active:parse() 
+       else
+               self:active()
+       end
+
+       if stat > FORM_PROCEED then
+               if Map.formvalue(self, "cbi.delg.back") then
+                       newcurrent = self:get_prev(self.current)
+               else
+                       newcurrent = self:get_next(self.current)
+               end
+       elseif stat < FORM_PROCEED then
+               return stat
+       end
+       
+
+       if not Map.formvalue(self, "cbi.submit") then
+               return FORM_NODATA
+       elseif not newcurrent or not self:get(newcurrent) then
+               return FORM_DONE
+       else
+               self.current = newcurrent
+               self.active = self:get(self.current)
+               if type(self.active) ~= "function" then
+                       self.active:parse(false)
+                       return FROM_PROCEED
+               else
+                       return self:parse(...)
+               end
+       end
+end
+
+function Delegator.get_next(self, state)
+       for k, v in ipairs(self.chain) do
+               if v == state then
+                       return self.chain[k+1]
+               end
+       end
+end
+
+function Delegator.get_prev(self, state)
+       for k, v in ipairs(self.chain) do
+               if v == state then
+                       return self.chain[k-1]
+               end
+       end
+end
+
+function Delegator.get_chain(self)
+       local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
+       return type(x) == "table" and x or {x}
+end
+
+function Delegator.get_active(self)
+       return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
+end
 
 --[[
 Page - A simple node
 
 --[[
 Page - A simple node
@@ -357,10 +645,21 @@ function SimpleForm.__init__(self, config, title, description, data)
        self.data = data or {}
        self.template = "cbi/simpleform"
        self.dorender = true
        self.data = data or {}
        self.template = "cbi/simpleform"
        self.dorender = true
+       self.pageaction = false
+       self.readinput = true
 end
 
 end
 
-function SimpleForm.parse(self, ...)
-       if luci.http.formvalue("cbi.submit") then
+SimpleForm.formvalue = Map.formvalue
+SimpleForm.formvaluetable = Map.formvaluetable
+
+function SimpleForm.parse(self, readinput, ...)
+       self.readinput = (readinput ~= false)
+
+       if self:formvalue("cbi.skip") then
+               return FORM_SKIP
+       end
+
+       if self:submitstate() then
                Node.parse(self, 1, ...)
        end
 
                Node.parse(self, 1, ...)
        end
 
@@ -370,15 +669,22 @@ function SimpleForm.parse(self, ...)
                        valid = valid
                         and (not v.tag_missing or not v.tag_missing[1])
                         and (not v.tag_invalid or not v.tag_invalid[1])
                        valid = valid
                         and (not v.tag_missing or not v.tag_missing[1])
                         and (not v.tag_invalid or not v.tag_invalid[1])
+                        and (not v.error)
                end
        end
 
        local state =
                end
        end
 
        local state =
-               not luci.http.formvalue("cbi.submit") and 0
-               or valid and 1
-               or -1
+               not self:submitstate() and FORM_NODATA
+               or valid and FORM_VALID
+               or FORM_INVALID
 
 
-       self.dorender = not self.handle or self:handle(state, self.data) ~= false
+       self.dorender = not self.handle
+       if self.handle then
+               local nrender, nstate = self:handle(state, self.data)
+               self.dorender = self.dorender or (nrender ~= false)
+               state = nstate or state
+       end
+       return state
 end
 
 function SimpleForm.render(self, ...)
 end
 
 function SimpleForm.render(self, ...)
@@ -387,6 +693,10 @@ function SimpleForm.render(self, ...)
        end
 end
 
        end
 end
 
+function SimpleForm.submitstate(self)
+       return self:formvalue("cbi.submit")
+end
+
 function SimpleForm.section(self, class, ...)
        if instanceof(class, AbstractSection) then
                local obj  = class(self, ...)
 function SimpleForm.section(self, class, ...)
        if instanceof(class, AbstractSection) then
                local obj  = class(self, ...)
@@ -440,6 +750,13 @@ function SimpleForm.get_scheme()
 end
 
 
 end
 
 
+Form = class(SimpleForm)
+
+function Form.__init__(self, ...)
+       SimpleForm.__init__(self, ...)
+       self.embedded = true
+end
+
 
 --[[
 AbstractSection
 
 --[[
 AbstractSection
@@ -457,12 +774,26 @@ function AbstractSection.__init__(self, map, sectiontype, ...)
        self.tag_error = {}
        self.tag_invalid = {}
        self.tag_deperror = {}
        self.tag_error = {}
        self.tag_invalid = {}
        self.tag_deperror = {}
+       self.changed = false
 
        self.optional = true
        self.addremove = false
        self.dynamic = false
 end
 
 
        self.optional = true
        self.addremove = false
        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
 -- Appends a new option
 function AbstractSection.option(self, class, option, ...)
        -- Autodetect from UVL
@@ -494,6 +825,31 @@ function AbstractSection.option(self, class, option, ...)
        end
 end
 
        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
 -- Parse optional options
 function AbstractSection.parse_optionals(self, section)
        if not self.optional then
@@ -502,11 +858,12 @@ function AbstractSection.parse_optionals(self, section)
 
        self.optionals[section] = {}
 
 
        self.optionals[section] = {}
 
-       local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
+       local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
        for k,v in ipairs(self.children) do
                if v.optional and not v:cfgvalue(section) then
                        if field == v.option then
                                field = nil
        for k,v in ipairs(self.children) do
                if v.optional and not v:cfgvalue(section) then
                        if field == v.option then
                                field = nil
+                               self.map.proceed = true
                        else
                                table.insert(self.optionals[section], v)
                        end
                        else
                                table.insert(self.optionals[section], v)
                        end
@@ -531,7 +888,7 @@ function AbstractSection.parse_dynamic(self, section)
        end
 
        local arr  = luci.util.clone(self:cfgvalue(section))
        end
 
        local arr  = luci.util.clone(self:cfgvalue(section))
-       local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
+       local form = self.map:formvaluetable("cbid."..self.config.."."..section)
        for k, v in pairs(form) do
                arr[k] = v
        end
        for k, v in pairs(form) do
                arr[k] = v
        end
@@ -546,6 +903,7 @@ function AbstractSection.parse_dynamic(self, section)
                end
 
                if create and key:sub(1, 1) ~= "." then
                end
 
                if create and key:sub(1, 1) ~= "." then
+                       self.map.proceed = true
                        self:add_dynamic(key, true)
                end
        end
                        self:add_dynamic(key, true)
                end
        end
@@ -556,8 +914,15 @@ function AbstractSection.cfgvalue(self, section)
        return self.map:get(section)
 end
 
        return self.map:get(section)
 end
 
+-- Push events
+function AbstractSection.push_events(self)
+       --luci.util.append(self.map.events, self.events)
+       self.map.changed = true
+end
+
 -- Removes the section
 function AbstractSection.remove(self, section)
 -- Removes the section
 function AbstractSection.remove(self, section)
+       self.map.proceed = true
        return self.map:del(section)
 end
 
        return self.map:del(section)
 end
 
@@ -566,7 +931,7 @@ function AbstractSection.create(self, section)
        local stat
 
        if section then
        local stat
 
        if section then
-               stat = self.map:set(section, nil, self.sectiontype)
+               stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
        else
                section = self.map:add(self.sectiontype)
                stat = section
        else
                section = self.map:add(self.sectiontype)
                stat = section
@@ -584,6 +949,8 @@ function AbstractSection.create(self, section)
                end
        end
 
                end
        end
 
+       self.map.proceed = true
+
        return stat
 end
 
        return stat
 end
 
@@ -600,11 +967,20 @@ Table = class(AbstractSection)
 
 function Table.__init__(self, form, data, ...)
        local datasource = {}
 
 function Table.__init__(self, form, data, ...)
        local datasource = {}
+       local tself = self
        datasource.config = "table"
        datasource.config = "table"
-       self.data = data
+       self.data = data or {}
+
+       datasource.formvalue = Map.formvalue
+       datasource.formvaluetable = Map.formvaluetable
+       datasource.readinput = true
 
        function datasource.get(self, section, option)
 
        function datasource.get(self, section, option)
-               return data[section] and data[section][option]
+               return tself.data[section] and tself.data[section][option]
+       end
+
+       function datasource.submitstate(self)
+               return Map.formvalue(self, "cbi.submit")
        end
 
        function datasource.del(...)
        end
 
        function datasource.del(...)
@@ -621,9 +997,10 @@ function Table.__init__(self, form, data, ...)
        self.anonymous = true
 end
 
        self.anonymous = true
 end
 
-function Table.parse(self)
+function Table.parse(self, readinput)
+       self.map.readinput = (readinput ~= false)
        for i, k in ipairs(self:cfgsections()) do
        for i, k in ipairs(self:cfgsections()) do
-               if luci.http.formvalue("cbi.submit") then
+               if self.map:submitstate() then
                        Node.parse(self, k)
                end
        end
                        Node.parse(self, k)
                end
        end
@@ -639,6 +1016,10 @@ function Table.cfgsections(self)
        return sections
 end
 
        return sections
 end
 
+function Table.update(self, data)
+       self.data = data
+end
+
 
 
 --[[
 
 
 --[[
@@ -673,11 +1054,12 @@ function NamedSection.parse(self, novld)
        if self.addremove then
                local path = self.config.."."..s
                if active then -- Remove the section
        if self.addremove then
                local path = self.config.."."..s
                if active then -- Remove the section
-                       if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
+                       if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
+                               self:push_events()
                                return
                        end
                else           -- Create and apply default values
                                return
                        end
                else           -- Create and apply default values
-                       if luci.http.formvalue("cbi.cns."..path) then
+                       if self.map:formvalue("cbi.cns."..path) then
                                self:create(s)
                                return
                        end
                                self:create(s)
                                return
                        end
@@ -686,14 +1068,18 @@ function NamedSection.parse(self, novld)
 
        if active then
                AbstractSection.parse_dynamic(self, s)
 
        if active then
                AbstractSection.parse_dynamic(self, s)
-               if luci.http.formvalue("cbi.submit") then
+               if self.map:submitstate() then
                        Node.parse(self, s)
 
                        Node.parse(self, s)
 
-                       if not novld not self.override_scheme and self.map.scheme then
+                       if not novld and not self.override_scheme and self.map.scheme then
                                _uvl_validate_section(self, s)
                        end
                end
                AbstractSection.parse_optionals(self, s)
                                _uvl_validate_section(self, s)
                        end
                end
                AbstractSection.parse_optionals(self, s)
+
+               if self.changed then
+                       self:push_events()
+               end
        end
 end
 
        end
 end
 
@@ -747,7 +1133,7 @@ function TypedSection.parse(self, novld)
        if self.addremove then
                -- Remove
                local crval = REMOVE_PREFIX .. self.config
        if self.addremove then
                -- Remove
                local crval = REMOVE_PREFIX .. self.config
-               local name = luci.http.formvaluetable(crval)
+               local name = self.map:formvaluetable(crval)
                for k,v in pairs(name) do
                        if k:sub(-2) == ".x" then
                                k = k:sub(1, #k - 2)
                for k,v in pairs(name) do
                        if k:sub(-2) == ".x" then
                                k = k:sub(1, #k - 2)
@@ -761,8 +1147,8 @@ function TypedSection.parse(self, novld)
        local co
        for i, k in ipairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
        local co
        for i, k in ipairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
-               if luci.http.formvalue("cbi.submit") then
-                       Node.parse(self, k)
+               if self.map:submitstate() then
+                       Node.parse(self, k, novld)
 
                        if not novld and not self.override_scheme and self.map.scheme then
                                _uvl_validate_section(self, k)
 
                        if not novld and not self.override_scheme and self.map.scheme then
                                _uvl_validate_section(self, k)
@@ -775,7 +1161,7 @@ function TypedSection.parse(self, novld)
                -- Create
                local created
                local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
                -- Create
                local created
                local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
-               local name  = luci.http.formvalue(crval)
+               local name  = self.map:formvalue(crval)
                if self.anonymous then
                        if name then
                                created = self:create()
                if self.anonymous then
                        if name then
                                created = self:create()
@@ -795,6 +1181,9 @@ function TypedSection.parse(self, novld)
 
                                if name and #name > 0 then
                                        created = self:create(name) and name
 
                                if name and #name > 0 then
                                        created = self:create(name) and name
+                                       if not created then
+                                               self.invalid_cts = true
+                                       end
                                end
                        end
                end
                                end
                        end
                end
@@ -803,6 +1192,10 @@ function TypedSection.parse(self, novld)
                        AbstractSection.parse_optionals(self, created)
                end
        end
                        AbstractSection.parse_optionals(self, created)
                end
        end
+
+       if created or self.changed then
+               self:push_events()
+       end
 end
 
 -- Verifies scope of sections
 end
 
 -- Verifies scope of sections
@@ -860,23 +1253,28 @@ function AbstractValue.__init__(self, map, section, option, ...)
        self.tag_reqerror = {}
        self.tag_error = {}
        self.deps = {}
        self.tag_reqerror = {}
        self.tag_error = {}
        self.deps = {}
-       self.cast = "string"
+       --self.cast = "string"
 
        self.track_missing = false
 
        self.track_missing = false
-       self.rmempty   = false
+       self.rmempty   = true
        self.default   = nil
        self.size      = nil
        self.optional  = false
        self.default   = nil
        self.size      = nil
        self.optional  = false
+end
 
 
+function AbstractValue.prepare(self)
        -- Use defaults from UVL
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
        -- Use defaults from UVL
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
-               self.rmempty     = not vs.required
-               self.cast        = (vs.type == "list") and "list" or "string"
+               if self.cast == nil then
+                       self.cast = (vs.type == "list") and "list" or "string"
+               end
                self.title       = self.title or vs.title
                self.description = self.description or vs.descr
                self.title       = self.title or vs.title
                self.description = self.description or vs.descr
-               self.default     = vs.default
+               if self.default == nil then
+                       self.default = vs.default
+               end
 
                if vs.depends and not self.override_dependencies then
                        for i, deps in ipairs(vs.depends) do
 
                if vs.depends and not self.override_dependencies then
                        for i, deps in ipairs(vs.depends) do
@@ -887,6 +1285,8 @@ function AbstractValue.__init__(self, map, section, option, ...)
                        end
                end
        end
                        end
                end
        end
+
+       self.cast = self.cast or "string"
 end
 
 -- Add a dependencie to another section field
 end
 
 -- Add a dependencie to another section field
@@ -910,12 +1310,12 @@ end
 -- Return whether this object should be created
 function AbstractValue.formcreated(self, section)
        local key = "cbi.opt."..self.config.."."..section
 -- Return whether this object should be created
 function AbstractValue.formcreated(self, section)
        local key = "cbi.opt."..self.config.."."..section
-       return (luci.http.formvalue(key) == self.option)
+       return (self.map:formvalue(key) == self.option)
 end
 
 -- Returns the formvalue for this object
 function AbstractValue.formvalue(self, section)
 end
 
 -- Returns the formvalue for this object
 function AbstractValue.formvalue(self, section)
-       return luci.http.formvalue(self:cbid(section))
+       return self.map:formvalue(self:cbid(section))
 end
 
 function AbstractValue.additional(self, value)
 end
 
 function AbstractValue.additional(self, value)
@@ -926,23 +1326,47 @@ function AbstractValue.mandatory(self, value)
        self.rmempty = not value
 end
 
        self.rmempty = not value
 end
 
-function AbstractValue.parse(self, section)
+function AbstractValue.parse(self, section, novld)
        local fvalue = self:formvalue(section)
        local cvalue = self:cfgvalue(section)
 
        local fvalue = self:formvalue(section)
        local cvalue = self:cfgvalue(section)
 
-       if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
+       if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
                fvalue = self:transform(self:validate(fvalue, section))
                fvalue = self:transform(self:validate(fvalue, section))
-               if not fvalue then
-                       self.tag_invalid[section] = true
+               if not fvalue and not novld then
+                       if self.error then
+                               self.error[section] = "invalid"
+                       else
+                               self.error = { [section] = "invalid" }
+                       end
+                       if self.section.error then
+                               table.insert(self.section.error[section], "invalid")
+                       else
+                               self.section.error = {[section] = {"invalid"}}
+                       end 
+                       self.map.save = false
                end
                if fvalue and not (fvalue == cvalue) then
                end
                if fvalue and not (fvalue == cvalue) then
-                       self:write(section, fvalue)
+                       if self:write(section, fvalue) then
+                               -- Push events
+                               self.section.changed = true
+                               --luci.util.append(self.map.events, self.events)
+                       end
                end
        else                                                    -- Unset the UCI or error
                if self.rmempty or self.optional then
                end
        else                                                    -- Unset the UCI or error
                if self.rmempty or self.optional then
-                       self:remove(section)
-               elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
-                       self.tag_missing[section] = true
+                       if self:remove(section) 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
                end
        end
 end
                end
        end
 end
@@ -954,6 +1378,7 @@ function AbstractValue.render(self, s, scope)
                scope.section   = s
                scope.cbid      = self:cbid(s)
                scope.striptags = luci.util.striptags
                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
 
                scope.ifattr = function(cond,key,val)
                        if cond then
@@ -980,14 +1405,16 @@ end
 -- Return the UCI value of this object
 function AbstractValue.cfgvalue(self, section)
        local value = self.map:get(section, self.option)
 -- Return the UCI value of this object
 function AbstractValue.cfgvalue(self, section)
        local value = self.map:get(section, self.option)
-       if not self.cast or self.cast == type(value) then
+       if not value then
+               return nil
+       elseif not self.cast or self.cast == type(value) then
                return value
        elseif self.cast == "string" then
                if type(value) == "table" then
                        return value[1]
                end
        elseif self.cast == "table" then
                return value
        elseif self.cast == "string" then
                if type(value) == "table" then
                        return value[1]
                end
        elseif self.cast == "table" then
-               return {value}
+               return luci.util.split(value, "%s+", nil, true)
        end
 end
 
        end
 end
 
@@ -1041,6 +1468,20 @@ function DummyValue.__init__(self, ...)
        self.value = nil
 end
 
        self.value = nil
 end
 
+function DummyValue.cfgvalue(self, section)
+       local value
+       if self.value then
+               if type(self.value) == "function" then
+                       value = self:value(section)
+               else
+                       value = self.value
+               end
+       else
+               value = AbstractValue.cfgvalue(self, section)
+       end
+       return value
+end
+
 function DummyValue.parse(self)
 
 end
 function DummyValue.parse(self)
 
 end
@@ -1094,23 +1535,23 @@ function ListValue.__init__(self, ...)
        self.vallist = {}
        self.size   = 1
        self.widget = "select"
        self.vallist = {}
        self.size   = 1
        self.widget = "select"
+end
 
 
+function ListValue.prepare(self, ...)
+       AbstractValue.prepare(self, ...)
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
-               if self.value and vs.values and not self.override_values then
-                       if self.rmempty or self.optional then
-                               self:value("")
-                       end
-                       for k, v in pairs(vs.values) do
+               if self.value and vs.valuelist and not self.override_values then
+                       for k, v in ipairs(vs.valuelist) do
                                local deps = {}
                                if not self.override_dependencies
                                local deps = {}
                                if not self.override_dependencies
-                                and vs.enum_depends and vs.enum_depends[k] then
-                                       for i, dep in ipairs(vs.enum_depends[k]) do
+                                and vs.enum_depends and vs.enum_depends[v.value] then
+                                       for i, dep in ipairs(vs.enum_depends[v.value]) do
                                                table.insert(deps, _uvl_strip_remote_dependencies(dep))
                                        end
                                end
                                                table.insert(deps, _uvl_strip_remote_dependencies(dep))
                                        end
                                end
-                               self:value(k, v, unpack(deps))
+                               self:value(v.value, v.title or v.value, unpack(deps))
                        end
                end
        end
                        end
                end
        end
@@ -1224,7 +1665,7 @@ function StaticList.validate(self, value)
 
        local valid = {}
        for i, v in ipairs(value) do
 
        local valid = {}
        for i, v in ipairs(value) do
-               if luci.util.contains(self.valuelist, v) then
+               if luci.util.contains(self.keylist, v) then
                        table.insert(valid, v)
                end
        end
                        table.insert(valid, v)
                end
        end
@@ -1248,14 +1689,20 @@ function DynamicList.value(self, key, val)
        table.insert(self.vallist, tostring(val))
 end
 
        table.insert(self.vallist, tostring(val))
 end
 
-function DynamicList.validate(self, value, section)
+function DynamicList.write(self, ...)
+       self.map.proceed = true
+       return AbstractValue.write(self, ...)
+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
        value = (type(value) == "table") and value or {value}
 
        local valid = {}
        for i, v in ipairs(value) do
                if v and #v > 0
-                and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i)
-                and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
+                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)
                end
        end
                        table.insert(valid, v)
                end
        end
@@ -1286,3 +1733,58 @@ function Button.__init__(self, ...)
        self.inputstyle = nil
        self.rmempty = true
 end
        self.inputstyle = nil
        self.rmempty = true
 end
+
+
+FileUpload = class(AbstractValue)
+
+function FileUpload.__init__(self, ...)
+       AbstractValue.__init__(self, ...)
+       self.template = "cbi/upload"
+       if not self.map.upload_fields then
+               self.map.upload_fields = { self }
+       else
+               self.map.upload_fields[#self.map.upload_fields+1] = self
+       end
+end
+
+function FileUpload.formcreated(self, section)
+       return AbstractValue.formcreated(self, section) or
+               self.map:formvalue("cbi.rlf."..section.."."..self.option) or
+               self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
+end
+
+function FileUpload.cfgvalue(self, section)
+       local val = AbstractValue.cfgvalue(self, section)
+       if val and fs.access(val) then
+               return val
+       end
+       return nil
+end
+
+function FileUpload.formvalue(self, section)
+       local val = AbstractValue.formvalue(self, section)
+       if val then
+               if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
+                  not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
+               then
+                       return val
+               end
+               fs.unlink(val)
+               self.value = nil
+       end
+       return nil
+end
+
+function FileUpload.remove(self, section)
+       local val = AbstractValue.formvalue(self, section)
+       if val and fs.access(val) then fs.unlink(val) end
+       return AbstractValue.remove(self, section)
+end
+
+
+FileBrowser = class(AbstractValue)
+
+function FileBrowser.__init__(self, ...)
+       AbstractValue.__init__(self, ...)
+       self.template = "cbi/browser"
+end