6cc090882395c11521c9de5726d58962318974e4
[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 local deps
714 if type(field) == "string" then
715 deps = {}
716 deps[field] = value
717 else
718 deps = field
719 end
720
721 table.insert(self.deps, {deps=deps, add=""})
722 end
723
724 -- Generates the unique CBID
725 function AbstractValue.cbid(self, section)
726 return "cbid."..self.map.config.."."..section.."."..self.option
727 end
728
729 -- Return whether this object should be created
730 function AbstractValue.formcreated(self, section)
731 local key = "cbi.opt."..self.config.."."..section
732 return (luci.http.formvalue(key) == self.option)
733 end
734
735 -- Returns the formvalue for this object
736 function AbstractValue.formvalue(self, section)
737 return luci.http.formvalue(self:cbid(section))
738 end
739
740 function AbstractValue.additional(self, value)
741 self.optional = value
742 end
743
744 function AbstractValue.mandatory(self, value)
745 self.rmempty = not value
746 end
747
748 function AbstractValue.parse(self, section)
749 local fvalue = self:formvalue(section)
750 local cvalue = self:cfgvalue(section)
751
752 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
753 fvalue = self:transform(self:validate(fvalue, section))
754 if not fvalue then
755 self.tag_invalid[section] = true
756 end
757 if fvalue and not (fvalue == cvalue) then
758 self:write(section, fvalue)
759 end
760 else -- Unset the UCI or error
761 if self.rmempty or self.optional then
762 self:remove(section)
763 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
764 self.tag_missing[section] = true
765 end
766 end
767 end
768
769 -- Render if this value exists or if it is mandatory
770 function AbstractValue.render(self, s, scope)
771 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
772 scope = scope or {}
773 scope.section = s
774 scope.cbid = self:cbid(s)
775
776 scope.ifattr = function(cond,key,val)
777 if cond then
778 return string.format(
779 ' %s="%s"', tostring(key),
780 luci.util.pcdata(tostring( val
781 or scope[key]
782 or (type(self[key]) ~= "function" and self[key])
783 or "" ))
784 )
785 else
786 return ''
787 end
788 end
789
790 scope.attr = function(...)
791 return scope.ifattr( true, ... )
792 end
793
794 Node.render(self, scope)
795 end
796 end
797
798 -- Return the UCI value of this object
799 function AbstractValue.cfgvalue(self, section)
800 return self.map:get(section, self.option)
801 end
802
803 -- Validate the form value
804 function AbstractValue.validate(self, value)
805 return value
806 end
807
808 AbstractValue.transform = AbstractValue.validate
809
810
811 -- Write to UCI
812 function AbstractValue.write(self, section, value)
813 return self.map:set(section, self.option, value)
814 end
815
816 -- Remove from UCI
817 function AbstractValue.remove(self, section)
818 return self.map:del(section, self.option)
819 end
820
821
822
823
824 --[[
825 Value - A one-line value
826 maxlength: The maximum length
827 ]]--
828 Value = class(AbstractValue)
829
830 function Value.__init__(self, ...)
831 AbstractValue.__init__(self, ...)
832 self.template = "cbi/value"
833 self.keylist = {}
834 self.vallist = {}
835 end
836
837 function Value.value(self, key, val)
838 val = val or key
839 table.insert(self.keylist, tostring(key))
840 table.insert(self.vallist, tostring(val))
841 end
842
843
844 -- DummyValue - This does nothing except being there
845 DummyValue = class(AbstractValue)
846
847 function DummyValue.__init__(self, map, ...)
848 AbstractValue.__init__(self, map, ...)
849 self.template = "cbi/dvalue"
850 self.value = nil
851 end
852
853 function DummyValue.parse(self)
854
855 end
856
857
858 --[[
859 Flag - A flag being enabled or disabled
860 ]]--
861 Flag = class(AbstractValue)
862
863 function Flag.__init__(self, ...)
864 AbstractValue.__init__(self, ...)
865 self.template = "cbi/fvalue"
866
867 self.enabled = "1"
868 self.disabled = "0"
869 end
870
871 -- A flag can only have two states: set or unset
872 function Flag.parse(self, section)
873 local fvalue = self:formvalue(section)
874
875 if fvalue then
876 fvalue = self.enabled
877 else
878 fvalue = self.disabled
879 end
880
881 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
882 if not(fvalue == self:cfgvalue(section)) then
883 self:write(section, fvalue)
884 end
885 else
886 self:remove(section)
887 end
888 end
889
890
891
892 --[[
893 ListValue - A one-line value predefined in a list
894 widget: The widget that will be used (select, radio)
895 ]]--
896 ListValue = class(AbstractValue)
897
898 function ListValue.__init__(self, ...)
899 AbstractValue.__init__(self, ...)
900 self.template = "cbi/lvalue"
901 self.keylist = {}
902 self.vallist = {}
903
904 self.size = 1
905 self.widget = "select"
906 end
907
908 function ListValue.value(self, key, val, ...)
909 val = val or key
910 table.insert(self.keylist, tostring(key))
911 table.insert(self.vallist, tostring(val))
912
913 for i, deps in ipairs({...}) do
914 table.insert(self.deps, {add = "-"..key, deps=deps})
915 end
916 end
917
918 function ListValue.validate(self, val)
919 if luci.util.contains(self.keylist, val) then
920 return val
921 else
922 return nil
923 end
924 end
925
926
927
928 --[[
929 MultiValue - Multiple delimited values
930 widget: The widget that will be used (select, checkbox)
931 delimiter: The delimiter that will separate the values (default: " ")
932 ]]--
933 MultiValue = class(AbstractValue)
934
935 function MultiValue.__init__(self, ...)
936 AbstractValue.__init__(self, ...)
937 self.template = "cbi/mvalue"
938 self.keylist = {}
939 self.vallist = {}
940
941 self.widget = "checkbox"
942 self.delimiter = " "
943 end
944
945 function MultiValue.render(self, ...)
946 if self.widget == "select" and not self.size then
947 self.size = #self.vallist
948 end
949
950 AbstractValue.render(self, ...)
951 end
952
953 function MultiValue.value(self, key, val)
954 val = val or key
955 table.insert(self.keylist, tostring(key))
956 table.insert(self.vallist, tostring(val))
957 end
958
959 function MultiValue.valuelist(self, section)
960 local val = self:cfgvalue(section)
961
962 if not(type(val) == "string") then
963 return {}
964 end
965
966 return luci.util.split(val, self.delimiter)
967 end
968
969 function MultiValue.validate(self, val)
970 val = (type(val) == "table") and val or {val}
971
972 local result
973
974 for i, value in ipairs(val) do
975 if luci.util.contains(self.keylist, value) then
976 result = result and (result .. self.delimiter .. value) or value
977 end
978 end
979
980 return result
981 end
982
983 --[[
984 TextValue - A multi-line value
985 rows: Rows
986 ]]--
987 TextValue = class(AbstractValue)
988
989 function TextValue.__init__(self, ...)
990 AbstractValue.__init__(self, ...)
991 self.template = "cbi/tvalue"
992 end
993
994 --[[
995 Button
996 ]]--
997 Button = class(AbstractValue)
998
999 function Button.__init__(self, ...)
1000 AbstractValue.__init__(self, ...)
1001 self.template = "cbi/button"
1002 self.inputstyle = nil
1003 self.rmempty = true
1004 end