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 and datatypes[self.datatype] then
1362 if type(value) == "table" then
1364 for _, v in ipairs(value) do
1365 if v and #v > 0 and not datatypes[self.datatype](v) then
1370 if not datatypes[self.datatype](value) then
1378 AbstractValue.transform = AbstractValue.validate
1382 function AbstractValue.write(self, section, value)
1383 return self.map:set(section, self.option, value)
1387 function AbstractValue.remove(self, section)
1388 return self.map:del(section, self.option)
1395 Value - A one-line value
1396 maxlength: The maximum length
1398 Value = class(AbstractValue)
1400 function Value.__init__(self, ...)
1401 AbstractValue.__init__(self, ...)
1402 self.template = "cbi/value"
1407 function Value.reset_values(self)
1412 function Value.value(self, key, val)
1414 table.insert(self.keylist, tostring(key))
1415 table.insert(self.vallist, tostring(val))
1419 -- DummyValue - This does nothing except being there
1420 DummyValue = class(AbstractValue)
1422 function DummyValue.__init__(self, ...)
1423 AbstractValue.__init__(self, ...)
1424 self.template = "cbi/dvalue"
1428 function DummyValue.cfgvalue(self, section)
1431 if type(self.value) == "function" then
1432 value = self:value(section)
1437 value = AbstractValue.cfgvalue(self, section)
1442 function DummyValue.parse(self)
1448 Flag - A flag being enabled or disabled
1450 Flag = class(AbstractValue)
1452 function Flag.__init__(self, ...)
1453 AbstractValue.__init__(self, ...)
1454 self.template = "cbi/fvalue"
1460 -- A flag can only have two states: set or unset
1461 function Flag.parse(self, section)
1462 local fvalue = self:formvalue(section)
1465 fvalue = self.enabled
1467 fvalue = self.disabled
1470 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1471 if not(fvalue == self:cfgvalue(section)) then
1472 self:write(section, fvalue)
1475 self:remove(section)
1482 ListValue - A one-line value predefined in a list
1483 widget: The widget that will be used (select, radio)
1485 ListValue = class(AbstractValue)
1487 function ListValue.__init__(self, ...)
1488 AbstractValue.__init__(self, ...)
1489 self.template = "cbi/lvalue"
1494 self.widget = "select"
1497 function ListValue.reset_values(self)
1502 function ListValue.value(self, key, val, ...)
1503 if luci.util.contains(self.keylist, key) then
1508 table.insert(self.keylist, tostring(key))
1509 table.insert(self.vallist, tostring(val))
1511 for i, deps in ipairs({...}) do
1512 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1516 function ListValue.validate(self, val)
1517 if luci.util.contains(self.keylist, val) then
1527 MultiValue - Multiple delimited values
1528 widget: The widget that will be used (select, checkbox)
1529 delimiter: The delimiter that will separate the values (default: " ")
1531 MultiValue = class(AbstractValue)
1533 function MultiValue.__init__(self, ...)
1534 AbstractValue.__init__(self, ...)
1535 self.template = "cbi/mvalue"
1540 self.widget = "checkbox"
1541 self.delimiter = " "
1544 function MultiValue.render(self, ...)
1545 if self.widget == "select" and not self.size then
1546 self.size = #self.vallist
1549 AbstractValue.render(self, ...)
1552 function MultiValue.reset_values(self)
1557 function MultiValue.value(self, key, val)
1558 if luci.util.contains(self.keylist, key) then
1563 table.insert(self.keylist, tostring(key))
1564 table.insert(self.vallist, tostring(val))
1567 function MultiValue.valuelist(self, section)
1568 local val = self:cfgvalue(section)
1570 if not(type(val) == "string") then
1574 return luci.util.split(val, self.delimiter)
1577 function MultiValue.validate(self, val)
1578 val = (type(val) == "table") and val or {val}
1582 for i, value in ipairs(val) do
1583 if luci.util.contains(self.keylist, value) then
1584 result = result and (result .. self.delimiter .. value) or value
1592 StaticList = class(MultiValue)
1594 function StaticList.__init__(self, ...)
1595 MultiValue.__init__(self, ...)
1597 self.valuelist = self.cfgvalue
1599 if not self.override_scheme
1600 and self.map:get_scheme(self.section.sectiontype, self.option) then
1601 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1602 if self.value and vs.values and not self.override_values then
1603 for k, v in pairs(vs.values) do
1610 function StaticList.validate(self, value)
1611 value = (type(value) == "table") and value or {value}
1614 for i, v in ipairs(value) do
1615 if luci.util.contains(self.keylist, v) then
1616 table.insert(valid, v)
1623 DynamicList = class(AbstractValue)
1625 function DynamicList.__init__(self, ...)
1626 AbstractValue.__init__(self, ...)
1627 self.template = "cbi/dynlist"
1633 function DynamicList.reset_values(self)
1638 function DynamicList.value(self, key, val)
1640 table.insert(self.keylist, tostring(key))
1641 table.insert(self.vallist, tostring(val))
1644 function DynamicList.write(self, section, value)
1645 if self.cast == "string" and type(value) == "table" then
1646 value = table.concat(value, " ")
1647 elseif self.cast == "table" and type(value) == "string" then
1649 for x in value:gmatch("%S+") do
1655 return AbstractValue.write(self, section, value)
1658 function DynamicList.cfgvalue(self, section)
1659 local value = AbstractValue.cfgvalue(self, section)
1661 if type(value) == "string" then
1664 for x in value:gmatch("%S+") do
1673 function DynamicList.formvalue(self, section)
1674 local value = AbstractValue.formvalue(self, section)
1676 if type(value) == "string" then
1679 for x in value:gmatch("%S+") do
1690 TextValue - A multi-line value
1693 TextValue = class(AbstractValue)
1695 function TextValue.__init__(self, ...)
1696 AbstractValue.__init__(self, ...)
1697 self.template = "cbi/tvalue"
1703 Button = class(AbstractValue)
1705 function Button.__init__(self, ...)
1706 AbstractValue.__init__(self, ...)
1707 self.template = "cbi/button"
1708 self.inputstyle = nil
1713 FileUpload = class(AbstractValue)
1715 function FileUpload.__init__(self, ...)
1716 AbstractValue.__init__(self, ...)
1717 self.template = "cbi/upload"
1718 if not self.map.upload_fields then
1719 self.map.upload_fields = { self }
1721 self.map.upload_fields[#self.map.upload_fields+1] = self
1725 function FileUpload.formcreated(self, section)
1726 return AbstractValue.formcreated(self, section) or
1727 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1728 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1731 function FileUpload.cfgvalue(self, section)
1732 local val = AbstractValue.cfgvalue(self, section)
1733 if val and fs.access(val) then
1739 function FileUpload.formvalue(self, section)
1740 local val = AbstractValue.formvalue(self, section)
1742 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1743 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1753 function FileUpload.remove(self, section)
1754 local val = AbstractValue.formvalue(self, section)
1755 if val and fs.access(val) then fs.unlink(val) end
1756 return AbstractValue.remove(self, section)
1760 FileBrowser = class(AbstractValue)
1762 function FileBrowser.__init__(self, ...)
1763 AbstractValue.__init__(self, ...)
1764 self.template = "cbi/browser"