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 node.fields[e.option].error = e
94 for _, c in ipairs(e.childs) do tag_fields(c) end
98 local function tag_section(e)
100 for _, c in ipairs(e.childs) do
101 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
102 table.insert( s, c.childs[1]:string() )
104 table.insert( s, c:string() )
107 if #s > 0 then node.error = s end
110 local stat, err = node.map.validator:validate_section(node.config, name, co)
112 node.map.save = false
119 local function _uvl_strip_remote_dependencies(deps)
122 for k, v in pairs(deps) do
123 k = k:gsub("%$config%.%$section%.", "")
124 if k:match("^[%w_]+$") and type(v) == "string" then
133 -- Node pseudo abstract class
136 function Node.__init__(self, title, description)
138 self.title = title or ""
139 self.description = description or ""
140 self.template = "cbi/node"
144 function Node._i18n(self, config, section, option, title, description)
147 if type(luci.i18n) == "table" then
149 local key = config and config:gsub("[^%w]+", "") or ""
151 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
152 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
154 self.title = title or luci.i18n.translate( key, option or section or config )
155 self.description = description or luci.i18n.translate( key .. "_desc", "" )
159 -- Append child nodes
160 function Node.append(self, obj)
161 table.insert(self.children, obj)
164 -- Parse this node and its children
165 function Node.parse(self, ...)
166 for k, child in ipairs(self.children) do
172 function Node.render(self, scope)
176 luci.template.render(self.template, scope)
179 -- Render the children
180 function Node.render_children(self, ...)
181 for k, node in ipairs(self.children) do
188 A simple template element
190 Template = class(Node)
192 function Template.__init__(self, template)
194 self.template = template
197 function Template.render(self)
198 luci.template.render(self.template, {self=self})
203 Map - A map describing a configuration file
207 function Map.__init__(self, config, ...)
208 Node.__init__(self, ...)
209 Node._i18n(self, config, nil, nil, ...)
212 self.parsechain = {self.config}
213 self.template = "cbi/map"
214 self.apply_on_parse = nil
215 self.uci = uci.cursor()
217 if not self.uci:load(self.config) then
218 error("Unable to read UCI data: " .. self.config)
221 self.validator = luci.uvl.UVL()
222 self.scheme = self.validator:get_scheme(self.config)
226 function Map.get_scheme(self, sectiontype, option)
228 return self.scheme and self.scheme.sections[sectiontype]
230 return self.scheme and self.scheme.variables[sectiontype]
231 and self.scheme.variables[sectiontype][option]
236 -- Chain foreign config
237 function Map.chain(self, config)
238 table.insert(self.parsechain, config)
241 -- Use optimized UCI writing
242 function Map.parse(self, ...)
243 Node.parse(self, ...)
246 for i, config in ipairs(self.parsechain) do
247 self.uci:save(config)
249 if luci.http.formvalue("cbi.apply") then
250 for i, config in ipairs(self.parsechain) do
251 self.uci:commit(config)
253 -- Refresh data because commit changes section names
254 self.uci:load(config)
256 if self.apply_on_parse then
257 self.uci:apply(self.parsechain)
259 self._apply = function()
260 local cmd = self.uci:apply(self.parsechain, true)
266 Node.parse(self, ...)
269 for i, config in ipairs(self.parsechain) do
270 self.uci:unload(config)
275 function Map.render(self, ...)
276 Node.render(self, ...)
278 local fp = self._apply()
284 -- Creates a child section
285 function Map.section(self, class, ...)
286 if instanceof(class, AbstractSection) then
287 local obj = class(self, ...)
291 error("class must be a descendent of AbstractSection")
296 function Map.add(self, sectiontype)
297 return self.uci:add(self.config, sectiontype)
301 function Map.set(self, section, option, value)
303 return self.uci:set(self.config, section, option, value)
305 return self.uci:set(self.config, section, value)
310 function Map.del(self, section, option)
312 return self.uci:delete(self.config, section, option)
314 return self.uci:delete(self.config, section)
319 function Map.get(self, section, option)
321 return self.uci:get_all(self.config)
323 return self.uci:get(self.config, section, option)
325 return self.uci:get_all(self.config, section)
335 Page.__init__ = Node.__init__
336 Page.parse = function() end
340 SimpleForm - A Simple non-UCI form
342 SimpleForm = class(Node)
344 function SimpleForm.__init__(self, config, title, description, data)
345 Node.__init__(self, title, description)
347 self.data = data or {}
348 self.template = "cbi/simpleform"
352 function SimpleForm.parse(self, ...)
353 if luci.http.formvalue("cbi.submit") then
354 Node.parse(self, 1, ...)
358 for k, j in ipairs(self.children) do
359 for i, v in ipairs(j.children) do
361 and (not v.tag_missing or not v.tag_missing[1])
362 and (not v.tag_invalid or not v.tag_invalid[1])
367 not luci.http.formvalue("cbi.submit") and 0
371 self.dorender = not self.handle or self:handle(state, self.data) ~= false
374 function SimpleForm.render(self, ...)
375 if self.dorender then
376 Node.render(self, ...)
380 function SimpleForm.section(self, class, ...)
381 if instanceof(class, AbstractSection) then
382 local obj = class(self, ...)
386 error("class must be a descendent of AbstractSection")
390 -- Creates a child field
391 function SimpleForm.field(self, class, ...)
393 for k, v in ipairs(self.children) do
394 if instanceof(v, SimpleSection) then
400 section = self:section(SimpleSection)
403 if instanceof(class, AbstractValue) then
404 local obj = class(self, section, ...)
405 obj.track_missing = true
409 error("class must be a descendent of AbstractValue")
413 function SimpleForm.set(self, section, option, value)
414 self.data[option] = value
418 function SimpleForm.del(self, section, option)
419 self.data[option] = nil
423 function SimpleForm.get(self, section, option)
424 return self.data[option]
428 function SimpleForm.get_scheme()
437 AbstractSection = class(Node)
439 function AbstractSection.__init__(self, map, sectiontype, ...)
440 Node.__init__(self, ...)
441 self.sectiontype = sectiontype
443 self.config = map.config
448 self.tag_invalid = {}
449 self.tag_deperror = {}
452 self.addremove = false
456 -- Appends a new option
457 function AbstractSection.option(self, class, option, ...)
458 -- Autodetect from UVL
459 if class == true and self.map:get_scheme(self.sectiontype, option) then
460 local vs = self.map:get_scheme(self.sectiontype, option)
461 if vs.type == "boolean" then
463 elseif vs.type == "list" then
465 elseif vs.type == "enum" or vs.type == "reference" then
472 if instanceof(class, AbstractValue) then
473 local obj = class(self.map, self, option, ...)
475 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
478 self.fields[option] = obj
480 elseif class == true then
481 error("No valid class was given and autodetection failed.")
483 error("class must be a descendant of AbstractValue")
487 -- Parse optional options
488 function AbstractSection.parse_optionals(self, section)
489 if not self.optional then
493 self.optionals[section] = {}
495 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
496 for k,v in ipairs(self.children) do
497 if v.optional and not v:cfgvalue(section) then
498 if field == v.option then
501 table.insert(self.optionals[section], v)
506 if field and #field > 0 and self.dynamic then
507 self:add_dynamic(field)
511 -- Add a dynamic option
512 function AbstractSection.add_dynamic(self, field, optional)
513 local o = self:option(Value, field, field)
514 o.optional = optional
517 -- Parse all dynamic options
518 function AbstractSection.parse_dynamic(self, section)
519 if not self.dynamic then
523 local arr = luci.util.clone(self:cfgvalue(section))
524 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
525 for k, v in pairs(form) do
529 for key,val in pairs(arr) do
532 for i,c in ipairs(self.children) do
533 if c.option == key then
538 if create and key:sub(1, 1) ~= "." then
539 self:add_dynamic(key, true)
544 -- Returns the section's UCI table
545 function AbstractSection.cfgvalue(self, section)
546 return self.map:get(section)
549 -- Removes the section
550 function AbstractSection.remove(self, section)
551 return self.map:del(section)
554 -- Creates the section
555 function AbstractSection.create(self, section)
559 stat = self.map:set(section, nil, self.sectiontype)
561 section = self.map:add(self.sectiontype)
566 for k,v in pairs(self.children) do
568 self.map:set(section, v.option, v.default)
572 for k,v in pairs(self.defaults) do
573 self.map:set(section, k, v)
581 SimpleSection = class(AbstractSection)
583 function SimpleSection.__init__(self, form, ...)
584 AbstractSection.__init__(self, form, nil, ...)
585 self.template = "cbi/nullsection"
589 Table = class(AbstractSection)
591 function Table.__init__(self, form, data, ...)
592 local datasource = {}
593 datasource.config = "table"
596 function datasource.get(self, section, option)
597 return data[section] and data[section][option]
600 function datasource.del(...)
604 function datasource.get_scheme()
608 AbstractSection.__init__(self, datasource, "table", ...)
609 self.template = "cbi/tblsection"
610 self.rowcolors = true
611 self.anonymous = true
614 function Table.parse(self)
615 for i, k in ipairs(self:cfgsections()) do
616 if luci.http.formvalue("cbi.submit") then
622 function Table.cfgsections(self)
625 for i, v in luci.util.kspairs(self.data) do
626 table.insert(sections, i)
635 NamedSection - A fixed configuration section defined by its name
637 NamedSection = class(AbstractSection)
639 function NamedSection.__init__(self, map, section, stype, ...)
640 AbstractSection.__init__(self, map, stype, ...)
641 Node._i18n(self, map.config, section, nil, ...)
644 self.addremove = false
646 -- Use defaults from UVL
647 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
648 local vs = self.map:get_scheme(self.sectiontype)
649 self.addremove = not vs.unique and not vs.required
650 self.dynamic = vs.dynamic
651 self.title = self.title or vs.title
652 self.description = self.description or vs.descr
655 self.template = "cbi/nsection"
656 self.section = section
659 function NamedSection.parse(self)
660 local s = self.section
661 local active = self:cfgvalue(s)
663 if self.addremove then
664 local path = self.config.."."..s
665 if active then -- Remove the section
666 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
669 else -- Create and apply default values
670 if luci.http.formvalue("cbi.cns."..path) then
678 AbstractSection.parse_dynamic(self, s)
679 if luci.http.formvalue("cbi.submit") then
682 if not self.override_scheme and self.map.scheme then
683 _uvl_validate_section(self, s)
686 AbstractSection.parse_optionals(self, s)
692 TypedSection - A (set of) configuration section(s) defined by the type
693 addremove: Defines whether the user can add/remove sections of this type
694 anonymous: Allow creating anonymous sections
695 validate: a validation function returning nil if the section is invalid
697 TypedSection = class(AbstractSection)
699 function TypedSection.__init__(self, map, type, ...)
700 AbstractSection.__init__(self, map, type, ...)
701 Node._i18n(self, map.config, type, nil, ...)
703 self.template = "cbi/tsection"
705 self.anonymous = false
707 -- Use defaults from UVL
708 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
709 local vs = self.map:get_scheme(self.sectiontype)
710 self.addremove = not vs.unique and not vs.required
711 self.dynamic = vs.dynamic
712 self.anonymous = not vs.named
713 self.title = self.title or vs.title
714 self.description = self.description or vs.descr
718 -- Return all matching UCI sections for this TypedSection
719 function TypedSection.cfgsections(self)
721 self.map.uci:foreach(self.map.config, self.sectiontype,
723 if self:checkscope(section[".name"]) then
724 table.insert(sections, section[".name"])
731 -- Limits scope to sections that have certain option => value pairs
732 function TypedSection.depends(self, option, value)
733 table.insert(self.deps, {option=option, value=value})
736 function TypedSection.parse(self)
737 if self.addremove then
739 local crval = REMOVE_PREFIX .. self.config
740 local name = luci.http.formvaluetable(crval)
741 for k,v in pairs(name) do
742 if k:sub(-2) == ".x" then
745 if self:cfgvalue(k) and self:checkscope(k) then
752 for i, k in ipairs(self:cfgsections()) do
753 AbstractSection.parse_dynamic(self, k)
754 if luci.http.formvalue("cbi.submit") then
757 if not self.override_scheme and self.map.scheme then
758 _uvl_validate_section(self, k)
761 AbstractSection.parse_optionals(self, k)
764 if self.addremove then
767 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
768 local name = luci.http.formvalue(crval)
769 if self.anonymous then
771 created = self:create()
775 -- Ignore if it already exists
776 if self:cfgvalue(name) then
780 name = self:checkscope(name)
783 self.err_invalid = true
786 if name and #name > 0 then
787 created = self:create(name) and name
793 AbstractSection.parse_optionals(self, created)
798 -- Verifies scope of sections
799 function TypedSection.checkscope(self, section)
800 -- Check if we are not excluded
801 if self.filter and not self:filter(section) then
805 -- Check if at least one dependency is met
806 if #self.deps > 0 and self:cfgvalue(section) then
809 for k, v in ipairs(self.deps) do
810 if self:cfgvalue(section)[v.option] == v.value then
820 return self:validate(section)
824 -- Dummy validate function
825 function TypedSection.validate(self, section)
831 AbstractValue - An abstract Value Type
832 null: Value can be empty
833 valid: A function returning the value if it is valid otherwise nil
834 depends: A table of option => value pairs of which one must be true
835 default: The default value
836 size: The size of the input fields
837 rmempty: Unset value if empty
838 optional: This value is optional (see AbstractSection.optionals)
840 AbstractValue = class(Node)
842 function AbstractValue.__init__(self, map, section, option, ...)
843 Node.__init__(self, ...)
844 self.section = section
847 self.config = map.config
848 self.tag_invalid = {}
849 self.tag_missing = {}
850 self.tag_reqerror = {}
855 self.track_missing = false
859 self.optional = false
861 -- Use defaults from UVL
862 if not self.override_scheme
863 and self.map:get_scheme(self.section.sectiontype, self.option) then
864 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
865 self.rmempty = not vs.required
866 self.cast = (vs.type == "list") and "list" or "string"
867 self.title = self.title or vs.title
868 self.description = self.description or vs.descr
869 self.default = vs.default
871 if vs.depends and not self.override_dependencies then
872 for i, deps in ipairs(vs.depends) do
873 deps = _uvl_strip_remote_dependencies(deps)
882 -- Add a dependencie to another section field
883 function AbstractValue.depends(self, field, value)
885 if type(field) == "string" then
892 table.insert(self.deps, {deps=deps, add=""})
895 -- Generates the unique CBID
896 function AbstractValue.cbid(self, section)
897 return "cbid."..self.map.config.."."..section.."."..self.option
900 -- Return whether this object should be created
901 function AbstractValue.formcreated(self, section)
902 local key = "cbi.opt."..self.config.."."..section
903 return (luci.http.formvalue(key) == self.option)
906 -- Returns the formvalue for this object
907 function AbstractValue.formvalue(self, section)
908 return luci.http.formvalue(self:cbid(section))
911 function AbstractValue.additional(self, value)
912 self.optional = value
915 function AbstractValue.mandatory(self, value)
916 self.rmempty = not value
919 function AbstractValue.parse(self, section)
920 local fvalue = self:formvalue(section)
921 local cvalue = self:cfgvalue(section)
923 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
924 fvalue = self:transform(self:validate(fvalue, section))
926 self.tag_invalid[section] = true
928 if fvalue and not (fvalue == cvalue) then
929 self:write(section, fvalue)
931 else -- Unset the UCI or error
932 if self.rmempty or self.optional then
934 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
935 self.tag_missing[section] = true
940 -- Render if this value exists or if it is mandatory
941 function AbstractValue.render(self, s, scope)
942 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
945 scope.cbid = self:cbid(s)
946 scope.striptags = luci.util.striptags
948 scope.ifattr = function(cond,key,val)
950 return string.format(
951 ' %s="%s"', tostring(key),
952 luci.util.pcdata(tostring( val
954 or (type(self[key]) ~= "function" and self[key])
962 scope.attr = function(...)
963 return scope.ifattr( true, ... )
966 Node.render(self, scope)
970 -- Return the UCI value of this object
971 function AbstractValue.cfgvalue(self, section)
972 local value = self.map:get(section, self.option)
973 if not self.cast or self.cast == type(value) then
975 elseif self.cast == "string" then
976 if type(value) == "table" then
979 elseif self.cast == "table" then
984 -- Validate the form value
985 function AbstractValue.validate(self, value)
989 AbstractValue.transform = AbstractValue.validate
993 function AbstractValue.write(self, section, value)
994 return self.map:set(section, self.option, value)
998 function AbstractValue.remove(self, section)
999 return self.map:del(section, self.option)
1006 Value - A one-line value
1007 maxlength: The maximum length
1009 Value = class(AbstractValue)
1011 function Value.__init__(self, ...)
1012 AbstractValue.__init__(self, ...)
1013 self.template = "cbi/value"
1018 function Value.value(self, key, val)
1020 table.insert(self.keylist, tostring(key))
1021 table.insert(self.vallist, tostring(val))
1025 -- DummyValue - This does nothing except being there
1026 DummyValue = class(AbstractValue)
1028 function DummyValue.__init__(self, ...)
1029 AbstractValue.__init__(self, ...)
1030 self.template = "cbi/dvalue"
1034 function DummyValue.parse(self)
1040 Flag - A flag being enabled or disabled
1042 Flag = class(AbstractValue)
1044 function Flag.__init__(self, ...)
1045 AbstractValue.__init__(self, ...)
1046 self.template = "cbi/fvalue"
1052 -- A flag can only have two states: set or unset
1053 function Flag.parse(self, section)
1054 local fvalue = self:formvalue(section)
1057 fvalue = self.enabled
1059 fvalue = self.disabled
1062 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1063 if not(fvalue == self:cfgvalue(section)) then
1064 self:write(section, fvalue)
1067 self:remove(section)
1074 ListValue - A one-line value predefined in a list
1075 widget: The widget that will be used (select, radio)
1077 ListValue = class(AbstractValue)
1079 function ListValue.__init__(self, ...)
1080 AbstractValue.__init__(self, ...)
1081 self.template = "cbi/lvalue"
1086 self.widget = "select"
1088 if not self.override_scheme
1089 and self.map:get_scheme(self.section.sectiontype, self.option) then
1090 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1091 if self.value and vs.values and not self.override_values then
1092 if self.rmempty or self.optional then
1095 for k, v in pairs(vs.values) do
1097 if not self.override_dependencies
1098 and vs.enum_depends and vs.enum_depends[k] then
1099 for i, dep in ipairs(vs.enum_depends[k]) do
1100 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1103 self:value(k, v, unpack(deps))
1109 function ListValue.value(self, key, val, ...)
1110 if luci.util.contains(self.keylist, key) then
1115 table.insert(self.keylist, tostring(key))
1116 table.insert(self.vallist, tostring(val))
1118 for i, deps in ipairs({...}) do
1119 table.insert(self.deps, {add = "-"..key, deps=deps})
1123 function ListValue.validate(self, val)
1124 if luci.util.contains(self.keylist, val) then
1134 MultiValue - Multiple delimited values
1135 widget: The widget that will be used (select, checkbox)
1136 delimiter: The delimiter that will separate the values (default: " ")
1138 MultiValue = class(AbstractValue)
1140 function MultiValue.__init__(self, ...)
1141 AbstractValue.__init__(self, ...)
1142 self.template = "cbi/mvalue"
1147 self.widget = "checkbox"
1148 self.delimiter = " "
1151 function MultiValue.render(self, ...)
1152 if self.widget == "select" and not self.size then
1153 self.size = #self.vallist
1156 AbstractValue.render(self, ...)
1159 function MultiValue.value(self, key, val)
1160 if luci.util.contains(self.keylist, key) then
1165 table.insert(self.keylist, tostring(key))
1166 table.insert(self.vallist, tostring(val))
1169 function MultiValue.valuelist(self, section)
1170 local val = self:cfgvalue(section)
1172 if not(type(val) == "string") then
1176 return luci.util.split(val, self.delimiter)
1179 function MultiValue.validate(self, val)
1180 val = (type(val) == "table") and val or {val}
1184 for i, value in ipairs(val) do
1185 if luci.util.contains(self.keylist, value) then
1186 result = result and (result .. self.delimiter .. value) or value
1194 StaticList = class(MultiValue)
1196 function StaticList.__init__(self, ...)
1197 MultiValue.__init__(self, ...)
1199 self.valuelist = self.cfgvalue
1201 if not self.override_scheme
1202 and self.map:get_scheme(self.section.sectiontype, self.option) then
1203 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1204 if self.value and vs.values and not self.override_values then
1205 for k, v in pairs(vs.values) do
1212 function StaticList.validate(self, value)
1213 value = (type(value) == "table") and value or {value}
1216 for i, v in ipairs(value) do
1217 if luci.util.contains(self.valuelist, v) then
1218 table.insert(valid, v)
1225 DynamicList = class(AbstractValue)
1227 function DynamicList.__init__(self, ...)
1228 AbstractValue.__init__(self, ...)
1229 self.template = "cbi/dynlist"
1235 function DynamicList.value(self, key, val)
1237 table.insert(self.keylist, tostring(key))
1238 table.insert(self.vallist, tostring(val))
1241 function DynamicList.validate(self, value, section)
1242 value = (type(value) == "table") and value or {value}
1245 for i, v in ipairs(value) do
1247 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i)
1248 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1249 table.insert(valid, v)
1258 TextValue - A multi-line value
1261 TextValue = class(AbstractValue)
1263 function TextValue.__init__(self, ...)
1264 AbstractValue.__init__(self, ...)
1265 self.template = "cbi/tvalue"
1271 Button = class(AbstractValue)
1273 function Button.__init__(self, ...)
1274 AbstractValue.__init__(self, ...)
1275 self.template = "cbi/button"
1276 self.inputstyle = nil