2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
30 local util = require("luci.util")
34 --local event = require "luci.sys.event"
35 local fs = require("nixio.fs")
36 local uci = require("luci.model.uci")
37 local datatypes = require("luci.cbi.datatypes")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
65 if fs.access(cbidir..cbimap..".lua") then
66 func, err = loadfile(cbidir..cbimap..".lua")
67 elseif fs.access(cbimap) then
68 func, err = loadfile(cbimap)
70 func, err = nil, "Model '" .. cbimap .. "' not found!"
75 luci.i18n.loadc("base")
78 translate=i18n.translate,
79 translatef=i18n.translatef,
83 setfenv(func, setmetatable(env, {__index =
85 return rawget(tbl, key) or _M[key] or _G[key]
88 local maps = { func() }
90 local has_upload = false
92 for i, map in ipairs(maps) do
93 if not instanceof(map, Node) then
94 error("CBI map returns no valid map object!")
98 if map.upload_fields then
100 for _, field in ipairs(map.upload_fields) do
102 field.config .. '.' ..
103 field.section.sectiontype .. '.' ..
112 local uci = luci.model.uci.cursor()
113 local prm = luci.http.context.request.message.params
116 luci.http.setfilehandler(
117 function( field, chunk, eof )
118 if not field then return end
119 if field.name and not cbid then
120 local c, s, o = field.name:gmatch(
121 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
124 if c and s and o then
125 local t = uci:get( c, s )
126 if t and uploads[c.."."..t.."."..o] then
127 local path = upldir .. field.name
128 fd = io.open(path, "w")
137 if field.name == cbid and fd then
154 -- Node pseudo abstract class
157 function Node.__init__(self, title, description)
159 self.title = title or ""
160 self.description = description or ""
161 self.template = "cbi/node"
165 function Node._run_hook(self, hook)
166 if type(self[hook]) == "function" then
167 return self[hook](self)
171 function Node._run_hooks(self, ...)
174 for _, f in ipairs(arg) do
175 if type(self[f]) == "function" then
184 function Node.prepare(self, ...)
185 for k, child in ipairs(self.children) do
190 -- Append child nodes
191 function Node.append(self, obj)
192 table.insert(self.children, obj)
195 -- Parse this node and its children
196 function Node.parse(self, ...)
197 for k, child in ipairs(self.children) do
203 function Node.render(self, scope)
207 luci.template.render(self.template, scope)
210 -- Render the children
211 function Node.render_children(self, ...)
212 for k, node in ipairs(self.children) do
219 A simple template element
221 Template = class(Node)
223 function Template.__init__(self, template)
225 self.template = template
228 function Template.render(self)
229 luci.template.render(self.template, {self=self})
232 function Template.parse(self, readinput)
233 self.readinput = (readinput ~= false)
234 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
239 Map - A map describing a configuration file
243 function Map.__init__(self, config, ...)
244 Node.__init__(self, ...)
247 self.parsechain = {self.config}
248 self.template = "cbi/map"
249 self.apply_on_parse = nil
250 self.readinput = true
254 self.uci = uci.cursor()
259 if not self.uci:load(self.config) then
260 error("Unable to read UCI data: " .. self.config)
264 function Map.formvalue(self, key)
265 return self.readinput and luci.http.formvalue(key)
268 function Map.formvaluetable(self, key)
269 return self.readinput and luci.http.formvaluetable(key) or {}
272 function Map.get_scheme(self, sectiontype, option)
274 return self.scheme and self.scheme.sections[sectiontype]
276 return self.scheme and self.scheme.variables[sectiontype]
277 and self.scheme.variables[sectiontype][option]
281 function Map.submitstate(self)
282 return self:formvalue("cbi.submit")
285 -- Chain foreign config
286 function Map.chain(self, config)
287 table.insert(self.parsechain, config)
290 function Map.state_handler(self, state)
294 -- Use optimized UCI writing
295 function Map.parse(self, readinput, ...)
296 self.readinput = (readinput ~= false)
297 self:_run_hooks("on_parse")
299 if self:formvalue("cbi.skip") then
300 self.state = FORM_SKIP
301 return self:state_handler(self.state)
304 Node.parse(self, ...)
307 self:_run_hooks("on_save", "on_before_save")
308 for i, config in ipairs(self.parsechain) do
309 self.uci:save(config)
311 self:_run_hooks("on_after_save")
312 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
313 self:_run_hooks("on_before_commit")
314 for i, config in ipairs(self.parsechain) do
315 self.uci:commit(config)
317 -- Refresh data because commit changes section names
318 self.uci:load(config)
320 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
321 if self.apply_on_parse then
322 self.uci:apply(self.parsechain)
323 self:_run_hooks("on_apply", "on_after_apply")
325 self._apply = function()
326 local cmd = self.uci:apply(self.parsechain, true)
332 Node.parse(self, true)
335 for i, config in ipairs(self.parsechain) do
336 self.uci:unload(config)
338 if type(self.commit_handler) == "function" then
339 self:commit_handler(self:submitstate())
343 if self:submitstate() then
344 if not self.save then
345 self.state = FORM_INVALID
346 elseif self.proceed then
347 self.state = FORM_PROCEED
349 self.state = self.changed and FORM_CHANGED or FORM_VALID
352 self.state = FORM_NODATA
355 return self:state_handler(self.state)
358 function Map.render(self, ...)
359 self:_run_hooks("on_init")
360 Node.render(self, ...)
361 if false and self._apply then
362 local fp = self._apply()
365 self:_run_hooks("on_apply")
369 -- Creates a child section
370 function Map.section(self, class, ...)
371 if instanceof(class, AbstractSection) then
372 local obj = class(self, ...)
376 error("class must be a descendent of AbstractSection")
381 function Map.add(self, sectiontype)
382 return self.uci:add(self.config, sectiontype)
386 function Map.set(self, section, option, value)
388 return self.uci:set(self.config, section, option, value)
390 return self.uci:set(self.config, section, value)
395 function Map.del(self, section, option)
397 return self.uci:delete(self.config, section, option)
399 return self.uci:delete(self.config, section)
404 function Map.get(self, section, option)
406 return self.uci:get_all(self.config)
408 return self.uci:get(self.config, section, option)
410 return self.uci:get_all(self.config, section)
417 Compound = class(Node)
419 function Compound.__init__(self, ...)
421 self.template = "cbi/compound"
422 self.children = {...}
425 function Compound.populate_delegator(self, delegator)
426 for _, v in ipairs(self.children) do
427 v.delegator = delegator
431 function Compound.parse(self, ...)
432 local cstate, state = 0
434 for k, child in ipairs(self.children) do
435 cstate = child:parse(...)
436 state = (not state or cstate < state) and cstate or state
444 Delegator - Node controller
446 Delegator = class(Node)
447 function Delegator.__init__(self, ...)
448 Node.__init__(self, ...)
450 self.defaultpath = {}
451 self.pageaction = false
452 self.readinput = true
453 self.allow_reset = false
454 self.allow_cancel = false
455 self.allow_back = false
456 self.allow_finish = false
457 self.template = "cbi/delegator"
460 function Delegator.set(self, name, node)
461 assert(not self.nodes[name], "Duplicate entry")
463 self.nodes[name] = node
466 function Delegator.add(self, name, node)
467 node = self:set(name, node)
468 self.defaultpath[#self.defaultpath+1] = name
471 function Delegator.insert_after(self, name, after)
472 local n = #self.chain + 1
473 for k, v in ipairs(self.chain) do
479 table.insert(self.chain, n, name)
482 function Delegator.set_route(self, ...)
483 local n, chain, route = 0, self.chain, {...}
485 if chain[i] == self.current then
494 for i = n + 1, #chain do
499 function Delegator.get(self, name)
500 local node = self.nodes[name]
502 if type(node) == "string" then
503 node = load(node, name)
506 if type(node) == "table" and getmetatable(node) == nil then
507 node = Compound(unpack(node))
513 function Delegator.parse(self, ...)
514 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
515 if self:_run_hooks("on_cancel") then
520 if not Map.formvalue(self, "cbi.delg.current") then
521 self:_run_hooks("on_init")
525 self.chain = self.chain or self:get_chain()
526 self.current = self.current or self:get_active()
527 self.active = self.active or self:get(self.current)
528 assert(self.active, "Invalid state")
530 local stat = FORM_DONE
531 if type(self.active) ~= "function" then
532 self.active:populate_delegator(self)
533 stat = self.active:parse()
538 if stat > FORM_PROCEED then
539 if Map.formvalue(self, "cbi.delg.back") then
540 newcurrent = self:get_prev(self.current)
542 newcurrent = self:get_next(self.current)
544 elseif stat < FORM_PROCEED then
549 if not Map.formvalue(self, "cbi.submit") then
551 elseif stat > FORM_PROCEED
552 and (not newcurrent or not self:get(newcurrent)) then
553 return self:_run_hook("on_done") or FORM_DONE
555 self.current = newcurrent or self.current
556 self.active = self:get(self.current)
557 if type(self.active) ~= "function" then
558 self.active:populate_delegator(self)
559 local stat = self.active:parse(false)
560 if stat == FORM_SKIP then
561 return self:parse(...)
566 return self:parse(...)
571 function Delegator.get_next(self, state)
572 for k, v in ipairs(self.chain) do
574 return self.chain[k+1]
579 function Delegator.get_prev(self, state)
580 for k, v in ipairs(self.chain) do
582 return self.chain[k-1]
587 function Delegator.get_chain(self)
588 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
589 return type(x) == "table" and x or {x}
592 function Delegator.get_active(self)
593 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
601 Page.__init__ = Node.__init__
602 Page.parse = function() end
606 SimpleForm - A Simple non-UCI form
608 SimpleForm = class(Node)
610 function SimpleForm.__init__(self, config, title, description, data)
611 Node.__init__(self, title, description)
613 self.data = data or {}
614 self.template = "cbi/simpleform"
616 self.pageaction = false
617 self.readinput = true
620 SimpleForm.formvalue = Map.formvalue
621 SimpleForm.formvaluetable = Map.formvaluetable
623 function SimpleForm.parse(self, readinput, ...)
624 self.readinput = (readinput ~= false)
626 if self:formvalue("cbi.skip") then
630 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
634 if self:submitstate() then
635 Node.parse(self, 1, ...)
639 for k, j in ipairs(self.children) do
640 for i, v in ipairs(j.children) do
642 and (not v.tag_missing or not v.tag_missing[1])
643 and (not v.tag_invalid or not v.tag_invalid[1])
649 not self:submitstate() and FORM_NODATA
650 or valid and FORM_VALID
653 self.dorender = not self.handle
655 local nrender, nstate = self:handle(state, self.data)
656 self.dorender = self.dorender or (nrender ~= false)
657 state = nstate or state
662 function SimpleForm.render(self, ...)
663 if self.dorender then
664 Node.render(self, ...)
668 function SimpleForm.submitstate(self)
669 return self:formvalue("cbi.submit")
672 function SimpleForm.section(self, class, ...)
673 if instanceof(class, AbstractSection) then
674 local obj = class(self, ...)
678 error("class must be a descendent of AbstractSection")
682 -- Creates a child field
683 function SimpleForm.field(self, class, ...)
685 for k, v in ipairs(self.children) do
686 if instanceof(v, SimpleSection) then
692 section = self:section(SimpleSection)
695 if instanceof(class, AbstractValue) then
696 local obj = class(self, section, ...)
697 obj.track_missing = true
701 error("class must be a descendent of AbstractValue")
705 function SimpleForm.set(self, section, option, value)
706 self.data[option] = value
710 function SimpleForm.del(self, section, option)
711 self.data[option] = nil
715 function SimpleForm.get(self, section, option)
716 return self.data[option]
720 function SimpleForm.get_scheme()
725 Form = class(SimpleForm)
727 function Form.__init__(self, ...)
728 SimpleForm.__init__(self, ...)
736 AbstractSection = class(Node)
738 function AbstractSection.__init__(self, map, sectiontype, ...)
739 Node.__init__(self, ...)
740 self.sectiontype = sectiontype
742 self.config = map.config
747 self.tag_invalid = {}
748 self.tag_deperror = {}
752 self.addremove = false
756 -- Define a tab for the section
757 function AbstractSection.tab(self, tab, title, desc)
758 self.tabs = self.tabs or { }
759 self.tab_names = self.tab_names or { }
761 self.tab_names[#self.tab_names+1] = tab
769 -- Check whether the section has tabs
770 function AbstractSection.has_tabs(self)
771 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
774 -- Appends a new option
775 function AbstractSection.option(self, class, option, ...)
776 if instanceof(class, AbstractValue) then
777 local obj = class(self.map, self, option, ...)
779 self.fields[option] = obj
781 elseif class == true then
782 error("No valid class was given and autodetection failed.")
784 error("class must be a descendant of AbstractValue")
788 -- Appends a new tabbed option
789 function AbstractSection.taboption(self, tab, ...)
791 assert(tab and self.tabs and self.tabs[tab],
792 "Cannot assign option to not existing tab %q" % tostring(tab))
794 local l = self.tabs[tab].childs
795 local o = AbstractSection.option(self, ...)
797 if o then l[#l+1] = o end
802 -- Render a single tab
803 function AbstractSection.render_tab(self, tab, ...)
805 assert(tab and self.tabs and self.tabs[tab],
806 "Cannot render not existing tab %q" % tostring(tab))
808 for _, node in ipairs(self.tabs[tab].childs) do
813 -- Parse optional options
814 function AbstractSection.parse_optionals(self, section)
815 if not self.optional then
819 self.optionals[section] = {}
821 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
822 for k,v in ipairs(self.children) do
823 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
824 if field == v.option then
826 self.map.proceed = true
828 table.insert(self.optionals[section], v)
833 if field and #field > 0 and self.dynamic then
834 self:add_dynamic(field)
838 -- Add a dynamic option
839 function AbstractSection.add_dynamic(self, field, optional)
840 local o = self:option(Value, field, field)
841 o.optional = optional
844 -- Parse all dynamic options
845 function AbstractSection.parse_dynamic(self, section)
846 if not self.dynamic then
850 local arr = luci.util.clone(self:cfgvalue(section))
851 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
852 for k, v in pairs(form) do
856 for key,val in pairs(arr) do
859 for i,c in ipairs(self.children) do
860 if c.option == key then
865 if create and key:sub(1, 1) ~= "." then
866 self.map.proceed = true
867 self:add_dynamic(key, true)
872 -- Returns the section's UCI table
873 function AbstractSection.cfgvalue(self, section)
874 return self.map:get(section)
878 function AbstractSection.push_events(self)
879 --luci.util.append(self.map.events, self.events)
880 self.map.changed = true
883 -- Removes the section
884 function AbstractSection.remove(self, section)
885 self.map.proceed = true
886 return self.map:del(section)
889 -- Creates the section
890 function AbstractSection.create(self, section)
894 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
896 section = self.map:add(self.sectiontype)
901 for k,v in pairs(self.children) do
903 self.map:set(section, v.option, v.default)
907 for k,v in pairs(self.defaults) do
908 self.map:set(section, k, v)
912 self.map.proceed = true
918 SimpleSection = class(AbstractSection)
920 function SimpleSection.__init__(self, form, ...)
921 AbstractSection.__init__(self, form, nil, ...)
922 self.template = "cbi/nullsection"
926 Table = class(AbstractSection)
928 function Table.__init__(self, form, data, ...)
929 local datasource = {}
931 datasource.config = "table"
932 self.data = data or {}
934 datasource.formvalue = Map.formvalue
935 datasource.formvaluetable = Map.formvaluetable
936 datasource.readinput = true
938 function datasource.get(self, section, option)
939 return tself.data[section] and tself.data[section][option]
942 function datasource.submitstate(self)
943 return Map.formvalue(self, "cbi.submit")
946 function datasource.del(...)
950 function datasource.get_scheme()
954 AbstractSection.__init__(self, datasource, "table", ...)
955 self.template = "cbi/tblsection"
956 self.rowcolors = true
957 self.anonymous = true
960 function Table.parse(self, readinput)
961 self.map.readinput = (readinput ~= false)
962 for i, k in ipairs(self:cfgsections()) do
963 if self.map:submitstate() then
969 function Table.cfgsections(self)
972 for i, v in luci.util.kspairs(self.data) do
973 table.insert(sections, i)
979 function Table.update(self, data)
986 NamedSection - A fixed configuration section defined by its name
988 NamedSection = class(AbstractSection)
990 function NamedSection.__init__(self, map, section, stype, ...)
991 AbstractSection.__init__(self, map, stype, ...)
994 self.addremove = false
995 self.template = "cbi/nsection"
996 self.section = section
999 function NamedSection.parse(self, novld)
1000 local s = self.section
1001 local active = self:cfgvalue(s)
1003 if self.addremove then
1004 local path = self.config.."."..s
1005 if active then -- Remove the section
1006 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1010 else -- Create and apply default values
1011 if self.map:formvalue("cbi.cns."..path) then
1019 AbstractSection.parse_dynamic(self, s)
1020 if self.map:submitstate() then
1023 AbstractSection.parse_optionals(self, s)
1025 if self.changed then
1033 TypedSection - A (set of) configuration section(s) defined by the type
1034 addremove: Defines whether the user can add/remove sections of this type
1035 anonymous: Allow creating anonymous sections
1036 validate: a validation function returning nil if the section is invalid
1038 TypedSection = class(AbstractSection)
1040 function TypedSection.__init__(self, map, type, ...)
1041 AbstractSection.__init__(self, map, type, ...)
1043 self.template = "cbi/tsection"
1045 self.anonymous = false
1048 -- Return all matching UCI sections for this TypedSection
1049 function TypedSection.cfgsections(self)
1051 self.map.uci:foreach(self.map.config, self.sectiontype,
1053 if self:checkscope(section[".name"]) then
1054 table.insert(sections, section[".name"])
1061 -- Limits scope to sections that have certain option => value pairs
1062 function TypedSection.depends(self, option, value)
1063 table.insert(self.deps, {option=option, value=value})
1066 function TypedSection.parse(self, novld)
1067 if self.addremove then
1069 local crval = REMOVE_PREFIX .. self.config
1070 local name = self.map:formvaluetable(crval)
1071 for k,v in pairs(name) do
1072 if k:sub(-2) == ".x" then
1073 k = k:sub(1, #k - 2)
1075 if self:cfgvalue(k) and self:checkscope(k) then
1082 for i, k in ipairs(self:cfgsections()) do
1083 AbstractSection.parse_dynamic(self, k)
1084 if self.map:submitstate() then
1085 Node.parse(self, k, novld)
1087 AbstractSection.parse_optionals(self, k)
1090 if self.addremove then
1093 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1094 local name = self.map:formvalue(crval)
1095 if self.anonymous then
1097 created = self:create()
1101 -- Ignore if it already exists
1102 if self:cfgvalue(name) then
1106 name = self:checkscope(name)
1109 self.err_invalid = true
1112 if name and #name > 0 then
1113 created = self:create(name) and name
1115 self.invalid_cts = true
1122 AbstractSection.parse_optionals(self, created)
1126 if created or self.changed then
1131 -- Verifies scope of sections
1132 function TypedSection.checkscope(self, section)
1133 -- Check if we are not excluded
1134 if self.filter and not self:filter(section) then
1138 -- Check if at least one dependency is met
1139 if #self.deps > 0 and self:cfgvalue(section) then
1142 for k, v in ipairs(self.deps) do
1143 if self:cfgvalue(section)[v.option] == v.value then
1153 return self:validate(section)
1157 -- Dummy validate function
1158 function TypedSection.validate(self, section)
1164 AbstractValue - An abstract Value Type
1165 null: Value can be empty
1166 valid: A function returning the value if it is valid otherwise nil
1167 depends: A table of option => value pairs of which one must be true
1168 default: The default value
1169 size: The size of the input fields
1170 rmempty: Unset value if empty
1171 optional: This value is optional (see AbstractSection.optionals)
1173 AbstractValue = class(Node)
1175 function AbstractValue.__init__(self, map, section, option, ...)
1176 Node.__init__(self, ...)
1177 self.section = section
1178 self.option = option
1180 self.config = map.config
1181 self.tag_invalid = {}
1182 self.tag_missing = {}
1183 self.tag_reqerror = {}
1187 --self.cast = "string"
1189 self.track_missing = false
1193 self.optional = false
1196 function AbstractValue.prepare(self)
1197 self.cast = self.cast or "string"
1200 -- Add a dependencie to another section field
1201 function AbstractValue.depends(self, field, value)
1203 if type(field) == "string" then
1210 table.insert(self.deps, {deps=deps, add=""})
1213 -- Generates the unique CBID
1214 function AbstractValue.cbid(self, section)
1215 return "cbid."..self.map.config.."."..section.."."..self.option
1218 -- Return whether this object should be created
1219 function AbstractValue.formcreated(self, section)
1220 local key = "cbi.opt."..self.config.."."..section
1221 return (self.map:formvalue(key) == self.option)
1224 -- Returns the formvalue for this object
1225 function AbstractValue.formvalue(self, section)
1226 return self.map:formvalue(self:cbid(section))
1229 function AbstractValue.additional(self, value)
1230 self.optional = value
1233 function AbstractValue.mandatory(self, value)
1234 self.rmempty = not value
1237 function AbstractValue.add_error(self, section, type, msg)
1238 self.error = self.error or { }
1239 self.error[section] = msg or type
1241 self.section.error = self.section.error or { }
1242 self.section.error[section] = self.section.error[section] or { }
1243 table.insert(self.section.error[section], msg or type)
1245 if type == "invalid" then
1246 self.tag_invalid[section] = true
1247 elseif type == "missing" then
1248 self.tag_missing[section] = true
1251 self.tag_error[section] = true
1252 self.map.save = false
1255 function AbstractValue.parse(self, section, novld)
1256 local fvalue = self:formvalue(section)
1257 local cvalue = self:cfgvalue(section)
1259 -- If favlue and cvalue are both tables and have the same content
1260 -- make them identical
1261 if type(fvalue) == "table" and type(cvalue) == "table" then
1262 local equal = #fvalue == #cvalue
1265 if cvalue[i] ~= fvalue[i] then
1275 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1277 fvalue, val_err = self:validate(fvalue, section)
1278 fvalue = self:transform(fvalue)
1280 if not fvalue and not novld then
1281 self:add_error(section, "invalid", val_err)
1284 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1285 if self:write(section, fvalue) then
1287 self.section.changed = true
1288 --luci.util.append(self.map.events, self.events)
1291 else -- Unset the UCI or error
1292 if self.rmempty or self.optional then
1293 if self:remove(section) then
1295 self.section.changed = true
1296 --luci.util.append(self.map.events, self.events)
1298 elseif cvalue ~= fvalue and not novld then
1299 -- trigger validator with nil value to get custom user error msg.
1300 local _, val_err = self:validate(nil, section)
1301 self:add_error(section, "missing", val_err)
1306 -- Render if this value exists or if it is mandatory
1307 function AbstractValue.render(self, s, scope)
1308 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1311 scope.cbid = self:cbid(s)
1312 scope.striptags = luci.util.striptags
1313 scope.pcdata = luci.util.pcdata
1315 scope.ifattr = function(cond,key,val)
1317 return string.format(
1318 ' %s="%s"', tostring(key),
1319 luci.util.pcdata(tostring( val
1321 or (type(self[key]) ~= "function" and self[key])
1329 scope.attr = function(...)
1330 return scope.ifattr( true, ... )
1333 Node.render(self, scope)
1337 -- Return the UCI value of this object
1338 function AbstractValue.cfgvalue(self, section)
1340 if self.tag_error[section] then
1341 value = self:formvalue(section)
1343 value = self.map:get(section, self.option)
1348 elseif not self.cast or self.cast == type(value) then
1350 elseif self.cast == "string" then
1351 if type(value) == "table" then
1354 elseif self.cast == "table" then
1355 return luci.util.split(value, "%s+", nil, true)
1359 -- Validate the form value
1360 function AbstractValue.validate(self, value)
1361 if self.datatype and value then
1363 local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
1367 for a in ar:gmatch("[^%s,]+") do
1374 if dt and datatypes[dt] then
1375 if type(value) == "table" then
1377 for _, v in ipairs(value) do
1378 if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
1383 if not datatypes[dt](value, unpack(args)) then
1393 AbstractValue.transform = AbstractValue.validate
1397 function AbstractValue.write(self, section, value)
1398 return self.map:set(section, self.option, value)
1402 function AbstractValue.remove(self, section)
1403 return self.map:del(section, self.option)
1410 Value - A one-line value
1411 maxlength: The maximum length
1413 Value = class(AbstractValue)
1415 function Value.__init__(self, ...)
1416 AbstractValue.__init__(self, ...)
1417 self.template = "cbi/value"
1422 function Value.reset_values(self)
1427 function Value.value(self, key, val)
1429 table.insert(self.keylist, tostring(key))
1430 table.insert(self.vallist, tostring(val))
1434 -- DummyValue - This does nothing except being there
1435 DummyValue = class(AbstractValue)
1437 function DummyValue.__init__(self, ...)
1438 AbstractValue.__init__(self, ...)
1439 self.template = "cbi/dvalue"
1443 function DummyValue.cfgvalue(self, section)
1446 if type(self.value) == "function" then
1447 value = self:value(section)
1452 value = AbstractValue.cfgvalue(self, section)
1457 function DummyValue.parse(self)
1463 Flag - A flag being enabled or disabled
1465 Flag = class(AbstractValue)
1467 function Flag.__init__(self, ...)
1468 AbstractValue.__init__(self, ...)
1469 self.template = "cbi/fvalue"
1475 -- A flag can only have two states: set or unset
1476 function Flag.parse(self, section)
1477 local fvalue = self:formvalue(section)
1480 fvalue = self.enabled
1482 fvalue = self.disabled
1485 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1486 if not(fvalue == self:cfgvalue(section)) then
1487 self:write(section, fvalue)
1490 self:remove(section)
1497 ListValue - A one-line value predefined in a list
1498 widget: The widget that will be used (select, radio)
1500 ListValue = class(AbstractValue)
1502 function ListValue.__init__(self, ...)
1503 AbstractValue.__init__(self, ...)
1504 self.template = "cbi/lvalue"
1509 self.widget = "select"
1512 function ListValue.reset_values(self)
1517 function ListValue.value(self, key, val, ...)
1518 if luci.util.contains(self.keylist, key) then
1523 table.insert(self.keylist, tostring(key))
1524 table.insert(self.vallist, tostring(val))
1526 for i, deps in ipairs({...}) do
1527 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1531 function ListValue.validate(self, val)
1532 if luci.util.contains(self.keylist, val) then
1542 MultiValue - Multiple delimited values
1543 widget: The widget that will be used (select, checkbox)
1544 delimiter: The delimiter that will separate the values (default: " ")
1546 MultiValue = class(AbstractValue)
1548 function MultiValue.__init__(self, ...)
1549 AbstractValue.__init__(self, ...)
1550 self.template = "cbi/mvalue"
1555 self.widget = "checkbox"
1556 self.delimiter = " "
1559 function MultiValue.render(self, ...)
1560 if self.widget == "select" and not self.size then
1561 self.size = #self.vallist
1564 AbstractValue.render(self, ...)
1567 function MultiValue.reset_values(self)
1572 function MultiValue.value(self, key, val)
1573 if luci.util.contains(self.keylist, key) then
1578 table.insert(self.keylist, tostring(key))
1579 table.insert(self.vallist, tostring(val))
1582 function MultiValue.valuelist(self, section)
1583 local val = self:cfgvalue(section)
1585 if not(type(val) == "string") then
1589 return luci.util.split(val, self.delimiter)
1592 function MultiValue.validate(self, val)
1593 val = (type(val) == "table") and val or {val}
1597 for i, value in ipairs(val) do
1598 if luci.util.contains(self.keylist, value) then
1599 result = result and (result .. self.delimiter .. value) or value
1607 StaticList = class(MultiValue)
1609 function StaticList.__init__(self, ...)
1610 MultiValue.__init__(self, ...)
1612 self.valuelist = self.cfgvalue
1614 if not self.override_scheme
1615 and self.map:get_scheme(self.section.sectiontype, self.option) then
1616 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1617 if self.value and vs.values and not self.override_values then
1618 for k, v in pairs(vs.values) do
1625 function StaticList.validate(self, value)
1626 value = (type(value) == "table") and value or {value}
1629 for i, v in ipairs(value) do
1630 if luci.util.contains(self.keylist, v) then
1631 table.insert(valid, v)
1638 DynamicList = class(AbstractValue)
1640 function DynamicList.__init__(self, ...)
1641 AbstractValue.__init__(self, ...)
1642 self.template = "cbi/dynlist"
1648 function DynamicList.reset_values(self)
1653 function DynamicList.value(self, key, val)
1655 table.insert(self.keylist, tostring(key))
1656 table.insert(self.vallist, tostring(val))
1659 function DynamicList.write(self, section, value)
1662 if type(value) == "table" then
1664 for _, x in ipairs(value) do
1665 if x and #x > 0 then
1669 elseif self.cast == "table" then
1671 for x in util.imatch(value) do
1678 if self.cast == "string" then
1679 value = table.concat(t, " ")
1684 return AbstractValue.write(self, section, value)
1687 function DynamicList.cfgvalue(self, section)
1688 local value = AbstractValue.cfgvalue(self, section)
1690 if type(value) == "string" then
1693 for x in value:gmatch("%S+") do
1704 function DynamicList.formvalue(self, section)
1705 local value = AbstractValue.formvalue(self, section)
1707 if type(value) == "string" then
1710 for x in value:gmatch("%S+") do
1721 TextValue - A multi-line value
1724 TextValue = class(AbstractValue)
1726 function TextValue.__init__(self, ...)
1727 AbstractValue.__init__(self, ...)
1728 self.template = "cbi/tvalue"
1734 Button = class(AbstractValue)
1736 function Button.__init__(self, ...)
1737 AbstractValue.__init__(self, ...)
1738 self.template = "cbi/button"
1739 self.inputstyle = nil
1744 FileUpload = class(AbstractValue)
1746 function FileUpload.__init__(self, ...)
1747 AbstractValue.__init__(self, ...)
1748 self.template = "cbi/upload"
1749 if not self.map.upload_fields then
1750 self.map.upload_fields = { self }
1752 self.map.upload_fields[#self.map.upload_fields+1] = self
1756 function FileUpload.formcreated(self, section)
1757 return AbstractValue.formcreated(self, section) or
1758 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1759 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1762 function FileUpload.cfgvalue(self, section)
1763 local val = AbstractValue.cfgvalue(self, section)
1764 if val and fs.access(val) then
1770 function FileUpload.formvalue(self, section)
1771 local val = AbstractValue.formvalue(self, section)
1773 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1774 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1784 function FileUpload.remove(self, section)
1785 local val = AbstractValue.formvalue(self, section)
1786 if val and fs.access(val) then fs.unlink(val) end
1787 return AbstractValue.remove(self, section)
1791 FileBrowser = class(AbstractValue)
1793 function FileBrowser.__init__(self, ...)
1794 AbstractValue.__init__(self, ...)
1795 self.template = "cbi/browser"