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, ...)
359 if false and self._apply then
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
1257 fvalue, val_err = self:validate(fvalue, section)
1258 fvalue = self:transform(fvalue)
1260 if not fvalue and not novld then
1261 val_err = val_err or "invalid"
1263 self.error = self.error or { }
1264 self.error[section] = val_err
1266 self.section.error = self.section.error or { }
1267 self.section.error[section] = self.section.error[section] or { }
1268 table.insert(self.section.error[section], val_err)
1270 self.map.save = false
1272 if fvalue and not (fvalue == cvalue) then
1273 if self:write(section, fvalue) then
1275 self.section.changed = true
1276 --luci.util.append(self.map.events, self.events)
1279 else -- Unset the UCI or error
1280 if self.rmempty or self.optional then
1281 if self:remove(section) then
1283 self.section.changed = true
1284 --luci.util.append(self.map.events, self.events)
1286 elseif cvalue ~= fvalue and not novld then
1287 self:write(section, fvalue or "")
1289 self.error[section] = "missing"
1291 self.error = { [section] = "missing" }
1293 self.map.save = false
1298 -- Render if this value exists or if it is mandatory
1299 function AbstractValue.render(self, s, scope)
1300 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1303 scope.cbid = self:cbid(s)
1304 scope.striptags = luci.util.striptags
1305 scope.pcdata = luci.util.pcdata
1307 scope.ifattr = function(cond,key,val)
1309 return string.format(
1310 ' %s="%s"', tostring(key),
1311 luci.util.pcdata(tostring( val
1313 or (type(self[key]) ~= "function" and self[key])
1321 scope.attr = function(...)
1322 return scope.ifattr( true, ... )
1325 Node.render(self, scope)
1329 -- Return the UCI value of this object
1330 function AbstractValue.cfgvalue(self, section)
1331 local value = (self.error and self.error[section] == "invalid")
1332 and self:formvalue(section) or self.map:get(section, self.option)
1335 elseif not self.cast or self.cast == type(value) then
1337 elseif self.cast == "string" then
1338 if type(value) == "table" then
1341 elseif self.cast == "table" then
1342 return luci.util.split(value, "%s+", nil, true)
1346 -- Validate the form value
1347 function AbstractValue.validate(self, value)
1348 if self.datatype and value and datatypes[self.datatype] then
1349 if type(value) == "table" then
1351 for _, v in ipairs(value) do
1352 if v and #v > 0 and not datatypes[self.datatype](v) then
1357 if not datatypes[self.datatype](value) then
1365 AbstractValue.transform = AbstractValue.validate
1369 function AbstractValue.write(self, section, value)
1370 return self.map:set(section, self.option, value)
1374 function AbstractValue.remove(self, section)
1375 return self.map:del(section, self.option)
1382 Value - A one-line value
1383 maxlength: The maximum length
1385 Value = class(AbstractValue)
1387 function Value.__init__(self, ...)
1388 AbstractValue.__init__(self, ...)
1389 self.template = "cbi/value"
1394 function Value.value(self, key, val)
1396 table.insert(self.keylist, tostring(key))
1397 table.insert(self.vallist, tostring(val))
1401 -- DummyValue - This does nothing except being there
1402 DummyValue = class(AbstractValue)
1404 function DummyValue.__init__(self, ...)
1405 AbstractValue.__init__(self, ...)
1406 self.template = "cbi/dvalue"
1410 function DummyValue.cfgvalue(self, section)
1413 if type(self.value) == "function" then
1414 value = self:value(section)
1419 value = AbstractValue.cfgvalue(self, section)
1424 function DummyValue.parse(self)
1430 Flag - A flag being enabled or disabled
1432 Flag = class(AbstractValue)
1434 function Flag.__init__(self, ...)
1435 AbstractValue.__init__(self, ...)
1436 self.template = "cbi/fvalue"
1442 -- A flag can only have two states: set or unset
1443 function Flag.parse(self, section)
1444 local fvalue = self:formvalue(section)
1447 fvalue = self.enabled
1449 fvalue = self.disabled
1452 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1453 if not(fvalue == self:cfgvalue(section)) then
1454 self:write(section, fvalue)
1457 self:remove(section)
1464 ListValue - A one-line value predefined in a list
1465 widget: The widget that will be used (select, radio)
1467 ListValue = class(AbstractValue)
1469 function ListValue.__init__(self, ...)
1470 AbstractValue.__init__(self, ...)
1471 self.template = "cbi/lvalue"
1476 self.widget = "select"
1479 function ListValue.value(self, key, val, ...)
1480 if luci.util.contains(self.keylist, key) then
1485 table.insert(self.keylist, tostring(key))
1486 table.insert(self.vallist, tostring(val))
1488 for i, deps in ipairs({...}) do
1489 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1493 function ListValue.validate(self, val)
1494 if luci.util.contains(self.keylist, val) then
1504 MultiValue - Multiple delimited values
1505 widget: The widget that will be used (select, checkbox)
1506 delimiter: The delimiter that will separate the values (default: " ")
1508 MultiValue = class(AbstractValue)
1510 function MultiValue.__init__(self, ...)
1511 AbstractValue.__init__(self, ...)
1512 self.template = "cbi/mvalue"
1517 self.widget = "checkbox"
1518 self.delimiter = " "
1521 function MultiValue.render(self, ...)
1522 if self.widget == "select" and not self.size then
1523 self.size = #self.vallist
1526 AbstractValue.render(self, ...)
1529 function MultiValue.value(self, key, val)
1530 if luci.util.contains(self.keylist, key) then
1535 table.insert(self.keylist, tostring(key))
1536 table.insert(self.vallist, tostring(val))
1539 function MultiValue.valuelist(self, section)
1540 local val = self:cfgvalue(section)
1542 if not(type(val) == "string") then
1546 return luci.util.split(val, self.delimiter)
1549 function MultiValue.validate(self, val)
1550 val = (type(val) == "table") and val or {val}
1554 for i, value in ipairs(val) do
1555 if luci.util.contains(self.keylist, value) then
1556 result = result and (result .. self.delimiter .. value) or value
1564 StaticList = class(MultiValue)
1566 function StaticList.__init__(self, ...)
1567 MultiValue.__init__(self, ...)
1569 self.valuelist = self.cfgvalue
1571 if not self.override_scheme
1572 and self.map:get_scheme(self.section.sectiontype, self.option) then
1573 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1574 if self.value and vs.values and not self.override_values then
1575 for k, v in pairs(vs.values) do
1582 function StaticList.validate(self, value)
1583 value = (type(value) == "table") and value or {value}
1586 for i, v in ipairs(value) do
1587 if luci.util.contains(self.keylist, v) then
1588 table.insert(valid, v)
1595 DynamicList = class(AbstractValue)
1597 function DynamicList.__init__(self, ...)
1598 AbstractValue.__init__(self, ...)
1599 self.template = "cbi/dynlist"
1605 function DynamicList.value(self, key, val)
1607 table.insert(self.keylist, tostring(key))
1608 table.insert(self.vallist, tostring(val))
1611 function DynamicList.write(self, ...)
1612 self.map.proceed = true
1613 return AbstractValue.write(self, ...)
1616 function DynamicList.formvalue(self, section)
1617 local value = AbstractValue.formvalue(self, section)
1618 value = (type(value) == "table") and value or {value}
1621 for i, v in ipairs(value) do
1623 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1624 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1625 table.insert(valid, v)
1634 TextValue - A multi-line value
1637 TextValue = class(AbstractValue)
1639 function TextValue.__init__(self, ...)
1640 AbstractValue.__init__(self, ...)
1641 self.template = "cbi/tvalue"
1647 Button = class(AbstractValue)
1649 function Button.__init__(self, ...)
1650 AbstractValue.__init__(self, ...)
1651 self.template = "cbi/button"
1652 self.inputstyle = nil
1657 FileUpload = class(AbstractValue)
1659 function FileUpload.__init__(self, ...)
1660 AbstractValue.__init__(self, ...)
1661 self.template = "cbi/upload"
1662 if not self.map.upload_fields then
1663 self.map.upload_fields = { self }
1665 self.map.upload_fields[#self.map.upload_fields+1] = self
1669 function FileUpload.formcreated(self, section)
1670 return AbstractValue.formcreated(self, section) or
1671 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1672 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1675 function FileUpload.cfgvalue(self, section)
1676 local val = AbstractValue.cfgvalue(self, section)
1677 if val and fs.access(val) then
1683 function FileUpload.formvalue(self, section)
1684 local val = AbstractValue.formvalue(self, section)
1686 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1687 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1697 function FileUpload.remove(self, section)
1698 local val = AbstractValue.formvalue(self, section)
1699 if val and fs.access(val) then fs.unlink(val) end
1700 return AbstractValue.remove(self, section)
1704 FileBrowser = class(AbstractValue)
1706 function FileBrowser.__init__(self, ...)
1707 AbstractValue.__init__(self, ...)
1708 self.template = "cbi/browser"