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 -- This is evaluated by the dispatcher and delegated to the
326 -- template which in turn fires XHR to perform the actual
328 self.apply_needed = 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, ...)
363 -- Creates a child section
364 function Map.section(self, class, ...)
365 if instanceof(class, AbstractSection) then
366 local obj = class(self, ...)
370 error("class must be a descendent of AbstractSection")
375 function Map.add(self, sectiontype)
376 return self.uci:add(self.config, sectiontype)
380 function Map.set(self, section, option, value)
381 if type(value) ~= "table" or #value > 0 then
383 return self.uci:set(self.config, section, option, value)
385 return self.uci:set(self.config, section, value)
388 return Map.del(self, section, option)
393 function Map.del(self, section, option)
395 return self.uci:delete(self.config, section, option)
397 return self.uci:delete(self.config, section)
402 function Map.get(self, section, option)
404 return self.uci:get_all(self.config)
406 return self.uci:get(self.config, section, option)
408 return self.uci:get_all(self.config, section)
415 Compound = class(Node)
417 function Compound.__init__(self, ...)
419 self.template = "cbi/compound"
420 self.children = {...}
423 function Compound.populate_delegator(self, delegator)
424 for _, v in ipairs(self.children) do
425 v.delegator = delegator
429 function Compound.parse(self, ...)
430 local cstate, state = 0
432 for k, child in ipairs(self.children) do
433 cstate = child:parse(...)
434 state = (not state or cstate < state) and cstate or state
442 Delegator - Node controller
444 Delegator = class(Node)
445 function Delegator.__init__(self, ...)
446 Node.__init__(self, ...)
448 self.defaultpath = {}
449 self.pageaction = false
450 self.readinput = true
451 self.allow_reset = false
452 self.allow_cancel = false
453 self.allow_back = false
454 self.allow_finish = false
455 self.template = "cbi/delegator"
458 function Delegator.set(self, name, node)
459 assert(not self.nodes[name], "Duplicate entry")
461 self.nodes[name] = node
464 function Delegator.add(self, name, node)
465 node = self:set(name, node)
466 self.defaultpath[#self.defaultpath+1] = name
469 function Delegator.insert_after(self, name, after)
470 local n = #self.chain + 1
471 for k, v in ipairs(self.chain) do
477 table.insert(self.chain, n, name)
480 function Delegator.set_route(self, ...)
481 local n, chain, route = 0, self.chain, {...}
483 if chain[i] == self.current then
492 for i = n + 1, #chain do
497 function Delegator.get(self, name)
498 local node = self.nodes[name]
500 if type(node) == "string" then
501 node = load(node, name)
504 if type(node) == "table" and getmetatable(node) == nil then
505 node = Compound(unpack(node))
511 function Delegator.parse(self, ...)
512 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
513 if self:_run_hooks("on_cancel") then
518 if not Map.formvalue(self, "cbi.delg.current") then
519 self:_run_hooks("on_init")
523 self.chain = self.chain or self:get_chain()
524 self.current = self.current or self:get_active()
525 self.active = self.active or self:get(self.current)
526 assert(self.active, "Invalid state")
528 local stat = FORM_DONE
529 if type(self.active) ~= "function" then
530 self.active:populate_delegator(self)
531 stat = self.active:parse()
536 if stat > FORM_PROCEED then
537 if Map.formvalue(self, "cbi.delg.back") then
538 newcurrent = self:get_prev(self.current)
540 newcurrent = self:get_next(self.current)
542 elseif stat < FORM_PROCEED then
547 if not Map.formvalue(self, "cbi.submit") then
549 elseif stat > FORM_PROCEED
550 and (not newcurrent or not self:get(newcurrent)) then
551 return self:_run_hook("on_done") or FORM_DONE
553 self.current = newcurrent or self.current
554 self.active = self:get(self.current)
555 if type(self.active) ~= "function" then
556 self.active:populate_delegator(self)
557 local stat = self.active:parse(false)
558 if stat == FORM_SKIP then
559 return self:parse(...)
564 return self:parse(...)
569 function Delegator.get_next(self, state)
570 for k, v in ipairs(self.chain) do
572 return self.chain[k+1]
577 function Delegator.get_prev(self, state)
578 for k, v in ipairs(self.chain) do
580 return self.chain[k-1]
585 function Delegator.get_chain(self)
586 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
587 return type(x) == "table" and x or {x}
590 function Delegator.get_active(self)
591 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
599 Page.__init__ = Node.__init__
600 Page.parse = function() end
604 SimpleForm - A Simple non-UCI form
606 SimpleForm = class(Node)
608 function SimpleForm.__init__(self, config, title, description, data)
609 Node.__init__(self, title, description)
611 self.data = data or {}
612 self.template = "cbi/simpleform"
614 self.pageaction = false
615 self.readinput = true
618 SimpleForm.formvalue = Map.formvalue
619 SimpleForm.formvaluetable = Map.formvaluetable
621 function SimpleForm.parse(self, readinput, ...)
622 self.readinput = (readinput ~= false)
624 if self:formvalue("cbi.skip") then
628 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
632 if self:submitstate() then
633 Node.parse(self, 1, ...)
637 for k, j in ipairs(self.children) do
638 for i, v in ipairs(j.children) do
640 and (not v.tag_missing or not v.tag_missing[1])
641 and (not v.tag_invalid or not v.tag_invalid[1])
647 not self:submitstate() and FORM_NODATA
648 or valid and FORM_VALID
651 self.dorender = not self.handle
653 local nrender, nstate = self:handle(state, self.data)
654 self.dorender = self.dorender or (nrender ~= false)
655 state = nstate or state
660 function SimpleForm.render(self, ...)
661 if self.dorender then
662 Node.render(self, ...)
666 function SimpleForm.submitstate(self)
667 return self:formvalue("cbi.submit")
670 function SimpleForm.section(self, class, ...)
671 if instanceof(class, AbstractSection) then
672 local obj = class(self, ...)
676 error("class must be a descendent of AbstractSection")
680 -- Creates a child field
681 function SimpleForm.field(self, class, ...)
683 for k, v in ipairs(self.children) do
684 if instanceof(v, SimpleSection) then
690 section = self:section(SimpleSection)
693 if instanceof(class, AbstractValue) then
694 local obj = class(self, section, ...)
695 obj.track_missing = true
699 error("class must be a descendent of AbstractValue")
703 function SimpleForm.set(self, section, option, value)
704 self.data[option] = value
708 function SimpleForm.del(self, section, option)
709 self.data[option] = nil
713 function SimpleForm.get(self, section, option)
714 return self.data[option]
718 function SimpleForm.get_scheme()
723 Form = class(SimpleForm)
725 function Form.__init__(self, ...)
726 SimpleForm.__init__(self, ...)
734 AbstractSection = class(Node)
736 function AbstractSection.__init__(self, map, sectiontype, ...)
737 Node.__init__(self, ...)
738 self.sectiontype = sectiontype
740 self.config = map.config
745 self.tag_invalid = {}
746 self.tag_deperror = {}
750 self.addremove = false
754 -- Define a tab for the section
755 function AbstractSection.tab(self, tab, title, desc)
756 self.tabs = self.tabs or { }
757 self.tab_names = self.tab_names or { }
759 self.tab_names[#self.tab_names+1] = tab
767 -- Check whether the section has tabs
768 function AbstractSection.has_tabs(self)
769 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
772 -- Appends a new option
773 function AbstractSection.option(self, class, option, ...)
774 if instanceof(class, AbstractValue) then
775 local obj = class(self.map, self, option, ...)
777 self.fields[option] = obj
779 elseif class == true then
780 error("No valid class was given and autodetection failed.")
782 error("class must be a descendant of AbstractValue")
786 -- Appends a new tabbed option
787 function AbstractSection.taboption(self, tab, ...)
789 assert(tab and self.tabs and self.tabs[tab],
790 "Cannot assign option to not existing tab %q" % tostring(tab))
792 local l = self.tabs[tab].childs
793 local o = AbstractSection.option(self, ...)
795 if o then l[#l+1] = o end
800 -- Render a single tab
801 function AbstractSection.render_tab(self, tab, ...)
803 assert(tab and self.tabs and self.tabs[tab],
804 "Cannot render not existing tab %q" % tostring(tab))
806 for _, node in ipairs(self.tabs[tab].childs) do
811 -- Parse optional options
812 function AbstractSection.parse_optionals(self, section)
813 if not self.optional then
817 self.optionals[section] = {}
819 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
820 for k,v in ipairs(self.children) do
821 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
822 if field == v.option then
824 self.map.proceed = true
826 table.insert(self.optionals[section], v)
831 if field and #field > 0 and self.dynamic then
832 self:add_dynamic(field)
836 -- Add a dynamic option
837 function AbstractSection.add_dynamic(self, field, optional)
838 local o = self:option(Value, field, field)
839 o.optional = optional
842 -- Parse all dynamic options
843 function AbstractSection.parse_dynamic(self, section)
844 if not self.dynamic then
848 local arr = luci.util.clone(self:cfgvalue(section))
849 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
850 for k, v in pairs(form) do
854 for key,val in pairs(arr) do
857 for i,c in ipairs(self.children) do
858 if c.option == key then
863 if create and key:sub(1, 1) ~= "." then
864 self.map.proceed = true
865 self:add_dynamic(key, true)
870 -- Returns the section's UCI table
871 function AbstractSection.cfgvalue(self, section)
872 return self.map:get(section)
876 function AbstractSection.push_events(self)
877 --luci.util.append(self.map.events, self.events)
878 self.map.changed = true
881 -- Removes the section
882 function AbstractSection.remove(self, section)
883 self.map.proceed = true
884 return self.map:del(section)
887 -- Creates the section
888 function AbstractSection.create(self, section)
892 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
894 section = self.map:add(self.sectiontype)
899 for k,v in pairs(self.children) do
901 self.map:set(section, v.option, v.default)
905 for k,v in pairs(self.defaults) do
906 self.map:set(section, k, v)
910 self.map.proceed = true
916 SimpleSection = class(AbstractSection)
918 function SimpleSection.__init__(self, form, ...)
919 AbstractSection.__init__(self, form, nil, ...)
920 self.template = "cbi/nullsection"
924 Table = class(AbstractSection)
926 function Table.__init__(self, form, data, ...)
927 local datasource = {}
929 datasource.config = "table"
930 self.data = data or {}
932 datasource.formvalue = Map.formvalue
933 datasource.formvaluetable = Map.formvaluetable
934 datasource.readinput = true
936 function datasource.get(self, section, option)
937 return tself.data[section] and tself.data[section][option]
940 function datasource.submitstate(self)
941 return Map.formvalue(self, "cbi.submit")
944 function datasource.del(...)
948 function datasource.get_scheme()
952 AbstractSection.__init__(self, datasource, "table", ...)
953 self.template = "cbi/tblsection"
954 self.rowcolors = true
955 self.anonymous = true
958 function Table.parse(self, readinput)
959 self.map.readinput = (readinput ~= false)
960 for i, k in ipairs(self:cfgsections()) do
961 if self.map:submitstate() then
967 function Table.cfgsections(self)
970 for i, v in luci.util.kspairs(self.data) do
971 table.insert(sections, i)
977 function Table.update(self, data)
984 NamedSection - A fixed configuration section defined by its name
986 NamedSection = class(AbstractSection)
988 function NamedSection.__init__(self, map, section, stype, ...)
989 AbstractSection.__init__(self, map, stype, ...)
992 self.addremove = false
993 self.template = "cbi/nsection"
994 self.section = section
997 function NamedSection.parse(self, novld)
998 local s = self.section
999 local active = self:cfgvalue(s)
1001 if self.addremove then
1002 local path = self.config.."."..s
1003 if active then -- Remove the section
1004 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1008 else -- Create and apply default values
1009 if self.map:formvalue("cbi.cns."..path) then
1017 AbstractSection.parse_dynamic(self, s)
1018 if self.map:submitstate() then
1021 AbstractSection.parse_optionals(self, s)
1023 if self.changed then
1031 TypedSection - A (set of) configuration section(s) defined by the type
1032 addremove: Defines whether the user can add/remove sections of this type
1033 anonymous: Allow creating anonymous sections
1034 validate: a validation function returning nil if the section is invalid
1036 TypedSection = class(AbstractSection)
1038 function TypedSection.__init__(self, map, type, ...)
1039 AbstractSection.__init__(self, map, type, ...)
1041 self.template = "cbi/tsection"
1043 self.anonymous = false
1046 -- Return all matching UCI sections for this TypedSection
1047 function TypedSection.cfgsections(self)
1049 self.map.uci:foreach(self.map.config, self.sectiontype,
1051 if self:checkscope(section[".name"]) then
1052 table.insert(sections, section[".name"])
1059 -- Limits scope to sections that have certain option => value pairs
1060 function TypedSection.depends(self, option, value)
1061 table.insert(self.deps, {option=option, value=value})
1064 function TypedSection.parse(self, novld)
1065 if self.addremove then
1067 local crval = REMOVE_PREFIX .. self.config
1068 local name = self.map:formvaluetable(crval)
1069 for k,v in pairs(name) do
1070 if k:sub(-2) == ".x" then
1071 k = k:sub(1, #k - 2)
1073 if self:cfgvalue(k) and self:checkscope(k) then
1080 for i, k in ipairs(self:cfgsections()) do
1081 AbstractSection.parse_dynamic(self, k)
1082 if self.map:submitstate() then
1083 Node.parse(self, k, novld)
1085 AbstractSection.parse_optionals(self, k)
1088 if self.addremove then
1091 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1092 local name = self.map:formvalue(crval)
1093 if self.anonymous then
1095 created = self:create()
1099 -- Ignore if it already exists
1100 if self:cfgvalue(name) then
1104 name = self:checkscope(name)
1107 self.err_invalid = true
1110 if name and #name > 0 then
1111 created = self:create(name) and name
1113 self.invalid_cts = true
1120 AbstractSection.parse_optionals(self, created)
1124 if created or self.changed then
1129 -- Verifies scope of sections
1130 function TypedSection.checkscope(self, section)
1131 -- Check if we are not excluded
1132 if self.filter and not self:filter(section) then
1136 -- Check if at least one dependency is met
1137 if #self.deps > 0 and self:cfgvalue(section) then
1140 for k, v in ipairs(self.deps) do
1141 if self:cfgvalue(section)[v.option] == v.value then
1151 return self:validate(section)
1155 -- Dummy validate function
1156 function TypedSection.validate(self, section)
1162 AbstractValue - An abstract Value Type
1163 null: Value can be empty
1164 valid: A function returning the value if it is valid otherwise nil
1165 depends: A table of option => value pairs of which one must be true
1166 default: The default value
1167 size: The size of the input fields
1168 rmempty: Unset value if empty
1169 optional: This value is optional (see AbstractSection.optionals)
1171 AbstractValue = class(Node)
1173 function AbstractValue.__init__(self, map, section, option, ...)
1174 Node.__init__(self, ...)
1175 self.section = section
1176 self.option = option
1178 self.config = map.config
1179 self.tag_invalid = {}
1180 self.tag_missing = {}
1181 self.tag_reqerror = {}
1185 --self.cast = "string"
1187 self.track_missing = false
1191 self.optional = false
1194 function AbstractValue.prepare(self)
1195 self.cast = self.cast or "string"
1198 -- Add a dependencie to another section field
1199 function AbstractValue.depends(self, field, value)
1201 if type(field) == "string" then
1208 table.insert(self.deps, {deps=deps, add=""})
1211 -- Generates the unique CBID
1212 function AbstractValue.cbid(self, section)
1213 return "cbid."..self.map.config.."."..section.."."..self.option
1216 -- Return whether this object should be created
1217 function AbstractValue.formcreated(self, section)
1218 local key = "cbi.opt."..self.config.."."..section
1219 return (self.map:formvalue(key) == self.option)
1222 -- Returns the formvalue for this object
1223 function AbstractValue.formvalue(self, section)
1224 return self.map:formvalue(self:cbid(section))
1227 function AbstractValue.additional(self, value)
1228 self.optional = value
1231 function AbstractValue.mandatory(self, value)
1232 self.rmempty = not value
1235 function AbstractValue.add_error(self, section, type, msg)
1236 self.error = self.error or { }
1237 self.error[section] = msg or type
1239 self.section.error = self.section.error or { }
1240 self.section.error[section] = self.section.error[section] or { }
1241 table.insert(self.section.error[section], msg or type)
1243 if type == "invalid" then
1244 self.tag_invalid[section] = true
1245 elseif type == "missing" then
1246 self.tag_missing[section] = true
1249 self.tag_error[section] = true
1250 self.map.save = false
1253 function AbstractValue.parse(self, section, novld)
1254 local fvalue = self:formvalue(section)
1255 local cvalue = self:cfgvalue(section)
1257 -- If favlue and cvalue are both tables and have the same content
1258 -- make them identical
1259 if type(fvalue) == "table" and type(cvalue) == "table" then
1260 local equal = #fvalue == #cvalue
1263 if cvalue[i] ~= fvalue[i] then
1273 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1275 fvalue, val_err = self:validate(fvalue, section)
1276 fvalue = self:transform(fvalue)
1278 if not fvalue and not novld then
1279 self:add_error(section, "invalid", val_err)
1282 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1283 if self:write(section, fvalue) then
1285 self.section.changed = true
1286 --luci.util.append(self.map.events, self.events)
1289 else -- Unset the UCI or error
1290 if self.rmempty or self.optional then
1291 if self:remove(section) then
1293 self.section.changed = true
1294 --luci.util.append(self.map.events, self.events)
1296 elseif cvalue ~= fvalue and not novld then
1297 -- trigger validator with nil value to get custom user error msg.
1298 local _, val_err = self:validate(nil, section)
1299 self:add_error(section, "missing", val_err)
1304 -- Render if this value exists or if it is mandatory
1305 function AbstractValue.render(self, s, scope)
1306 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1309 scope.cbid = self:cbid(s)
1310 scope.striptags = luci.util.striptags
1311 scope.pcdata = luci.util.pcdata
1313 scope.ifattr = function(cond,key,val)
1315 return string.format(
1316 ' %s="%s"', tostring(key),
1317 luci.util.pcdata(tostring( val
1319 or (type(self[key]) ~= "function" and self[key])
1327 scope.attr = function(...)
1328 return scope.ifattr( true, ... )
1331 Node.render(self, scope)
1335 -- Return the UCI value of this object
1336 function AbstractValue.cfgvalue(self, section)
1338 if self.tag_error[section] then
1339 value = self:formvalue(section)
1341 value = self.map:get(section, self.option)
1346 elseif not self.cast or self.cast == type(value) then
1348 elseif self.cast == "string" then
1349 if type(value) == "table" then
1352 elseif self.cast == "table" then
1357 -- Validate the form value
1358 function AbstractValue.validate(self, value)
1359 if self.datatype and value then
1361 local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
1365 for a in ar:gmatch("[^%s,]+") do
1372 if dt and datatypes[dt] then
1373 if type(value) == "table" then
1375 for _, v in ipairs(value) do
1376 if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
1381 if not datatypes[dt](value, unpack(args)) then
1391 AbstractValue.transform = AbstractValue.validate
1395 function AbstractValue.write(self, section, value)
1396 return self.map:set(section, self.option, value)
1400 function AbstractValue.remove(self, section)
1401 return self.map:del(section, self.option)
1408 Value - A one-line value
1409 maxlength: The maximum length
1411 Value = class(AbstractValue)
1413 function Value.__init__(self, ...)
1414 AbstractValue.__init__(self, ...)
1415 self.template = "cbi/value"
1420 function Value.reset_values(self)
1425 function Value.value(self, key, val)
1427 table.insert(self.keylist, tostring(key))
1428 table.insert(self.vallist, tostring(val))
1432 -- DummyValue - This does nothing except being there
1433 DummyValue = class(AbstractValue)
1435 function DummyValue.__init__(self, ...)
1436 AbstractValue.__init__(self, ...)
1437 self.template = "cbi/dvalue"
1441 function DummyValue.cfgvalue(self, section)
1444 if type(self.value) == "function" then
1445 value = self:value(section)
1450 value = AbstractValue.cfgvalue(self, section)
1455 function DummyValue.parse(self)
1461 Flag - A flag being enabled or disabled
1463 Flag = class(AbstractValue)
1465 function Flag.__init__(self, ...)
1466 AbstractValue.__init__(self, ...)
1467 self.template = "cbi/fvalue"
1473 -- A flag can only have two states: set or unset
1474 function Flag.parse(self, section)
1475 local fvalue = self:formvalue(section)
1478 fvalue = self.enabled
1480 fvalue = self.disabled
1483 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1484 if not(fvalue == self:cfgvalue(section)) then
1485 self:write(section, fvalue)
1488 self:remove(section)
1495 ListValue - A one-line value predefined in a list
1496 widget: The widget that will be used (select, radio)
1498 ListValue = class(AbstractValue)
1500 function ListValue.__init__(self, ...)
1501 AbstractValue.__init__(self, ...)
1502 self.template = "cbi/lvalue"
1507 self.widget = "select"
1510 function ListValue.reset_values(self)
1515 function ListValue.value(self, key, val, ...)
1516 if luci.util.contains(self.keylist, key) then
1521 table.insert(self.keylist, tostring(key))
1522 table.insert(self.vallist, tostring(val))
1524 for i, deps in ipairs({...}) do
1525 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1529 function ListValue.validate(self, val)
1530 if luci.util.contains(self.keylist, val) then
1540 MultiValue - Multiple delimited values
1541 widget: The widget that will be used (select, checkbox)
1542 delimiter: The delimiter that will separate the values (default: " ")
1544 MultiValue = class(AbstractValue)
1546 function MultiValue.__init__(self, ...)
1547 AbstractValue.__init__(self, ...)
1548 self.template = "cbi/mvalue"
1553 self.widget = "checkbox"
1554 self.delimiter = " "
1557 function MultiValue.render(self, ...)
1558 if self.widget == "select" and not self.size then
1559 self.size = #self.vallist
1562 AbstractValue.render(self, ...)
1565 function MultiValue.reset_values(self)
1570 function MultiValue.value(self, key, val)
1571 if luci.util.contains(self.keylist, key) then
1576 table.insert(self.keylist, tostring(key))
1577 table.insert(self.vallist, tostring(val))
1580 function MultiValue.valuelist(self, section)
1581 local val = self:cfgvalue(section)
1583 if not(type(val) == "string") then
1587 return luci.util.split(val, self.delimiter)
1590 function MultiValue.validate(self, val)
1591 val = (type(val) == "table") and val or {val}
1595 for i, value in ipairs(val) do
1596 if luci.util.contains(self.keylist, value) then
1597 result = result and (result .. self.delimiter .. value) or value
1605 StaticList = class(MultiValue)
1607 function StaticList.__init__(self, ...)
1608 MultiValue.__init__(self, ...)
1610 self.valuelist = self.cfgvalue
1612 if not self.override_scheme
1613 and self.map:get_scheme(self.section.sectiontype, self.option) then
1614 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1615 if self.value and vs.values and not self.override_values then
1616 for k, v in pairs(vs.values) do
1623 function StaticList.validate(self, value)
1624 value = (type(value) == "table") and value or {value}
1627 for i, v in ipairs(value) do
1628 if luci.util.contains(self.keylist, v) then
1629 table.insert(valid, v)
1636 DynamicList = class(AbstractValue)
1638 function DynamicList.__init__(self, ...)
1639 AbstractValue.__init__(self, ...)
1640 self.template = "cbi/dynlist"
1646 function DynamicList.reset_values(self)
1651 function DynamicList.value(self, key, val)
1653 table.insert(self.keylist, tostring(key))
1654 table.insert(self.vallist, tostring(val))
1657 function DynamicList.write(self, section, value)
1660 if type(value) == "table" then
1662 for _, x in ipairs(value) do
1663 if x and #x > 0 then
1671 if self.cast == "string" then
1672 value = table.concat(t, " ")
1677 return AbstractValue.write(self, section, value)
1680 function DynamicList.cfgvalue(self, section)
1681 local value = AbstractValue.cfgvalue(self, section)
1683 if type(value) == "string" then
1686 for x in value:gmatch("%S+") do
1697 function DynamicList.formvalue(self, section)
1698 local value = AbstractValue.formvalue(self, section)
1700 if type(value) == "string" then
1701 if self.cast == "string" then
1704 for x in value:gmatch("%S+") do
1718 TextValue - A multi-line value
1721 TextValue = class(AbstractValue)
1723 function TextValue.__init__(self, ...)
1724 AbstractValue.__init__(self, ...)
1725 self.template = "cbi/tvalue"
1731 Button = class(AbstractValue)
1733 function Button.__init__(self, ...)
1734 AbstractValue.__init__(self, ...)
1735 self.template = "cbi/button"
1736 self.inputstyle = nil
1741 FileUpload = class(AbstractValue)
1743 function FileUpload.__init__(self, ...)
1744 AbstractValue.__init__(self, ...)
1745 self.template = "cbi/upload"
1746 if not self.map.upload_fields then
1747 self.map.upload_fields = { self }
1749 self.map.upload_fields[#self.map.upload_fields+1] = self
1753 function FileUpload.formcreated(self, section)
1754 return AbstractValue.formcreated(self, section) or
1755 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1756 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1759 function FileUpload.cfgvalue(self, section)
1760 local val = AbstractValue.cfgvalue(self, section)
1761 if val and fs.access(val) then
1767 function FileUpload.formvalue(self, section)
1768 local val = AbstractValue.formvalue(self, section)
1770 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1771 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1781 function FileUpload.remove(self, section)
1782 local val = AbstractValue.formvalue(self, section)
1783 if val and fs.access(val) then fs.unlink(val) end
1784 return AbstractValue.remove(self, section)
1788 FileBrowser = class(AbstractValue)
1790 function FileBrowser.__init__(self, ...)
1791 AbstractValue.__init__(self, ...)
1792 self.template = "cbi/browser"