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."
53 RESORT_PREFIX = "cbi.sts."
55 -- Loads a CBI map from given file, creating an environment and returns it
56 function load(cbimap, ...)
57 local fs = require "nixio.fs"
58 local i18n = require "luci.i18n"
59 require("luci.config")
62 local upldir = "/lib/uci/upload/"
63 local cbidir = luci.util.libpath() .. "/model/cbi/"
66 if fs.access(cbidir..cbimap..".lua") then
67 func, err = loadfile(cbidir..cbimap..".lua")
68 elseif fs.access(cbimap) then
69 func, err = loadfile(cbimap)
71 func, err = nil, "Model '" .. cbimap .. "' not found!"
76 luci.i18n.loadc("base")
79 translate=i18n.translate,
80 translatef=i18n.translatef,
84 setfenv(func, setmetatable(env, {__index =
86 return rawget(tbl, key) or _M[key] or _G[key]
89 local maps = { func() }
91 local has_upload = false
93 for i, map in ipairs(maps) do
94 if not instanceof(map, Node) then
95 error("CBI map returns no valid map object!")
99 if map.upload_fields then
101 for _, field in ipairs(map.upload_fields) do
103 field.config .. '.' ..
104 field.section.sectiontype .. '.' ..
113 local uci = luci.model.uci.cursor()
114 local prm = luci.http.context.request.message.params
117 luci.http.setfilehandler(
118 function( field, chunk, eof )
119 if not field then return end
120 if field.name and not cbid then
121 local c, s, o = field.name:gmatch(
122 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
125 if c and s and o then
126 local t = uci:get( c, s )
127 if t and uploads[c.."."..t.."."..o] then
128 local path = upldir .. field.name
129 fd = io.open(path, "w")
138 if field.name == cbid and fd then
155 -- Node pseudo abstract class
158 function Node.__init__(self, title, description)
160 self.title = title or ""
161 self.description = description or ""
162 self.template = "cbi/node"
166 function Node._run_hook(self, hook)
167 if type(self[hook]) == "function" then
168 return self[hook](self)
172 function Node._run_hooks(self, ...)
175 for _, f in ipairs(arg) do
176 if type(self[f]) == "function" then
185 function Node.prepare(self, ...)
186 for k, child in ipairs(self.children) do
191 -- Append child nodes
192 function Node.append(self, obj)
193 table.insert(self.children, obj)
196 -- Parse this node and its children
197 function Node.parse(self, ...)
198 for k, child in ipairs(self.children) do
204 function Node.render(self, scope)
208 luci.template.render(self.template, scope)
211 -- Render the children
212 function Node.render_children(self, ...)
213 for k, node in ipairs(self.children) do
220 A simple template element
222 Template = class(Node)
224 function Template.__init__(self, template)
226 self.template = template
229 function Template.render(self)
230 luci.template.render(self.template, {self=self})
233 function Template.parse(self, readinput)
234 self.readinput = (readinput ~= false)
235 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
240 Map - A map describing a configuration file
244 function Map.__init__(self, config, ...)
245 Node.__init__(self, ...)
248 self.parsechain = {self.config}
249 self.template = "cbi/map"
250 self.apply_on_parse = nil
251 self.readinput = true
255 self.uci = uci.cursor()
260 if not self.uci:load(self.config) then
261 error("Unable to read UCI data: " .. self.config)
265 function Map.formvalue(self, key)
266 return self.readinput and luci.http.formvalue(key)
269 function Map.formvaluetable(self, key)
270 return self.readinput and luci.http.formvaluetable(key) or {}
273 function Map.get_scheme(self, sectiontype, option)
275 return self.scheme and self.scheme.sections[sectiontype]
277 return self.scheme and self.scheme.variables[sectiontype]
278 and self.scheme.variables[sectiontype][option]
282 function Map.submitstate(self)
283 return self:formvalue("cbi.submit")
286 -- Chain foreign config
287 function Map.chain(self, config)
288 table.insert(self.parsechain, config)
291 function Map.state_handler(self, state)
295 -- Use optimized UCI writing
296 function Map.parse(self, readinput, ...)
297 self.readinput = (readinput ~= false)
298 self:_run_hooks("on_parse")
300 if self:formvalue("cbi.skip") then
301 self.state = FORM_SKIP
302 return self:state_handler(self.state)
305 Node.parse(self, ...)
308 self:_run_hooks("on_save", "on_before_save")
309 for i, config in ipairs(self.parsechain) do
310 self.uci:save(config)
312 self:_run_hooks("on_after_save")
313 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
314 self:_run_hooks("on_before_commit")
315 for i, config in ipairs(self.parsechain) do
316 self.uci:commit(config)
318 -- Refresh data because commit changes section names
319 self.uci:load(config)
321 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
322 if self.apply_on_parse then
323 self.uci:apply(self.parsechain)
324 self:_run_hooks("on_apply", "on_after_apply")
326 -- This is evaluated by the dispatcher and delegated to the
327 -- template which in turn fires XHR to perform the actual
329 self.apply_needed = true
333 Node.parse(self, true)
336 for i, config in ipairs(self.parsechain) do
337 self.uci:unload(config)
339 if type(self.commit_handler) == "function" then
340 self:commit_handler(self:submitstate())
344 if self:submitstate() then
345 if not self.save then
346 self.state = FORM_INVALID
347 elseif self.proceed then
348 self.state = FORM_PROCEED
350 self.state = self.changed and FORM_CHANGED or FORM_VALID
353 self.state = FORM_NODATA
356 return self:state_handler(self.state)
359 function Map.render(self, ...)
360 self:_run_hooks("on_init")
361 Node.render(self, ...)
364 -- Creates a child section
365 function Map.section(self, class, ...)
366 if instanceof(class, AbstractSection) then
367 local obj = class(self, ...)
371 error("class must be a descendent of AbstractSection")
376 function Map.add(self, sectiontype)
377 return self.uci:add(self.config, sectiontype)
381 function Map.set(self, section, option, value)
382 if type(value) ~= "table" or #value > 0 then
384 return self.uci:set(self.config, section, option, value)
386 return self.uci:set(self.config, section, value)
389 return Map.del(self, section, option)
394 function Map.del(self, section, option)
396 return self.uci:delete(self.config, section, option)
398 return self.uci:delete(self.config, section)
403 function Map.get(self, section, option)
405 return self.uci:get_all(self.config)
407 return self.uci:get(self.config, section, option)
409 return self.uci:get_all(self.config, section)
416 Compound = class(Node)
418 function Compound.__init__(self, ...)
420 self.template = "cbi/compound"
421 self.children = {...}
424 function Compound.populate_delegator(self, delegator)
425 for _, v in ipairs(self.children) do
426 v.delegator = delegator
430 function Compound.parse(self, ...)
431 local cstate, state = 0
433 for k, child in ipairs(self.children) do
434 cstate = child:parse(...)
435 state = (not state or cstate < state) and cstate or state
443 Delegator - Node controller
445 Delegator = class(Node)
446 function Delegator.__init__(self, ...)
447 Node.__init__(self, ...)
449 self.defaultpath = {}
450 self.pageaction = false
451 self.readinput = true
452 self.allow_reset = false
453 self.allow_cancel = false
454 self.allow_back = false
455 self.allow_finish = false
456 self.template = "cbi/delegator"
459 function Delegator.set(self, name, node)
460 assert(not self.nodes[name], "Duplicate entry")
462 self.nodes[name] = node
465 function Delegator.add(self, name, node)
466 node = self:set(name, node)
467 self.defaultpath[#self.defaultpath+1] = name
470 function Delegator.insert_after(self, name, after)
471 local n = #self.chain + 1
472 for k, v in ipairs(self.chain) do
478 table.insert(self.chain, n, name)
481 function Delegator.set_route(self, ...)
482 local n, chain, route = 0, self.chain, {...}
484 if chain[i] == self.current then
493 for i = n + 1, #chain do
498 function Delegator.get(self, name)
499 local node = self.nodes[name]
501 if type(node) == "string" then
502 node = load(node, name)
505 if type(node) == "table" and getmetatable(node) == nil then
506 node = Compound(unpack(node))
512 function Delegator.parse(self, ...)
513 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
514 if self:_run_hooks("on_cancel") then
519 if not Map.formvalue(self, "cbi.delg.current") then
520 self:_run_hooks("on_init")
524 self.chain = self.chain or self:get_chain()
525 self.current = self.current or self:get_active()
526 self.active = self.active or self:get(self.current)
527 assert(self.active, "Invalid state")
529 local stat = FORM_DONE
530 if type(self.active) ~= "function" then
531 self.active:populate_delegator(self)
532 stat = self.active:parse()
537 if stat > FORM_PROCEED then
538 if Map.formvalue(self, "cbi.delg.back") then
539 newcurrent = self:get_prev(self.current)
541 newcurrent = self:get_next(self.current)
543 elseif stat < FORM_PROCEED then
548 if not Map.formvalue(self, "cbi.submit") then
550 elseif stat > FORM_PROCEED
551 and (not newcurrent or not self:get(newcurrent)) then
552 return self:_run_hook("on_done") or FORM_DONE
554 self.current = newcurrent or self.current
555 self.active = self:get(self.current)
556 if type(self.active) ~= "function" then
557 self.active:populate_delegator(self)
558 local stat = self.active:parse(false)
559 if stat == FORM_SKIP then
560 return self:parse(...)
565 return self:parse(...)
570 function Delegator.get_next(self, state)
571 for k, v in ipairs(self.chain) do
573 return self.chain[k+1]
578 function Delegator.get_prev(self, state)
579 for k, v in ipairs(self.chain) do
581 return self.chain[k-1]
586 function Delegator.get_chain(self)
587 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
588 return type(x) == "table" and x or {x}
591 function Delegator.get_active(self)
592 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
600 Page.__init__ = Node.__init__
601 Page.parse = function() end
605 SimpleForm - A Simple non-UCI form
607 SimpleForm = class(Node)
609 function SimpleForm.__init__(self, config, title, description, data)
610 Node.__init__(self, title, description)
612 self.data = data or {}
613 self.template = "cbi/simpleform"
615 self.pageaction = false
616 self.readinput = true
619 SimpleForm.formvalue = Map.formvalue
620 SimpleForm.formvaluetable = Map.formvaluetable
622 function SimpleForm.parse(self, readinput, ...)
623 self.readinput = (readinput ~= false)
625 if self:formvalue("cbi.skip") then
629 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
633 if self:submitstate() then
634 Node.parse(self, 1, ...)
638 for k, j in ipairs(self.children) do
639 for i, v in ipairs(j.children) do
641 and (not v.tag_missing or not v.tag_missing[1])
642 and (not v.tag_invalid or not v.tag_invalid[1])
648 not self:submitstate() and FORM_NODATA
649 or valid and FORM_VALID
652 self.dorender = not self.handle
654 local nrender, nstate = self:handle(state, self.data)
655 self.dorender = self.dorender or (nrender ~= false)
656 state = nstate or state
661 function SimpleForm.render(self, ...)
662 if self.dorender then
663 Node.render(self, ...)
667 function SimpleForm.submitstate(self)
668 return self:formvalue("cbi.submit")
671 function SimpleForm.section(self, class, ...)
672 if instanceof(class, AbstractSection) then
673 local obj = class(self, ...)
677 error("class must be a descendent of AbstractSection")
681 -- Creates a child field
682 function SimpleForm.field(self, class, ...)
684 for k, v in ipairs(self.children) do
685 if instanceof(v, SimpleSection) then
691 section = self:section(SimpleSection)
694 if instanceof(class, AbstractValue) then
695 local obj = class(self, section, ...)
696 obj.track_missing = true
700 error("class must be a descendent of AbstractValue")
704 function SimpleForm.set(self, section, option, value)
705 self.data[option] = value
709 function SimpleForm.del(self, section, option)
710 self.data[option] = nil
714 function SimpleForm.get(self, section, option)
715 return self.data[option]
719 function SimpleForm.get_scheme()
724 Form = class(SimpleForm)
726 function Form.__init__(self, ...)
727 SimpleForm.__init__(self, ...)
735 AbstractSection = class(Node)
737 function AbstractSection.__init__(self, map, sectiontype, ...)
738 Node.__init__(self, ...)
739 self.sectiontype = sectiontype
741 self.config = map.config
746 self.tag_invalid = {}
747 self.tag_deperror = {}
751 self.addremove = false
755 -- Define a tab for the section
756 function AbstractSection.tab(self, tab, title, desc)
757 self.tabs = self.tabs or { }
758 self.tab_names = self.tab_names or { }
760 self.tab_names[#self.tab_names+1] = tab
768 -- Check whether the section has tabs
769 function AbstractSection.has_tabs(self)
770 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
773 -- Appends a new option
774 function AbstractSection.option(self, class, option, ...)
775 if instanceof(class, AbstractValue) then
776 local obj = class(self.map, self, option, ...)
778 self.fields[option] = obj
780 elseif class == true then
781 error("No valid class was given and autodetection failed.")
783 error("class must be a descendant of AbstractValue")
787 -- Appends a new tabbed option
788 function AbstractSection.taboption(self, tab, ...)
790 assert(tab and self.tabs and self.tabs[tab],
791 "Cannot assign option to not existing tab %q" % tostring(tab))
793 local l = self.tabs[tab].childs
794 local o = AbstractSection.option(self, ...)
796 if o then l[#l+1] = o end
801 -- Render a single tab
802 function AbstractSection.render_tab(self, tab, ...)
804 assert(tab and self.tabs and self.tabs[tab],
805 "Cannot render not existing tab %q" % tostring(tab))
807 for _, node in ipairs(self.tabs[tab].childs) do
812 -- Parse optional options
813 function AbstractSection.parse_optionals(self, section)
814 if not self.optional then
818 self.optionals[section] = {}
820 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
821 for k,v in ipairs(self.children) do
822 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
823 if field == v.option then
825 self.map.proceed = true
827 table.insert(self.optionals[section], v)
832 if field and #field > 0 and self.dynamic then
833 self:add_dynamic(field)
837 -- Add a dynamic option
838 function AbstractSection.add_dynamic(self, field, optional)
839 local o = self:option(Value, field, field)
840 o.optional = optional
843 -- Parse all dynamic options
844 function AbstractSection.parse_dynamic(self, section)
845 if not self.dynamic then
849 local arr = luci.util.clone(self:cfgvalue(section))
850 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
851 for k, v in pairs(form) do
855 for key,val in pairs(arr) do
858 for i,c in ipairs(self.children) do
859 if c.option == key then
864 if create and key:sub(1, 1) ~= "." then
865 self.map.proceed = true
866 self:add_dynamic(key, true)
871 -- Returns the section's UCI table
872 function AbstractSection.cfgvalue(self, section)
873 return self.map:get(section)
877 function AbstractSection.push_events(self)
878 --luci.util.append(self.map.events, self.events)
879 self.map.changed = true
882 -- Removes the section
883 function AbstractSection.remove(self, section)
884 self.map.proceed = true
885 return self.map:del(section)
888 -- Creates the section
889 function AbstractSection.create(self, section)
893 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
895 section = self.map:add(self.sectiontype)
900 for k,v in pairs(self.children) do
902 self.map:set(section, v.option, v.default)
906 for k,v in pairs(self.defaults) do
907 self.map:set(section, k, v)
911 self.map.proceed = true
917 SimpleSection = class(AbstractSection)
919 function SimpleSection.__init__(self, form, ...)
920 AbstractSection.__init__(self, form, nil, ...)
921 self.template = "cbi/nullsection"
925 Table = class(AbstractSection)
927 function Table.__init__(self, form, data, ...)
928 local datasource = {}
930 datasource.config = "table"
931 self.data = data or {}
933 datasource.formvalue = Map.formvalue
934 datasource.formvaluetable = Map.formvaluetable
935 datasource.readinput = true
937 function datasource.get(self, section, option)
938 return tself.data[section] and tself.data[section][option]
941 function datasource.submitstate(self)
942 return Map.formvalue(self, "cbi.submit")
945 function datasource.del(...)
949 function datasource.get_scheme()
953 AbstractSection.__init__(self, datasource, "table", ...)
954 self.template = "cbi/tblsection"
955 self.rowcolors = true
956 self.anonymous = true
959 function Table.parse(self, readinput)
960 self.map.readinput = (readinput ~= false)
961 for i, k in ipairs(self:cfgsections()) do
962 if self.map:submitstate() then
968 function Table.cfgsections(self)
971 for i, v in luci.util.kspairs(self.data) do
972 table.insert(sections, i)
978 function Table.update(self, data)
985 NamedSection - A fixed configuration section defined by its name
987 NamedSection = class(AbstractSection)
989 function NamedSection.__init__(self, map, section, stype, ...)
990 AbstractSection.__init__(self, map, stype, ...)
993 self.addremove = false
994 self.template = "cbi/nsection"
995 self.section = section
998 function NamedSection.parse(self, novld)
999 local s = self.section
1000 local active = self:cfgvalue(s)
1002 if self.addremove then
1003 local path = self.config.."."..s
1004 if active then -- Remove the section
1005 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1009 else -- Create and apply default values
1010 if self.map:formvalue("cbi.cns."..path) then
1018 AbstractSection.parse_dynamic(self, s)
1019 if self.map:submitstate() then
1022 AbstractSection.parse_optionals(self, s)
1024 if self.changed then
1032 TypedSection - A (set of) configuration section(s) defined by the type
1033 addremove: Defines whether the user can add/remove sections of this type
1034 anonymous: Allow creating anonymous sections
1035 validate: a validation function returning nil if the section is invalid
1037 TypedSection = class(AbstractSection)
1039 function TypedSection.__init__(self, map, type, ...)
1040 AbstractSection.__init__(self, map, type, ...)
1042 self.template = "cbi/tsection"
1044 self.anonymous = false
1047 -- Return all matching UCI sections for this TypedSection
1048 function TypedSection.cfgsections(self)
1050 self.map.uci:foreach(self.map.config, self.sectiontype,
1052 if self:checkscope(section[".name"]) then
1053 table.insert(sections, section[".name"])
1060 -- Limits scope to sections that have certain option => value pairs
1061 function TypedSection.depends(self, option, value)
1062 table.insert(self.deps, {option=option, value=value})
1065 function TypedSection.parse(self, novld)
1066 if self.addremove then
1068 local crval = REMOVE_PREFIX .. self.config
1069 local name = self.map:formvaluetable(crval)
1070 for k,v in pairs(name) do
1071 if k:sub(-2) == ".x" then
1072 k = k:sub(1, #k - 2)
1074 if self:cfgvalue(k) and self:checkscope(k) then
1081 for i, k in ipairs(self:cfgsections()) do
1082 AbstractSection.parse_dynamic(self, k)
1083 if self.map:submitstate() then
1084 Node.parse(self, k, novld)
1086 AbstractSection.parse_optionals(self, k)
1089 if self.addremove then
1092 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1093 local name = self.map:formvalue(crval)
1094 if self.anonymous then
1096 created = self:create()
1100 -- Ignore if it already exists
1101 if self:cfgvalue(name) then
1105 name = self:checkscope(name)
1108 self.err_invalid = true
1111 if name and #name > 0 then
1112 created = self:create(name) and name
1114 self.invalid_cts = true
1121 AbstractSection.parse_optionals(self, created)
1125 if self.sortable then
1126 local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
1127 local order = self.map:formvalue(stval)
1128 if order and #order > 0 then
1131 for sid in util.imatch(order) do
1132 self.map.uci:reorder(self.config, sid, num)
1135 self.changed = (num > 0)
1139 if created or self.changed then
1144 -- Verifies scope of sections
1145 function TypedSection.checkscope(self, section)
1146 -- Check if we are not excluded
1147 if self.filter and not self:filter(section) then
1151 -- Check if at least one dependency is met
1152 if #self.deps > 0 and self:cfgvalue(section) then
1155 for k, v in ipairs(self.deps) do
1156 if self:cfgvalue(section)[v.option] == v.value then
1166 return self:validate(section)
1170 -- Dummy validate function
1171 function TypedSection.validate(self, section)
1177 AbstractValue - An abstract Value Type
1178 null: Value can be empty
1179 valid: A function returning the value if it is valid otherwise nil
1180 depends: A table of option => value pairs of which one must be true
1181 default: The default value
1182 size: The size of the input fields
1183 rmempty: Unset value if empty
1184 optional: This value is optional (see AbstractSection.optionals)
1186 AbstractValue = class(Node)
1188 function AbstractValue.__init__(self, map, section, option, ...)
1189 Node.__init__(self, ...)
1190 self.section = section
1191 self.option = option
1193 self.config = map.config
1194 self.tag_invalid = {}
1195 self.tag_missing = {}
1196 self.tag_reqerror = {}
1200 --self.cast = "string"
1202 self.track_missing = false
1206 self.optional = false
1209 function AbstractValue.prepare(self)
1210 self.cast = self.cast or "string"
1213 -- Add a dependencie to another section field
1214 function AbstractValue.depends(self, field, value)
1216 if type(field) == "string" then
1223 table.insert(self.deps, {deps=deps, add=""})
1226 -- Generates the unique CBID
1227 function AbstractValue.cbid(self, section)
1228 return "cbid."..self.map.config.."."..section.."."..self.option
1231 -- Return whether this object should be created
1232 function AbstractValue.formcreated(self, section)
1233 local key = "cbi.opt."..self.config.."."..section
1234 return (self.map:formvalue(key) == self.option)
1237 -- Returns the formvalue for this object
1238 function AbstractValue.formvalue(self, section)
1239 return self.map:formvalue(self:cbid(section))
1242 function AbstractValue.additional(self, value)
1243 self.optional = value
1246 function AbstractValue.mandatory(self, value)
1247 self.rmempty = not value
1250 function AbstractValue.add_error(self, section, type, msg)
1251 self.error = self.error or { }
1252 self.error[section] = msg or type
1254 self.section.error = self.section.error or { }
1255 self.section.error[section] = self.section.error[section] or { }
1256 table.insert(self.section.error[section], msg or type)
1258 if type == "invalid" then
1259 self.tag_invalid[section] = true
1260 elseif type == "missing" then
1261 self.tag_missing[section] = true
1264 self.tag_error[section] = true
1265 self.map.save = false
1268 function AbstractValue.parse(self, section, novld)
1269 local fvalue = self:formvalue(section)
1270 local cvalue = self:cfgvalue(section)
1272 -- If favlue and cvalue are both tables and have the same content
1273 -- make them identical
1274 if type(fvalue) == "table" and type(cvalue) == "table" then
1275 local equal = #fvalue == #cvalue
1278 if cvalue[i] ~= fvalue[i] then
1288 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1290 fvalue, val_err = self:validate(fvalue, section)
1291 fvalue = self:transform(fvalue)
1293 if not fvalue and not novld then
1294 self:add_error(section, "invalid", val_err)
1297 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1298 if self:write(section, fvalue) then
1300 self.section.changed = true
1301 --luci.util.append(self.map.events, self.events)
1304 else -- Unset the UCI or error
1305 if self.rmempty or self.optional then
1306 if self:remove(section) then
1308 self.section.changed = true
1309 --luci.util.append(self.map.events, self.events)
1311 elseif cvalue ~= fvalue and not novld then
1312 -- trigger validator with nil value to get custom user error msg.
1313 local _, val_err = self:validate(nil, section)
1314 self:add_error(section, "missing", val_err)
1319 -- Render if this value exists or if it is mandatory
1320 function AbstractValue.render(self, s, scope)
1321 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1324 scope.cbid = self:cbid(s)
1325 scope.striptags = luci.util.striptags
1326 scope.pcdata = luci.util.pcdata
1328 scope.ifattr = function(cond,key,val)
1330 return string.format(
1331 ' %s="%s"', tostring(key),
1332 luci.util.pcdata(tostring( val
1334 or (type(self[key]) ~= "function" and self[key])
1342 scope.attr = function(...)
1343 return scope.ifattr( true, ... )
1346 Node.render(self, scope)
1350 -- Return the UCI value of this object
1351 function AbstractValue.cfgvalue(self, section)
1353 if self.tag_error[section] then
1354 value = self:formvalue(section)
1356 value = self.map:get(section, self.option)
1361 elseif not self.cast or self.cast == type(value) then
1363 elseif self.cast == "string" then
1364 if type(value) == "table" then
1367 elseif self.cast == "table" then
1372 -- Validate the form value
1373 function AbstractValue.validate(self, value)
1374 if self.datatype and value then
1376 local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
1380 for a in ar:gmatch("[^%s,]+") do
1387 if dt and datatypes[dt] then
1388 if type(value) == "table" then
1390 for _, v in ipairs(value) do
1391 if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
1396 if not datatypes[dt](value, unpack(args)) then
1406 AbstractValue.transform = AbstractValue.validate
1410 function AbstractValue.write(self, section, value)
1411 return self.map:set(section, self.option, value)
1415 function AbstractValue.remove(self, section)
1416 return self.map:del(section, self.option)
1423 Value - A one-line value
1424 maxlength: The maximum length
1426 Value = class(AbstractValue)
1428 function Value.__init__(self, ...)
1429 AbstractValue.__init__(self, ...)
1430 self.template = "cbi/value"
1435 function Value.reset_values(self)
1440 function Value.value(self, key, val)
1442 table.insert(self.keylist, tostring(key))
1443 table.insert(self.vallist, tostring(val))
1447 -- DummyValue - This does nothing except being there
1448 DummyValue = class(AbstractValue)
1450 function DummyValue.__init__(self, ...)
1451 AbstractValue.__init__(self, ...)
1452 self.template = "cbi/dvalue"
1456 function DummyValue.cfgvalue(self, section)
1459 if type(self.value) == "function" then
1460 value = self:value(section)
1465 value = AbstractValue.cfgvalue(self, section)
1470 function DummyValue.parse(self)
1476 Flag - A flag being enabled or disabled
1478 Flag = class(AbstractValue)
1480 function Flag.__init__(self, ...)
1481 AbstractValue.__init__(self, ...)
1482 self.template = "cbi/fvalue"
1488 -- A flag can only have two states: set or unset
1489 function Flag.parse(self, section)
1490 local fvalue = self:formvalue(section)
1493 fvalue = self.enabled
1495 fvalue = self.disabled
1498 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1499 if not(fvalue == self:cfgvalue(section)) then
1500 self:write(section, fvalue)
1503 self:remove(section)
1510 ListValue - A one-line value predefined in a list
1511 widget: The widget that will be used (select, radio)
1513 ListValue = class(AbstractValue)
1515 function ListValue.__init__(self, ...)
1516 AbstractValue.__init__(self, ...)
1517 self.template = "cbi/lvalue"
1522 self.widget = "select"
1525 function ListValue.reset_values(self)
1530 function ListValue.value(self, key, val, ...)
1531 if luci.util.contains(self.keylist, key) then
1536 table.insert(self.keylist, tostring(key))
1537 table.insert(self.vallist, tostring(val))
1539 for i, deps in ipairs({...}) do
1540 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1544 function ListValue.validate(self, val)
1545 if luci.util.contains(self.keylist, val) then
1555 MultiValue - Multiple delimited values
1556 widget: The widget that will be used (select, checkbox)
1557 delimiter: The delimiter that will separate the values (default: " ")
1559 MultiValue = class(AbstractValue)
1561 function MultiValue.__init__(self, ...)
1562 AbstractValue.__init__(self, ...)
1563 self.template = "cbi/mvalue"
1568 self.widget = "checkbox"
1569 self.delimiter = " "
1572 function MultiValue.render(self, ...)
1573 if self.widget == "select" and not self.size then
1574 self.size = #self.vallist
1577 AbstractValue.render(self, ...)
1580 function MultiValue.reset_values(self)
1585 function MultiValue.value(self, key, val)
1586 if luci.util.contains(self.keylist, key) then
1591 table.insert(self.keylist, tostring(key))
1592 table.insert(self.vallist, tostring(val))
1595 function MultiValue.valuelist(self, section)
1596 local val = self:cfgvalue(section)
1598 if not(type(val) == "string") then
1602 return luci.util.split(val, self.delimiter)
1605 function MultiValue.validate(self, val)
1606 val = (type(val) == "table") and val or {val}
1610 for i, value in ipairs(val) do
1611 if luci.util.contains(self.keylist, value) then
1612 result = result and (result .. self.delimiter .. value) or value
1620 StaticList = class(MultiValue)
1622 function StaticList.__init__(self, ...)
1623 MultiValue.__init__(self, ...)
1625 self.valuelist = self.cfgvalue
1627 if not self.override_scheme
1628 and self.map:get_scheme(self.section.sectiontype, self.option) then
1629 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1630 if self.value and vs.values and not self.override_values then
1631 for k, v in pairs(vs.values) do
1638 function StaticList.validate(self, value)
1639 value = (type(value) == "table") and value or {value}
1642 for i, v in ipairs(value) do
1643 if luci.util.contains(self.keylist, v) then
1644 table.insert(valid, v)
1651 DynamicList = class(AbstractValue)
1653 function DynamicList.__init__(self, ...)
1654 AbstractValue.__init__(self, ...)
1655 self.template = "cbi/dynlist"
1661 function DynamicList.reset_values(self)
1666 function DynamicList.value(self, key, val)
1668 table.insert(self.keylist, tostring(key))
1669 table.insert(self.vallist, tostring(val))
1672 function DynamicList.write(self, section, value)
1675 if type(value) == "table" then
1677 for _, x in ipairs(value) do
1678 if x and #x > 0 then
1686 if self.cast == "string" then
1687 value = table.concat(t, " ")
1692 return AbstractValue.write(self, section, value)
1695 function DynamicList.cfgvalue(self, section)
1696 local value = AbstractValue.cfgvalue(self, section)
1698 if type(value) == "string" then
1701 for x in value:gmatch("%S+") do
1712 function DynamicList.formvalue(self, section)
1713 local value = AbstractValue.formvalue(self, section)
1715 if type(value) == "string" then
1716 if self.cast == "string" then
1719 for x in value:gmatch("%S+") do
1733 TextValue - A multi-line value
1736 TextValue = class(AbstractValue)
1738 function TextValue.__init__(self, ...)
1739 AbstractValue.__init__(self, ...)
1740 self.template = "cbi/tvalue"
1746 Button = class(AbstractValue)
1748 function Button.__init__(self, ...)
1749 AbstractValue.__init__(self, ...)
1750 self.template = "cbi/button"
1751 self.inputstyle = nil
1756 FileUpload = class(AbstractValue)
1758 function FileUpload.__init__(self, ...)
1759 AbstractValue.__init__(self, ...)
1760 self.template = "cbi/upload"
1761 if not self.map.upload_fields then
1762 self.map.upload_fields = { self }
1764 self.map.upload_fields[#self.map.upload_fields+1] = self
1768 function FileUpload.formcreated(self, section)
1769 return AbstractValue.formcreated(self, section) or
1770 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1771 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1774 function FileUpload.cfgvalue(self, section)
1775 local val = AbstractValue.cfgvalue(self, section)
1776 if val and fs.access(val) then
1782 function FileUpload.formvalue(self, section)
1783 local val = AbstractValue.formvalue(self, section)
1785 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1786 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1796 function FileUpload.remove(self, section)
1797 local val = AbstractValue.formvalue(self, section)
1798 if val and fs.access(val) then fs.unlink(val) end
1799 return AbstractValue.remove(self, section)
1803 FileBrowser = class(AbstractValue)
1805 function FileBrowser.__init__(self, ...)
1806 AbstractValue.__init__(self, ...)
1807 self.template = "cbi/browser"