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