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)
382 return self.uci:set(self.config, section, option, value)
384 return self.uci:set(self.config, section, value)
389 function Map.del(self, section, option)
391 return self.uci:delete(self.config, section, option)
393 return self.uci:delete(self.config, section)
398 function Map.get(self, section, option)
400 return self.uci:get_all(self.config)
402 return self.uci:get(self.config, section, option)
404 return self.uci:get_all(self.config, section)
411 Compound = class(Node)
413 function Compound.__init__(self, ...)
415 self.template = "cbi/compound"
416 self.children = {...}
419 function Compound.populate_delegator(self, delegator)
420 for _, v in ipairs(self.children) do
421 v.delegator = delegator
425 function Compound.parse(self, ...)
426 local cstate, state = 0
428 for k, child in ipairs(self.children) do
429 cstate = child:parse(...)
430 state = (not state or cstate < state) and cstate or state
438 Delegator - Node controller
440 Delegator = class(Node)
441 function Delegator.__init__(self, ...)
442 Node.__init__(self, ...)
444 self.defaultpath = {}
445 self.pageaction = false
446 self.readinput = true
447 self.allow_reset = false
448 self.allow_cancel = false
449 self.allow_back = false
450 self.allow_finish = false
451 self.template = "cbi/delegator"
454 function Delegator.set(self, name, node)
455 assert(not self.nodes[name], "Duplicate entry")
457 self.nodes[name] = node
460 function Delegator.add(self, name, node)
461 node = self:set(name, node)
462 self.defaultpath[#self.defaultpath+1] = name
465 function Delegator.insert_after(self, name, after)
466 local n = #self.chain + 1
467 for k, v in ipairs(self.chain) do
473 table.insert(self.chain, n, name)
476 function Delegator.set_route(self, ...)
477 local n, chain, route = 0, self.chain, {...}
479 if chain[i] == self.current then
488 for i = n + 1, #chain do
493 function Delegator.get(self, name)
494 local node = self.nodes[name]
496 if type(node) == "string" then
497 node = load(node, name)
500 if type(node) == "table" and getmetatable(node) == nil then
501 node = Compound(unpack(node))
507 function Delegator.parse(self, ...)
508 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
509 if self:_run_hooks("on_cancel") then
514 if not Map.formvalue(self, "cbi.delg.current") then
515 self:_run_hooks("on_init")
519 self.chain = self.chain or self:get_chain()
520 self.current = self.current or self:get_active()
521 self.active = self.active or self:get(self.current)
522 assert(self.active, "Invalid state")
524 local stat = FORM_DONE
525 if type(self.active) ~= "function" then
526 self.active:populate_delegator(self)
527 stat = self.active:parse()
532 if stat > FORM_PROCEED then
533 if Map.formvalue(self, "cbi.delg.back") then
534 newcurrent = self:get_prev(self.current)
536 newcurrent = self:get_next(self.current)
538 elseif stat < FORM_PROCEED then
543 if not Map.formvalue(self, "cbi.submit") then
545 elseif stat > FORM_PROCEED
546 and (not newcurrent or not self:get(newcurrent)) then
547 return self:_run_hook("on_done") or FORM_DONE
549 self.current = newcurrent or self.current
550 self.active = self:get(self.current)
551 if type(self.active) ~= "function" then
552 self.active:populate_delegator(self)
553 local stat = self.active:parse(false)
554 if stat == FORM_SKIP then
555 return self:parse(...)
560 return self:parse(...)
565 function Delegator.get_next(self, state)
566 for k, v in ipairs(self.chain) do
568 return self.chain[k+1]
573 function Delegator.get_prev(self, state)
574 for k, v in ipairs(self.chain) do
576 return self.chain[k-1]
581 function Delegator.get_chain(self)
582 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
583 return type(x) == "table" and x or {x}
586 function Delegator.get_active(self)
587 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
595 Page.__init__ = Node.__init__
596 Page.parse = function() end
600 SimpleForm - A Simple non-UCI form
602 SimpleForm = class(Node)
604 function SimpleForm.__init__(self, config, title, description, data)
605 Node.__init__(self, title, description)
607 self.data = data or {}
608 self.template = "cbi/simpleform"
610 self.pageaction = false
611 self.readinput = true
614 SimpleForm.formvalue = Map.formvalue
615 SimpleForm.formvaluetable = Map.formvaluetable
617 function SimpleForm.parse(self, readinput, ...)
618 self.readinput = (readinput ~= false)
620 if self:formvalue("cbi.skip") then
624 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
628 if self:submitstate() then
629 Node.parse(self, 1, ...)
633 for k, j in ipairs(self.children) do
634 for i, v in ipairs(j.children) do
636 and (not v.tag_missing or not v.tag_missing[1])
637 and (not v.tag_invalid or not v.tag_invalid[1])
643 not self:submitstate() and FORM_NODATA
644 or valid and FORM_VALID
647 self.dorender = not self.handle
649 local nrender, nstate = self:handle(state, self.data)
650 self.dorender = self.dorender or (nrender ~= false)
651 state = nstate or state
656 function SimpleForm.render(self, ...)
657 if self.dorender then
658 Node.render(self, ...)
662 function SimpleForm.submitstate(self)
663 return self:formvalue("cbi.submit")
666 function SimpleForm.section(self, class, ...)
667 if instanceof(class, AbstractSection) then
668 local obj = class(self, ...)
672 error("class must be a descendent of AbstractSection")
676 -- Creates a child field
677 function SimpleForm.field(self, class, ...)
679 for k, v in ipairs(self.children) do
680 if instanceof(v, SimpleSection) then
686 section = self:section(SimpleSection)
689 if instanceof(class, AbstractValue) then
690 local obj = class(self, section, ...)
691 obj.track_missing = true
695 error("class must be a descendent of AbstractValue")
699 function SimpleForm.set(self, section, option, value)
700 self.data[option] = value
704 function SimpleForm.del(self, section, option)
705 self.data[option] = nil
709 function SimpleForm.get(self, section, option)
710 return self.data[option]
714 function SimpleForm.get_scheme()
719 Form = class(SimpleForm)
721 function Form.__init__(self, ...)
722 SimpleForm.__init__(self, ...)
730 AbstractSection = class(Node)
732 function AbstractSection.__init__(self, map, sectiontype, ...)
733 Node.__init__(self, ...)
734 self.sectiontype = sectiontype
736 self.config = map.config
741 self.tag_invalid = {}
742 self.tag_deperror = {}
746 self.addremove = false
750 -- Define a tab for the section
751 function AbstractSection.tab(self, tab, title, desc)
752 self.tabs = self.tabs or { }
753 self.tab_names = self.tab_names or { }
755 self.tab_names[#self.tab_names+1] = tab
763 -- Check whether the section has tabs
764 function AbstractSection.has_tabs(self)
765 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
768 -- Appends a new option
769 function AbstractSection.option(self, class, option, ...)
770 if instanceof(class, AbstractValue) then
771 local obj = class(self.map, self, option, ...)
773 self.fields[option] = obj
775 elseif class == true then
776 error("No valid class was given and autodetection failed.")
778 error("class must be a descendant of AbstractValue")
782 -- Appends a new tabbed option
783 function AbstractSection.taboption(self, tab, ...)
785 assert(tab and self.tabs and self.tabs[tab],
786 "Cannot assign option to not existing tab %q" % tostring(tab))
788 local l = self.tabs[tab].childs
789 local o = AbstractSection.option(self, ...)
791 if o then l[#l+1] = o end
796 -- Render a single tab
797 function AbstractSection.render_tab(self, tab, ...)
799 assert(tab and self.tabs and self.tabs[tab],
800 "Cannot render not existing tab %q" % tostring(tab))
802 for _, node in ipairs(self.tabs[tab].childs) do
807 -- Parse optional options
808 function AbstractSection.parse_optionals(self, section)
809 if not self.optional then
813 self.optionals[section] = {}
815 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
816 for k,v in ipairs(self.children) do
817 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
818 if field == v.option then
820 self.map.proceed = true
822 table.insert(self.optionals[section], v)
827 if field and #field > 0 and self.dynamic then
828 self:add_dynamic(field)
832 -- Add a dynamic option
833 function AbstractSection.add_dynamic(self, field, optional)
834 local o = self:option(Value, field, field)
835 o.optional = optional
838 -- Parse all dynamic options
839 function AbstractSection.parse_dynamic(self, section)
840 if not self.dynamic then
844 local arr = luci.util.clone(self:cfgvalue(section))
845 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
846 for k, v in pairs(form) do
850 for key,val in pairs(arr) do
853 for i,c in ipairs(self.children) do
854 if c.option == key then
859 if create and key:sub(1, 1) ~= "." then
860 self.map.proceed = true
861 self:add_dynamic(key, true)
866 -- Returns the section's UCI table
867 function AbstractSection.cfgvalue(self, section)
868 return self.map:get(section)
872 function AbstractSection.push_events(self)
873 --luci.util.append(self.map.events, self.events)
874 self.map.changed = true
877 -- Removes the section
878 function AbstractSection.remove(self, section)
879 self.map.proceed = true
880 return self.map:del(section)
883 -- Creates the section
884 function AbstractSection.create(self, section)
888 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
890 section = self.map:add(self.sectiontype)
895 for k,v in pairs(self.children) do
897 self.map:set(section, v.option, v.default)
901 for k,v in pairs(self.defaults) do
902 self.map:set(section, k, v)
906 self.map.proceed = true
912 SimpleSection = class(AbstractSection)
914 function SimpleSection.__init__(self, form, ...)
915 AbstractSection.__init__(self, form, nil, ...)
916 self.template = "cbi/nullsection"
920 Table = class(AbstractSection)
922 function Table.__init__(self, form, data, ...)
923 local datasource = {}
925 datasource.config = "table"
926 self.data = data or {}
928 datasource.formvalue = Map.formvalue
929 datasource.formvaluetable = Map.formvaluetable
930 datasource.readinput = true
932 function datasource.get(self, section, option)
933 return tself.data[section] and tself.data[section][option]
936 function datasource.submitstate(self)
937 return Map.formvalue(self, "cbi.submit")
940 function datasource.del(...)
944 function datasource.get_scheme()
948 AbstractSection.__init__(self, datasource, "table", ...)
949 self.template = "cbi/tblsection"
950 self.rowcolors = true
951 self.anonymous = true
954 function Table.parse(self, readinput)
955 self.map.readinput = (readinput ~= false)
956 for i, k in ipairs(self:cfgsections()) do
957 if self.map:submitstate() then
963 function Table.cfgsections(self)
966 for i, v in luci.util.kspairs(self.data) do
967 table.insert(sections, i)
973 function Table.update(self, data)
980 NamedSection - A fixed configuration section defined by its name
982 NamedSection = class(AbstractSection)
984 function NamedSection.__init__(self, map, section, stype, ...)
985 AbstractSection.__init__(self, map, stype, ...)
988 self.addremove = false
989 self.template = "cbi/nsection"
990 self.section = section
993 function NamedSection.parse(self, novld)
994 local s = self.section
995 local active = self:cfgvalue(s)
997 if self.addremove then
998 local path = self.config.."."..s
999 if active then -- Remove the section
1000 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1004 else -- Create and apply default values
1005 if self.map:formvalue("cbi.cns."..path) then
1013 AbstractSection.parse_dynamic(self, s)
1014 if self.map:submitstate() then
1017 AbstractSection.parse_optionals(self, s)
1019 if self.changed then
1027 TypedSection - A (set of) configuration section(s) defined by the type
1028 addremove: Defines whether the user can add/remove sections of this type
1029 anonymous: Allow creating anonymous sections
1030 validate: a validation function returning nil if the section is invalid
1032 TypedSection = class(AbstractSection)
1034 function TypedSection.__init__(self, map, type, ...)
1035 AbstractSection.__init__(self, map, type, ...)
1037 self.template = "cbi/tsection"
1039 self.anonymous = false
1042 -- Return all matching UCI sections for this TypedSection
1043 function TypedSection.cfgsections(self)
1045 self.map.uci:foreach(self.map.config, self.sectiontype,
1047 if self:checkscope(section[".name"]) then
1048 table.insert(sections, section[".name"])
1055 -- Limits scope to sections that have certain option => value pairs
1056 function TypedSection.depends(self, option, value)
1057 table.insert(self.deps, {option=option, value=value})
1060 function TypedSection.parse(self, novld)
1061 if self.addremove then
1063 local crval = REMOVE_PREFIX .. self.config
1064 local name = self.map:formvaluetable(crval)
1065 for k,v in pairs(name) do
1066 if k:sub(-2) == ".x" then
1067 k = k:sub(1, #k - 2)
1069 if self:cfgvalue(k) and self:checkscope(k) then
1076 for i, k in ipairs(self:cfgsections()) do
1077 AbstractSection.parse_dynamic(self, k)
1078 if self.map:submitstate() then
1079 Node.parse(self, k, novld)
1081 AbstractSection.parse_optionals(self, k)
1084 if self.addremove then
1087 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1088 local name = self.map:formvalue(crval)
1089 if self.anonymous then
1091 created = self:create()
1095 -- Ignore if it already exists
1096 if self:cfgvalue(name) then
1100 name = self:checkscope(name)
1103 self.err_invalid = true
1106 if name and #name > 0 then
1107 created = self:create(name) and name
1109 self.invalid_cts = true
1116 AbstractSection.parse_optionals(self, created)
1120 if created or self.changed then
1125 -- Verifies scope of sections
1126 function TypedSection.checkscope(self, section)
1127 -- Check if we are not excluded
1128 if self.filter and not self:filter(section) then
1132 -- Check if at least one dependency is met
1133 if #self.deps > 0 and self:cfgvalue(section) then
1136 for k, v in ipairs(self.deps) do
1137 if self:cfgvalue(section)[v.option] == v.value then
1147 return self:validate(section)
1151 -- Dummy validate function
1152 function TypedSection.validate(self, section)
1158 AbstractValue - An abstract Value Type
1159 null: Value can be empty
1160 valid: A function returning the value if it is valid otherwise nil
1161 depends: A table of option => value pairs of which one must be true
1162 default: The default value
1163 size: The size of the input fields
1164 rmempty: Unset value if empty
1165 optional: This value is optional (see AbstractSection.optionals)
1167 AbstractValue = class(Node)
1169 function AbstractValue.__init__(self, map, section, option, ...)
1170 Node.__init__(self, ...)
1171 self.section = section
1172 self.option = option
1174 self.config = map.config
1175 self.tag_invalid = {}
1176 self.tag_missing = {}
1177 self.tag_reqerror = {}
1181 --self.cast = "string"
1183 self.track_missing = false
1187 self.optional = false
1190 function AbstractValue.prepare(self)
1191 self.cast = self.cast or "string"
1194 -- Add a dependencie to another section field
1195 function AbstractValue.depends(self, field, value)
1197 if type(field) == "string" then
1204 table.insert(self.deps, {deps=deps, add=""})
1207 -- Generates the unique CBID
1208 function AbstractValue.cbid(self, section)
1209 return "cbid."..self.map.config.."."..section.."."..self.option
1212 -- Return whether this object should be created
1213 function AbstractValue.formcreated(self, section)
1214 local key = "cbi.opt."..self.config.."."..section
1215 return (self.map:formvalue(key) == self.option)
1218 -- Returns the formvalue for this object
1219 function AbstractValue.formvalue(self, section)
1220 return self.map:formvalue(self:cbid(section))
1223 function AbstractValue.additional(self, value)
1224 self.optional = value
1227 function AbstractValue.mandatory(self, value)
1228 self.rmempty = not value
1231 function AbstractValue.add_error(self, section, type, msg)
1232 self.error = self.error or { }
1233 self.error[section] = msg or type
1235 self.section.error = self.section.error or { }
1236 self.section.error[section] = self.section.error[section] or { }
1237 table.insert(self.section.error[section], msg or type)
1239 if type == "invalid" then
1240 self.tag_invalid[section] = true
1241 elseif type == "missing" then
1242 self.tag_missing[section] = true
1245 self.tag_error[section] = true
1246 self.map.save = false
1249 function AbstractValue.parse(self, section, novld)
1250 local fvalue = self:formvalue(section)
1251 local cvalue = self:cfgvalue(section)
1253 -- If favlue and cvalue are both tables and have the same content
1254 -- make them identical
1255 if type(fvalue) == "table" and type(cvalue) == "table" then
1256 local equal = #fvalue == #cvalue
1259 if cvalue[i] ~= fvalue[i] then
1269 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1271 fvalue, val_err = self:validate(fvalue, section)
1272 fvalue = self:transform(fvalue)
1274 if not fvalue and not novld then
1275 self:add_error(section, "invalid", val_err)
1278 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1279 if self:write(section, fvalue) then
1281 self.section.changed = true
1282 --luci.util.append(self.map.events, self.events)
1285 else -- Unset the UCI or error
1286 if self.rmempty or self.optional then
1287 if self:remove(section) then
1289 self.section.changed = true
1290 --luci.util.append(self.map.events, self.events)
1292 elseif cvalue ~= fvalue and not novld then
1293 -- trigger validator with nil value to get custom user error msg.
1294 local _, val_err = self:validate(nil, section)
1295 self:add_error(section, "missing", val_err)
1300 -- Render if this value exists or if it is mandatory
1301 function AbstractValue.render(self, s, scope)
1302 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1305 scope.cbid = self:cbid(s)
1306 scope.striptags = luci.util.striptags
1307 scope.pcdata = luci.util.pcdata
1309 scope.ifattr = function(cond,key,val)
1311 return string.format(
1312 ' %s="%s"', tostring(key),
1313 luci.util.pcdata(tostring( val
1315 or (type(self[key]) ~= "function" and self[key])
1323 scope.attr = function(...)
1324 return scope.ifattr( true, ... )
1327 Node.render(self, scope)
1331 -- Return the UCI value of this object
1332 function AbstractValue.cfgvalue(self, section)
1334 if self.tag_error[section] then
1335 value = self:formvalue(section)
1337 value = self.map:get(section, self.option)
1342 elseif not self.cast or self.cast == type(value) then
1344 elseif self.cast == "string" then
1345 if type(value) == "table" then
1348 elseif self.cast == "table" then
1353 -- Validate the form value
1354 function AbstractValue.validate(self, value)
1355 if self.datatype and value then
1357 local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
1361 for a in ar:gmatch("[^%s,]+") do
1368 if dt and datatypes[dt] then
1369 if type(value) == "table" then
1371 for _, v in ipairs(value) do
1372 if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
1377 if not datatypes[dt](value, unpack(args)) then
1387 AbstractValue.transform = AbstractValue.validate
1391 function AbstractValue.write(self, section, value)
1392 return self.map:set(section, self.option, value)
1396 function AbstractValue.remove(self, section)
1397 return self.map:del(section, self.option)
1404 Value - A one-line value
1405 maxlength: The maximum length
1407 Value = class(AbstractValue)
1409 function Value.__init__(self, ...)
1410 AbstractValue.__init__(self, ...)
1411 self.template = "cbi/value"
1416 function Value.reset_values(self)
1421 function Value.value(self, key, val)
1423 table.insert(self.keylist, tostring(key))
1424 table.insert(self.vallist, tostring(val))
1428 -- DummyValue - This does nothing except being there
1429 DummyValue = class(AbstractValue)
1431 function DummyValue.__init__(self, ...)
1432 AbstractValue.__init__(self, ...)
1433 self.template = "cbi/dvalue"
1437 function DummyValue.cfgvalue(self, section)
1440 if type(self.value) == "function" then
1441 value = self:value(section)
1446 value = AbstractValue.cfgvalue(self, section)
1451 function DummyValue.parse(self)
1457 Flag - A flag being enabled or disabled
1459 Flag = class(AbstractValue)
1461 function Flag.__init__(self, ...)
1462 AbstractValue.__init__(self, ...)
1463 self.template = "cbi/fvalue"
1469 -- A flag can only have two states: set or unset
1470 function Flag.parse(self, section)
1471 local fvalue = self:formvalue(section)
1474 fvalue = self.enabled
1476 fvalue = self.disabled
1479 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1480 if not(fvalue == self:cfgvalue(section)) then
1481 self:write(section, fvalue)
1484 self:remove(section)
1491 ListValue - A one-line value predefined in a list
1492 widget: The widget that will be used (select, radio)
1494 ListValue = class(AbstractValue)
1496 function ListValue.__init__(self, ...)
1497 AbstractValue.__init__(self, ...)
1498 self.template = "cbi/lvalue"
1503 self.widget = "select"
1506 function ListValue.reset_values(self)
1511 function ListValue.value(self, key, val, ...)
1512 if luci.util.contains(self.keylist, key) then
1517 table.insert(self.keylist, tostring(key))
1518 table.insert(self.vallist, tostring(val))
1520 for i, deps in ipairs({...}) do
1521 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1525 function ListValue.validate(self, val)
1526 if luci.util.contains(self.keylist, val) then
1536 MultiValue - Multiple delimited values
1537 widget: The widget that will be used (select, checkbox)
1538 delimiter: The delimiter that will separate the values (default: " ")
1540 MultiValue = class(AbstractValue)
1542 function MultiValue.__init__(self, ...)
1543 AbstractValue.__init__(self, ...)
1544 self.template = "cbi/mvalue"
1549 self.widget = "checkbox"
1550 self.delimiter = " "
1553 function MultiValue.render(self, ...)
1554 if self.widget == "select" and not self.size then
1555 self.size = #self.vallist
1558 AbstractValue.render(self, ...)
1561 function MultiValue.reset_values(self)
1566 function MultiValue.value(self, key, val)
1567 if luci.util.contains(self.keylist, key) then
1572 table.insert(self.keylist, tostring(key))
1573 table.insert(self.vallist, tostring(val))
1576 function MultiValue.valuelist(self, section)
1577 local val = self:cfgvalue(section)
1579 if not(type(val) == "string") then
1583 return luci.util.split(val, self.delimiter)
1586 function MultiValue.validate(self, val)
1587 val = (type(val) == "table") and val or {val}
1591 for i, value in ipairs(val) do
1592 if luci.util.contains(self.keylist, value) then
1593 result = result and (result .. self.delimiter .. value) or value
1601 StaticList = class(MultiValue)
1603 function StaticList.__init__(self, ...)
1604 MultiValue.__init__(self, ...)
1606 self.valuelist = self.cfgvalue
1608 if not self.override_scheme
1609 and self.map:get_scheme(self.section.sectiontype, self.option) then
1610 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1611 if self.value and vs.values and not self.override_values then
1612 for k, v in pairs(vs.values) do
1619 function StaticList.validate(self, value)
1620 value = (type(value) == "table") and value or {value}
1623 for i, v in ipairs(value) do
1624 if luci.util.contains(self.keylist, v) then
1625 table.insert(valid, v)
1632 DynamicList = class(AbstractValue)
1634 function DynamicList.__init__(self, ...)
1635 AbstractValue.__init__(self, ...)
1636 self.template = "cbi/dynlist"
1642 function DynamicList.reset_values(self)
1647 function DynamicList.value(self, key, val)
1649 table.insert(self.keylist, tostring(key))
1650 table.insert(self.vallist, tostring(val))
1653 function DynamicList.write(self, section, value)
1656 if type(value) == "table" then
1658 for _, x in ipairs(value) do
1659 if x and #x > 0 then
1667 if self.cast == "string" then
1668 value = table.concat(t, " ")
1673 return AbstractValue.write(self, section, value)
1676 function DynamicList.cfgvalue(self, section)
1677 local value = AbstractValue.cfgvalue(self, section)
1679 if type(value) == "string" then
1682 for x in value:gmatch("%S+") do
1693 function DynamicList.formvalue(self, section)
1694 local value = AbstractValue.formvalue(self, section)
1696 if type(value) == "string" then
1697 if self.cast == "string" then
1700 for x in value:gmatch("%S+") do
1714 TextValue - A multi-line value
1717 TextValue = class(AbstractValue)
1719 function TextValue.__init__(self, ...)
1720 AbstractValue.__init__(self, ...)
1721 self.template = "cbi/tvalue"
1727 Button = class(AbstractValue)
1729 function Button.__init__(self, ...)
1730 AbstractValue.__init__(self, ...)
1731 self.template = "cbi/button"
1732 self.inputstyle = nil
1737 FileUpload = class(AbstractValue)
1739 function FileUpload.__init__(self, ...)
1740 AbstractValue.__init__(self, ...)
1741 self.template = "cbi/upload"
1742 if not self.map.upload_fields then
1743 self.map.upload_fields = { self }
1745 self.map.upload_fields[#self.map.upload_fields+1] = self
1749 function FileUpload.formcreated(self, section)
1750 return AbstractValue.formcreated(self, section) or
1751 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1752 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1755 function FileUpload.cfgvalue(self, section)
1756 local val = AbstractValue.cfgvalue(self, section)
1757 if val and fs.access(val) then
1763 function FileUpload.formvalue(self, section)
1764 local val = AbstractValue.formvalue(self, section)
1766 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1767 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1777 function FileUpload.remove(self, section)
1778 local val = AbstractValue.formvalue(self, section)
1779 if val and fs.access(val) then fs.unlink(val) end
1780 return AbstractValue.remove(self, section)
1784 FileBrowser = class(AbstractValue)
1786 function FileBrowser.__init__(self, ...)
1787 AbstractValue.__init__(self, ...)
1788 self.template = "cbi/browser"