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!")
84 local function _uvl_validate_section(node, name)
85 local co = node.map:get()
87 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
88 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
90 local function tag_fields(e)
91 if e.option and node.fields[e.option] then
92 if node.fields[e.option].error then
93 node.fields[e.option].error[name] = e
95 node.fields[e.option].error = { [name] = e }
98 for _, c in ipairs(e.childs) do tag_fields(c) end
102 local function tag_section(e)
104 for _, c in ipairs(e.childs) do
105 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
106 table.insert( s, c.childs[1]:string() )
108 table.insert( s, c:string() )
115 node.error = { [name] = s }
120 local stat, err = node.map.validator:validate_section(node.config, name, co)
122 node.map.save = false
129 local function _uvl_strip_remote_dependencies(deps)
132 for k, v in pairs(deps) do
133 k = k:gsub("%$config%.%$section%.", "")
134 if k:match("^[%w_]+$") and type(v) == "string" then
143 -- Node pseudo abstract class
146 function Node.__init__(self, title, description)
148 self.title = title or ""
149 self.description = description or ""
150 self.template = "cbi/node"
154 function Node._i18n(self, config, section, option, title, description)
157 if type(luci.i18n) == "table" then
159 local key = config and config:gsub("[^%w]+", "") or ""
161 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
162 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
164 self.title = title or luci.i18n.translate( key, option or section or config )
165 self.description = description or luci.i18n.translate( key .. "_desc", "" )
169 -- Append child nodes
170 function Node.append(self, obj)
171 table.insert(self.children, obj)
174 -- Parse this node and its children
175 function Node.parse(self, ...)
176 for k, child in ipairs(self.children) do
182 function Node.render(self, scope)
186 luci.template.render(self.template, scope)
189 -- Render the children
190 function Node.render_children(self, ...)
191 for k, node in ipairs(self.children) do
198 A simple template element
200 Template = class(Node)
202 function Template.__init__(self, template)
204 self.template = template
207 function Template.render(self)
208 luci.template.render(self.template, {self=self})
213 Map - A map describing a configuration file
217 function Map.__init__(self, config, ...)
218 Node.__init__(self, ...)
219 Node._i18n(self, config, nil, nil, ...)
222 self.parsechain = {self.config}
223 self.template = "cbi/map"
224 self.apply_on_parse = nil
225 self.uci = uci.cursor()
227 if not self.uci:load(self.config) then
228 error("Unable to read UCI data: " .. self.config)
231 self.validator = luci.uvl.UVL()
232 self.scheme = self.validator:get_scheme(self.config)
236 function Map.get_scheme(self, sectiontype, option)
238 return self.scheme and self.scheme.sections[sectiontype]
240 return self.scheme and self.scheme.variables[sectiontype]
241 and self.scheme.variables[sectiontype][option]
246 -- Chain foreign config
247 function Map.chain(self, config)
248 table.insert(self.parsechain, config)
251 -- Use optimized UCI writing
252 function Map.parse(self)
256 for i, config in ipairs(self.parsechain) do
257 self.uci:save(config)
259 if luci.http.formvalue("cbi.apply") then
260 for i, config in ipairs(self.parsechain) do
261 self.uci:commit(config)
263 -- Refresh data because commit changes section names
264 self.uci:load(config)
266 if self.apply_on_parse then
267 self.uci:apply(self.parsechain)
269 self._apply = function()
270 local cmd = self.uci:apply(self.parsechain, true)
276 Node.parse(self, true)
279 for i, config in ipairs(self.parsechain) do
280 self.uci:unload(config)
285 function Map.render(self, ...)
286 Node.render(self, ...)
288 local fp = self._apply()
294 -- Creates a child section
295 function Map.section(self, class, ...)
296 if instanceof(class, AbstractSection) then
297 local obj = class(self, ...)
301 error("class must be a descendent of AbstractSection")
306 function Map.add(self, sectiontype)
307 return self.uci:add(self.config, sectiontype)
311 function Map.set(self, section, option, value)
313 return self.uci:set(self.config, section, option, value)
315 return self.uci:set(self.config, section, value)
320 function Map.del(self, section, option)
322 return self.uci:delete(self.config, section, option)
324 return self.uci:delete(self.config, section)
329 function Map.get(self, section, option)
331 return self.uci:get_all(self.config)
333 return self.uci:get(self.config, section, option)
335 return self.uci:get_all(self.config, section)
345 Page.__init__ = Node.__init__
346 Page.parse = function() end
350 SimpleForm - A Simple non-UCI form
352 SimpleForm = class(Node)
354 function SimpleForm.__init__(self, config, title, description, data)
355 Node.__init__(self, title, description)
357 self.data = data or {}
358 self.template = "cbi/simpleform"
362 function SimpleForm.parse(self, ...)
363 if luci.http.formvalue("cbi.submit") then
364 Node.parse(self, 1, ...)
368 for k, j in ipairs(self.children) do
369 for i, v in ipairs(j.children) do
371 and (not v.tag_missing or not v.tag_missing[1])
372 and (not v.tag_invalid or not v.tag_invalid[1])
377 not luci.http.formvalue("cbi.submit") and 0
381 self.dorender = not self.handle or self:handle(state, self.data) ~= false
384 function SimpleForm.render(self, ...)
385 if self.dorender then
386 Node.render(self, ...)
390 function SimpleForm.section(self, class, ...)
391 if instanceof(class, AbstractSection) then
392 local obj = class(self, ...)
396 error("class must be a descendent of AbstractSection")
400 -- Creates a child field
401 function SimpleForm.field(self, class, ...)
403 for k, v in ipairs(self.children) do
404 if instanceof(v, SimpleSection) then
410 section = self:section(SimpleSection)
413 if instanceof(class, AbstractValue) then
414 local obj = class(self, section, ...)
415 obj.track_missing = true
419 error("class must be a descendent of AbstractValue")
423 function SimpleForm.set(self, section, option, value)
424 self.data[option] = value
428 function SimpleForm.del(self, section, option)
429 self.data[option] = nil
433 function SimpleForm.get(self, section, option)
434 return self.data[option]
438 function SimpleForm.get_scheme()
447 AbstractSection = class(Node)
449 function AbstractSection.__init__(self, map, sectiontype, ...)
450 Node.__init__(self, ...)
451 self.sectiontype = sectiontype
453 self.config = map.config
458 self.tag_invalid = {}
459 self.tag_deperror = {}
462 self.addremove = false
466 -- Appends a new option
467 function AbstractSection.option(self, class, option, ...)
468 -- Autodetect from UVL
469 if class == true and self.map:get_scheme(self.sectiontype, option) then
470 local vs = self.map:get_scheme(self.sectiontype, option)
471 if vs.type == "boolean" then
473 elseif vs.type == "list" then
475 elseif vs.type == "enum" or vs.type == "reference" then
482 if instanceof(class, AbstractValue) then
483 local obj = class(self.map, self, option, ...)
485 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
488 self.fields[option] = obj
490 elseif class == true then
491 error("No valid class was given and autodetection failed.")
493 error("class must be a descendant of AbstractValue")
497 -- Parse optional options
498 function AbstractSection.parse_optionals(self, section)
499 if not self.optional then
503 self.optionals[section] = {}
505 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
506 for k,v in ipairs(self.children) do
507 if v.optional and not v:cfgvalue(section) then
508 if field == v.option then
511 table.insert(self.optionals[section], v)
516 if field and #field > 0 and self.dynamic then
517 self:add_dynamic(field)
521 -- Add a dynamic option
522 function AbstractSection.add_dynamic(self, field, optional)
523 local o = self:option(Value, field, field)
524 o.optional = optional
527 -- Parse all dynamic options
528 function AbstractSection.parse_dynamic(self, section)
529 if not self.dynamic then
533 local arr = luci.util.clone(self:cfgvalue(section))
534 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
535 for k, v in pairs(form) do
539 for key,val in pairs(arr) do
542 for i,c in ipairs(self.children) do
543 if c.option == key then
548 if create and key:sub(1, 1) ~= "." then
549 self:add_dynamic(key, true)
554 -- Returns the section's UCI table
555 function AbstractSection.cfgvalue(self, section)
556 return self.map:get(section)
559 -- Removes the section
560 function AbstractSection.remove(self, section)
561 return self.map:del(section)
564 -- Creates the section
565 function AbstractSection.create(self, section)
569 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
571 section = self.map:add(self.sectiontype)
576 for k,v in pairs(self.children) do
578 self.map:set(section, v.option, v.default)
582 for k,v in pairs(self.defaults) do
583 self.map:set(section, k, v)
591 SimpleSection = class(AbstractSection)
593 function SimpleSection.__init__(self, form, ...)
594 AbstractSection.__init__(self, form, nil, ...)
595 self.template = "cbi/nullsection"
599 Table = class(AbstractSection)
601 function Table.__init__(self, form, data, ...)
602 local datasource = {}
603 datasource.config = "table"
606 function datasource.get(self, section, option)
607 return data[section] and data[section][option]
610 function datasource.del(...)
614 function datasource.get_scheme()
618 AbstractSection.__init__(self, datasource, "table", ...)
619 self.template = "cbi/tblsection"
620 self.rowcolors = true
621 self.anonymous = true
624 function Table.parse(self)
625 for i, k in ipairs(self:cfgsections()) do
626 if luci.http.formvalue("cbi.submit") then
632 function Table.cfgsections(self)
635 for i, v in luci.util.kspairs(self.data) do
636 table.insert(sections, i)
645 NamedSection - A fixed configuration section defined by its name
647 NamedSection = class(AbstractSection)
649 function NamedSection.__init__(self, map, section, stype, ...)
650 AbstractSection.__init__(self, map, stype, ...)
651 Node._i18n(self, map.config, section, nil, ...)
654 self.addremove = false
656 -- Use defaults from UVL
657 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
658 local vs = self.map:get_scheme(self.sectiontype)
659 self.addremove = not vs.unique and not vs.required
660 self.dynamic = vs.dynamic
661 self.title = self.title or vs.title
662 self.description = self.description or vs.descr
665 self.template = "cbi/nsection"
666 self.section = section
669 function NamedSection.parse(self, novld)
670 local s = self.section
671 local active = self:cfgvalue(s)
673 if self.addremove then
674 local path = self.config.."."..s
675 if active then -- Remove the section
676 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
679 else -- Create and apply default values
680 if luci.http.formvalue("cbi.cns."..path) then
688 AbstractSection.parse_dynamic(self, s)
689 if luci.http.formvalue("cbi.submit") then
692 if not novld and not self.override_scheme and self.map.scheme then
693 _uvl_validate_section(self, s)
696 AbstractSection.parse_optionals(self, s)
702 TypedSection - A (set of) configuration section(s) defined by the type
703 addremove: Defines whether the user can add/remove sections of this type
704 anonymous: Allow creating anonymous sections
705 validate: a validation function returning nil if the section is invalid
707 TypedSection = class(AbstractSection)
709 function TypedSection.__init__(self, map, type, ...)
710 AbstractSection.__init__(self, map, type, ...)
711 Node._i18n(self, map.config, type, nil, ...)
713 self.template = "cbi/tsection"
715 self.anonymous = false
717 -- Use defaults from UVL
718 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
719 local vs = self.map:get_scheme(self.sectiontype)
720 self.addremove = not vs.unique and not vs.required
721 self.dynamic = vs.dynamic
722 self.anonymous = not vs.named
723 self.title = self.title or vs.title
724 self.description = self.description or vs.descr
728 -- Return all matching UCI sections for this TypedSection
729 function TypedSection.cfgsections(self)
731 self.map.uci:foreach(self.map.config, self.sectiontype,
733 if self:checkscope(section[".name"]) then
734 table.insert(sections, section[".name"])
741 -- Limits scope to sections that have certain option => value pairs
742 function TypedSection.depends(self, option, value)
743 table.insert(self.deps, {option=option, value=value})
746 function TypedSection.parse(self, novld)
747 if self.addremove then
749 local crval = REMOVE_PREFIX .. self.config
750 local name = luci.http.formvaluetable(crval)
751 for k,v in pairs(name) do
752 if k:sub(-2) == ".x" then
755 if self:cfgvalue(k) and self:checkscope(k) then
762 for i, k in ipairs(self:cfgsections()) do
763 AbstractSection.parse_dynamic(self, k)
764 if luci.http.formvalue("cbi.submit") then
767 if not novld and not self.override_scheme and self.map.scheme then
768 _uvl_validate_section(self, k)
771 AbstractSection.parse_optionals(self, k)
774 if self.addremove then
777 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
778 local name = luci.http.formvalue(crval)
779 if self.anonymous then
781 created = self:create()
785 -- Ignore if it already exists
786 if self:cfgvalue(name) then
790 name = self:checkscope(name)
793 self.err_invalid = true
796 if name and #name > 0 then
797 created = self:create(name) and name
799 self.invalid_cts = true
806 AbstractSection.parse_optionals(self, created)
811 -- Verifies scope of sections
812 function TypedSection.checkscope(self, section)
813 -- Check if we are not excluded
814 if self.filter and not self:filter(section) then
818 -- Check if at least one dependency is met
819 if #self.deps > 0 and self:cfgvalue(section) then
822 for k, v in ipairs(self.deps) do
823 if self:cfgvalue(section)[v.option] == v.value then
833 return self:validate(section)
837 -- Dummy validate function
838 function TypedSection.validate(self, section)
844 AbstractValue - An abstract Value Type
845 null: Value can be empty
846 valid: A function returning the value if it is valid otherwise nil
847 depends: A table of option => value pairs of which one must be true
848 default: The default value
849 size: The size of the input fields
850 rmempty: Unset value if empty
851 optional: This value is optional (see AbstractSection.optionals)
853 AbstractValue = class(Node)
855 function AbstractValue.__init__(self, map, section, option, ...)
856 Node.__init__(self, ...)
857 self.section = section
860 self.config = map.config
861 self.tag_invalid = {}
862 self.tag_missing = {}
863 self.tag_reqerror = {}
868 self.track_missing = false
872 self.optional = false
874 -- Use defaults from UVL
875 if not self.override_scheme
876 and self.map:get_scheme(self.section.sectiontype, self.option) then
877 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
878 self.rmempty = not vs.required
879 self.cast = (vs.type == "list") and "list" or "string"
880 self.title = self.title or vs.title
881 self.description = self.description or vs.descr
882 self.default = vs.default
884 if vs.depends and not self.override_dependencies then
885 for i, deps in ipairs(vs.depends) do
886 deps = _uvl_strip_remote_dependencies(deps)
895 -- Add a dependencie to another section field
896 function AbstractValue.depends(self, field, value)
898 if type(field) == "string" then
905 table.insert(self.deps, {deps=deps, add=""})
908 -- Generates the unique CBID
909 function AbstractValue.cbid(self, section)
910 return "cbid."..self.map.config.."."..section.."."..self.option
913 -- Return whether this object should be created
914 function AbstractValue.formcreated(self, section)
915 local key = "cbi.opt."..self.config.."."..section
916 return (luci.http.formvalue(key) == self.option)
919 -- Returns the formvalue for this object
920 function AbstractValue.formvalue(self, section)
921 return luci.http.formvalue(self:cbid(section))
924 function AbstractValue.additional(self, value)
925 self.optional = value
928 function AbstractValue.mandatory(self, value)
929 self.rmempty = not value
932 function AbstractValue.parse(self, section)
933 local fvalue = self:formvalue(section)
934 local cvalue = self:cfgvalue(section)
936 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
937 fvalue = self:transform(self:validate(fvalue, section))
939 self.tag_invalid[section] = true
941 if fvalue and not (fvalue == cvalue) then
942 self:write(section, fvalue)
944 else -- Unset the UCI or error
945 if self.rmempty or self.optional then
947 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
948 self.tag_missing[section] = true
953 -- Render if this value exists or if it is mandatory
954 function AbstractValue.render(self, s, scope)
955 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
958 scope.cbid = self:cbid(s)
959 scope.striptags = luci.util.striptags
961 scope.ifattr = function(cond,key,val)
963 return string.format(
964 ' %s="%s"', tostring(key),
965 luci.util.pcdata(tostring( val
967 or (type(self[key]) ~= "function" and self[key])
975 scope.attr = function(...)
976 return scope.ifattr( true, ... )
979 Node.render(self, scope)
983 -- Return the UCI value of this object
984 function AbstractValue.cfgvalue(self, section)
985 local value = self.map:get(section, self.option)
986 if not self.cast or self.cast == type(value) then
988 elseif self.cast == "string" then
989 if type(value) == "table" then
992 elseif self.cast == "table" then
997 -- Validate the form value
998 function AbstractValue.validate(self, value)
1002 AbstractValue.transform = AbstractValue.validate
1006 function AbstractValue.write(self, section, value)
1007 return self.map:set(section, self.option, value)
1011 function AbstractValue.remove(self, section)
1012 return self.map:del(section, self.option)
1019 Value - A one-line value
1020 maxlength: The maximum length
1022 Value = class(AbstractValue)
1024 function Value.__init__(self, ...)
1025 AbstractValue.__init__(self, ...)
1026 self.template = "cbi/value"
1031 function Value.value(self, key, val)
1033 table.insert(self.keylist, tostring(key))
1034 table.insert(self.vallist, tostring(val))
1038 -- DummyValue - This does nothing except being there
1039 DummyValue = class(AbstractValue)
1041 function DummyValue.__init__(self, ...)
1042 AbstractValue.__init__(self, ...)
1043 self.template = "cbi/dvalue"
1047 function DummyValue.parse(self)
1053 Flag - A flag being enabled or disabled
1055 Flag = class(AbstractValue)
1057 function Flag.__init__(self, ...)
1058 AbstractValue.__init__(self, ...)
1059 self.template = "cbi/fvalue"
1065 -- A flag can only have two states: set or unset
1066 function Flag.parse(self, section)
1067 local fvalue = self:formvalue(section)
1070 fvalue = self.enabled
1072 fvalue = self.disabled
1075 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1076 if not(fvalue == self:cfgvalue(section)) then
1077 self:write(section, fvalue)
1080 self:remove(section)
1087 ListValue - A one-line value predefined in a list
1088 widget: The widget that will be used (select, radio)
1090 ListValue = class(AbstractValue)
1092 function ListValue.__init__(self, ...)
1093 AbstractValue.__init__(self, ...)
1094 self.template = "cbi/lvalue"
1099 self.widget = "select"
1101 if not self.override_scheme
1102 and self.map:get_scheme(self.section.sectiontype, self.option) then
1103 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1104 if self.value and vs.values and not self.override_values then
1105 if self.rmempty or self.optional then
1108 for k, v in pairs(vs.values) do
1110 if not self.override_dependencies
1111 and vs.enum_depends and vs.enum_depends[k] then
1112 for i, dep in ipairs(vs.enum_depends[k]) do
1113 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1116 self:value(k, v, unpack(deps))
1122 function ListValue.value(self, key, val, ...)
1123 if luci.util.contains(self.keylist, key) then
1128 table.insert(self.keylist, tostring(key))
1129 table.insert(self.vallist, tostring(val))
1131 for i, deps in ipairs({...}) do
1132 table.insert(self.deps, {add = "-"..key, deps=deps})
1136 function ListValue.validate(self, val)
1137 if luci.util.contains(self.keylist, val) then
1147 MultiValue - Multiple delimited values
1148 widget: The widget that will be used (select, checkbox)
1149 delimiter: The delimiter that will separate the values (default: " ")
1151 MultiValue = class(AbstractValue)
1153 function MultiValue.__init__(self, ...)
1154 AbstractValue.__init__(self, ...)
1155 self.template = "cbi/mvalue"
1160 self.widget = "checkbox"
1161 self.delimiter = " "
1164 function MultiValue.render(self, ...)
1165 if self.widget == "select" and not self.size then
1166 self.size = #self.vallist
1169 AbstractValue.render(self, ...)
1172 function MultiValue.value(self, key, val)
1173 if luci.util.contains(self.keylist, key) then
1178 table.insert(self.keylist, tostring(key))
1179 table.insert(self.vallist, tostring(val))
1182 function MultiValue.valuelist(self, section)
1183 local val = self:cfgvalue(section)
1185 if not(type(val) == "string") then
1189 return luci.util.split(val, self.delimiter)
1192 function MultiValue.validate(self, val)
1193 val = (type(val) == "table") and val or {val}
1197 for i, value in ipairs(val) do
1198 if luci.util.contains(self.keylist, value) then
1199 result = result and (result .. self.delimiter .. value) or value
1207 StaticList = class(MultiValue)
1209 function StaticList.__init__(self, ...)
1210 MultiValue.__init__(self, ...)
1212 self.valuelist = self.cfgvalue
1214 if not self.override_scheme
1215 and self.map:get_scheme(self.section.sectiontype, self.option) then
1216 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1217 if self.value and vs.values and not self.override_values then
1218 for k, v in pairs(vs.values) do
1225 function StaticList.validate(self, value)
1226 value = (type(value) == "table") and value or {value}
1229 for i, v in ipairs(value) do
1230 if luci.util.contains(self.vallist, v) then
1231 table.insert(valid, v)
1238 DynamicList = class(AbstractValue)
1240 function DynamicList.__init__(self, ...)
1241 AbstractValue.__init__(self, ...)
1242 self.template = "cbi/dynlist"
1248 function DynamicList.value(self, key, val)
1250 table.insert(self.keylist, tostring(key))
1251 table.insert(self.vallist, tostring(val))
1254 function DynamicList.validate(self, value, section)
1255 value = (type(value) == "table") and value or {value}
1258 for i, v in ipairs(value) do
1260 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i)
1261 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1262 table.insert(valid, v)
1271 TextValue - A multi-line value
1274 TextValue = class(AbstractValue)
1276 function TextValue.__init__(self, ...)
1277 AbstractValue.__init__(self, ...)
1278 self.template = "cbi/tvalue"
1284 Button = class(AbstractValue)
1286 function Button.__init__(self, ...)
1287 AbstractValue.__init__(self, ...)
1288 self.template = "cbi/button"
1289 self.inputstyle = nil