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