2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
30 local util = require("luci.util")
34 --local event = require "luci.sys.event"
35 local fs = require("nixio.fs")
36 local uci = require("luci.model.uci")
37 local datatypes = require("luci.cbi.datatypes")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
65 if fs.access(cbidir..cbimap..".lua") then
66 func, err = loadfile(cbidir..cbimap..".lua")
67 elseif fs.access(cbimap) then
68 func, err = loadfile(cbimap)
70 func, err = nil, "Model '" .. cbimap .. "' not found!"
75 luci.i18n.loadc("base")
78 translate=i18n.translate,
79 translatef=i18n.translatef,
83 setfenv(func, setmetatable(env, {__index =
85 return rawget(tbl, key) or _M[key] or _G[key]
88 local maps = { func() }
90 local has_upload = false
92 for i, map in ipairs(maps) do
93 if not instanceof(map, Node) then
94 error("CBI map returns no valid map object!")
98 if map.upload_fields then
100 for _, field in ipairs(map.upload_fields) do
102 field.config .. '.' ..
103 field.section.sectiontype .. '.' ..
112 local uci = luci.model.uci.cursor()
113 local prm = luci.http.context.request.message.params
116 luci.http.setfilehandler(
117 function( field, chunk, eof )
118 if not field then return end
119 if field.name and not cbid then
120 local c, s, o = field.name:gmatch(
121 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
124 if c and s and o then
125 local t = uci:get( c, s )
126 if t and uploads[c.."."..t.."."..o] then
127 local path = upldir .. field.name
128 fd = io.open(path, "w")
137 if field.name == cbid and fd then
154 -- Node pseudo abstract class
157 function Node.__init__(self, title, description)
159 self.title = title or ""
160 self.description = description or ""
161 self.template = "cbi/node"
165 function Node._run_hook(self, hook)
166 if type(self[hook]) == "function" then
167 return self[hook](self)
171 function Node._run_hooks(self, ...)
174 for _, f in ipairs(arg) do
175 if type(self[f]) == "function" then
184 function Node.prepare(self, ...)
185 for k, child in ipairs(self.children) do
190 -- Append child nodes
191 function Node.append(self, obj)
192 table.insert(self.children, obj)
195 -- Parse this node and its children
196 function Node.parse(self, ...)
197 for k, child in ipairs(self.children) do
203 function Node.render(self, scope)
207 luci.template.render(self.template, scope)
210 -- Render the children
211 function Node.render_children(self, ...)
212 for k, node in ipairs(self.children) do
219 A simple template element
221 Template = class(Node)
223 function Template.__init__(self, template)
225 self.template = template
228 function Template.render(self)
229 luci.template.render(self.template, {self=self})
232 function Template.parse(self, readinput)
233 self.readinput = (readinput ~= false)
234 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
239 Map - A map describing a configuration file
243 function Map.__init__(self, config, ...)
244 Node.__init__(self, ...)
247 self.parsechain = {self.config}
248 self.template = "cbi/map"
249 self.apply_on_parse = nil
250 self.readinput = true
254 self.uci = uci.cursor()
259 if not self.uci:load(self.config) then
260 error("Unable to read UCI data: " .. self.config)
264 function Map.formvalue(self, key)
265 return self.readinput and luci.http.formvalue(key)
268 function Map.formvaluetable(self, key)
269 return self.readinput and luci.http.formvaluetable(key) or {}
272 function Map.get_scheme(self, sectiontype, option)
274 return self.scheme and self.scheme.sections[sectiontype]
276 return self.scheme and self.scheme.variables[sectiontype]
277 and self.scheme.variables[sectiontype][option]
281 function Map.submitstate(self)
282 return self:formvalue("cbi.submit")
285 -- Chain foreign config
286 function Map.chain(self, config)
287 table.insert(self.parsechain, config)
290 function Map.state_handler(self, state)
294 -- Use optimized UCI writing
295 function Map.parse(self, readinput, ...)
296 self.readinput = (readinput ~= false)
297 self:_run_hooks("on_parse")
299 if self:formvalue("cbi.skip") then
300 self.state = FORM_SKIP
301 return self:state_handler(self.state)
304 Node.parse(self, ...)
307 for i, config in ipairs(self.parsechain) do
308 self.uci:save(config)
310 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
311 self:_run_hooks("on_before_commit")
312 for i, config in ipairs(self.parsechain) do
313 self.uci:commit(config)
315 -- Refresh data because commit changes section names
316 self.uci:load(config)
318 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
319 if self.apply_on_parse then
320 self.uci:apply(self.parsechain)
321 self:_run_hooks("on_apply", "on_after_apply")
323 self._apply = function()
324 local cmd = self.uci:apply(self.parsechain, true)
330 Node.parse(self, true)
333 for i, config in ipairs(self.parsechain) do
334 self.uci:unload(config)
336 if type(self.commit_handler) == "function" then
337 self:commit_handler(self:submitstate())
341 if self:submitstate() then
342 if not self.save then
343 self.state = FORM_INVALID
344 elseif self.proceed then
345 self.state = FORM_PROCEED
347 self.state = self.changed and FORM_CHANGED or FORM_VALID
350 self.state = FORM_NODATA
353 return self:state_handler(self.state)
356 function Map.render(self, ...)
357 self:_run_hooks("on_init")
358 Node.render(self, ...)
360 local fp = self._apply()
363 self:_run_hooks("on_apply")
367 -- Creates a child section
368 function Map.section(self, class, ...)
369 if instanceof(class, AbstractSection) then
370 local obj = class(self, ...)
374 error("class must be a descendent of AbstractSection")
379 function Map.add(self, sectiontype)
380 return self.uci:add(self.config, sectiontype)
384 function Map.set(self, section, option, value)
386 return self.uci:set(self.config, section, option, value)
388 return self.uci:set(self.config, section, value)
393 function Map.del(self, section, option)
395 return self.uci:delete(self.config, section, option)
397 return self.uci:delete(self.config, section)
402 function Map.get(self, section, option)
404 return self.uci:get_all(self.config)
406 return self.uci:get(self.config, section, option)
408 return self.uci:get_all(self.config, section)
415 Compound = class(Node)
417 function Compound.__init__(self, ...)
419 self.template = "cbi/compound"
420 self.children = {...}
423 function Compound.populate_delegator(self, delegator)
424 for _, v in ipairs(self.children) do
425 v.delegator = delegator
429 function Compound.parse(self, ...)
430 local cstate, state = 0
432 for k, child in ipairs(self.children) do
433 cstate = child:parse(...)
434 state = (not state or cstate < state) and cstate or state
442 Delegator - Node controller
444 Delegator = class(Node)
445 function Delegator.__init__(self, ...)
446 Node.__init__(self, ...)
448 self.defaultpath = {}
449 self.pageaction = false
450 self.readinput = true
451 self.allow_reset = false
452 self.allow_cancel = false
453 self.allow_back = false
454 self.allow_finish = false
455 self.template = "cbi/delegator"
458 function Delegator.set(self, name, node)
459 assert(not self.nodes[name], "Duplicate entry")
461 self.nodes[name] = node
464 function Delegator.add(self, name, node)
465 node = self:set(name, node)
466 self.defaultpath[#self.defaultpath+1] = name
469 function Delegator.insert_after(self, name, after)
470 local n = #self.chain + 1
471 for k, v in ipairs(self.chain) do
477 table.insert(self.chain, n, name)
480 function Delegator.set_route(self, ...)
481 local n, chain, route = 0, self.chain, {...}
483 if chain[i] == self.current then
492 for i = n + 1, #chain do
497 function Delegator.get(self, name)
498 local node = self.nodes[name]
500 if type(node) == "string" then
501 node = load(node, name)
504 if type(node) == "table" and getmetatable(node) == nil then
505 node = Compound(unpack(node))
511 function Delegator.parse(self, ...)
512 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
513 if self:_run_hooks("on_cancel") then
518 if not Map.formvalue(self, "cbi.delg.current") then
519 self:_run_hooks("on_init")
523 self.chain = self.chain or self:get_chain()
524 self.current = self.current or self:get_active()
525 self.active = self.active or self:get(self.current)
526 assert(self.active, "Invalid state")
528 local stat = FORM_DONE
529 if type(self.active) ~= "function" then
530 self.active:populate_delegator(self)
531 stat = self.active:parse()
536 if stat > FORM_PROCEED then
537 if Map.formvalue(self, "cbi.delg.back") then
538 newcurrent = self:get_prev(self.current)
540 newcurrent = self:get_next(self.current)
542 elseif stat < FORM_PROCEED then
547 if not Map.formvalue(self, "cbi.submit") then
549 elseif stat > FORM_PROCEED
550 and (not newcurrent or not self:get(newcurrent)) then
551 return self:_run_hook("on_done") or FORM_DONE
553 self.current = newcurrent or self.current
554 self.active = self:get(self.current)
555 if type(self.active) ~= "function" then
556 self.active:populate_delegator(self)
557 local stat = self.active:parse(false)
558 if stat == FORM_SKIP then
559 return self:parse(...)
564 return self:parse(...)
569 function Delegator.get_next(self, state)
570 for k, v in ipairs(self.chain) do
572 return self.chain[k+1]
577 function Delegator.get_prev(self, state)
578 for k, v in ipairs(self.chain) do
580 return self.chain[k-1]
585 function Delegator.get_chain(self)
586 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
587 return type(x) == "table" and x or {x}
590 function Delegator.get_active(self)
591 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
599 Page.__init__ = Node.__init__
600 Page.parse = function() end
604 SimpleForm - A Simple non-UCI form
606 SimpleForm = class(Node)
608 function SimpleForm.__init__(self, config, title, description, data)
609 Node.__init__(self, title, description)
611 self.data = data or {}
612 self.template = "cbi/simpleform"
614 self.pageaction = false
615 self.readinput = true
618 SimpleForm.formvalue = Map.formvalue
619 SimpleForm.formvaluetable = Map.formvaluetable
621 function SimpleForm.parse(self, readinput, ...)
622 self.readinput = (readinput ~= false)
624 if self:formvalue("cbi.skip") then
628 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
632 if self:submitstate() then
633 Node.parse(self, 1, ...)
637 for k, j in ipairs(self.children) do
638 for i, v in ipairs(j.children) do
640 and (not v.tag_missing or not v.tag_missing[1])
641 and (not v.tag_invalid or not v.tag_invalid[1])
647 not self:submitstate() and FORM_NODATA
648 or valid and FORM_VALID
651 self.dorender = not self.handle
653 local nrender, nstate = self:handle(state, self.data)
654 self.dorender = self.dorender or (nrender ~= false)
655 state = nstate or state
660 function SimpleForm.render(self, ...)
661 if self.dorender then
662 Node.render(self, ...)
666 function SimpleForm.submitstate(self)
667 return self:formvalue("cbi.submit")
670 function SimpleForm.section(self, class, ...)
671 if instanceof(class, AbstractSection) then
672 local obj = class(self, ...)
676 error("class must be a descendent of AbstractSection")
680 -- Creates a child field
681 function SimpleForm.field(self, class, ...)
683 for k, v in ipairs(self.children) do
684 if instanceof(v, SimpleSection) then
690 section = self:section(SimpleSection)
693 if instanceof(class, AbstractValue) then
694 local obj = class(self, section, ...)
695 obj.track_missing = true
699 error("class must be a descendent of AbstractValue")
703 function SimpleForm.set(self, section, option, value)
704 self.data[option] = value
708 function SimpleForm.del(self, section, option)
709 self.data[option] = nil
713 function SimpleForm.get(self, section, option)
714 return self.data[option]
718 function SimpleForm.get_scheme()
723 Form = class(SimpleForm)
725 function Form.__init__(self, ...)
726 SimpleForm.__init__(self, ...)
734 AbstractSection = class(Node)
736 function AbstractSection.__init__(self, map, sectiontype, ...)
737 Node.__init__(self, ...)
738 self.sectiontype = sectiontype
740 self.config = map.config
745 self.tag_invalid = {}
746 self.tag_deperror = {}
750 self.addremove = false
754 -- Define a tab for the section
755 function AbstractSection.tab(self, tab, title, desc)
756 self.tabs = self.tabs or { }
757 self.tab_names = self.tab_names or { }
759 self.tab_names[#self.tab_names+1] = tab
767 -- Appends a new option
768 function AbstractSection.option(self, class, option, ...)
769 if instanceof(class, AbstractValue) then
770 local obj = class(self.map, self, option, ...)
772 self.fields[option] = obj
774 elseif class == true then
775 error("No valid class was given and autodetection failed.")
777 error("class must be a descendant of AbstractValue")
781 -- Appends a new tabbed option
782 function AbstractSection.taboption(self, tab, ...)
784 assert(tab and self.tabs and self.tabs[tab],
785 "Cannot assign option to not existing tab %q" % tostring(tab))
787 local l = self.tabs[tab].childs
788 local o = AbstractSection.option(self, ...)
790 if o then l[#l+1] = o end
795 -- Render a single tab
796 function AbstractSection.render_tab(self, tab, ...)
798 assert(tab and self.tabs and self.tabs[tab],
799 "Cannot render not existing tab %q" % tostring(tab))
801 for _, node in ipairs(self.tabs[tab].childs) do
806 -- Parse optional options
807 function AbstractSection.parse_optionals(self, section)
808 if not self.optional then
812 self.optionals[section] = {}
814 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
815 for k,v in ipairs(self.children) do
816 if v.optional and not v:cfgvalue(section) then
817 if field == v.option then
819 self.map.proceed = true
821 table.insert(self.optionals[section], v)
826 if field and #field > 0 and self.dynamic then
827 self:add_dynamic(field)
831 -- Add a dynamic option
832 function AbstractSection.add_dynamic(self, field, optional)
833 local o = self:option(Value, field, field)
834 o.optional = optional
837 -- Parse all dynamic options
838 function AbstractSection.parse_dynamic(self, section)
839 if not self.dynamic then
843 local arr = luci.util.clone(self:cfgvalue(section))
844 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
845 for k, v in pairs(form) do
849 for key,val in pairs(arr) do
852 for i,c in ipairs(self.children) do
853 if c.option == key then
858 if create and key:sub(1, 1) ~= "." then
859 self.map.proceed = true
860 self:add_dynamic(key, true)
865 -- Returns the section's UCI table
866 function AbstractSection.cfgvalue(self, section)
867 return self.map:get(section)
871 function AbstractSection.push_events(self)
872 --luci.util.append(self.map.events, self.events)
873 self.map.changed = true
876 -- Removes the section
877 function AbstractSection.remove(self, section)
878 self.map.proceed = true
879 return self.map:del(section)
882 -- Creates the section
883 function AbstractSection.create(self, section)
887 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
889 section = self.map:add(self.sectiontype)
894 for k,v in pairs(self.children) do
896 self.map:set(section, v.option, v.default)
900 for k,v in pairs(self.defaults) do
901 self.map:set(section, k, v)
905 self.map.proceed = true
911 SimpleSection = class(AbstractSection)
913 function SimpleSection.__init__(self, form, ...)
914 AbstractSection.__init__(self, form, nil, ...)
915 self.template = "cbi/nullsection"
919 Table = class(AbstractSection)
921 function Table.__init__(self, form, data, ...)
922 local datasource = {}
924 datasource.config = "table"
925 self.data = data or {}
927 datasource.formvalue = Map.formvalue
928 datasource.formvaluetable = Map.formvaluetable
929 datasource.readinput = true
931 function datasource.get(self, section, option)
932 return tself.data[section] and tself.data[section][option]
935 function datasource.submitstate(self)
936 return Map.formvalue(self, "cbi.submit")
939 function datasource.del(...)
943 function datasource.get_scheme()
947 AbstractSection.__init__(self, datasource, "table", ...)
948 self.template = "cbi/tblsection"
949 self.rowcolors = true
950 self.anonymous = true
953 function Table.parse(self, readinput)
954 self.map.readinput = (readinput ~= false)
955 for i, k in ipairs(self:cfgsections()) do
956 if self.map:submitstate() then
962 function Table.cfgsections(self)
965 for i, v in luci.util.kspairs(self.data) do
966 table.insert(sections, i)
972 function Table.update(self, data)
979 NamedSection - A fixed configuration section defined by its name
981 NamedSection = class(AbstractSection)
983 function NamedSection.__init__(self, map, section, stype, ...)
984 AbstractSection.__init__(self, map, stype, ...)
987 self.addremove = false
988 self.template = "cbi/nsection"
989 self.section = section
992 function NamedSection.parse(self, novld)
993 local s = self.section
994 local active = self:cfgvalue(s)
996 if self.addremove then
997 local path = self.config.."."..s
998 if active then -- Remove the section
999 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1003 else -- Create and apply default values
1004 if self.map:formvalue("cbi.cns."..path) then
1012 AbstractSection.parse_dynamic(self, s)
1013 if self.map:submitstate() then
1016 AbstractSection.parse_optionals(self, s)
1018 if self.changed then
1026 TypedSection - A (set of) configuration section(s) defined by the type
1027 addremove: Defines whether the user can add/remove sections of this type
1028 anonymous: Allow creating anonymous sections
1029 validate: a validation function returning nil if the section is invalid
1031 TypedSection = class(AbstractSection)
1033 function TypedSection.__init__(self, map, type, ...)
1034 AbstractSection.__init__(self, map, type, ...)
1036 self.template = "cbi/tsection"
1038 self.anonymous = false
1041 -- Return all matching UCI sections for this TypedSection
1042 function TypedSection.cfgsections(self)
1044 self.map.uci:foreach(self.map.config, self.sectiontype,
1046 if self:checkscope(section[".name"]) then
1047 table.insert(sections, section[".name"])
1054 -- Limits scope to sections that have certain option => value pairs
1055 function TypedSection.depends(self, option, value)
1056 table.insert(self.deps, {option=option, value=value})
1059 function TypedSection.parse(self, novld)
1060 if self.addremove then
1062 local crval = REMOVE_PREFIX .. self.config
1063 local name = self.map:formvaluetable(crval)
1064 for k,v in pairs(name) do
1065 if k:sub(-2) == ".x" then
1066 k = k:sub(1, #k - 2)
1068 if self:cfgvalue(k) and self:checkscope(k) then
1075 for i, k in ipairs(self:cfgsections()) do
1076 AbstractSection.parse_dynamic(self, k)
1077 if self.map:submitstate() then
1078 Node.parse(self, k, novld)
1080 AbstractSection.parse_optionals(self, k)
1083 if self.addremove then
1086 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1087 local name = self.map:formvalue(crval)
1088 if self.anonymous then
1090 created = self:create()
1094 -- Ignore if it already exists
1095 if self:cfgvalue(name) then
1099 name = self:checkscope(name)
1102 self.err_invalid = true
1105 if name and #name > 0 then
1106 created = self:create(name) and name
1108 self.invalid_cts = true
1115 AbstractSection.parse_optionals(self, created)
1119 if created or self.changed then
1124 -- Verifies scope of sections
1125 function TypedSection.checkscope(self, section)
1126 -- Check if we are not excluded
1127 if self.filter and not self:filter(section) then
1131 -- Check if at least one dependency is met
1132 if #self.deps > 0 and self:cfgvalue(section) then
1135 for k, v in ipairs(self.deps) do
1136 if self:cfgvalue(section)[v.option] == v.value then
1146 return self:validate(section)
1150 -- Dummy validate function
1151 function TypedSection.validate(self, section)
1157 AbstractValue - An abstract Value Type
1158 null: Value can be empty
1159 valid: A function returning the value if it is valid otherwise nil
1160 depends: A table of option => value pairs of which one must be true
1161 default: The default value
1162 size: The size of the input fields
1163 rmempty: Unset value if empty
1164 optional: This value is optional (see AbstractSection.optionals)
1166 AbstractValue = class(Node)
1168 function AbstractValue.__init__(self, map, section, option, ...)
1169 Node.__init__(self, ...)
1170 self.section = section
1171 self.option = option
1173 self.config = map.config
1174 self.tag_invalid = {}
1175 self.tag_missing = {}
1176 self.tag_reqerror = {}
1180 --self.cast = "string"
1182 self.track_missing = false
1186 self.optional = false
1189 function AbstractValue.prepare(self)
1190 self.cast = self.cast or "string"
1193 -- Add a dependencie to another section field
1194 function AbstractValue.depends(self, field, value)
1196 if type(field) == "string" then
1203 table.insert(self.deps, {deps=deps, add=""})
1206 -- Generates the unique CBID
1207 function AbstractValue.cbid(self, section)
1208 return "cbid."..self.map.config.."."..section.."."..self.option
1211 -- Return whether this object should be created
1212 function AbstractValue.formcreated(self, section)
1213 local key = "cbi.opt."..self.config.."."..section
1214 return (self.map:formvalue(key) == self.option)
1217 -- Returns the formvalue for this object
1218 function AbstractValue.formvalue(self, section)
1219 return self.map:formvalue(self:cbid(section))
1222 function AbstractValue.additional(self, value)
1223 self.optional = value
1226 function AbstractValue.mandatory(self, value)
1227 self.rmempty = not value
1230 function AbstractValue.parse(self, section, novld)
1231 local fvalue = self:formvalue(section)
1232 local cvalue = self:cfgvalue(section)
1234 -- If favlue and cvalue are both tables and have the same content
1235 -- make them identical
1236 if type(fvalue) == "table" and type(cvalue) == "table" then
1237 local equal = #fvalue == #cvalue
1240 if cvalue[i] ~= fvalue[i] then
1250 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1251 fvalue = self:transform(self:validate(fvalue, section))
1252 if not fvalue and not novld then
1254 self.error[section] = "invalid"
1256 self.error = { [section] = "invalid" }
1258 if self.section.error then
1259 table.insert(self.section.error[section], "invalid")
1261 self.section.error = {[section] = {"invalid"}}
1263 self.map.save = false
1265 if fvalue and not (fvalue == cvalue) then
1266 if self:write(section, fvalue) then
1268 self.section.changed = true
1269 --luci.util.append(self.map.events, self.events)
1272 else -- Unset the UCI or error
1273 if self.rmempty or self.optional then
1274 if self:remove(section) then
1276 self.section.changed = true
1277 --luci.util.append(self.map.events, self.events)
1279 elseif cvalue ~= fvalue and not novld then
1280 self:write(section, fvalue or "")
1282 self.error[section] = "missing"
1284 self.error = { [section] = "missing" }
1286 self.map.save = false
1291 -- Render if this value exists or if it is mandatory
1292 function AbstractValue.render(self, s, scope)
1293 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1296 scope.cbid = self:cbid(s)
1297 scope.striptags = luci.util.striptags
1298 scope.pcdata = luci.util.pcdata
1300 scope.ifattr = function(cond,key,val)
1302 return string.format(
1303 ' %s="%s"', tostring(key),
1304 luci.util.pcdata(tostring( val
1306 or (type(self[key]) ~= "function" and self[key])
1314 scope.attr = function(...)
1315 return scope.ifattr( true, ... )
1318 Node.render(self, scope)
1322 -- Return the UCI value of this object
1323 function AbstractValue.cfgvalue(self, section)
1324 local value = (self.error and self.error[section] == "invalid")
1325 and self:formvalue(section) or self.map:get(section, self.option)
1328 elseif not self.cast or self.cast == type(value) then
1330 elseif self.cast == "string" then
1331 if type(value) == "table" then
1334 elseif self.cast == "table" then
1335 return luci.util.split(value, "%s+", nil, true)
1339 -- Validate the form value
1340 function AbstractValue.validate(self, value)
1341 if self.datatype and value and datatypes[self.datatype] then
1342 if datatypes[self.datatype](value) then
1350 AbstractValue.transform = AbstractValue.validate
1354 function AbstractValue.write(self, section, value)
1355 return self.map:set(section, self.option, value)
1359 function AbstractValue.remove(self, section)
1360 return self.map:del(section, self.option)
1367 Value - A one-line value
1368 maxlength: The maximum length
1370 Value = class(AbstractValue)
1372 function Value.__init__(self, ...)
1373 AbstractValue.__init__(self, ...)
1374 self.template = "cbi/value"
1379 function Value.value(self, key, val)
1381 table.insert(self.keylist, tostring(key))
1382 table.insert(self.vallist, tostring(val))
1386 -- DummyValue - This does nothing except being there
1387 DummyValue = class(AbstractValue)
1389 function DummyValue.__init__(self, ...)
1390 AbstractValue.__init__(self, ...)
1391 self.template = "cbi/dvalue"
1395 function DummyValue.cfgvalue(self, section)
1398 if type(self.value) == "function" then
1399 value = self:value(section)
1404 value = AbstractValue.cfgvalue(self, section)
1409 function DummyValue.parse(self)
1415 Flag - A flag being enabled or disabled
1417 Flag = class(AbstractValue)
1419 function Flag.__init__(self, ...)
1420 AbstractValue.__init__(self, ...)
1421 self.template = "cbi/fvalue"
1427 -- A flag can only have two states: set or unset
1428 function Flag.parse(self, section)
1429 local fvalue = self:formvalue(section)
1432 fvalue = self.enabled
1434 fvalue = self.disabled
1437 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1438 if not(fvalue == self:cfgvalue(section)) then
1439 self:write(section, fvalue)
1442 self:remove(section)
1449 ListValue - A one-line value predefined in a list
1450 widget: The widget that will be used (select, radio)
1452 ListValue = class(AbstractValue)
1454 function ListValue.__init__(self, ...)
1455 AbstractValue.__init__(self, ...)
1456 self.template = "cbi/lvalue"
1461 self.widget = "select"
1464 function ListValue.value(self, key, val, ...)
1465 if luci.util.contains(self.keylist, key) then
1470 table.insert(self.keylist, tostring(key))
1471 table.insert(self.vallist, tostring(val))
1473 for i, deps in ipairs({...}) do
1474 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1478 function ListValue.validate(self, val)
1479 if luci.util.contains(self.keylist, val) then
1489 MultiValue - Multiple delimited values
1490 widget: The widget that will be used (select, checkbox)
1491 delimiter: The delimiter that will separate the values (default: " ")
1493 MultiValue = class(AbstractValue)
1495 function MultiValue.__init__(self, ...)
1496 AbstractValue.__init__(self, ...)
1497 self.template = "cbi/mvalue"
1502 self.widget = "checkbox"
1503 self.delimiter = " "
1506 function MultiValue.render(self, ...)
1507 if self.widget == "select" and not self.size then
1508 self.size = #self.vallist
1511 AbstractValue.render(self, ...)
1514 function MultiValue.value(self, key, val)
1515 if luci.util.contains(self.keylist, key) then
1520 table.insert(self.keylist, tostring(key))
1521 table.insert(self.vallist, tostring(val))
1524 function MultiValue.valuelist(self, section)
1525 local val = self:cfgvalue(section)
1527 if not(type(val) == "string") then
1531 return luci.util.split(val, self.delimiter)
1534 function MultiValue.validate(self, val)
1535 val = (type(val) == "table") and val or {val}
1539 for i, value in ipairs(val) do
1540 if luci.util.contains(self.keylist, value) then
1541 result = result and (result .. self.delimiter .. value) or value
1549 StaticList = class(MultiValue)
1551 function StaticList.__init__(self, ...)
1552 MultiValue.__init__(self, ...)
1554 self.valuelist = self.cfgvalue
1556 if not self.override_scheme
1557 and self.map:get_scheme(self.section.sectiontype, self.option) then
1558 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1559 if self.value and vs.values and not self.override_values then
1560 for k, v in pairs(vs.values) do
1567 function StaticList.validate(self, value)
1568 value = (type(value) == "table") and value or {value}
1571 for i, v in ipairs(value) do
1572 if luci.util.contains(self.keylist, v) then
1573 table.insert(valid, v)
1580 DynamicList = class(AbstractValue)
1582 function DynamicList.__init__(self, ...)
1583 AbstractValue.__init__(self, ...)
1584 self.template = "cbi/dynlist"
1590 function DynamicList.value(self, key, val)
1592 table.insert(self.keylist, tostring(key))
1593 table.insert(self.vallist, tostring(val))
1596 function DynamicList.write(self, ...)
1597 self.map.proceed = true
1598 return AbstractValue.write(self, ...)
1601 function DynamicList.formvalue(self, section)
1602 local value = AbstractValue.formvalue(self, section)
1603 value = (type(value) == "table") and value or {value}
1606 for i, v in ipairs(value) do
1608 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1609 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1610 table.insert(valid, v)
1619 TextValue - A multi-line value
1622 TextValue = class(AbstractValue)
1624 function TextValue.__init__(self, ...)
1625 AbstractValue.__init__(self, ...)
1626 self.template = "cbi/tvalue"
1632 Button = class(AbstractValue)
1634 function Button.__init__(self, ...)
1635 AbstractValue.__init__(self, ...)
1636 self.template = "cbi/button"
1637 self.inputstyle = nil
1642 FileUpload = class(AbstractValue)
1644 function FileUpload.__init__(self, ...)
1645 AbstractValue.__init__(self, ...)
1646 self.template = "cbi/upload"
1647 if not self.map.upload_fields then
1648 self.map.upload_fields = { self }
1650 self.map.upload_fields[#self.map.upload_fields+1] = self
1654 function FileUpload.formcreated(self, section)
1655 return AbstractValue.formcreated(self, section) or
1656 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1657 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1660 function FileUpload.cfgvalue(self, section)
1661 local val = AbstractValue.cfgvalue(self, section)
1662 if val and fs.access(val) then
1668 function FileUpload.formvalue(self, section)
1669 local val = AbstractValue.formvalue(self, section)
1671 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1672 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1682 function FileUpload.remove(self, section)
1683 local val = AbstractValue.formvalue(self, section)
1684 if val and fs.access(val) then fs.unlink(val) end
1685 return AbstractValue.remove(self, section)
1689 FileBrowser = class(AbstractValue)
1691 function FileBrowser.__init__(self, ...)
1692 AbstractValue.__init__(self, ...)
1693 self.template = "cbi/browser"