libs/cbi: Added preliminary UCI list datatype support... for great justice
[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 function Map.render(self, ...)
166         if self.stateful then
167                 uci.load_state(self.config)
168         else
169                 uci.load_config(self.config)
170         end
171         Node.render(self, ...)
172 end
173
174
175 -- Chain foreign config
176 function Map.chain(self, config)
177         table.insert(self.parsechain, config)
178 end
179
180 -- Use optimized UCI writing
181 function Map.parse(self, ...)
182         if self.stateful then
183                 uci.load_state(self.config)
184         else
185                 uci.load_config(self.config)
186         end
187         
188         Node.parse(self, ...)
189         
190         for i, config in ipairs(self.parsechain) do
191                 uci.save_config(config)
192         end
193         if luci.http.formvalue("cbi.apply") then
194                 for i, config in ipairs(self.parsechain) do
195                         uci.commit(config)
196                         if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
197                                 luci.util.exec(luci.config.uci_oncommit[config])
198                         end
199
200                         -- Refresh data because commit changes section names
201                         uci.load_config(config)
202                 end
203
204                 -- Reparse sections
205                 Node.parse(self, ...)
206
207         end
208         for i, config in ipairs(self.parsechain) do
209                 uci.unload(config)
210         end
211 end
212
213 -- Creates a child section
214 function Map.section(self, class, ...)
215         if instanceof(class, AbstractSection) then
216                 local obj  = class(self, ...)
217                 self:append(obj)
218                 return obj
219         else
220                 error("class must be a descendent of AbstractSection")
221         end
222 end
223
224 -- UCI add
225 function Map.add(self, sectiontype)
226         return uci.add(self.config, sectiontype)
227 end
228
229 -- UCI set
230 function Map.set(self, section, option, value)
231         if option then
232                 return uci.set(self.config, section, option, value)
233         else
234                 return uci.set(self.config, section, value)
235         end
236 end
237
238 -- UCI del
239 function Map.del(self, section, option)
240         if option then
241                 return uci.delete(self.config, section, option)
242         else
243                 return uci.delete(self.config, section)
244         end
245 end
246
247 -- UCI get
248 function Map.get(self, section, option)
249         if not section then
250                 return uci.get_all(self.config)
251         elseif option then
252                 return uci.get(self.config, section, option)
253         else
254                 return uci.get_all(self.config, section)
255         end
256 end
257
258
259 --[[
260 Page - A simple node
261 ]]--
262
263 Page = class(Node)
264 Page.__init__ = Node.__init__
265 Page.parse    = function() end
266
267
268 --[[
269 SimpleForm - A Simple non-UCI form
270 ]]--
271 SimpleForm = class(Node)
272
273 function SimpleForm.__init__(self, config, title, description, data)
274         Node.__init__(self, title, description)
275         self.config = config
276         self.data = data or {}
277         self.template = "cbi/simpleform"
278         self.dorender = true
279 end
280
281 function SimpleForm.parse(self, ...)
282         if luci.http.formvalue("cbi.submit") then
283                 Node.parse(self, 1, ...)
284         end
285                 
286         local valid = true
287         for k, j in ipairs(self.children) do 
288                 for i, v in ipairs(j.children) do
289                         valid = valid 
290                          and (not v.tag_missing or not v.tag_missing[1])
291                          and (not v.tag_invalid or not v.tag_invalid[1])
292                 end
293         end
294         
295         local state = 
296                 not luci.http.formvalue("cbi.submit") and 0
297                 or valid and 1
298                 or -1
299
300         self.dorender = not self.handle or self:handle(state, self.data) ~= false
301 end
302
303 function SimpleForm.render(self, ...)
304         if self.dorender then
305                 Node.render(self, ...)
306         end
307 end
308
309 function SimpleForm.section(self, class, ...)
310         if instanceof(class, AbstractSection) then
311                 local obj  = class(self, ...)
312                 self:append(obj)
313                 return obj
314         else
315                 error("class must be a descendent of AbstractSection")
316         end
317 end
318
319 -- Creates a child field
320 function SimpleForm.field(self, class, ...)
321         local section
322         for k, v in ipairs(self.children) do
323                 if instanceof(v, SimpleSection) then
324                         section = v
325                         break
326                 end
327         end
328         if not section then
329                 section = self:section(SimpleSection)
330         end
331         
332         if instanceof(class, AbstractValue) then
333                 local obj  = class(self, ...)
334                 obj.track_missing = true
335                 section:append(obj)
336                 return obj
337         else
338                 error("class must be a descendent of AbstractValue")
339         end
340 end
341
342 function SimpleForm.set(self, section, option, value)
343         self.data[option] = value
344 end
345
346
347 function SimpleForm.del(self, section, option)
348         self.data[option] = nil
349 end
350
351
352 function SimpleForm.get(self, section, option)
353         return self.data[option]
354 end
355
356
357
358 --[[
359 AbstractSection
360 ]]--
361 AbstractSection = class(Node)
362
363 function AbstractSection.__init__(self, map, sectiontype, ...)
364         Node.__init__(self, ...)
365         self.sectiontype = sectiontype
366         self.map = map
367         self.config = map.config
368         self.optionals = {}
369         self.defaults = {}
370         self.cast = "string"
371
372         self.optional = true
373         self.addremove = false
374         self.dynamic = false
375 end
376
377 -- Appends a new option
378 function AbstractSection.option(self, class, option, ...)
379         if instanceof(class, AbstractValue) then
380                 local obj  = class(self.map, option, ...)
381
382                 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
383
384                 self:append(obj)
385                 return obj
386         else
387                 error("class must be a descendent of AbstractValue")
388         end
389 end
390
391 -- Parse optional options
392 function AbstractSection.parse_optionals(self, section)
393         if not self.optional then
394                 return
395         end
396
397         self.optionals[section] = {}
398
399         local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
400         for k,v in ipairs(self.children) do
401                 if v.optional and not v:cfgvalue(section) then
402                         if field == v.option then
403                                 field = nil
404                         else
405                                 table.insert(self.optionals[section], v)
406                         end
407                 end
408         end
409
410         if field and #field > 0 and self.dynamic then
411                 self:add_dynamic(field)
412         end
413 end
414
415 -- Add a dynamic option
416 function AbstractSection.add_dynamic(self, field, optional)
417         local o = self:option(Value, field, field)
418         o.optional = optional
419 end
420
421 -- Parse all dynamic options
422 function AbstractSection.parse_dynamic(self, section)
423         if not self.dynamic then
424                 return
425         end
426
427         local arr  = luci.util.clone(self:cfgvalue(section))
428         local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
429         for k, v in pairs(form) do
430                 arr[k] = v
431         end
432
433         for key,val in pairs(arr) do
434                 local create = true
435
436                 for i,c in ipairs(self.children) do
437                         if c.option == key then
438                                 create = false
439                         end
440                 end
441
442                 if create and key:sub(1, 1) ~= "." then
443                         self:add_dynamic(key, true)
444                 end
445         end
446 end
447
448 -- Returns the section's UCI table
449 function AbstractSection.cfgvalue(self, section)
450         local value = self.map:get(section)
451         if not self.cast or self.cast == type(value) then
452                 return value
453         elseif self.cast == "string" then
454                 if type(value) == "table" then
455                         return value[1]
456                 end
457         elseif self.cast == "table" then
458                 return {value}
459         end
460 end
461
462 -- Removes the section
463 function AbstractSection.remove(self, section)
464         return self.map:del(section)
465 end
466
467 -- Creates the section
468 function AbstractSection.create(self, section)
469         local stat
470         
471         if section then
472                 stat = self.map:set(section, nil, self.sectiontype)
473         else
474                 section = self.map:add(self.sectiontype)
475                 stat = section
476         end
477
478         if stat then
479                 for k,v in pairs(self.children) do
480                         if v.default then
481                                 self.map:set(section, v.option, v.default)
482                         end
483                 end
484
485                 for k,v in pairs(self.defaults) do
486                         self.map:set(section, k, v)
487                 end
488         end
489
490         return stat
491 end
492
493
494 SimpleSection = class(AbstractSection)
495
496 function SimpleSection.__init__(self, form, ...)
497         AbstractSection.__init__(self, form, nil, ...)
498         self.template = "cbi/nullsection"
499 end
500
501
502 Table = class(AbstractSection)
503
504 function Table.__init__(self, form, data, ...)
505         local datasource = {}
506         datasource.config = "table"
507         self.data = data
508         
509         function datasource.get(self, section, option)
510                 return data[section] and data[section][option]
511         end
512         
513         function datasource.del(...)
514                 return true
515         end
516         
517         AbstractSection.__init__(self, datasource, "table", ...)
518         self.template = "cbi/tblsection"
519         self.rowcolors = true
520         self.anonymous = true
521 end
522
523 function Table.parse(self)
524         for i, k in ipairs(self:cfgsections()) do
525                 if luci.http.formvalue("cbi.submit") then
526                         Node.parse(self, k)
527                 end
528         end
529 end
530
531 function Table.cfgsections(self)
532         local sections = {}
533         
534         for i, v in luci.util.kspairs(self.data) do
535                 table.insert(sections, i)
536         end
537         
538         return sections
539 end
540
541
542
543 --[[
544 NamedSection - A fixed configuration section defined by its name
545 ]]--
546 NamedSection = class(AbstractSection)
547
548 function NamedSection.__init__(self, map, section, type, ...)
549         AbstractSection.__init__(self, map, type, ...)
550         Node._i18n(self, map.config, section, nil, ...)
551
552         self.template = "cbi/nsection"
553         self.section = section
554         self.addremove = false
555 end
556
557 function NamedSection.parse(self)
558         local s = self.section
559         local active = self:cfgvalue(s)
560
561
562         if self.addremove then
563                 local path = self.config.."."..s
564                 if active then -- Remove the section
565                         if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
566                                 return
567                         end
568                 else           -- Create and apply default values
569                         if luci.http.formvalue("cbi.cns."..path) then
570                                 self:create(s)
571                                 return
572                         end
573                 end
574         end
575
576         if active then
577                 AbstractSection.parse_dynamic(self, s)
578                 if luci.http.formvalue("cbi.submit") then
579                         Node.parse(self, s)
580                 end
581                 AbstractSection.parse_optionals(self, s)
582         end
583 end
584
585
586 --[[
587 TypedSection - A (set of) configuration section(s) defined by the type
588         addremove:      Defines whether the user can add/remove sections of this type
589         anonymous:  Allow creating anonymous sections
590         validate:       a validation function returning nil if the section is invalid
591 ]]--
592 TypedSection = class(AbstractSection)
593
594 function TypedSection.__init__(self, map, type, ...)
595         AbstractSection.__init__(self, map, type, ...)
596         Node._i18n(self, map.config, type, nil, ...)
597
598         self.template  = "cbi/tsection"
599         self.deps = {}
600
601         self.anonymous = false
602 end
603
604 -- Return all matching UCI sections for this TypedSection
605 function TypedSection.cfgsections(self)
606         local sections = {}
607         uci.foreach(self.map.config, self.sectiontype,
608                 function (section)
609                         if self:checkscope(section[".name"]) then
610                                 table.insert(sections, section[".name"])
611                         end
612                 end)
613
614         return sections
615 end
616
617 -- Limits scope to sections that have certain option => value pairs
618 function TypedSection.depends(self, option, value)
619         table.insert(self.deps, {option=option, value=value})
620 end
621
622 function TypedSection.parse(self)
623         if self.addremove then
624                 -- Create
625                 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
626                 local name  = luci.http.formvalue(crval)
627                 if self.anonymous then
628                         if name then
629                                 self:create()
630                         end
631                 else
632                         if name then
633                                 -- Ignore if it already exists
634                                 if self:cfgvalue(name) then
635                                         name = nil;
636                                 end
637
638                                 name = self:checkscope(name)
639
640                                 if not name then
641                                         self.err_invalid = true
642                                 end
643
644                                 if name and name:len() > 0 then
645                                         self:create(name)
646                                 end
647                         end
648                 end
649
650                 -- Remove
651                 crval = REMOVE_PREFIX .. self.config
652                 name = luci.http.formvaluetable(crval)
653                 for k,v in pairs(name) do
654                         if self:cfgvalue(k) and self:checkscope(k) then
655                                 self:remove(k)
656                         end
657                 end
658         end
659
660         for i, k in ipairs(self:cfgsections()) do
661                 AbstractSection.parse_dynamic(self, k)
662                 if luci.http.formvalue("cbi.submit") then
663                         Node.parse(self, k)
664                 end
665                 AbstractSection.parse_optionals(self, k)
666         end
667 end
668
669 -- Verifies scope of sections
670 function TypedSection.checkscope(self, section)
671         -- Check if we are not excluded
672         if self.filter and not self:filter(section) then
673                 return nil
674         end
675
676         -- Check if at least one dependency is met
677         if #self.deps > 0 and self:cfgvalue(section) then
678                 local stat = false
679
680                 for k, v in ipairs(self.deps) do
681                         if self:cfgvalue(section)[v.option] == v.value then
682                                 stat = true
683                         end
684                 end
685
686                 if not stat then
687                         return nil
688                 end
689         end
690
691         return self:validate(section)
692 end
693
694
695 -- Dummy validate function
696 function TypedSection.validate(self, section)
697         return section
698 end
699
700
701 --[[
702 AbstractValue - An abstract Value Type
703         null:           Value can be empty
704         valid:          A function returning the value if it is valid otherwise nil
705         depends:        A table of option => value pairs of which one must be true
706         default:        The default value
707         size:           The size of the input fields
708         rmempty:        Unset value if empty
709         optional:       This value is optional (see AbstractSection.optionals)
710 ]]--
711 AbstractValue = class(Node)
712
713 function AbstractValue.__init__(self, map, option, ...)
714         Node.__init__(self, ...)
715         self.option = option
716         self.map    = map
717         self.config = map.config
718         self.tag_invalid = {}
719         self.tag_missing = {}
720         self.tag_error = {}
721         self.deps = {}
722
723         self.track_missing = false
724         self.rmempty   = false
725         self.default   = nil
726         self.size      = nil
727         self.optional  = false
728 end
729
730 -- Add a dependencie to another section field
731 function AbstractValue.depends(self, field, value)
732         local deps
733         if type(field) == "string" then
734                 deps = {}
735                 deps[field] = value
736         else
737                 deps = field
738         end
739         
740         table.insert(self.deps, {deps=deps, add=""})
741 end
742
743 -- Generates the unique CBID
744 function AbstractValue.cbid(self, section)
745         return "cbid."..self.map.config.."."..section.."."..self.option
746 end
747
748 -- Return whether this object should be created
749 function AbstractValue.formcreated(self, section)
750         local key = "cbi.opt."..self.config.."."..section
751         return (luci.http.formvalue(key) == self.option)
752 end
753
754 -- Returns the formvalue for this object
755 function AbstractValue.formvalue(self, section)
756         return luci.http.formvalue(self:cbid(section))
757 end
758
759 function AbstractValue.additional(self, value)
760         self.optional = value
761 end
762
763 function AbstractValue.mandatory(self, value)
764         self.rmempty = not value
765 end
766
767 function AbstractValue.parse(self, section)
768         local fvalue = self:formvalue(section)
769         local cvalue = self:cfgvalue(section)
770
771         if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
772                 fvalue = self:transform(self:validate(fvalue, section))
773                 if not fvalue then
774                         self.tag_invalid[section] = true
775                 end
776                 if fvalue and not (fvalue == cvalue) then
777                         self:write(section, fvalue)
778                 end
779         else                                                    -- Unset the UCI or error
780                 if self.rmempty or self.optional then
781                         self:remove(section)
782                 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
783                         self.tag_missing[section] = true
784                 end
785         end
786 end
787
788 -- Render if this value exists or if it is mandatory
789 function AbstractValue.render(self, s, scope)
790         if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
791                 scope = scope or {}
792                 scope.section = s
793                 scope.cbid    = self:cbid(s)
794
795                 scope.ifattr = function(cond,key,val)
796                         if cond then
797                                 return string.format(
798                                         ' %s="%s"', tostring(key),
799                                         luci.util.pcdata(tostring( val
800                                          or scope[key]
801                                          or (type(self[key]) ~= "function" and self[key])
802                                          or "" ))
803                                 )
804                         else
805                                 return ''
806                         end
807                 end
808
809                 scope.attr = function(...)
810                         return scope.ifattr( true, ... )
811                 end
812
813                 Node.render(self, scope)
814         end
815 end
816
817 -- Return the UCI value of this object
818 function AbstractValue.cfgvalue(self, section)
819         return self.map:get(section, self.option)
820 end
821
822 -- Validate the form value
823 function AbstractValue.validate(self, value)
824         return value
825 end
826
827 AbstractValue.transform = AbstractValue.validate
828
829
830 -- Write to UCI
831 function AbstractValue.write(self, section, value)
832         return self.map:set(section, self.option, value)
833 end
834
835 -- Remove from UCI
836 function AbstractValue.remove(self, section)
837         return self.map:del(section, self.option)
838 end
839
840
841
842
843 --[[
844 Value - A one-line value
845         maxlength:      The maximum length
846 ]]--
847 Value = class(AbstractValue)
848
849 function Value.__init__(self, ...)
850         AbstractValue.__init__(self, ...)
851         self.template  = "cbi/value"
852         self.keylist = {}
853         self.vallist = {}
854 end
855
856 function Value.value(self, key, val)
857         val = val or key
858         table.insert(self.keylist, tostring(key))
859         table.insert(self.vallist, tostring(val))
860 end
861
862
863 -- DummyValue - This does nothing except being there
864 DummyValue = class(AbstractValue)
865
866 function DummyValue.__init__(self, map, ...)
867         AbstractValue.__init__(self, map, ...)
868         self.template = "cbi/dvalue"
869         self.value = nil
870 end
871
872 function DummyValue.parse(self)
873
874 end
875
876
877 --[[
878 Flag - A flag being enabled or disabled
879 ]]--
880 Flag = class(AbstractValue)
881
882 function Flag.__init__(self, ...)
883         AbstractValue.__init__(self, ...)
884         self.template  = "cbi/fvalue"
885
886         self.enabled = "1"
887         self.disabled = "0"
888 end
889
890 -- A flag can only have two states: set or unset
891 function Flag.parse(self, section)
892         local fvalue = self:formvalue(section)
893
894         if fvalue then
895                 fvalue = self.enabled
896         else
897                 fvalue = self.disabled
898         end
899
900         if fvalue == self.enabled or (not self.optional and not self.rmempty) then
901                 if not(fvalue == self:cfgvalue(section)) then
902                         self:write(section, fvalue)
903                 end
904         else
905                 self:remove(section)
906         end
907 end
908
909
910
911 --[[
912 ListValue - A one-line value predefined in a list
913         widget: The widget that will be used (select, radio)
914 ]]--
915 ListValue = class(AbstractValue)
916
917 function ListValue.__init__(self, ...)
918         AbstractValue.__init__(self, ...)
919         self.template  = "cbi/lvalue"
920         self.keylist = {}
921         self.vallist = {}
922
923         self.size   = 1
924         self.widget = "select"
925 end
926
927 function ListValue.value(self, key, val, ...)
928         val = val or key
929         table.insert(self.keylist, tostring(key))
930         table.insert(self.vallist, tostring(val))
931         
932         for i, deps in ipairs({...}) do
933                 table.insert(self.deps, {add = "-"..key, deps=deps})
934         end
935 end
936
937 function ListValue.validate(self, val)
938         if luci.util.contains(self.keylist, val) then
939                 return val
940         else
941                 return nil
942         end
943 end
944
945
946
947 --[[
948 MultiValue - Multiple delimited values
949         widget: The widget that will be used (select, checkbox)
950         delimiter: The delimiter that will separate the values (default: " ")
951 ]]--
952 MultiValue = class(AbstractValue)
953
954 function MultiValue.__init__(self, ...)
955         AbstractValue.__init__(self, ...)
956         self.template = "cbi/mvalue"
957         self.keylist = {}
958         self.vallist = {}
959
960         self.widget = "checkbox"
961         self.delimiter = " "
962 end
963
964 function MultiValue.render(self, ...)
965         if self.widget == "select" and not self.size then
966                 self.size = #self.vallist
967         end
968
969         AbstractValue.render(self, ...)
970 end
971
972 function MultiValue.value(self, key, val)
973         val = val or key
974         table.insert(self.keylist, tostring(key))
975         table.insert(self.vallist, tostring(val))
976 end
977
978 function MultiValue.valuelist(self, section)
979         local val = self:cfgvalue(section)
980
981         if not(type(val) == "string") then
982                 return {}
983         end
984
985         return luci.util.split(val, self.delimiter)
986 end
987
988 function MultiValue.validate(self, val)
989         val = (type(val) == "table") and val or {val}
990
991         local result
992
993         for i, value in ipairs(val) do
994                 if luci.util.contains(self.keylist, value) then
995                         result = result and (result .. self.delimiter .. value) or value
996                 end
997         end
998
999         return result
1000 end
1001
1002
1003 StaticList = class(MultiValue)
1004
1005 function StaticList.__init__(self, ...)
1006         MultiValue.__init__(self, ...)
1007         self.cast = "table"
1008         self.valuelist = self.cfgvalue
1009 end
1010
1011 function StaticList.validate(self, value)
1012         value = (type(value) == "table") and value or {value}
1013
1014         local valid = {}
1015         for i, v in ipairs(value) do
1016                 if luci.util.contains(self.valuelist, v) then
1017                         table.insert(valid, v)
1018                 end
1019         end
1020         return valid
1021 end
1022
1023
1024 DynamicList = class(AbstractValue)
1025
1026 function DynamicList.__init__(self, ...)
1027         AbstractValue.__init__(self, ...)
1028         self.template  = "cbi/dynlist"
1029         self.cast = "table"
1030         
1031         self.keylist = {}
1032         self.vallist = {}
1033 end
1034
1035 function DynamicList.value(self, key, val)
1036         val = val or key
1037         table.insert(self.keylist, tostring(key))
1038         table.insert(self.vallist, tostring(val))
1039 end
1040
1041 function DynamicList.validate(self, value, section)
1042         value = (type(value) == "table") and value or {value}
1043         
1044         local valid = {}
1045         for i, v in ipairs(value) do
1046                 if v and #v > 0 and
1047                  not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1048                         table.insert(valid, v)
1049                 end
1050         end
1051         
1052         return valid
1053 end
1054
1055
1056 --[[
1057 TextValue - A multi-line value
1058         rows:   Rows
1059 ]]--
1060 TextValue = class(AbstractValue)
1061
1062 function TextValue.__init__(self, ...)
1063         AbstractValue.__init__(self, ...)
1064         self.template  = "cbi/tvalue"
1065 end
1066
1067 --[[
1068 Button
1069 ]]--
1070 Button = class(AbstractValue)
1071
1072 function Button.__init__(self, ...)
1073         AbstractValue.__init__(self, ...)
1074         self.template  = "cbi/button"
1075         self.inputstyle = nil
1076         self.rmempty = true
1077 end