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 for i, config in ipairs(self.parsechain) do
308 self.uci:save(config)
310 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
311 self:_run_hooks("on_before_commit")
312 for i, config in ipairs(self.parsechain) do
313 self.uci:commit(config)
315 -- Refresh data because commit changes section names
316 self.uci:load(config)
318 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
319 if self.apply_on_parse then
320 self.uci:apply(self.parsechain)
321 self:_run_hooks("on_apply", "on_after_apply")
323 self._apply = function()
324 local cmd = self.uci:apply(self.parsechain, true)
330 Node.parse(self, true)
333 for i, config in ipairs(self.parsechain) do
334 self.uci:unload(config)
336 if type(self.commit_handler) == "function" then
337 self:commit_handler(self:submitstate())
341 if self:submitstate() then
342 if not self.save then
343 self.state = FORM_INVALID
344 elseif self.proceed then
345 self.state = FORM_PROCEED
347 self.state = self.changed and FORM_CHANGED or FORM_VALID
350 self.state = FORM_NODATA
353 return self:state_handler(self.state)
356 function Map.render(self, ...)
357 self:_run_hooks("on_init")
358 Node.render(self, ...)
360 local fp = self._apply()
363 self:_run_hooks("on_apply")
367 -- Creates a child section
368 function Map.section(self, class, ...)
369 if instanceof(class, AbstractSection) then
370 local obj = class(self, ...)
374 error("class must be a descendent of AbstractSection")
379 function Map.add(self, sectiontype)
380 return self.uci:add(self.config, sectiontype)
384 function Map.set(self, section, option, value)
386 return self.uci:set(self.config, section, option, value)
388 return self.uci:set(self.config, section, value)
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.parse(self, section, novld)
1236 local fvalue = self:formvalue(section)
1237 local cvalue = self:cfgvalue(section)
1239 -- If favlue and cvalue are both tables and have the same content
1240 -- make them identical
1241 if type(fvalue) == "table" and type(cvalue) == "table" then
1242 local equal = #fvalue == #cvalue
1245 if cvalue[i] ~= fvalue[i] then
1255 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1256 fvalue = self:transform(self:validate(fvalue, section))
1257 if not fvalue and not novld then
1259 self.error[section] = "invalid"
1261 self.error = { [section] = "invalid" }
1263 if self.section.error then
1264 table.insert(self.section.error[section], "invalid")
1266 self.section.error = {[section] = {"invalid"}}
1268 self.map.save = false
1270 if fvalue and not (fvalue == cvalue) then
1271 if self:write(section, fvalue) then
1273 self.section.changed = true
1274 --luci.util.append(self.map.events, self.events)
1277 else -- Unset the UCI or error
1278 if self.rmempty or self.optional then
1279 if self:remove(section) then
1281 self.section.changed = true
1282 --luci.util.append(self.map.events, self.events)
1284 elseif cvalue ~= fvalue and not novld then
1285 self:write(section, fvalue or "")
1287 self.error[section] = "missing"
1289 self.error = { [section] = "missing" }
1291 self.map.save = false
1296 -- Render if this value exists or if it is mandatory
1297 function AbstractValue.render(self, s, scope)
1298 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1301 scope.cbid = self:cbid(s)
1302 scope.striptags = luci.util.striptags
1303 scope.pcdata = luci.util.pcdata
1305 scope.ifattr = function(cond,key,val)
1307 return string.format(
1308 ' %s="%s"', tostring(key),
1309 luci.util.pcdata(tostring( val
1311 or (type(self[key]) ~= "function" and self[key])
1319 scope.attr = function(...)
1320 return scope.ifattr( true, ... )
1323 Node.render(self, scope)
1327 -- Return the UCI value of this object
1328 function AbstractValue.cfgvalue(self, section)
1329 local value = (self.error and self.error[section] == "invalid")
1330 and self:formvalue(section) or self.map:get(section, self.option)
1333 elseif not self.cast or self.cast == type(value) then
1335 elseif self.cast == "string" then
1336 if type(value) == "table" then
1339 elseif self.cast == "table" then
1340 return luci.util.split(value, "%s+", nil, true)
1344 -- Validate the form value
1345 function AbstractValue.validate(self, value)
1346 if self.datatype and value and datatypes[self.datatype] then
1347 if type(value) == "table" then
1349 for _, v in ipairs(value) do
1350 if v and #v > 0 and not datatypes[self.datatype](v) then
1355 if not datatypes[self.datatype](value) then
1363 AbstractValue.transform = AbstractValue.validate
1367 function AbstractValue.write(self, section, value)
1368 return self.map:set(section, self.option, value)
1372 function AbstractValue.remove(self, section)
1373 return self.map:del(section, self.option)
1380 Value - A one-line value
1381 maxlength: The maximum length
1383 Value = class(AbstractValue)
1385 function Value.__init__(self, ...)
1386 AbstractValue.__init__(self, ...)
1387 self.template = "cbi/value"
1392 function Value.value(self, key, val)
1394 table.insert(self.keylist, tostring(key))
1395 table.insert(self.vallist, tostring(val))
1399 -- DummyValue - This does nothing except being there
1400 DummyValue = class(AbstractValue)
1402 function DummyValue.__init__(self, ...)
1403 AbstractValue.__init__(self, ...)
1404 self.template = "cbi/dvalue"
1408 function DummyValue.cfgvalue(self, section)
1411 if type(self.value) == "function" then
1412 value = self:value(section)
1417 value = AbstractValue.cfgvalue(self, section)
1422 function DummyValue.parse(self)
1428 Flag - A flag being enabled or disabled
1430 Flag = class(AbstractValue)
1432 function Flag.__init__(self, ...)
1433 AbstractValue.__init__(self, ...)
1434 self.template = "cbi/fvalue"
1440 -- A flag can only have two states: set or unset
1441 function Flag.parse(self, section)
1442 local fvalue = self:formvalue(section)
1445 fvalue = self.enabled
1447 fvalue = self.disabled
1450 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1451 if not(fvalue == self:cfgvalue(section)) then
1452 self:write(section, fvalue)
1455 self:remove(section)
1462 ListValue - A one-line value predefined in a list
1463 widget: The widget that will be used (select, radio)
1465 ListValue = class(AbstractValue)
1467 function ListValue.__init__(self, ...)
1468 AbstractValue.__init__(self, ...)
1469 self.template = "cbi/lvalue"
1474 self.widget = "select"
1477 function ListValue.value(self, key, val, ...)
1478 if luci.util.contains(self.keylist, key) then
1483 table.insert(self.keylist, tostring(key))
1484 table.insert(self.vallist, tostring(val))
1486 for i, deps in ipairs({...}) do
1487 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1491 function ListValue.validate(self, val)
1492 if luci.util.contains(self.keylist, val) then
1502 MultiValue - Multiple delimited values
1503 widget: The widget that will be used (select, checkbox)
1504 delimiter: The delimiter that will separate the values (default: " ")
1506 MultiValue = class(AbstractValue)
1508 function MultiValue.__init__(self, ...)
1509 AbstractValue.__init__(self, ...)
1510 self.template = "cbi/mvalue"
1515 self.widget = "checkbox"
1516 self.delimiter = " "
1519 function MultiValue.render(self, ...)
1520 if self.widget == "select" and not self.size then
1521 self.size = #self.vallist
1524 AbstractValue.render(self, ...)
1527 function MultiValue.value(self, key, val)
1528 if luci.util.contains(self.keylist, key) then
1533 table.insert(self.keylist, tostring(key))
1534 table.insert(self.vallist, tostring(val))
1537 function MultiValue.valuelist(self, section)
1538 local val = self:cfgvalue(section)
1540 if not(type(val) == "string") then
1544 return luci.util.split(val, self.delimiter)
1547 function MultiValue.validate(self, val)
1548 val = (type(val) == "table") and val or {val}
1552 for i, value in ipairs(val) do
1553 if luci.util.contains(self.keylist, value) then
1554 result = result and (result .. self.delimiter .. value) or value
1562 StaticList = class(MultiValue)
1564 function StaticList.__init__(self, ...)
1565 MultiValue.__init__(self, ...)
1567 self.valuelist = self.cfgvalue
1569 if not self.override_scheme
1570 and self.map:get_scheme(self.section.sectiontype, self.option) then
1571 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1572 if self.value and vs.values and not self.override_values then
1573 for k, v in pairs(vs.values) do
1580 function StaticList.validate(self, value)
1581 value = (type(value) == "table") and value or {value}
1584 for i, v in ipairs(value) do
1585 if luci.util.contains(self.keylist, v) then
1586 table.insert(valid, v)
1593 DynamicList = class(AbstractValue)
1595 function DynamicList.__init__(self, ...)
1596 AbstractValue.__init__(self, ...)
1597 self.template = "cbi/dynlist"
1603 function DynamicList.value(self, key, val)
1605 table.insert(self.keylist, tostring(key))
1606 table.insert(self.vallist, tostring(val))
1609 function DynamicList.write(self, ...)
1610 self.map.proceed = true
1611 return AbstractValue.write(self, ...)
1614 function DynamicList.formvalue(self, section)
1615 local value = AbstractValue.formvalue(self, section)
1616 value = (type(value) == "table") and value or {value}
1619 for i, v in ipairs(value) do
1621 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1622 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1623 table.insert(valid, v)
1632 TextValue - A multi-line value
1635 TextValue = class(AbstractValue)
1637 function TextValue.__init__(self, ...)
1638 AbstractValue.__init__(self, ...)
1639 self.template = "cbi/tvalue"
1645 Button = class(AbstractValue)
1647 function Button.__init__(self, ...)
1648 AbstractValue.__init__(self, ...)
1649 self.template = "cbi/button"
1650 self.inputstyle = nil
1655 FileUpload = class(AbstractValue)
1657 function FileUpload.__init__(self, ...)
1658 AbstractValue.__init__(self, ...)
1659 self.template = "cbi/upload"
1660 if not self.map.upload_fields then
1661 self.map.upload_fields = { self }
1663 self.map.upload_fields[#self.map.upload_fields+1] = self
1667 function FileUpload.formcreated(self, section)
1668 return AbstractValue.formcreated(self, section) or
1669 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1670 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1673 function FileUpload.cfgvalue(self, section)
1674 local val = AbstractValue.cfgvalue(self, section)
1675 if val and fs.access(val) then
1681 function FileUpload.formvalue(self, section)
1682 local val = AbstractValue.formvalue(self, section)
1684 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1685 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1695 function FileUpload.remove(self, section)
1696 local val = AbstractValue.formvalue(self, section)
1697 if val and fs.access(val) then fs.unlink(val) end
1698 return AbstractValue.remove(self, section)
1702 FileBrowser = class(AbstractValue)
1704 function FileBrowser.__init__(self, ...)
1705 AbstractValue.__init__(self, ...)
1706 self.template = "cbi/browser"