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)
291 if type(self.commit_handler) == "function" then
292 self:commit_handler()
297 function Map.render(self, ...)
298 Node.render(self, ...)
300 local fp = self._apply()
306 -- Creates a child section
307 function Map.section(self, class, ...)
308 if instanceof(class, AbstractSection) then
309 local obj = class(self, ...)
313 error("class must be a descendent of AbstractSection")
318 function Map.add(self, sectiontype)
319 return self.uci:add(self.config, sectiontype)
323 function Map.set(self, section, option, value)
325 return self.uci:set(self.config, section, option, value)
327 return self.uci:set(self.config, section, value)
332 function Map.del(self, section, option)
334 return self.uci:delete(self.config, section, option)
336 return self.uci:delete(self.config, section)
341 function Map.get(self, section, option)
343 return self.uci:get_all(self.config)
345 return self.uci:get(self.config, section, option)
347 return self.uci:get_all(self.config, section)
357 Page.__init__ = Node.__init__
358 Page.parse = function() end
362 SimpleForm - A Simple non-UCI form
364 SimpleForm = class(Node)
366 function SimpleForm.__init__(self, config, title, description, data)
367 Node.__init__(self, title, description)
369 self.data = data or {}
370 self.template = "cbi/simpleform"
374 function SimpleForm.parse(self, ...)
375 if luci.http.formvalue("cbi.submit") then
376 Node.parse(self, 1, ...)
380 for k, j in ipairs(self.children) do
381 for i, v in ipairs(j.children) do
383 and (not v.tag_missing or not v.tag_missing[1])
384 and (not v.tag_invalid or not v.tag_invalid[1])
389 not luci.http.formvalue("cbi.submit") and 0
393 self.dorender = not self.handle or self:handle(state, self.data) ~= false
396 function SimpleForm.render(self, ...)
397 if self.dorender then
398 Node.render(self, ...)
402 function SimpleForm.section(self, class, ...)
403 if instanceof(class, AbstractSection) then
404 local obj = class(self, ...)
408 error("class must be a descendent of AbstractSection")
412 -- Creates a child field
413 function SimpleForm.field(self, class, ...)
415 for k, v in ipairs(self.children) do
416 if instanceof(v, SimpleSection) then
422 section = self:section(SimpleSection)
425 if instanceof(class, AbstractValue) then
426 local obj = class(self, section, ...)
427 obj.track_missing = true
431 error("class must be a descendent of AbstractValue")
435 function SimpleForm.set(self, section, option, value)
436 self.data[option] = value
440 function SimpleForm.del(self, section, option)
441 self.data[option] = nil
445 function SimpleForm.get(self, section, option)
446 return self.data[option]
450 function SimpleForm.get_scheme()
459 AbstractSection = class(Node)
461 function AbstractSection.__init__(self, map, sectiontype, ...)
462 Node.__init__(self, ...)
463 self.sectiontype = sectiontype
465 self.config = map.config
470 self.tag_invalid = {}
471 self.tag_deperror = {}
474 self.addremove = false
478 -- Appends a new option
479 function AbstractSection.option(self, class, option, ...)
480 -- Autodetect from UVL
481 if class == true and self.map:get_scheme(self.sectiontype, option) then
482 local vs = self.map:get_scheme(self.sectiontype, option)
483 if vs.type == "boolean" then
485 elseif vs.type == "list" then
487 elseif vs.type == "enum" or vs.type == "reference" then
494 if instanceof(class, AbstractValue) then
495 local obj = class(self.map, self, option, ...)
497 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
500 self.fields[option] = obj
502 elseif class == true then
503 error("No valid class was given and autodetection failed.")
505 error("class must be a descendant of AbstractValue")
509 -- Parse optional options
510 function AbstractSection.parse_optionals(self, section)
511 if not self.optional then
515 self.optionals[section] = {}
517 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
518 for k,v in ipairs(self.children) do
519 if v.optional and not v:cfgvalue(section) then
520 if field == v.option then
523 table.insert(self.optionals[section], v)
528 if field and #field > 0 and self.dynamic then
529 self:add_dynamic(field)
533 -- Add a dynamic option
534 function AbstractSection.add_dynamic(self, field, optional)
535 local o = self:option(Value, field, field)
536 o.optional = optional
539 -- Parse all dynamic options
540 function AbstractSection.parse_dynamic(self, section)
541 if not self.dynamic then
545 local arr = luci.util.clone(self:cfgvalue(section))
546 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
547 for k, v in pairs(form) do
551 for key,val in pairs(arr) do
554 for i,c in ipairs(self.children) do
555 if c.option == key then
560 if create and key:sub(1, 1) ~= "." then
561 self:add_dynamic(key, true)
566 -- Returns the section's UCI table
567 function AbstractSection.cfgvalue(self, section)
568 return self.map:get(section)
571 -- Removes the section
572 function AbstractSection.remove(self, section)
573 return self.map:del(section)
576 -- Creates the section
577 function AbstractSection.create(self, section)
581 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
583 section = self.map:add(self.sectiontype)
588 for k,v in pairs(self.children) do
590 self.map:set(section, v.option, v.default)
594 for k,v in pairs(self.defaults) do
595 self.map:set(section, k, v)
603 SimpleSection = class(AbstractSection)
605 function SimpleSection.__init__(self, form, ...)
606 AbstractSection.__init__(self, form, nil, ...)
607 self.template = "cbi/nullsection"
611 Table = class(AbstractSection)
613 function Table.__init__(self, form, data, ...)
614 local datasource = {}
615 datasource.config = "table"
618 function datasource.get(self, section, option)
619 return data[section] and data[section][option]
622 function datasource.del(...)
626 function datasource.get_scheme()
630 AbstractSection.__init__(self, datasource, "table", ...)
631 self.template = "cbi/tblsection"
632 self.rowcolors = true
633 self.anonymous = true
636 function Table.parse(self)
637 for i, k in ipairs(self:cfgsections()) do
638 if luci.http.formvalue("cbi.submit") then
644 function Table.cfgsections(self)
647 for i, v in luci.util.kspairs(self.data) do
648 table.insert(sections, i)
657 NamedSection - A fixed configuration section defined by its name
659 NamedSection = class(AbstractSection)
661 function NamedSection.__init__(self, map, section, stype, ...)
662 AbstractSection.__init__(self, map, stype, ...)
663 Node._i18n(self, map.config, section, nil, ...)
666 self.addremove = false
668 -- Use defaults from UVL
669 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
670 local vs = self.map:get_scheme(self.sectiontype)
671 self.addremove = not vs.unique and not vs.required
672 self.dynamic = vs.dynamic
673 self.title = self.title or vs.title
674 self.description = self.description or vs.descr
677 self.template = "cbi/nsection"
678 self.section = section
681 function NamedSection.parse(self, novld)
682 local s = self.section
683 local active = self:cfgvalue(s)
685 if self.addremove then
686 local path = self.config.."."..s
687 if active then -- Remove the section
688 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
691 else -- Create and apply default values
692 if luci.http.formvalue("cbi.cns."..path) then
700 AbstractSection.parse_dynamic(self, s)
701 if luci.http.formvalue("cbi.submit") then
704 if not novld and not self.override_scheme and self.map.scheme then
705 _uvl_validate_section(self, s)
708 AbstractSection.parse_optionals(self, s)
714 TypedSection - A (set of) configuration section(s) defined by the type
715 addremove: Defines whether the user can add/remove sections of this type
716 anonymous: Allow creating anonymous sections
717 validate: a validation function returning nil if the section is invalid
719 TypedSection = class(AbstractSection)
721 function TypedSection.__init__(self, map, type, ...)
722 AbstractSection.__init__(self, map, type, ...)
723 Node._i18n(self, map.config, type, nil, ...)
725 self.template = "cbi/tsection"
727 self.anonymous = false
729 -- Use defaults from UVL
730 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
731 local vs = self.map:get_scheme(self.sectiontype)
732 self.addremove = not vs.unique and not vs.required
733 self.dynamic = vs.dynamic
734 self.anonymous = not vs.named
735 self.title = self.title or vs.title
736 self.description = self.description or vs.descr
740 -- Return all matching UCI sections for this TypedSection
741 function TypedSection.cfgsections(self)
743 self.map.uci:foreach(self.map.config, self.sectiontype,
745 if self:checkscope(section[".name"]) then
746 table.insert(sections, section[".name"])
753 -- Limits scope to sections that have certain option => value pairs
754 function TypedSection.depends(self, option, value)
755 table.insert(self.deps, {option=option, value=value})
758 function TypedSection.parse(self, novld)
759 if self.addremove then
761 local crval = REMOVE_PREFIX .. self.config
762 local name = luci.http.formvaluetable(crval)
763 for k,v in pairs(name) do
764 if k:sub(-2) == ".x" then
767 if self:cfgvalue(k) and self:checkscope(k) then
774 for i, k in ipairs(self:cfgsections()) do
775 AbstractSection.parse_dynamic(self, k)
776 if luci.http.formvalue("cbi.submit") then
779 if not novld and not self.override_scheme and self.map.scheme then
780 _uvl_validate_section(self, k)
783 AbstractSection.parse_optionals(self, k)
786 if self.addremove then
789 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
790 local name = luci.http.formvalue(crval)
791 if self.anonymous then
793 created = self:create()
797 -- Ignore if it already exists
798 if self:cfgvalue(name) then
802 name = self:checkscope(name)
805 self.err_invalid = true
808 if name and #name > 0 then
809 created = self:create(name) and name
811 self.invalid_cts = true
818 AbstractSection.parse_optionals(self, created)
823 -- Verifies scope of sections
824 function TypedSection.checkscope(self, section)
825 -- Check if we are not excluded
826 if self.filter and not self:filter(section) then
830 -- Check if at least one dependency is met
831 if #self.deps > 0 and self:cfgvalue(section) then
834 for k, v in ipairs(self.deps) do
835 if self:cfgvalue(section)[v.option] == v.value then
845 return self:validate(section)
849 -- Dummy validate function
850 function TypedSection.validate(self, section)
856 AbstractValue - An abstract Value Type
857 null: Value can be empty
858 valid: A function returning the value if it is valid otherwise nil
859 depends: A table of option => value pairs of which one must be true
860 default: The default value
861 size: The size of the input fields
862 rmempty: Unset value if empty
863 optional: This value is optional (see AbstractSection.optionals)
865 AbstractValue = class(Node)
867 function AbstractValue.__init__(self, map, section, option, ...)
868 Node.__init__(self, ...)
869 self.section = section
872 self.config = map.config
873 self.tag_invalid = {}
874 self.tag_missing = {}
875 self.tag_reqerror = {}
878 --self.cast = "string"
880 self.track_missing = false
881 --self.rmempty = false
884 self.optional = false
887 function AbstractValue.prepare(self)
888 -- Use defaults from UVL
889 if not self.override_scheme
890 and self.map:get_scheme(self.section.sectiontype, self.option) then
891 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
892 if self.rmempty == nil then
893 self.rmempty = not vs.required
895 if self.cast == nil then
896 self.cast = (vs.type == "list") and "list" or "string"
898 self.title = self.title or vs.title
899 self.description = self.description or vs.descr
900 if self.default == nil then
901 self.default = vs.default
904 if vs.depends and not self.override_dependencies then
905 for i, deps in ipairs(vs.depends) do
906 deps = _uvl_strip_remote_dependencies(deps)
914 self.cast = self.cast or "string"
917 -- Add a dependencie to another section field
918 function AbstractValue.depends(self, field, value)
920 if type(field) == "string" then
927 table.insert(self.deps, {deps=deps, add=""})
930 -- Generates the unique CBID
931 function AbstractValue.cbid(self, section)
932 return "cbid."..self.map.config.."."..section.."."..self.option
935 -- Return whether this object should be created
936 function AbstractValue.formcreated(self, section)
937 local key = "cbi.opt."..self.config.."."..section
938 return (luci.http.formvalue(key) == self.option)
941 -- Returns the formvalue for this object
942 function AbstractValue.formvalue(self, section)
943 return luci.http.formvalue(self:cbid(section))
946 function AbstractValue.additional(self, value)
947 self.optional = value
950 function AbstractValue.mandatory(self, value)
951 self.rmempty = not value
954 function AbstractValue.parse(self, section)
955 local fvalue = self:formvalue(section)
956 local cvalue = self:cfgvalue(section)
958 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
959 fvalue = self:transform(self:validate(fvalue, section))
961 self.tag_invalid[section] = true
963 if fvalue and not (fvalue == cvalue) then
964 self:write(section, fvalue)
966 else -- Unset the UCI or error
967 if self.rmempty or self.optional then
969 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
970 self.tag_missing[section] = true
975 -- Render if this value exists or if it is mandatory
976 function AbstractValue.render(self, s, scope)
977 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
980 scope.cbid = self:cbid(s)
981 scope.striptags = luci.util.striptags
983 scope.ifattr = function(cond,key,val)
985 return string.format(
986 ' %s="%s"', tostring(key),
987 luci.util.pcdata(tostring( val
989 or (type(self[key]) ~= "function" and self[key])
997 scope.attr = function(...)
998 return scope.ifattr( true, ... )
1001 Node.render(self, scope)
1005 -- Return the UCI value of this object
1006 function AbstractValue.cfgvalue(self, section)
1007 local value = self.map:get(section, self.option)
1010 elseif not self.cast or self.cast == type(value) then
1012 elseif self.cast == "string" then
1013 if type(value) == "table" then
1016 elseif self.cast == "table" then
1017 return luci.util.split(value, "%s+", nil, true)
1021 -- Validate the form value
1022 function AbstractValue.validate(self, value)
1026 AbstractValue.transform = AbstractValue.validate
1030 function AbstractValue.write(self, section, value)
1031 return self.map:set(section, self.option, value)
1035 function AbstractValue.remove(self, section)
1036 return self.map:del(section, self.option)
1043 Value - A one-line value
1044 maxlength: The maximum length
1046 Value = class(AbstractValue)
1048 function Value.__init__(self, ...)
1049 AbstractValue.__init__(self, ...)
1050 self.template = "cbi/value"
1055 function Value.value(self, key, val)
1057 table.insert(self.keylist, tostring(key))
1058 table.insert(self.vallist, tostring(val))
1062 -- DummyValue - This does nothing except being there
1063 DummyValue = class(AbstractValue)
1065 function DummyValue.__init__(self, ...)
1066 AbstractValue.__init__(self, ...)
1067 self.template = "cbi/dvalue"
1071 function DummyValue.cfgvalue(self, section)
1074 if type(self.value) == "function" then
1075 value = self:value(section)
1080 value = AbstractValue.cfgvalue(self, section)
1085 function DummyValue.parse(self)
1091 Flag - A flag being enabled or disabled
1093 Flag = class(AbstractValue)
1095 function Flag.__init__(self, ...)
1096 AbstractValue.__init__(self, ...)
1097 self.template = "cbi/fvalue"
1103 -- A flag can only have two states: set or unset
1104 function Flag.parse(self, section)
1105 local fvalue = self:formvalue(section)
1108 fvalue = self.enabled
1110 fvalue = self.disabled
1113 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1114 if not(fvalue == self:cfgvalue(section)) then
1115 self:write(section, fvalue)
1118 self:remove(section)
1125 ListValue - A one-line value predefined in a list
1126 widget: The widget that will be used (select, radio)
1128 ListValue = class(AbstractValue)
1130 function ListValue.__init__(self, ...)
1131 AbstractValue.__init__(self, ...)
1132 self.template = "cbi/lvalue"
1137 self.widget = "select"
1140 function ListValue.prepare(self, ...)
1141 AbstractValue.prepare(self, ...)
1142 if not self.override_scheme
1143 and self.map:get_scheme(self.section.sectiontype, self.option) then
1144 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1145 if self.value and vs.valuelist and not self.override_values then
1146 for k, v in ipairs(vs.valuelist) do
1148 if not self.override_dependencies
1149 and vs.enum_depends and vs.enum_depends[v.value] then
1150 for i, dep in ipairs(vs.enum_depends[v.value]) do
1151 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1154 self:value(v.value, v.title or v.value, unpack(deps))
1160 function ListValue.value(self, key, val, ...)
1161 if luci.util.contains(self.keylist, key) then
1166 table.insert(self.keylist, tostring(key))
1167 table.insert(self.vallist, tostring(val))
1169 for i, deps in ipairs({...}) do
1170 table.insert(self.deps, {add = "-"..key, deps=deps})
1174 function ListValue.validate(self, val)
1175 if luci.util.contains(self.keylist, val) then
1185 MultiValue - Multiple delimited values
1186 widget: The widget that will be used (select, checkbox)
1187 delimiter: The delimiter that will separate the values (default: " ")
1189 MultiValue = class(AbstractValue)
1191 function MultiValue.__init__(self, ...)
1192 AbstractValue.__init__(self, ...)
1193 self.template = "cbi/mvalue"
1198 self.widget = "checkbox"
1199 self.delimiter = " "
1202 function MultiValue.render(self, ...)
1203 if self.widget == "select" and not self.size then
1204 self.size = #self.vallist
1207 AbstractValue.render(self, ...)
1210 function MultiValue.value(self, key, val)
1211 if luci.util.contains(self.keylist, key) then
1216 table.insert(self.keylist, tostring(key))
1217 table.insert(self.vallist, tostring(val))
1220 function MultiValue.valuelist(self, section)
1221 local val = self:cfgvalue(section)
1223 if not(type(val) == "string") then
1227 return luci.util.split(val, self.delimiter)
1230 function MultiValue.validate(self, val)
1231 val = (type(val) == "table") and val or {val}
1235 for i, value in ipairs(val) do
1236 if luci.util.contains(self.keylist, value) then
1237 result = result and (result .. self.delimiter .. value) or value
1245 StaticList = class(MultiValue)
1247 function StaticList.__init__(self, ...)
1248 MultiValue.__init__(self, ...)
1250 self.valuelist = self.cfgvalue
1252 if not self.override_scheme
1253 and self.map:get_scheme(self.section.sectiontype, self.option) then
1254 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1255 if self.value and vs.values and not self.override_values then
1256 for k, v in pairs(vs.values) do
1263 function StaticList.validate(self, value)
1264 value = (type(value) == "table") and value or {value}
1267 for i, v in ipairs(value) do
1268 if luci.util.contains(self.vallist, v) then
1269 table.insert(valid, v)
1276 DynamicList = class(AbstractValue)
1278 function DynamicList.__init__(self, ...)
1279 AbstractValue.__init__(self, ...)
1280 self.template = "cbi/dynlist"
1286 function DynamicList.value(self, key, val)
1288 table.insert(self.keylist, tostring(key))
1289 table.insert(self.vallist, tostring(val))
1292 function DynamicList.formvalue(self, section)
1293 local value = AbstractValue.formvalue(self, section)
1294 value = (type(value) == "table") and value or {value}
1297 for i, v in ipairs(value) do
1299 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i)
1300 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1301 table.insert(valid, v)
1310 TextValue - A multi-line value
1313 TextValue = class(AbstractValue)
1315 function TextValue.__init__(self, ...)
1316 AbstractValue.__init__(self, ...)
1317 self.template = "cbi/tvalue"
1323 Button = class(AbstractValue)
1325 function Button.__init__(self, ...)
1326 AbstractValue.__init__(self, ...)
1327 self.template = "cbi/button"
1328 self.inputstyle = nil