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")
34 local uci = require("luci.model.uci")
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
44 CREATE_PREFIX = "cbi.cts."
45 REMOVE_PREFIX = "cbi.rts."
47 -- Loads a CBI map from given file, creating an environment and returns it
48 function load(cbimap, ...)
50 local i18n = require "luci.i18n"
51 require("luci.config")
54 local cbidir = luci.util.libpath() .. "/model/cbi/"
55 local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua")
58 luci.i18n.loadc("cbi")
59 luci.i18n.loadc("uvl")
62 translate=i18n.translate,
63 translatef=i18n.translatef,
67 setfenv(func, setmetatable(env, {__index =
69 return rawget(tbl, key) or _M[key] or _G[key]
74 for i, map in ipairs(maps) do
75 if not instanceof(map, Node) then
76 error("CBI map returns no valid map object!")
86 local function _uvl_validate_section(node, name)
87 local co = node.map:get()
89 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
90 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
92 local function tag_fields(e)
93 if e.option and node.fields[e.option] then
94 if node.fields[e.option].error then
95 node.fields[e.option].error[name] = e
97 node.fields[e.option].error = { [name] = e }
100 for _, c in ipairs(e.childs) do tag_fields(c) end
104 local function tag_section(e)
106 for _, c in ipairs(e.childs) do
107 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
108 table.insert( s, c.childs[1]:string() )
110 table.insert( s, c:string() )
117 node.error = { [name] = s }
122 local stat, err = node.map.validator:validate_section(node.config, name, co)
124 node.map.save = false
131 local function _uvl_strip_remote_dependencies(deps)
134 for k, v in pairs(deps) do
135 k = k:gsub("%$config%.%$section%.", "")
136 if k:match("^[%w_]+$") and type(v) == "string" then
145 -- Node pseudo abstract class
148 function Node.__init__(self, title, description)
150 self.title = title or ""
151 self.description = description or ""
152 self.template = "cbi/node"
156 function Node._i18n(self, config, section, option, title, description)
159 if type(luci.i18n) == "table" then
161 local key = config and config:gsub("[^%w]+", "") or ""
163 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
164 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
166 self.title = title or luci.i18n.translate( key, option or section or config )
167 self.description = description or luci.i18n.translate( key .. "_desc", "" )
172 function Node.prepare(self, ...)
173 for k, child in ipairs(self.children) do
178 -- Append child nodes
179 function Node.append(self, obj)
180 table.insert(self.children, obj)
183 -- Parse this node and its children
184 function Node.parse(self, ...)
185 for k, child in ipairs(self.children) do
191 function Node.render(self, scope)
195 luci.template.render(self.template, scope)
198 -- Render the children
199 function Node.render_children(self, ...)
200 for k, node in ipairs(self.children) do
207 A simple template element
209 Template = class(Node)
211 function Template.__init__(self, template)
213 self.template = template
216 function Template.render(self)
217 luci.template.render(self.template, {self=self})
222 Map - A map describing a configuration file
226 function Map.__init__(self, config, ...)
227 Node.__init__(self, ...)
228 Node._i18n(self, config, nil, nil, ...)
231 self.parsechain = {self.config}
232 self.template = "cbi/map"
233 self.apply_on_parse = nil
234 self.uci = uci.cursor()
236 if not self.uci:load(self.config) then
237 error("Unable to read UCI data: " .. self.config)
240 self.validator = luci.uvl.UVL()
241 self.scheme = self.validator:get_scheme(self.config)
245 function Map.get_scheme(self, sectiontype, option)
247 return self.scheme and self.scheme.sections[sectiontype]
249 return self.scheme and self.scheme.variables[sectiontype]
250 and self.scheme.variables[sectiontype][option]
255 -- Chain foreign config
256 function Map.chain(self, config)
257 table.insert(self.parsechain, config)
260 -- Use optimized UCI writing
261 function Map.parse(self)
265 for i, config in ipairs(self.parsechain) do
266 self.uci:save(config)
268 if luci.http.formvalue("cbi.apply") then
269 for i, config in ipairs(self.parsechain) do
270 self.uci:commit(config)
272 -- Refresh data because commit changes section names
273 self.uci:load(config)
275 if self.apply_on_parse then
276 self.uci:apply(self.parsechain)
278 self._apply = function()
279 local cmd = self.uci:apply(self.parsechain, true)
285 Node.parse(self, true)
288 for i, config in ipairs(self.parsechain) do
289 self.uci:unload(config)
294 function Map.render(self, ...)
295 Node.render(self, ...)
297 local fp = self._apply()
303 -- Creates a child section
304 function Map.section(self, class, ...)
305 if instanceof(class, AbstractSection) then
306 local obj = class(self, ...)
310 error("class must be a descendent of AbstractSection")
315 function Map.add(self, sectiontype)
316 return self.uci:add(self.config, sectiontype)
320 function Map.set(self, section, option, value)
322 return self.uci:set(self.config, section, option, value)
324 return self.uci:set(self.config, section, value)
329 function Map.del(self, section, option)
331 return self.uci:delete(self.config, section, option)
333 return self.uci:delete(self.config, section)
338 function Map.get(self, section, option)
340 return self.uci:get_all(self.config)
342 return self.uci:get(self.config, section, option)
344 return self.uci:get_all(self.config, section)
354 Page.__init__ = Node.__init__
355 Page.parse = function() end
359 SimpleForm - A Simple non-UCI form
361 SimpleForm = class(Node)
363 function SimpleForm.__init__(self, config, title, description, data)
364 Node.__init__(self, title, description)
366 self.data = data or {}
367 self.template = "cbi/simpleform"
371 function SimpleForm.parse(self, ...)
372 if luci.http.formvalue("cbi.submit") then
373 Node.parse(self, 1, ...)
377 for k, j in ipairs(self.children) do
378 for i, v in ipairs(j.children) do
380 and (not v.tag_missing or not v.tag_missing[1])
381 and (not v.tag_invalid or not v.tag_invalid[1])
386 not luci.http.formvalue("cbi.submit") and 0
390 self.dorender = not self.handle or self:handle(state, self.data) ~= false
393 function SimpleForm.render(self, ...)
394 if self.dorender then
395 Node.render(self, ...)
399 function SimpleForm.section(self, class, ...)
400 if instanceof(class, AbstractSection) then
401 local obj = class(self, ...)
405 error("class must be a descendent of AbstractSection")
409 -- Creates a child field
410 function SimpleForm.field(self, class, ...)
412 for k, v in ipairs(self.children) do
413 if instanceof(v, SimpleSection) then
419 section = self:section(SimpleSection)
422 if instanceof(class, AbstractValue) then
423 local obj = class(self, section, ...)
424 obj.track_missing = true
428 error("class must be a descendent of AbstractValue")
432 function SimpleForm.set(self, section, option, value)
433 self.data[option] = value
437 function SimpleForm.del(self, section, option)
438 self.data[option] = nil
442 function SimpleForm.get(self, section, option)
443 return self.data[option]
447 function SimpleForm.get_scheme()
456 AbstractSection = class(Node)
458 function AbstractSection.__init__(self, map, sectiontype, ...)
459 Node.__init__(self, ...)
460 self.sectiontype = sectiontype
462 self.config = map.config
467 self.tag_invalid = {}
468 self.tag_deperror = {}
471 self.addremove = false
475 -- Appends a new option
476 function AbstractSection.option(self, class, option, ...)
477 -- Autodetect from UVL
478 if class == true and self.map:get_scheme(self.sectiontype, option) then
479 local vs = self.map:get_scheme(self.sectiontype, option)
480 if vs.type == "boolean" then
482 elseif vs.type == "list" then
484 elseif vs.type == "enum" or vs.type == "reference" then
491 if instanceof(class, AbstractValue) then
492 local obj = class(self.map, self, option, ...)
494 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
497 self.fields[option] = obj
499 elseif class == true then
500 error("No valid class was given and autodetection failed.")
502 error("class must be a descendant of AbstractValue")
506 -- Parse optional options
507 function AbstractSection.parse_optionals(self, section)
508 if not self.optional then
512 self.optionals[section] = {}
514 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
515 for k,v in ipairs(self.children) do
516 if v.optional and not v:cfgvalue(section) then
517 if field == v.option then
520 table.insert(self.optionals[section], v)
525 if field and #field > 0 and self.dynamic then
526 self:add_dynamic(field)
530 -- Add a dynamic option
531 function AbstractSection.add_dynamic(self, field, optional)
532 local o = self:option(Value, field, field)
533 o.optional = optional
536 -- Parse all dynamic options
537 function AbstractSection.parse_dynamic(self, section)
538 if not self.dynamic then
542 local arr = luci.util.clone(self:cfgvalue(section))
543 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
544 for k, v in pairs(form) do
548 for key,val in pairs(arr) do
551 for i,c in ipairs(self.children) do
552 if c.option == key then
557 if create and key:sub(1, 1) ~= "." then
558 self:add_dynamic(key, true)
563 -- Returns the section's UCI table
564 function AbstractSection.cfgvalue(self, section)
565 return self.map:get(section)
568 -- Removes the section
569 function AbstractSection.remove(self, section)
570 return self.map:del(section)
573 -- Creates the section
574 function AbstractSection.create(self, section)
578 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
580 section = self.map:add(self.sectiontype)
585 for k,v in pairs(self.children) do
587 self.map:set(section, v.option, v.default)
591 for k,v in pairs(self.defaults) do
592 self.map:set(section, k, v)
600 SimpleSection = class(AbstractSection)
602 function SimpleSection.__init__(self, form, ...)
603 AbstractSection.__init__(self, form, nil, ...)
604 self.template = "cbi/nullsection"
608 Table = class(AbstractSection)
610 function Table.__init__(self, form, data, ...)
611 local datasource = {}
612 datasource.config = "table"
615 function datasource.get(self, section, option)
616 return data[section] and data[section][option]
619 function datasource.del(...)
623 function datasource.get_scheme()
627 AbstractSection.__init__(self, datasource, "table", ...)
628 self.template = "cbi/tblsection"
629 self.rowcolors = true
630 self.anonymous = true
633 function Table.parse(self)
634 for i, k in ipairs(self:cfgsections()) do
635 if luci.http.formvalue("cbi.submit") then
641 function Table.cfgsections(self)
644 for i, v in luci.util.kspairs(self.data) do
645 table.insert(sections, i)
654 NamedSection - A fixed configuration section defined by its name
656 NamedSection = class(AbstractSection)
658 function NamedSection.__init__(self, map, section, stype, ...)
659 AbstractSection.__init__(self, map, stype, ...)
660 Node._i18n(self, map.config, section, nil, ...)
663 self.addremove = false
665 -- Use defaults from UVL
666 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
667 local vs = self.map:get_scheme(self.sectiontype)
668 self.addremove = not vs.unique and not vs.required
669 self.dynamic = vs.dynamic
670 self.title = self.title or vs.title
671 self.description = self.description or vs.descr
674 self.template = "cbi/nsection"
675 self.section = section
678 function NamedSection.parse(self, novld)
679 local s = self.section
680 local active = self:cfgvalue(s)
682 if self.addremove then
683 local path = self.config.."."..s
684 if active then -- Remove the section
685 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
688 else -- Create and apply default values
689 if luci.http.formvalue("cbi.cns."..path) then
697 AbstractSection.parse_dynamic(self, s)
698 if luci.http.formvalue("cbi.submit") then
701 if not novld and not self.override_scheme and self.map.scheme then
702 _uvl_validate_section(self, s)
705 AbstractSection.parse_optionals(self, s)
711 TypedSection - A (set of) configuration section(s) defined by the type
712 addremove: Defines whether the user can add/remove sections of this type
713 anonymous: Allow creating anonymous sections
714 validate: a validation function returning nil if the section is invalid
716 TypedSection = class(AbstractSection)
718 function TypedSection.__init__(self, map, type, ...)
719 AbstractSection.__init__(self, map, type, ...)
720 Node._i18n(self, map.config, type, nil, ...)
722 self.template = "cbi/tsection"
724 self.anonymous = false
726 -- Use defaults from UVL
727 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
728 local vs = self.map:get_scheme(self.sectiontype)
729 self.addremove = not vs.unique and not vs.required
730 self.dynamic = vs.dynamic
731 self.anonymous = not vs.named
732 self.title = self.title or vs.title
733 self.description = self.description or vs.descr
737 -- Return all matching UCI sections for this TypedSection
738 function TypedSection.cfgsections(self)
740 self.map.uci:foreach(self.map.config, self.sectiontype,
742 if self:checkscope(section[".name"]) then
743 table.insert(sections, section[".name"])
750 -- Limits scope to sections that have certain option => value pairs
751 function TypedSection.depends(self, option, value)
752 table.insert(self.deps, {option=option, value=value})
755 function TypedSection.parse(self, novld)
756 if self.addremove then
758 local crval = REMOVE_PREFIX .. self.config
759 local name = luci.http.formvaluetable(crval)
760 for k,v in pairs(name) do
761 if k:sub(-2) == ".x" then
764 if self:cfgvalue(k) and self:checkscope(k) then
771 for i, k in ipairs(self:cfgsections()) do
772 AbstractSection.parse_dynamic(self, k)
773 if luci.http.formvalue("cbi.submit") then
776 if not novld and not self.override_scheme and self.map.scheme then
777 _uvl_validate_section(self, k)
780 AbstractSection.parse_optionals(self, k)
783 if self.addremove then
786 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
787 local name = luci.http.formvalue(crval)
788 if self.anonymous then
790 created = self:create()
794 -- Ignore if it already exists
795 if self:cfgvalue(name) then
799 name = self:checkscope(name)
802 self.err_invalid = true
805 if name and #name > 0 then
806 created = self:create(name) and name
808 self.invalid_cts = true
815 AbstractSection.parse_optionals(self, created)
820 -- Verifies scope of sections
821 function TypedSection.checkscope(self, section)
822 -- Check if we are not excluded
823 if self.filter and not self:filter(section) then
827 -- Check if at least one dependency is met
828 if #self.deps > 0 and self:cfgvalue(section) then
831 for k, v in ipairs(self.deps) do
832 if self:cfgvalue(section)[v.option] == v.value then
842 return self:validate(section)
846 -- Dummy validate function
847 function TypedSection.validate(self, section)
853 AbstractValue - An abstract Value Type
854 null: Value can be empty
855 valid: A function returning the value if it is valid otherwise nil
856 depends: A table of option => value pairs of which one must be true
857 default: The default value
858 size: The size of the input fields
859 rmempty: Unset value if empty
860 optional: This value is optional (see AbstractSection.optionals)
862 AbstractValue = class(Node)
864 function AbstractValue.__init__(self, map, section, option, ...)
865 Node.__init__(self, ...)
866 self.section = section
869 self.config = map.config
870 self.tag_invalid = {}
871 self.tag_missing = {}
872 self.tag_reqerror = {}
875 --self.cast = "string"
877 self.track_missing = false
878 --self.rmempty = false
881 self.optional = false
884 function AbstractValue.prepare(self)
885 -- Use defaults from UVL
886 if not self.override_scheme
887 and self.map:get_scheme(self.section.sectiontype, self.option) then
888 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
889 if self.rmempty == nil then
890 self.rmempty = not vs.required
892 if self.cast == nil then
893 self.cast = (vs.type == "list") and "list" or "string"
895 self.title = self.title or vs.title
896 self.description = self.description or vs.descr
897 if self.default == nil then
898 self.default = vs.default
901 if vs.depends and not self.override_dependencies then
902 for i, deps in ipairs(vs.depends) do
903 deps = _uvl_strip_remote_dependencies(deps)
911 self.cast = self.cast or "string"
914 -- Add a dependencie to another section field
915 function AbstractValue.depends(self, field, value)
917 if type(field) == "string" then
924 table.insert(self.deps, {deps=deps, add=""})
927 -- Generates the unique CBID
928 function AbstractValue.cbid(self, section)
929 return "cbid."..self.map.config.."."..section.."."..self.option
932 -- Return whether this object should be created
933 function AbstractValue.formcreated(self, section)
934 local key = "cbi.opt."..self.config.."."..section
935 return (luci.http.formvalue(key) == self.option)
938 -- Returns the formvalue for this object
939 function AbstractValue.formvalue(self, section)
940 return luci.http.formvalue(self:cbid(section))
943 function AbstractValue.additional(self, value)
944 self.optional = value
947 function AbstractValue.mandatory(self, value)
948 self.rmempty = not value
951 function AbstractValue.parse(self, section)
952 local fvalue = self:formvalue(section)
953 local cvalue = self:cfgvalue(section)
955 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
956 fvalue = self:transform(self:validate(fvalue, section))
958 self.tag_invalid[section] = true
960 if fvalue and not (fvalue == cvalue) then
961 self:write(section, fvalue)
963 else -- Unset the UCI or error
964 if self.rmempty or self.optional then
966 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
967 self.tag_missing[section] = true
972 -- Render if this value exists or if it is mandatory
973 function AbstractValue.render(self, s, scope)
974 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
977 scope.cbid = self:cbid(s)
978 scope.striptags = luci.util.striptags
980 scope.ifattr = function(cond,key,val)
982 return string.format(
983 ' %s="%s"', tostring(key),
984 luci.util.pcdata(tostring( val
986 or (type(self[key]) ~= "function" and self[key])
994 scope.attr = function(...)
995 return scope.ifattr( true, ... )
998 Node.render(self, scope)
1002 -- Return the UCI value of this object
1003 function AbstractValue.cfgvalue(self, section)
1004 local value = self.map:get(section, self.option)
1007 elseif not self.cast or self.cast == type(value) then
1009 elseif self.cast == "string" then
1010 if type(value) == "table" then
1013 elseif self.cast == "table" then
1018 -- Validate the form value
1019 function AbstractValue.validate(self, value)
1023 AbstractValue.transform = AbstractValue.validate
1027 function AbstractValue.write(self, section, value)
1028 return self.map:set(section, self.option, value)
1032 function AbstractValue.remove(self, section)
1033 return self.map:del(section, self.option)
1040 Value - A one-line value
1041 maxlength: The maximum length
1043 Value = class(AbstractValue)
1045 function Value.__init__(self, ...)
1046 AbstractValue.__init__(self, ...)
1047 self.template = "cbi/value"
1052 function Value.value(self, key, val)
1054 table.insert(self.keylist, tostring(key))
1055 table.insert(self.vallist, tostring(val))
1059 -- DummyValue - This does nothing except being there
1060 DummyValue = class(AbstractValue)
1062 function DummyValue.__init__(self, ...)
1063 AbstractValue.__init__(self, ...)
1064 self.template = "cbi/dvalue"
1068 function DummyValue.parse(self)
1074 Flag - A flag being enabled or disabled
1076 Flag = class(AbstractValue)
1078 function Flag.__init__(self, ...)
1079 AbstractValue.__init__(self, ...)
1080 self.template = "cbi/fvalue"
1086 -- A flag can only have two states: set or unset
1087 function Flag.parse(self, section)
1088 local fvalue = self:formvalue(section)
1091 fvalue = self.enabled
1093 fvalue = self.disabled
1096 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1097 if not(fvalue == self:cfgvalue(section)) then
1098 self:write(section, fvalue)
1101 self:remove(section)
1108 ListValue - A one-line value predefined in a list
1109 widget: The widget that will be used (select, radio)
1111 ListValue = class(AbstractValue)
1113 function ListValue.__init__(self, ...)
1114 AbstractValue.__init__(self, ...)
1115 self.template = "cbi/lvalue"
1120 self.widget = "select"
1123 function ListValue.prepare(self, ...)
1124 AbstractValue.prepare(self, ...)
1125 if not self.override_scheme
1126 and self.map:get_scheme(self.section.sectiontype, self.option) then
1127 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1128 if self.value and vs.valuelist and not self.override_values then
1129 for k, v in ipairs(vs.valuelist) do
1131 if not self.override_dependencies
1132 and vs.enum_depends and vs.enum_depends[v.value] then
1133 for i, dep in ipairs(vs.enum_depends[v.value]) do
1134 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1137 self:value(v.value, v.title or v.value, unpack(deps))
1143 function ListValue.value(self, key, val, ...)
1144 if luci.util.contains(self.keylist, key) then
1149 table.insert(self.keylist, tostring(key))
1150 table.insert(self.vallist, tostring(val))
1152 for i, deps in ipairs({...}) do
1153 table.insert(self.deps, {add = "-"..key, deps=deps})
1157 function ListValue.validate(self, val)
1158 if luci.util.contains(self.keylist, val) then
1168 MultiValue - Multiple delimited values
1169 widget: The widget that will be used (select, checkbox)
1170 delimiter: The delimiter that will separate the values (default: " ")
1172 MultiValue = class(AbstractValue)
1174 function MultiValue.__init__(self, ...)
1175 AbstractValue.__init__(self, ...)
1176 self.template = "cbi/mvalue"
1181 self.widget = "checkbox"
1182 self.delimiter = " "
1185 function MultiValue.render(self, ...)
1186 if self.widget == "select" and not self.size then
1187 self.size = #self.vallist
1190 AbstractValue.render(self, ...)
1193 function MultiValue.value(self, key, val)
1194 if luci.util.contains(self.keylist, key) then
1199 table.insert(self.keylist, tostring(key))
1200 table.insert(self.vallist, tostring(val))
1203 function MultiValue.valuelist(self, section)
1204 local val = self:cfgvalue(section)
1206 if not(type(val) == "string") then
1210 return luci.util.split(val, self.delimiter)
1213 function MultiValue.validate(self, val)
1214 val = (type(val) == "table") and val or {val}
1218 for i, value in ipairs(val) do
1219 if luci.util.contains(self.keylist, value) then
1220 result = result and (result .. self.delimiter .. value) or value
1228 StaticList = class(MultiValue)
1230 function StaticList.__init__(self, ...)
1231 MultiValue.__init__(self, ...)
1233 self.valuelist = self.cfgvalue
1235 if not self.override_scheme
1236 and self.map:get_scheme(self.section.sectiontype, self.option) then
1237 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1238 if self.value and vs.values and not self.override_values then
1239 for k, v in pairs(vs.values) do
1246 function StaticList.validate(self, value)
1247 value = (type(value) == "table") and value or {value}
1250 for i, v in ipairs(value) do
1251 if luci.util.contains(self.vallist, v) then
1252 table.insert(valid, v)
1259 DynamicList = class(AbstractValue)
1261 function DynamicList.__init__(self, ...)
1262 AbstractValue.__init__(self, ...)
1263 self.template = "cbi/dynlist"
1269 function DynamicList.value(self, key, val)
1271 table.insert(self.keylist, tostring(key))
1272 table.insert(self.vallist, tostring(val))
1275 function DynamicList.formvalue(self, section)
1276 local value = AbstractValue.formvalue(self, section)
1277 value = (type(value) == "table") and value or {value}
1280 for i, v in ipairs(value) do
1282 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i)
1283 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1284 table.insert(valid, v)
1293 TextValue - A multi-line value
1296 TextValue = class(AbstractValue)
1298 function TextValue.__init__(self, ...)
1299 AbstractValue.__init__(self, ...)
1300 self.template = "cbi/tvalue"
1306 Button = class(AbstractValue)
1308 function Button.__init__(self, ...)
1309 AbstractValue.__init__(self, ...)
1310 self.template = "cbi/button"
1311 self.inputstyle = nil