Fixed a design flaw in luci.model.uci
[project/luci.git] / libs / cbi / luasrc / cbi.lua
1 --[[
2 LuCI - Configuration Bind Interface
3
4 Description:
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
7
8 FileId:
9 $Id$
10
11 License:
12 Copyright 2008 Steven Barth <steven@midlink.org>
13
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
17
18 http://www.apache.org/licenses/LICENSE-2.0
19
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.
25
26 ]]--
27 module("luci.cbi", package.seeall)
28
29 require("luci.template")
30 require("luci.util")
31 require("luci.http")
32 require("luci.model.uci")
33
34 local uci = luci.model.uci
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
37
38 FORM_NODATA = 0
39 FORM_VALID = 1
40 FORM_INVALID = -1
41
42 CREATE_PREFIX = "cbi.cts."
43 REMOVE_PREFIX = "cbi.rts."
44
45 -- Loads a CBI map from given file, creating an environment and returns it
46 function load(cbimap, ...)
47 require("luci.fs")
48 require("luci.i18n")
49 require("luci.config")
50 require("luci.util")
51
52 local cbidir = luci.util.libpath() .. "/model/cbi/"
53 local func, err = loadfile(cbidir..cbimap..".lua")
54
55 if not func then
56 return nil
57 end
58
59 luci.i18n.loadc("cbi")
60
61 luci.util.resfenv(func)
62 luci.util.updfenv(func, luci.cbi)
63 luci.util.extfenv(func, "translate", luci.i18n.translate)
64 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
65 luci.util.extfenv(func, "arg", {...})
66
67 local maps = {func()}
68
69 for i, map in ipairs(maps) do
70 if not instanceof(map, Node) then
71 error("CBI map returns no valid map object!")
72 return nil
73 end
74 end
75
76 return maps
77 end
78
79 -- Node pseudo abstract class
80 Node = class()
81
82 function Node.__init__(self, title, description)
83 self.children = {}
84 self.title = title or ""
85 self.description = description or ""
86 self.template = "cbi/node"
87 end
88
89 -- i18n helper
90 function Node._i18n(self, config, section, option, title, description)
91
92 -- i18n loaded?
93 if type(luci.i18n) == "table" then
94
95 local key = config and config:gsub("[^%w]+", "") or ""
96
97 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
98 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
99
100 self.title = title or luci.i18n.translate( key, option or section or config )
101 self.description = description or luci.i18n.translate( key .. "_desc", "" )
102 end
103 end
104
105 -- Append child nodes
106 function Node.append(self, obj)
107 table.insert(self.children, obj)
108 end
109
110 -- Parse this node and its children
111 function Node.parse(self, ...)
112 for k, child in ipairs(self.children) do
113 child:parse(...)
114 end
115 end
116
117 -- Render this node
118 function Node.render(self, scope)
119 scope = scope or {}
120 scope.self = self
121
122 luci.template.render(self.template, scope)
123 end
124
125 -- Render the children
126 function Node.render_children(self, ...)
127 for k, node in ipairs(self.children) do
128 node:render(...)
129 end
130 end
131
132
133 --[[
134 A simple template element
135 ]]--
136 Template = class(Node)
137
138 function Template.__init__(self, template)
139 Node.__init__(self)
140 self.template = template
141 end
142
143 function Template.render(self)
144 luci.template.render(self.template, {self=self})
145 end
146
147
148 --[[
149 Map - A map describing a configuration file
150 ]]--
151 Map = class(Node)
152
153 function Map.__init__(self, config, ...)
154 Node.__init__(self, ...)
155 Node._i18n(self, config, nil, nil, ...)
156
157 self.config = config
158 self.parsechain = {self.config}
159 self.template = "cbi/map"
160 if not uci.load_config(self.config) then
161 error("Unable to read UCI data: " .. self.config)
162 end
163 end
164
165
166 -- Chain foreign config
167 function Map.chain(self, config)
168 table.insert(self.parsechain, config)
169 end
170
171 -- Use optimized UCI writing
172 function Map.parse(self, ...)
173 if self.stateful then
174 uci.load_state(self.config)
175 else
176 uci.load_config(self.config)
177 end
178
179 Node.parse(self, ...)
180
181 for i, config in ipairs(self.parsechain) do
182 uci.save_config(config)
183 end
184 if luci.http.formvalue("cbi.apply") then
185 for i, config in ipairs(self.parsechain) do
186 uci.commit(config)
187 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
188 luci.util.exec(luci.config.uci_oncommit[config])
189 end
190
191 -- Refresh data because commit changes section names
192 uci.load_config(config)
193 end
194
195 -- Reparse sections
196 Node.parse(self, ...)
197
198 end
199 for i, config in ipairs(self.parsechain) do
200 uci.unload(config)
201 end
202 end
203
204 -- Creates a child section
205 function Map.section(self, class, ...)
206 if instanceof(class, AbstractSection) then
207 local obj = class(self, ...)
208 self:append(obj)
209 return obj
210 else
211 error("class must be a descendent of AbstractSection")
212 end
213 end
214
215 -- UCI add
216 function Map.add(self, sectiontype)
217 return uci.add(self.config, sectiontype)
218 end
219
220 -- UCI set
221 function Map.set(self, section, option, value)
222 if option then
223 return uci.set(self.config, section, option, value)
224 else
225 return uci.set(self.config, section, value)
226 end
227 end
228
229 -- UCI del
230 function Map.del(self, section, option)
231 if option then
232 return uci.delete(self.config, section, option)
233 else
234 return uci.delete(self.config, section)
235 end
236 end
237
238 -- UCI get
239 function Map.get(self, section, option)
240 if not section then
241 return uci.get_all(self.config)
242 elseif option then
243 return uci.get(self.config, section, option)
244 else
245 return uci.get_all(self.config, section)
246 end
247 end
248
249
250 --[[
251 Page - A simple node
252 ]]--
253
254 Page = class(Node)
255 Page.__init__ = Node.__init__
256 Page.parse = function() end
257
258
259 --[[
260 SimpleForm - A Simple non-UCI form
261 ]]--
262 SimpleForm = class(Node)
263
264 function SimpleForm.__init__(self, config, title, description, data)
265 Node.__init__(self, title, description)
266 self.config = config
267 self.data = data or {}
268 self.template = "cbi/simpleform"
269 self.dorender = true
270 end
271
272 function SimpleForm.parse(self, ...)
273 if luci.http.formvalue("cbi.submit") then
274 Node.parse(self, 1, ...)
275 end
276
277 local valid = true
278 for k, j in ipairs(self.children) do
279 for i, v in ipairs(j.children) do
280 valid = valid
281 and (not v.tag_missing or not v.tag_missing[1])
282 and (not v.tag_invalid or not v.tag_invalid[1])
283 end
284 end
285
286 local state =
287 not luci.http.formvalue("cbi.submit") and 0
288 or valid and 1
289 or -1
290
291 self.dorender = not self.handle or self:handle(state, self.data) ~= false
292 end
293
294 function SimpleForm.render(self, ...)
295 if self.dorender then
296 Node.render(self, ...)
297 end
298 end
299
300 function SimpleForm.section(self, class, ...)
301 if instanceof(class, AbstractSection) then
302 local obj = class(self, ...)
303 self:append(obj)
304 return obj
305 else
306 error("class must be a descendent of AbstractSection")
307 end
308 end
309
310 -- Creates a child field
311 function SimpleForm.field(self, class, ...)
312 local section
313 for k, v in ipairs(self.children) do
314 if instanceof(v, SimpleSection) then
315 section = v
316 break
317 end
318 end
319 if not section then
320 section = self:section(SimpleSection)
321 end
322
323 if instanceof(class, AbstractValue) then
324 local obj = class(self, ...)
325 obj.track_missing = true
326 section:append(obj)
327 return obj
328 else
329 error("class must be a descendent of AbstractValue")
330 end
331 end
332
333 function SimpleForm.set(self, section, option, value)
334 self.data[option] = value
335 end
336
337
338 function SimpleForm.del(self, section, option)
339 self.data[option] = nil
340 end
341
342
343 function SimpleForm.get(self, section, option)
344 return self.data[option]
345 end
346
347
348
349 --[[
350 AbstractSection
351 ]]--
352 AbstractSection = class(Node)
353
354 function AbstractSection.__init__(self, map, sectiontype, ...)
355 Node.__init__(self, ...)
356 self.sectiontype = sectiontype
357 self.map = map
358 self.config = map.config
359 self.optionals = {}
360 self.defaults = {}
361
362 self.optional = true
363 self.addremove = false
364 self.dynamic = false
365 end
366
367 -- Appends a new option
368 function AbstractSection.option(self, class, option, ...)
369 if instanceof(class, AbstractValue) then
370 local obj = class(self.map, option, ...)
371
372 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
373
374 self:append(obj)
375 return obj
376 else
377 error("class must be a descendent of AbstractValue")
378 end
379 end
380
381 -- Parse optional options
382 function AbstractSection.parse_optionals(self, section)
383 if not self.optional then
384 return
385 end
386
387 self.optionals[section] = {}
388
389 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
390 for k,v in ipairs(self.children) do
391 if v.optional and not v:cfgvalue(section) then
392 if field == v.option then
393 field = nil
394 else
395 table.insert(self.optionals[section], v)
396 end
397 end
398 end
399
400 if field and #field > 0 and self.dynamic then
401 self:add_dynamic(field)
402 end
403 end
404
405 -- Add a dynamic option
406 function AbstractSection.add_dynamic(self, field, optional)
407 local o = self:option(Value, field, field)
408 o.optional = optional
409 end
410
411 -- Parse all dynamic options
412 function AbstractSection.parse_dynamic(self, section)
413 if not self.dynamic then
414 return
415 end
416
417 local arr = luci.util.clone(self:cfgvalue(section))
418 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
419 for k, v in pairs(form) do
420 arr[k] = v
421 end
422
423 for key,val in pairs(arr) do
424 local create = true
425
426 for i,c in ipairs(self.children) do
427 if c.option == key then
428 create = false
429 end
430 end
431
432 if create and key:sub(1, 1) ~= "." then
433 self:add_dynamic(key, true)
434 end
435 end
436 end
437
438 -- Returns the section's UCI table
439 function AbstractSection.cfgvalue(self, section)
440 return self.map:get(section)
441 end
442
443 -- Removes the section
444 function AbstractSection.remove(self, section)
445 return self.map:del(section)
446 end
447
448 -- Creates the section
449 function AbstractSection.create(self, section)
450 local stat
451
452 if section then
453 stat = self.map:set(section, nil, self.sectiontype)
454 else
455 section = self.map:add(self.sectiontype)
456 stat = section
457 end
458
459 if stat then
460 for k,v in pairs(self.children) do
461 if v.default then
462 self.map:set(section, v.option, v.default)
463 end
464 end
465
466 for k,v in pairs(self.defaults) do
467 self.map:set(section, k, v)
468 end
469 end
470
471 return stat
472 end
473
474
475 SimpleSection = class(AbstractSection)
476
477 function SimpleSection.__init__(self, form, ...)
478 AbstractSection.__init__(self, form, nil, ...)
479 self.template = "cbi/nullsection"
480 end
481
482
483 Table = class(AbstractSection)
484
485 function Table.__init__(self, form, data, ...)
486 local datasource = {}
487 datasource.config = "table"
488 self.data = data
489
490 function datasource.get(self, section, option)
491 return data[section] and data[section][option]
492 end
493
494 function datasource.del(...)
495 return true
496 end
497
498 AbstractSection.__init__(self, datasource, "table", ...)
499 self.template = "cbi/tblsection"
500 self.rowcolors = true
501 self.anonymous = true
502 end
503
504 function Table.parse(self)
505 for i, k in ipairs(self:cfgsections()) do
506 if luci.http.formvalue("cbi.submit") then
507 Node.parse(self, k)
508 end
509 end
510 end
511
512 function Table.cfgsections(self)
513 local sections = {}
514
515 for i, v in luci.util.kspairs(self.data) do
516 table.insert(sections, i)
517 end
518
519 return sections
520 end
521
522
523
524 --[[
525 NamedSection - A fixed configuration section defined by its name
526 ]]--
527 NamedSection = class(AbstractSection)
528
529 function NamedSection.__init__(self, map, section, type, ...)
530 AbstractSection.__init__(self, map, type, ...)
531 Node._i18n(self, map.config, section, nil, ...)
532
533 self.template = "cbi/nsection"
534 self.section = section
535 self.addremove = false
536 end
537
538 function NamedSection.parse(self)
539 local s = self.section
540 local active = self:cfgvalue(s)
541
542
543 if self.addremove then
544 local path = self.config.."."..s
545 if active then -- Remove the section
546 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
547 return
548 end
549 else -- Create and apply default values
550 if luci.http.formvalue("cbi.cns."..path) then
551 self:create(s)
552 return
553 end
554 end
555 end
556
557 if active then
558 AbstractSection.parse_dynamic(self, s)
559 if luci.http.formvalue("cbi.submit") then
560 Node.parse(self, s)
561 end
562 AbstractSection.parse_optionals(self, s)
563 end
564 end
565
566
567 --[[
568 TypedSection - A (set of) configuration section(s) defined by the type
569 addremove: Defines whether the user can add/remove sections of this type
570 anonymous: Allow creating anonymous sections
571 validate: a validation function returning nil if the section is invalid
572 ]]--
573 TypedSection = class(AbstractSection)
574
575 function TypedSection.__init__(self, map, type, ...)
576 AbstractSection.__init__(self, map, type, ...)
577 Node._i18n(self, map.config, type, nil, ...)
578
579 self.template = "cbi/tsection"
580 self.deps = {}
581
582 self.anonymous = false
583 end
584
585 -- Return all matching UCI sections for this TypedSection
586 function TypedSection.cfgsections(self)
587 local sections = {}
588 uci.foreach(self.map.config, self.sectiontype,
589 function (section)
590 if self:checkscope(section[".name"]) then
591 table.insert(sections, section[".name"])
592 end
593 end)
594
595 return sections
596 end
597
598 -- Limits scope to sections that have certain option => value pairs
599 function TypedSection.depends(self, option, value)
600 table.insert(self.deps, {option=option, value=value})
601 end
602
603 function TypedSection.parse(self)
604 if self.addremove then
605 -- Create
606 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
607 local name = luci.http.formvalue(crval)
608 if self.anonymous then
609 if name then
610 self:create()
611 end
612 else
613 if name then
614 -- Ignore if it already exists
615 if self:cfgvalue(name) then
616 name = nil;
617 end
618
619 name = self:checkscope(name)
620
621 if not name then
622 self.err_invalid = true
623 end
624
625 if name and name:len() > 0 then
626 self:create(name)
627 end
628 end
629 end
630
631 -- Remove
632 crval = REMOVE_PREFIX .. self.config
633 name = luci.http.formvaluetable(crval)
634 for k,v in pairs(name) do
635 if self:cfgvalue(k) and self:checkscope(k) then
636 self:remove(k)
637 end
638 end
639 end
640
641 for i, k in ipairs(self:cfgsections()) do
642 AbstractSection.parse_dynamic(self, k)
643 if luci.http.formvalue("cbi.submit") then
644 Node.parse(self, k)
645 end
646 AbstractSection.parse_optionals(self, k)
647 end
648 end
649
650 -- Verifies scope of sections
651 function TypedSection.checkscope(self, section)
652 -- Check if we are not excluded
653 if self.filter and not self:filter(section) then
654 return nil
655 end
656
657 -- Check if at least one dependency is met
658 if #self.deps > 0 and self:cfgvalue(section) then
659 local stat = false
660
661 for k, v in ipairs(self.deps) do
662 if self:cfgvalue(section)[v.option] == v.value then
663 stat = true
664 end
665 end
666
667 if not stat then
668 return nil
669 end
670 end
671
672 return self:validate(section)
673 end
674
675
676 -- Dummy validate function
677 function TypedSection.validate(self, section)
678 return section
679 end
680
681
682 --[[
683 AbstractValue - An abstract Value Type
684 null: Value can be empty
685 valid: A function returning the value if it is valid otherwise nil
686 depends: A table of option => value pairs of which one must be true
687 default: The default value
688 size: The size of the input fields
689 rmempty: Unset value if empty
690 optional: This value is optional (see AbstractSection.optionals)
691 ]]--
692 AbstractValue = class(Node)
693
694 function AbstractValue.__init__(self, map, option, ...)
695 Node.__init__(self, ...)
696 self.option = option
697 self.map = map
698 self.config = map.config
699 self.tag_invalid = {}
700 self.tag_missing = {}
701 self.tag_error = {}
702 self.deps = {}
703
704 self.track_missing = false
705 self.rmempty = false
706 self.default = nil
707 self.size = nil
708 self.optional = false
709 end
710
711 -- Add a dependencie to another section field
712 function AbstractValue.depends(self, field, value)
713 table.insert(self.deps, {field=field, value=value})
714 end
715
716 -- Generates the unique CBID
717 function AbstractValue.cbid(self, section)
718 return "cbid."..self.map.config.."."..section.."."..self.option
719 end
720
721 -- Return whether this object should be created
722 function AbstractValue.formcreated(self, section)
723 local key = "cbi.opt."..self.config.."."..section
724 return (luci.http.formvalue(key) == self.option)
725 end
726
727 -- Returns the formvalue for this object
728 function AbstractValue.formvalue(self, section)
729 return luci.http.formvalue(self:cbid(section))
730 end
731
732 function AbstractValue.additional(self, value)
733 self.optional = value
734 end
735
736 function AbstractValue.mandatory(self, value)
737 self.rmempty = not value
738 end
739
740 function AbstractValue.parse(self, section)
741 local fvalue = self:formvalue(section)
742 local cvalue = self:cfgvalue(section)
743
744 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
745 fvalue = self:transform(self:validate(fvalue, section))
746 if not fvalue then
747 self.tag_invalid[section] = true
748 end
749 if fvalue and not (fvalue == cvalue) then
750 self:write(section, fvalue)
751 end
752 else -- Unset the UCI or error
753 if self.rmempty or self.optional then
754 self:remove(section)
755 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
756 self.tag_missing[section] = true
757 end
758 end
759 end
760
761 -- Render if this value exists or if it is mandatory
762 function AbstractValue.render(self, s, scope)
763 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
764 scope = scope or {}
765 scope.section = s
766 scope.cbid = self:cbid(s)
767
768 scope.ifattr = function(cond,key,val)
769 if cond then
770 return string.format(
771 ' %s="%s"', tostring(key),
772 luci.util.pcdata(tostring( val
773 or scope[key]
774 or (type(self[key]) ~= "function" and self[key])
775 or "" ))
776 )
777 else
778 return ''
779 end
780 end
781
782 scope.attr = function(...)
783 return scope.ifattr( true, ... )
784 end
785
786 Node.render(self, scope)
787 end
788 end
789
790 -- Return the UCI value of this object
791 function AbstractValue.cfgvalue(self, section)
792 return self.map:get(section, self.option)
793 end
794
795 -- Validate the form value
796 function AbstractValue.validate(self, value)
797 return value
798 end
799
800 AbstractValue.transform = AbstractValue.validate
801
802
803 -- Write to UCI
804 function AbstractValue.write(self, section, value)
805 return self.map:set(section, self.option, value)
806 end
807
808 -- Remove from UCI
809 function AbstractValue.remove(self, section)
810 return self.map:del(section, self.option)
811 end
812
813
814
815
816 --[[
817 Value - A one-line value
818 maxlength: The maximum length
819 ]]--
820 Value = class(AbstractValue)
821
822 function Value.__init__(self, ...)
823 AbstractValue.__init__(self, ...)
824 self.template = "cbi/value"
825 self.keylist = {}
826 self.vallist = {}
827 end
828
829 function Value.value(self, key, val)
830 val = val or key
831 table.insert(self.keylist, tostring(key))
832 table.insert(self.vallist, tostring(val))
833 end
834
835
836 -- DummyValue - This does nothing except being there
837 DummyValue = class(AbstractValue)
838
839 function DummyValue.__init__(self, map, ...)
840 AbstractValue.__init__(self, map, ...)
841 self.template = "cbi/dvalue"
842 self.value = nil
843 end
844
845 function DummyValue.parse(self)
846
847 end
848
849
850 --[[
851 Flag - A flag being enabled or disabled
852 ]]--
853 Flag = class(AbstractValue)
854
855 function Flag.__init__(self, ...)
856 AbstractValue.__init__(self, ...)
857 self.template = "cbi/fvalue"
858
859 self.enabled = "1"
860 self.disabled = "0"
861 end
862
863 -- A flag can only have two states: set or unset
864 function Flag.parse(self, section)
865 local fvalue = self:formvalue(section)
866
867 if fvalue then
868 fvalue = self.enabled
869 else
870 fvalue = self.disabled
871 end
872
873 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
874 if not(fvalue == self:cfgvalue(section)) then
875 self:write(section, fvalue)
876 end
877 else
878 self:remove(section)
879 end
880 end
881
882
883
884 --[[
885 ListValue - A one-line value predefined in a list
886 widget: The widget that will be used (select, radio)
887 ]]--
888 ListValue = class(AbstractValue)
889
890 function ListValue.__init__(self, ...)
891 AbstractValue.__init__(self, ...)
892 self.template = "cbi/lvalue"
893 self.keylist = {}
894 self.vallist = {}
895
896 self.size = 1
897 self.widget = "select"
898 end
899
900 function ListValue.value(self, key, val)
901 val = val or key
902 table.insert(self.keylist, tostring(key))
903 table.insert(self.vallist, tostring(val))
904 end
905
906 function ListValue.validate(self, val)
907 if luci.util.contains(self.keylist, val) then
908 return val
909 else
910 return nil
911 end
912 end
913
914
915
916 --[[
917 MultiValue - Multiple delimited values
918 widget: The widget that will be used (select, checkbox)
919 delimiter: The delimiter that will separate the values (default: " ")
920 ]]--
921 MultiValue = class(AbstractValue)
922
923 function MultiValue.__init__(self, ...)
924 AbstractValue.__init__(self, ...)
925 self.template = "cbi/mvalue"
926 self.keylist = {}
927 self.vallist = {}
928
929 self.widget = "checkbox"
930 self.delimiter = " "
931 end
932
933 function MultiValue.render(self, ...)
934 if self.widget == "select" and not self.size then
935 self.size = #self.vallist
936 end
937
938 AbstractValue.render(self, ...)
939 end
940
941 function MultiValue.value(self, key, val)
942 val = val or key
943 table.insert(self.keylist, tostring(key))
944 table.insert(self.vallist, tostring(val))
945 end
946
947 function MultiValue.valuelist(self, section)
948 local val = self:cfgvalue(section)
949
950 if not(type(val) == "string") then
951 return {}
952 end
953
954 return luci.util.split(val, self.delimiter)
955 end
956
957 function MultiValue.validate(self, val)
958 val = (type(val) == "table") and val or {val}
959
960 local result
961
962 for i, value in ipairs(val) do
963 if luci.util.contains(self.keylist, value) then
964 result = result and (result .. self.delimiter .. value) or value
965 end
966 end
967
968 return result
969 end
970
971 --[[
972 TextValue - A multi-line value
973 rows: Rows
974 ]]--
975 TextValue = class(AbstractValue)
976
977 function TextValue.__init__(self, ...)
978 AbstractValue.__init__(self, ...)
979 self.template = "cbi/tvalue"
980 end
981
982 --[[
983 Button
984 ]]--
985 Button = class(AbstractValue)
986
987 function Button.__init__(self, ...)
988 AbstractValue.__init__(self, ...)
989 self.template = "cbi/button"
990 self.inputstyle = nil
991 self.rmempty = true
992 end