9 var CBIJSONConfig
= baseclass
.extend({
10 __init__: function(data
) {
11 data
= Object
.assign({}, data
);
18 for (var sectiontype
in data
) {
19 if (!data
.hasOwnProperty(sectiontype
))
22 if (L
.isObject(data
[sectiontype
])) {
23 this.data
[sectiontype
] = Object
.assign(data
[sectiontype
], {
29 section_ids
.push(sectiontype
);
32 else if (Array
.isArray(data
[sectiontype
])) {
33 for (var i
= 0, index
= 0; i
< data
[sectiontype
].length
; i
++) {
34 var item
= data
[sectiontype
][i
],
37 if (!L
.isObject(item
))
40 if (typeof(item
['.name']) == 'string') {
45 name
= sectiontype
+ num_sections
;
49 if (!this.data
.hasOwnProperty(name
))
50 section_ids
.push(name
);
52 this.data
[name
] = Object
.assign(item
, {
53 '.index': num_sections
++,
54 '.anonymous': anonymous
,
62 section_ids
.sort(L
.bind(function(a
, b
) {
63 var indexA
= (this.data
[a
]['.index'] != null) ? +this.data
[a
]['.index'] : 9999,
64 indexB
= (this.data
[b
]['.index'] != null) ? +this.data
[b
]['.index'] : 9999;
67 return (indexA
- indexB
);
72 for (var i
= 0; i
< section_ids
.length
; i
++)
73 this.data
[section_ids
[i
]]['.index'] = i
;
77 return Promise
.resolve(this.data
);
81 return Promise
.resolve();
84 get: function(config
, section
, option
) {
89 return this.data
[section
];
91 if (!this.data
.hasOwnProperty(section
))
94 var value
= this.data
[section
][option
];
96 if (Array
.isArray(value
))
100 return String(value
);
105 set: function(config
, section
, option
, value
) {
106 if (section
== null || option
== null || option
.charAt(0) == '.')
109 if (!this.data
.hasOwnProperty(section
))
113 delete this.data
[section
][option
];
114 else if (Array
.isArray(value
))
115 this.data
[section
][option
] = value
;
117 this.data
[section
][option
] = String(value
);
120 unset: function(config
, section
, option
) {
121 return this.set(config
, section
, option
, null);
124 sections: function(config
, sectiontype
, callback
) {
127 for (var section_id
in this.data
)
128 if (sectiontype
== null || this.data
[section_id
]['.type'] == sectiontype
)
129 rv
.push(this.data
[section_id
]);
131 rv
.sort(function(a
, b
) { return a
['.index'] - b
['.index'] });
133 if (typeof(callback
) == 'function')
134 for (var i
= 0; i
< rv
.length
; i
++)
135 callback
.call(this, rv
[i
], rv
[i
]['.name']);
140 add: function(config
, sectiontype
, sectionname
) {
141 var num_sections_type
= 0, next_index
= 0;
143 for (var name
in this.data
) {
144 num_sections_type
+= (this.data
[name
]['.type'] == sectiontype
);
145 next_index
= Math
.max(next_index
, this.data
[name
]['.index']);
148 var section_id
= sectionname
|| sectiontype
+ num_sections_type
;
150 if (!this.data
.hasOwnProperty(section_id
)) {
151 this.data
[section_id
] = {
153 '.type': sectiontype
,
154 '.anonymous': (sectionname
== null),
155 '.index': next_index
+ 1
162 remove: function(config
, section
) {
163 if (this.data
.hasOwnProperty(section
))
164 delete this.data
[section
];
167 resolveSID: function(config
, section_id
) {
171 move: function(config
, section_id1
, section_id2
, after
) {
172 return uci
.move.apply(this, [config
, section_id1
, section_id2
, after
]);
176 var CBINode
= baseclass
.extend({
177 __init__: function(title
, description
) {
178 this.title
= title
|| '';
179 this.description
= description
|| '';
183 append: function(obj
) {
184 this.children
.push(obj
);
188 var args
= arguments
;
189 this.children
.forEach(function(child
) {
190 child
.parse
.apply(child
, args
);
195 L
.error('InternalError', 'Not implemented');
198 loadChildren: function(/* ... */) {
201 if (Array
.isArray(this.children
))
202 for (var i
= 0; i
< this.children
.length
; i
++)
203 if (!this.children
[i
].disable
)
204 tasks
.push(this.children
[i
].load
.apply(this.children
[i
], arguments
));
206 return Promise
.all(tasks
);
209 renderChildren: function(tab_name
/*, ... */) {
213 if (Array
.isArray(this.children
))
214 for (var i
= 0; i
< this.children
.length
; i
++)
215 if (tab_name
=== null || this.children
[i
].tab
=== tab_name
)
216 if (!this.children
[i
].disable
)
217 tasks
.push(this.children
[i
].render
.apply(
218 this.children
[i
], this.varargs(arguments
, 1, index
++)));
220 return Promise
.all(tasks
);
223 stripTags: function(s
) {
224 if (typeof(s
) == 'string' && !s
.match(/[<>]/))
227 var x
= E('div', {}, s
);
228 return x
.textContent
|| x
.innerText
|| '';
231 titleFn: function(attr
/*, ... */) {
234 if (typeof(this[attr
]) == 'function')
235 s
= this[attr
].apply(this, this.varargs(arguments
, 1));
236 else if (typeof(this[attr
]) == 'string')
237 s
= (arguments
.length
> 1) ? ''.format
.apply(this[attr
], this.varargs(arguments
, 1)) : this[attr
];
240 s
= this.stripTags(String(s
)).trim();
242 if (s
== null || s
== '')
249 var CBIMap
= CBINode
.extend({
250 __init__: function(config
/*, ... */) {
251 this.super('__init__', this.varargs(arguments
, 1));
253 this.config
= config
;
254 this.parsechain
= [ config
];
258 findElements: function(/* ... */) {
261 if (arguments
.length
== 1)
263 else if (arguments
.length
== 2)
264 q
= '[%s="%s"]'.format(arguments
[0], arguments
[1]);
266 L
.error('InternalError', 'Expecting one or two arguments to findElements()');
268 return this.root
.querySelectorAll(q
);
271 findElement: function(/* ... */) {
272 var res
= this.findElements
.apply(this, arguments
);
273 return res
.length
? res
[0] : null;
276 chain: function(config
) {
277 if (this.parsechain
.indexOf(config
) == -1)
278 this.parsechain
.push(config
);
281 section: function(cbiClass
/*, ... */) {
282 if (!CBIAbstractSection
.isSubclass(cbiClass
))
283 L
.error('TypeError', 'Class must be a descendent of CBIAbstractSection');
285 var obj
= cbiClass
.instantiate(this.varargs(arguments
, 1, this));
291 return this.data
.load(this.parsechain
|| [ this.config
])
292 .then(this.loadChildren
.bind(this));
298 if (Array
.isArray(this.children
))
299 for (var i
= 0; i
< this.children
.length
; i
++)
300 tasks
.push(this.children
[i
].parse());
302 return Promise
.all(tasks
);
305 save: function(cb
, silent
) {
310 .then(this.data
.save
.bind(this.data
))
311 .then(this.load
.bind(this))
314 alert('Cannot save due to invalid values');
316 return Promise
.reject();
317 }).finally(this.renderContents
.bind(this));
321 return this.renderContents();
325 return this.load().then(this.renderContents
.bind(this));
328 renderContents: function() {
329 var mapEl
= this.root
|| (this.root
= E('div', {
330 'id': 'cbi-%s'.format(this.config
),
332 'cbi-dependency-check': L
.bind(this.checkDepends
, this)
335 dom
.bindClassInstance(mapEl
, this);
337 return this.renderChildren(null).then(L
.bind(function(nodes
) {
338 var initialRender
= !mapEl
.firstChild
;
340 dom
.content(mapEl
, null);
342 if (this.title
!= null && this.title
!= '')
343 mapEl
.appendChild(E('h2', { 'name': 'content' }, this.title
));
345 if (this.description
!= null && this.description
!= '')
346 mapEl
.appendChild(E('div', { 'class': 'cbi-map-descr' }, this.description
));
349 dom
.append(mapEl
, E('div', { 'class': 'cbi-map-tabbed' }, nodes
));
351 dom
.append(mapEl
, nodes
);
353 if (!initialRender
) {
354 mapEl
.classList
.remove('flash');
356 window
.setTimeout(function() {
357 mapEl
.classList
.add('flash');
363 var tabGroups
= mapEl
.querySelectorAll('.cbi-map-tabbed, .cbi-section-node-tabbed');
365 for (var i
= 0; i
< tabGroups
.length
; i
++)
366 ui
.tabs
.initTabGroup(tabGroups
[i
].childNodes
);
372 lookupOption: function(name
, section_id
, config_name
) {
373 var id
, elem
, sid
, inst
;
375 if (name
.indexOf('.') > -1)
376 id
= 'cbid.%s'.format(name
);
378 id
= 'cbid.%s.%s.%s'.format(config_name
|| this.config
, section_id
, name
);
380 elem
= this.findElement('data-field', id
);
381 sid
= elem
? id
.split(/\./)[2] : null;
382 inst
= elem
? dom
.findClassInstance(elem
) : null;
384 return (inst
instanceof CBIAbstractValue
) ? [ inst
, sid
] : null;
387 checkDepends: function(ev
, n
) {
390 for (var i
= 0, s
= this.children
[0]; (s
= this.children
[i
]) != null; i
++)
391 if (s
.checkDepends(ev
, n
))
394 if (changed
&& (n
|| 0) < 10)
395 this.checkDepends(ev
, (n
|| 10) + 1);
397 ui
.tabs
.updateTabs(ev
, this.root
);
400 isDependencySatisfied: function(depends
, config_name
, section_id
) {
403 if (!Array
.isArray(depends
) || !depends
.length
)
406 for (var i
= 0; i
< depends
.length
; i
++) {
408 reverse
= depends
[i
]['!reverse'],
409 contains
= depends
[i
]['!contains'];
411 for (var dep
in depends
[i
]) {
412 if (dep
== '!reverse' || dep
== '!contains') {
415 else if (dep
== '!default') {
420 var res
= this.lookupOption(dep
, section_id
, config_name
),
421 val
= (res
&& res
[0].isActive(res
[1])) ? res
[0].formvalue(res
[1]) : null;
424 ? isContained(val
, depends
[i
][dep
])
425 : isEqual(val
, depends
[i
][dep
]);
427 istat
= (istat
&& equal
);
439 var CBIJSONMap
= CBIMap
.extend({
440 __init__: function(data
/*, ... */) {
441 this.super('__init__', this.varargs(arguments
, 1, 'json'));
443 this.config
= 'json';
444 this.parsechain
= [ 'json' ];
445 this.data
= new CBIJSONConfig(data
);
449 var CBIAbstractSection
= CBINode
.extend({
450 __init__: function(map
, sectionType
/*, ... */) {
451 this.super('__init__', this.varargs(arguments
, 2));
453 this.sectiontype
= sectionType
;
455 this.config
= map
.config
;
457 this.optional
= true;
458 this.addremove
= false;
459 this.dynamic
= false;
462 cfgsections: function() {
463 L
.error('InternalError', 'Not implemented');
466 filter: function(section_id
) {
471 var section_ids
= this.cfgsections(),
474 if (Array
.isArray(this.children
))
475 for (var i
= 0; i
< section_ids
.length
; i
++)
476 tasks
.push(this.loadChildren(section_ids
[i
])
477 .then(Function
.prototype.bind
.call(function(section_id
, set_values
) {
478 for (var i
= 0; i
< set_values
.length
; i
++)
479 this.children
[i
].cfgvalue(section_id
, set_values
[i
]);
480 }, this, section_ids
[i
])));
482 return Promise
.all(tasks
);
486 var section_ids
= this.cfgsections(),
489 if (Array
.isArray(this.children
))
490 for (var i
= 0; i
< section_ids
.length
; i
++)
491 for (var j
= 0; j
< this.children
.length
; j
++)
492 tasks
.push(this.children
[j
].parse(section_ids
[i
]));
494 return Promise
.all(tasks
);
497 tab: function(name
, title
, description
) {
498 if (this.tabs
&& this.tabs
[name
])
499 throw 'Tab already declared';
504 description
: description
,
508 this.tabs
= this.tabs
|| [];
509 this.tabs
.push(entry
);
510 this.tabs
[name
] = entry
;
512 this.tab_names
= this.tab_names
|| [];
513 this.tab_names
.push(name
);
516 option: function(cbiClass
/*, ... */) {
517 if (!CBIAbstractValue
.isSubclass(cbiClass
))
518 throw L
.error('TypeError', 'Class must be a descendent of CBIAbstractValue');
520 var obj
= cbiClass
.instantiate(this.varargs(arguments
, 1, this.map
, this));
525 taboption: function(tabName
/*, ... */) {
526 if (!this.tabs
|| !this.tabs
[tabName
])
527 throw L
.error('ReferenceError', 'Associated tab not declared');
529 var obj
= this.option
.apply(this, this.varargs(arguments
, 1));
531 this.tabs
[tabName
].children
.push(obj
);
535 renderUCISection: function(section_id
) {
536 var renderTasks
= [];
539 return this.renderOptions(null, section_id
);
541 for (var i
= 0; i
< this.tab_names
.length
; i
++)
542 renderTasks
.push(this.renderOptions(this.tab_names
[i
], section_id
));
544 return Promise
.all(renderTasks
)
545 .then(this.renderTabContainers
.bind(this, section_id
));
548 renderTabContainers: function(section_id
, nodes
) {
549 var config_name
= this.uciconfig
|| this.map
.config
,
550 containerEls
= E([]);
552 for (var i
= 0; i
< nodes
.length
; i
++) {
553 var tab_name
= this.tab_names
[i
],
554 tab_data
= this.tabs
[tab_name
],
555 containerEl
= E('div', {
556 'id': 'container.%s.%s.%s'.format(config_name
, section_id
, tab_name
),
557 'data-tab': tab_name
,
558 'data-tab-title': tab_data
.title
,
559 'data-tab-active': tab_name
=== this.selected_tab
562 if (tab_data
.description
!= null && tab_data
.description
!= '')
563 containerEl
.appendChild(
564 E('div', { 'class': 'cbi-tab-descr' }, tab_data
.description
));
566 containerEl
.appendChild(nodes
[i
]);
567 containerEls
.appendChild(containerEl
);
573 renderOptions: function(tab_name
, section_id
) {
574 var in_table
= (this instanceof CBITableSection
);
575 return this.renderChildren(tab_name
, section_id
, in_table
).then(function(nodes
) {
576 var optionEls
= E([]);
577 for (var i
= 0; i
< nodes
.length
; i
++)
578 optionEls
.appendChild(nodes
[i
]);
583 checkDepends: function(ev
, n
) {
585 sids
= this.cfgsections();
587 for (var i
= 0, sid
= sids
[0]; (sid
= sids
[i
]) != null; i
++) {
588 for (var j
= 0, o
= this.children
[0]; (o
= this.children
[j
]) != null; j
++) {
589 var isActive
= o
.isActive(sid
),
590 isSatisified
= o
.checkDepends(sid
);
592 if (isActive
!= isSatisified
) {
593 o
.setActive(sid
, !isActive
);
594 isActive
= !isActive
;
599 o
.triggerValidation(sid
);
608 var isEqual = function(x
, y
) {
609 if (x
!= null && y
!= null && typeof(x
) != typeof(y
))
612 if ((x
== null && y
!= null) || (x
!= null && y
== null))
615 if (Array
.isArray(x
)) {
616 if (x
.length
!= y
.length
)
619 for (var i
= 0; i
< x
.length
; i
++)
620 if (!isEqual(x
[i
], y
[i
]))
623 else if (typeof(x
) == 'object') {
625 if (x
.hasOwnProperty(k
) && !y
.hasOwnProperty(k
))
628 if (!isEqual(x
[k
], y
[k
]))
633 if (y
.hasOwnProperty(k
) && !x
.hasOwnProperty(k
))
643 var isContained = function(x
, y
) {
644 if (Array
.isArray(x
)) {
645 for (var i
= 0; i
< x
.length
; i
++)
649 else if (L
.isObject(x
)) {
650 if (x
.hasOwnProperty(y
) && x
[y
] != null)
653 else if (typeof(x
) == 'string') {
654 return (x
.indexOf(y
) > -1);
660 var CBIAbstractValue
= CBINode
.extend({
661 __init__: function(map
, section
, option
/*, ... */) {
662 this.super('__init__', this.varargs(arguments
, 3));
664 this.section
= section
;
665 this.option
= option
;
667 this.config
= map
.config
;
674 this.optional
= false;
677 depends: function(field
, value
) {
680 if (typeof(field
) === 'string')
681 deps
= {}, deps
[field
] = value
;
685 this.deps
.push(deps
);
688 transformDepList: function(section_id
, deplist
) {
689 var list
= deplist
|| this.deps
,
692 if (Array
.isArray(list
)) {
693 for (var i
= 0; i
< list
.length
; i
++) {
696 for (var k
in list
[i
]) {
697 if (list
[i
].hasOwnProperty(k
)) {
698 if (k
.charAt(0) === '!')
700 else if (k
.indexOf('.') !== -1)
701 dep
['cbid.%s'.format(k
)] = list
[i
][k
];
703 dep
['cbid.%s.%s.%s'.format(
704 this.uciconfig
|| this.section
.uciconfig
|| this.map
.config
,
705 this.ucisection
|| section_id
,
712 if (dep
.hasOwnProperty(k
)) {
723 transformChoices: function() {
724 if (!Array
.isArray(this.keylist
) || this.keylist
.length
== 0)
729 for (var i
= 0; i
< this.keylist
.length
; i
++)
730 choices
[this.keylist
[i
]] = this.vallist
[i
];
735 checkDepends: function(section_id
) {
736 var config_name
= this.uciconfig
|| this.section
.uciconfig
|| this.map
.config
,
737 active
= this.map
.isDependencySatisfied(this.deps
, config_name
, section_id
);
740 this.updateDefaultValue(section_id
);
745 updateDefaultValue: function(section_id
) {
746 if (!L
.isObject(this.defaults
))
749 var config_name
= this.uciconfig
|| this.section
.uciconfig
|| this.map
.config
,
750 cfgvalue
= L
.toArray(this.cfgvalue(section_id
))[0],
751 default_defval
= null, satisified_defval
= null;
753 for (var value
in this.defaults
) {
754 if (!this.defaults
[value
] || this.defaults
[value
].length
== 0) {
755 default_defval
= value
;
758 else if (this.map
.isDependencySatisfied(this.defaults
[value
], config_name
, section_id
)) {
759 satisified_defval
= value
;
764 if (satisified_defval
== null)
765 satisified_defval
= default_defval
;
767 var node
= this.map
.findElement('id', this.cbid(section_id
));
768 if (node
&& node
.getAttribute('data-changed') != 'true' && satisified_defval
!= null && cfgvalue
== null)
769 dom
.callClassMethod(node
, 'setValue', satisified_defval
);
771 this.default = satisified_defval
;
774 cbid: function(section_id
) {
775 if (section_id
== null)
776 L
.error('TypeError', 'Section ID required');
778 return 'cbid.%s.%s.%s'.format(
779 this.uciconfig
|| this.section
.uciconfig
|| this.map
.config
,
780 section_id
, this.option
);
783 load: function(section_id
) {
784 if (section_id
== null)
785 L
.error('TypeError', 'Section ID required');
787 return this.map
.data
.get(
788 this.uciconfig
|| this.section
.uciconfig
|| this.map
.config
,
789 this.ucisection
|| section_id
,
790 this.ucioption
|| this.option
);
793 getUIElement: function(section_id
) {
794 var node
= this.map
.findElement('id', this.cbid(section_id
)),
795 inst
= node
? dom
.findClassInstance(node
) : null;
796 return (inst
instanceof ui
.AbstractElement
) ? inst
: null;
799 cfgvalue: function(section_id
, set_value
) {
800 if (section_id
== null)
801 L
.error('TypeError', 'Section ID required');
803 if (arguments
.length
== 2) {
804 this.data
= this.data
|| {};
805 this.data
[section_id
] = set_value
;
808 return this.data
? this.data
[section_id
] : null;
811 formvalue: function(section_id
) {
812 var elem
= this.getUIElement(section_id
);
813 return elem
? elem
.getValue() : null;
816 textvalue: function(section_id
) {
817 var cval
= this.cfgvalue(section_id
);
822 return (cval
!= null) ? '%h'.format(cval
) : null;
825 validate: function(section_id
, value
) {
829 isValid: function(section_id
) {
830 var elem
= this.getUIElement(section_id
);
831 return elem
? elem
.isValid() : true;
834 isActive: function(section_id
) {
835 var field
= this.map
.findElement('data-field', this.cbid(section_id
));
836 return (field
!= null && !field
.classList
.contains('hidden'));
839 setActive: function(section_id
, active
) {
840 var field
= this.map
.findElement('data-field', this.cbid(section_id
));
842 if (field
&& field
.classList
.contains('hidden') == active
) {
843 field
.classList
[active
? 'remove' : 'add']('hidden');
850 triggerValidation: function(section_id
) {
851 var elem
= this.getUIElement(section_id
);
852 return elem
? elem
.triggerValidation() : true;
855 parse: function(section_id
) {
856 var active
= this.isActive(section_id
),
857 cval
= this.cfgvalue(section_id
),
858 fval
= active
? this.formvalue(section_id
) : null;
860 if (active
&& !this.isValid(section_id
))
861 return Promise
.reject();
863 if (fval
!= '' && fval
!= null) {
864 if (this.forcewrite
|| !isEqual(cval
, fval
))
865 return Promise
.resolve(this.write(section_id
, fval
));
868 if (!active
|| this.rmempty
|| this.optional
) {
869 return Promise
.resolve(this.remove(section_id
));
871 else if (!isEqual(cval
, fval
)) {
872 console
.log('This should have been catched by isValid()');
873 return Promise
.reject();
877 return Promise
.resolve();
880 write: function(section_id
, formvalue
) {
881 return this.map
.data
.set(
882 this.uciconfig
|| this.section
.uciconfig
|| this.map
.config
,
883 this.ucisection
|| section_id
,
884 this.ucioption
|| this.option
,
888 remove: function(section_id
) {
889 return this.map
.data
.unset(
890 this.uciconfig
|| this.section
.uciconfig
|| this.map
.config
,
891 this.ucisection
|| section_id
,
892 this.ucioption
|| this.option
);
896 var CBITypedSection
= CBIAbstractSection
.extend({
897 __name__
: 'CBI.TypedSection',
899 cfgsections: function() {
900 return this.map
.data
.sections(this.uciconfig
|| this.map
.config
, this.sectiontype
)
901 .map(function(s
) { return s
['.name'] })
902 .filter(L
.bind(this.filter
, this));
905 handleAdd: function(ev
, name
) {
906 var config_name
= this.uciconfig
|| this.map
.config
;
908 this.map
.data
.add(config_name
, this.sectiontype
, name
);
909 return this.map
.save(null, true);
912 handleRemove: function(section_id
, ev
) {
913 var config_name
= this.uciconfig
|| this.map
.config
;
915 this.map
.data
.remove(config_name
, section_id
);
916 return this.map
.save(null, true);
919 renderSectionAdd: function(extra_class
) {
923 var createEl
= E('div', { 'class': 'cbi-section-create' }),
924 config_name
= this.uciconfig
|| this.map
.config
,
925 btn_title
= this.titleFn('addbtntitle');
927 if (extra_class
!= null)
928 createEl
.classList
.add(extra_class
);
930 if (this.anonymous
) {
931 createEl
.appendChild(E('button', {
932 'class': 'cbi-button cbi-button-add',
933 'title': btn_title
|| _('Add'),
934 'click': ui
.createHandlerFn(this, 'handleAdd')
935 }, [ btn_title
|| _('Add') ]));
938 var nameEl
= E('input', {
940 'class': 'cbi-section-create-name'
943 dom
.append(createEl
, [
944 E('div', {}, nameEl
),
946 'class': 'cbi-button cbi-button-add',
948 'value': btn_title
|| _('Add'),
949 'title': btn_title
|| _('Add'),
950 'click': ui
.createHandlerFn(this, function(ev
) {
951 if (nameEl
.classList
.contains('cbi-input-invalid'))
954 return this.handleAdd(ev
, nameEl
.value
);
959 ui
.addValidator(nameEl
, 'uciname', true, 'blur', 'keyup');
965 renderSectionPlaceholder: function() {
967 E('em', _('This section contains no values yet')),
972 renderContents: function(cfgsections
, nodes
) {
973 var section_id
= null,
974 config_name
= this.uciconfig
|| this.map
.config
,
975 sectionEl
= E('div', {
976 'id': 'cbi-%s-%s'.format(config_name
, this.sectiontype
),
977 'class': 'cbi-section',
978 'data-tab': (this.map
.tabbed
&& !this.parentoption
) ? this.sectiontype
: null,
979 'data-tab-title': (this.map
.tabbed
&& !this.parentoption
) ? this.title
|| this.sectiontype
: null
982 if (this.title
!= null && this.title
!= '')
983 sectionEl
.appendChild(E('legend', {}, this.title
));
985 if (this.description
!= null && this.description
!= '')
986 sectionEl
.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description
));
988 for (var i
= 0; i
< nodes
.length
; i
++) {
989 if (this.addremove
) {
990 sectionEl
.appendChild(
991 E('div', { 'class': 'cbi-section-remove right' },
993 'class': 'cbi-button',
994 'name': 'cbi.rts.%s.%s'.format(config_name
, cfgsections
[i
]),
995 'data-section-id': cfgsections
[i
],
996 'click': ui
.createHandlerFn(this, 'handleRemove', cfgsections
[i
])
997 }, [ _('Delete') ])));
1000 if (!this.anonymous
)
1001 sectionEl
.appendChild(E('h3', cfgsections
[i
].toUpperCase()));
1003 sectionEl
.appendChild(E('div', {
1004 'id': 'cbi-%s-%s'.format(config_name
, cfgsections
[i
]),
1006 ? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node',
1007 'data-section-id': cfgsections
[i
]
1011 if (nodes
.length
== 0)
1012 sectionEl
.appendChild(this.renderSectionPlaceholder());
1014 sectionEl
.appendChild(this.renderSectionAdd());
1016 dom
.bindClassInstance(sectionEl
, this);
1021 render: function() {
1022 var cfgsections
= this.cfgsections(),
1025 for (var i
= 0; i
< cfgsections
.length
; i
++)
1026 renderTasks
.push(this.renderUCISection(cfgsections
[i
]));
1028 return Promise
.all(renderTasks
).then(this.renderContents
.bind(this, cfgsections
));
1032 var CBITableSection
= CBITypedSection
.extend({
1033 __name__
: 'CBI.TableSection',
1036 throw 'Tabs are not supported by TableSection';
1039 renderContents: function(cfgsections
, nodes
) {
1040 var section_id
= null,
1041 config_name
= this.uciconfig
|| this.map
.config
,
1042 max_cols
= isNaN(this.max_cols
) ? this.children
.length
: this.max_cols
,
1043 has_more
= max_cols
< this.children
.length
,
1044 sectionEl
= E('div', {
1045 'id': 'cbi-%s-%s'.format(config_name
, this.sectiontype
),
1046 'class': 'cbi-section cbi-tblsection',
1047 'data-tab': (this.map
.tabbed
&& !this.parentoption
) ? this.sectiontype
: null,
1048 'data-tab-title': (this.map
.tabbed
&& !this.parentoption
) ? this.title
|| this.sectiontype
: null
1050 tableEl
= E('div', {
1051 'class': 'table cbi-section-table'
1054 if (this.title
!= null && this.title
!= '')
1055 sectionEl
.appendChild(E('h3', {}, this.title
));
1057 if (this.description
!= null && this.description
!= '')
1058 sectionEl
.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description
));
1060 tableEl
.appendChild(this.renderHeaderRows(max_cols
));
1062 for (var i
= 0; i
< nodes
.length
; i
++) {
1063 var sectionname
= this.titleFn('sectiontitle', cfgsections
[i
]);
1065 if (sectionname
== null)
1066 sectionname
= cfgsections
[i
];
1068 var trEl
= E('div', {
1069 'id': 'cbi-%s-%s'.format(config_name
, cfgsections
[i
]),
1070 'class': 'tr cbi-section-table-row',
1071 'data-sid': cfgsections
[i
],
1072 'draggable': this.sortable
? true : null,
1073 'mousedown': this.sortable
? L
.bind(this.handleDragInit
, this) : null,
1074 'dragstart': this.sortable
? L
.bind(this.handleDragStart
, this) : null,
1075 'dragover': this.sortable
? L
.bind(this.handleDragOver
, this) : null,
1076 'dragenter': this.sortable
? L
.bind(this.handleDragEnter
, this) : null,
1077 'dragleave': this.sortable
? L
.bind(this.handleDragLeave
, this) : null,
1078 'dragend': this.sortable
? L
.bind(this.handleDragEnd
, this) : null,
1079 'drop': this.sortable
? L
.bind(this.handleDrop
, this) : null,
1080 'data-title': (sectionname
&& (!this.anonymous
|| this.sectiontitle
)) ? sectionname
: null,
1081 'data-section-id': cfgsections
[i
]
1084 if (this.extedit
|| this.rowcolors
)
1085 trEl
.classList
.add(!(tableEl
.childNodes
.length
% 2)
1086 ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2');
1088 for (var j
= 0; j
< max_cols
&& nodes
[i
].firstChild
; j
++)
1089 trEl
.appendChild(nodes
[i
].firstChild
);
1091 trEl
.appendChild(this.renderRowActions(cfgsections
[i
], has_more
? _('More…') : null));
1092 tableEl
.appendChild(trEl
);
1095 if (nodes
.length
== 0)
1096 tableEl
.appendChild(E('div', { 'class': 'tr cbi-section-table-row placeholder' },
1097 E('div', { 'class': 'td' },
1098 E('em', {}, _('This section contains no values yet')))));
1100 sectionEl
.appendChild(tableEl
);
1102 sectionEl
.appendChild(this.renderSectionAdd('cbi-tblsection-create'));
1104 dom
.bindClassInstance(sectionEl
, this);
1109 renderHeaderRows: function(max_cols
, has_action
) {
1110 var has_titles
= false,
1111 has_descriptions
= false,
1112 max_cols
= isNaN(this.max_cols
) ? this.children
.length
: this.max_cols
,
1113 has_more
= max_cols
< this.children
.length
,
1114 anon_class
= (!this.anonymous
|| this.sectiontitle
) ? 'named' : 'anonymous',
1117 for (var i
= 0, opt
; i
< max_cols
&& (opt
= this.children
[i
]) != null; i
++) {
1121 has_titles
= has_titles
|| !!opt
.title
;
1122 has_descriptions
= has_descriptions
|| !!opt
.description
;
1126 var trEl
= E('div', {
1127 'class': 'tr cbi-section-table-titles ' + anon_class
,
1128 'data-title': (!this.anonymous
|| this.sectiontitle
) ? _('Name') : null
1131 for (var i
= 0, opt
; i
< max_cols
&& (opt
= this.children
[i
]) != null; i
++) {
1135 trEl
.appendChild(E('div', {
1136 'class': 'th cbi-section-table-cell',
1137 'data-widget': opt
.__name__
1140 if (opt
.width
!= null)
1141 trEl
.lastElementChild
.style
.width
=
1142 (typeof(opt
.width
) == 'number') ? opt
.width
+'px' : opt
.width
;
1145 trEl
.lastElementChild
.appendChild(E('a', {
1146 'href': opt
.titleref
,
1147 'class': 'cbi-title-ref',
1148 'title': this.titledesc
|| _('Go to relevant configuration page')
1151 dom
.content(trEl
.lastElementChild
, opt
.title
);
1154 if (this.sortable
|| this.extedit
|| this.addremove
|| has_more
|| has_action
)
1155 trEl
.appendChild(E('div', {
1156 'class': 'th cbi-section-table-cell cbi-section-actions'
1159 trEls
.appendChild(trEl
);
1162 if (has_descriptions
) {
1163 var trEl
= E('div', {
1164 'class': 'tr cbi-section-table-descr ' + anon_class
1167 for (var i
= 0, opt
; i
< max_cols
&& (opt
= this.children
[i
]) != null; i
++) {
1171 trEl
.appendChild(E('div', {
1172 'class': 'th cbi-section-table-cell',
1173 'data-widget': opt
.__name__
1174 }, opt
.description
));
1176 if (opt
.width
!= null)
1177 trEl
.lastElementChild
.style
.width
=
1178 (typeof(opt
.width
) == 'number') ? opt
.width
+'px' : opt
.width
;
1181 if (this.sortable
|| this.extedit
|| this.addremove
|| has_more
)
1182 trEl
.appendChild(E('div', {
1183 'class': 'th cbi-section-table-cell cbi-section-actions'
1186 trEls
.appendChild(trEl
);
1192 renderRowActions: function(section_id
, more_label
) {
1193 var config_name
= this.uciconfig
|| this.map
.config
;
1195 if (!this.sortable
&& !this.extedit
&& !this.addremove
&& !more_label
)
1198 var tdEl
= E('div', {
1199 'class': 'td cbi-section-table-cell nowrap cbi-section-actions'
1202 if (this.sortable
) {
1203 dom
.append(tdEl
.lastElementChild
, [
1205 'title': _('Drag to reorder'),
1206 'class': 'btn cbi-button drag-handle center',
1207 'style': 'cursor:move'
1215 if (typeof(this.extedit
) == 'function')
1216 evFn
= L
.bind(this.extedit
, this);
1217 else if (typeof(this.extedit
) == 'string')
1218 evFn
= L
.bind(function(sid
, ev
) {
1219 location
.href
= this.extedit
.format(sid
);
1220 }, this, section_id
);
1222 dom
.append(tdEl
.lastElementChild
,
1225 'class': 'cbi-button cbi-button-edit',
1232 dom
.append(tdEl
.lastElementChild
,
1234 'title': more_label
,
1235 'class': 'cbi-button cbi-button-edit',
1236 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
1241 if (this.addremove
) {
1242 var btn_title
= this.titleFn('removebtntitle', section_id
);
1244 dom
.append(tdEl
.lastElementChild
,
1246 'title': btn_title
|| _('Delete'),
1247 'class': 'cbi-button cbi-button-remove',
1248 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
1249 }, [ btn_title
|| _('Delete') ])
1256 handleDragInit: function(ev
) {
1257 scope
.dragState
= { node
: ev
.target
};
1260 handleDragStart: function(ev
) {
1261 if (!scope
.dragState
|| !scope
.dragState
.node
.classList
.contains('drag-handle')) {
1262 scope
.dragState
= null;
1263 ev
.preventDefault();
1267 scope
.dragState
.node
= dom
.parent(scope
.dragState
.node
, '.tr');
1268 ev
.dataTransfer
.setData('text', 'drag');
1269 ev
.target
.style
.opacity
= 0.4;
1272 handleDragOver: function(ev
) {
1273 var n
= scope
.dragState
.targetNode
,
1274 r
= scope
.dragState
.rect
,
1275 t
= r
.top
+ r
.height
/ 2;
1277 if (ev
.clientY
<= t
) {
1278 n
.classList
.remove('drag-over-below');
1279 n
.classList
.add('drag-over-above');
1282 n
.classList
.remove('drag-over-above');
1283 n
.classList
.add('drag-over-below');
1286 ev
.dataTransfer
.dropEffect
= 'move';
1287 ev
.preventDefault();
1291 handleDragEnter: function(ev
) {
1292 scope
.dragState
.rect
= ev
.currentTarget
.getBoundingClientRect();
1293 scope
.dragState
.targetNode
= ev
.currentTarget
;
1296 handleDragLeave: function(ev
) {
1297 ev
.currentTarget
.classList
.remove('drag-over-above');
1298 ev
.currentTarget
.classList
.remove('drag-over-below');
1301 handleDragEnd: function(ev
) {
1304 n
.style
.opacity
= '';
1305 n
.classList
.add('flash');
1306 n
.parentNode
.querySelectorAll('.drag-over-above, .drag-over-below')
1307 .forEach(function(tr
) {
1308 tr
.classList
.remove('drag-over-above');
1309 tr
.classList
.remove('drag-over-below');
1313 handleDrop: function(ev
) {
1314 var s
= scope
.dragState
;
1316 if (s
.node
&& s
.targetNode
) {
1317 var config_name
= this.uciconfig
|| this.map
.config
,
1318 ref_node
= s
.targetNode
,
1321 if (ref_node
.classList
.contains('drag-over-below')) {
1322 ref_node
= ref_node
.nextElementSibling
;
1326 var sid1
= s
.node
.getAttribute('data-sid'),
1327 sid2
= s
.targetNode
.getAttribute('data-sid');
1329 s
.node
.parentNode
.insertBefore(s
.node
, ref_node
);
1330 this.map
.data
.move(config_name
, sid1
, sid2
, after
);
1333 scope
.dragState
= null;
1334 ev
.target
.style
.opacity
= '';
1335 ev
.stopPropagation();
1336 ev
.preventDefault();
1340 handleModalCancel: function(modalMap
, ev
) {
1341 return Promise
.resolve(ui
.hideModal());
1344 handleModalSave: function(modalMap
, ev
) {
1345 return modalMap
.save()
1346 .then(L
.bind(this.map
.load
, this.map
))
1347 .then(L
.bind(this.map
.reset
, this.map
))
1349 .catch(function() {});
1352 addModalOptions: function(modalSection
, section_id
, ev
) {
1356 renderMoreOptionsModal: function(section_id
, ev
) {
1357 var parent
= this.map
,
1358 title
= parent
.title
,
1360 m
= new CBIMap(this.map
.config
, null, null),
1361 s
= m
.section(CBINamedSection
, section_id
, this.sectiontype
);
1366 s
.tab_names
= this.tab_names
;
1368 if ((name
= this.titleFn('modaltitle', section_id
)) != null)
1370 else if ((name
= this.titleFn('sectiontitle', section_id
)) != null)
1371 title
= '%s - %s'.format(parent
.title
, name
);
1372 else if (!this.anonymous
)
1373 title
= '%s - %s'.format(parent
.title
, section_id
);
1375 for (var i
= 0; i
< this.children
.length
; i
++) {
1376 var o1
= this.children
[i
];
1378 if (o1
.modalonly
=== false)
1381 var o2
= s
.option(o1
.constructor, o1
.option
, o1
.title
, o1
.description
);
1384 if (!o1
.hasOwnProperty(k
))
1401 return Promise
.resolve(this.addModalOptions(s
, section_id
, ev
)).then(L
.bind(m
.render
, m
)).then(L
.bind(function(nodes
) {
1402 ui
.showModal(title
, [
1404 E('div', { 'class': 'right' }, [
1407 'click': ui
.createHandlerFn(this, 'handleModalCancel', m
)
1408 }, [ _('Dismiss') ]), ' ',
1410 'class': 'cbi-button cbi-button-positive important',
1411 'click': ui
.createHandlerFn(this, 'handleModalSave', m
)
1415 }, this)).catch(L
.error
);
1419 var CBIGridSection
= CBITableSection
.extend({
1420 tab: function(name
, title
, description
) {
1421 CBIAbstractSection
.prototype.tab
.call(this, name
, title
, description
);
1424 handleAdd: function(ev
, name
) {
1425 var config_name
= this.uciconfig
|| this.map
.config
,
1426 section_id
= this.map
.data
.add(config_name
, this.sectiontype
, name
);
1428 this.addedSection
= section_id
;
1429 return this.renderMoreOptionsModal(section_id
);
1432 handleModalSave: function(/* ... */) {
1433 return this.super('handleModalSave', arguments
)
1434 .then(L
.bind(function() { this.addedSection
= null }, this));
1437 handleModalCancel: function(/* ... */) {
1438 var config_name
= this.uciconfig
|| this.map
.config
;
1440 if (this.addedSection
!= null) {
1441 this.map
.data
.remove(config_name
, this.addedSection
);
1442 this.addedSection
= null;
1445 return this.super('handleModalCancel', arguments
);
1448 renderUCISection: function(section_id
) {
1449 return this.renderOptions(null, section_id
);
1452 renderChildren: function(tab_name
, section_id
, in_table
) {
1453 var tasks
= [], index
= 0;
1455 for (var i
= 0, opt
; (opt
= this.children
[i
]) != null; i
++) {
1456 if (opt
.disable
|| opt
.modalonly
)
1460 tasks
.push(opt
.render(index
++, section_id
, in_table
));
1462 tasks
.push(this.renderTextValue(section_id
, opt
));
1465 return Promise
.all(tasks
);
1468 renderTextValue: function(section_id
, opt
) {
1469 var title
= this.stripTags(opt
.title
).trim(),
1470 descr
= this.stripTags(opt
.description
).trim(),
1471 value
= opt
.textvalue(section_id
);
1474 'class': 'td cbi-value-field',
1475 'data-title': (title
!= '') ? title
: null,
1476 'data-description': (descr
!= '') ? descr
: null,
1477 'data-name': opt
.option
,
1478 'data-widget': opt
.typename
|| opt
.__name__
1479 }, (value
!= null) ? value
: E('em', _('none')));
1482 renderHeaderRows: function(section_id
) {
1483 return this.super('renderHeaderRows', [ NaN
, true ]);
1486 renderRowActions: function(section_id
) {
1487 return this.super('renderRowActions', [ section_id
, _('Edit') ]);
1491 var section_ids
= this.cfgsections(),
1494 if (Array
.isArray(this.children
)) {
1495 for (var i
= 0; i
< section_ids
.length
; i
++) {
1496 for (var j
= 0; j
< this.children
.length
; j
++) {
1497 if (!this.children
[j
].editable
|| this.children
[j
].modalonly
)
1500 tasks
.push(this.children
[j
].parse(section_ids
[i
]));
1505 return Promise
.all(tasks
);
1509 var CBINamedSection
= CBIAbstractSection
.extend({
1510 __name__
: 'CBI.NamedSection',
1511 __init__: function(map
, section_id
/*, ... */) {
1512 this.super('__init__', this.varargs(arguments
, 2, map
));
1514 this.section
= section_id
;
1517 cfgsections: function() {
1518 return [ this.section
];
1521 handleAdd: function(ev
) {
1522 var section_id
= this.section
,
1523 config_name
= this.uciconfig
|| this.map
.config
;
1525 this.map
.data
.add(config_name
, this.sectiontype
, section_id
);
1526 return this.map
.save(null, true);
1529 handleRemove: function(ev
) {
1530 var section_id
= this.section
,
1531 config_name
= this.uciconfig
|| this.map
.config
;
1533 this.map
.data
.remove(config_name
, section_id
);
1534 return this.map
.save(null, true);
1537 renderContents: function(data
) {
1538 var ucidata
= data
[0], nodes
= data
[1],
1539 section_id
= this.section
,
1540 config_name
= this.uciconfig
|| this.map
.config
,
1541 sectionEl
= E('div', {
1542 'id': ucidata
? null : 'cbi-%s-%s'.format(config_name
, section_id
),
1543 'class': 'cbi-section',
1544 'data-tab': (this.map
.tabbed
&& !this.parentoption
) ? this.sectiontype
: null,
1545 'data-tab-title': (this.map
.tabbed
&& !this.parentoption
) ? this.title
|| this.sectiontype
: null
1548 if (typeof(this.title
) === 'string' && this.title
!== '')
1549 sectionEl
.appendChild(E('legend', {}, this.title
));
1551 if (typeof(this.description
) === 'string' && this.description
!== '')
1552 sectionEl
.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description
));
1555 if (this.addremove
) {
1556 sectionEl
.appendChild(
1557 E('div', { 'class': 'cbi-section-remove right' },
1559 'class': 'cbi-button',
1560 'click': ui
.createHandlerFn(this, 'handleRemove')
1561 }, [ _('Delete') ])));
1564 sectionEl
.appendChild(E('div', {
1565 'id': 'cbi-%s-%s'.format(config_name
, section_id
),
1567 ? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node',
1568 'data-section-id': section_id
1571 else if (this.addremove
) {
1572 sectionEl
.appendChild(
1574 'class': 'cbi-button cbi-button-add',
1575 'click': ui
.createHandlerFn(this, 'handleAdd')
1579 dom
.bindClassInstance(sectionEl
, this);
1584 render: function() {
1585 var config_name
= this.uciconfig
|| this.map
.config
,
1586 section_id
= this.section
;
1588 return Promise
.all([
1589 this.map
.data
.get(config_name
, section_id
),
1590 this.renderUCISection(section_id
)
1591 ]).then(this.renderContents
.bind(this));
1595 var CBIValue
= CBIAbstractValue
.extend({
1596 __name__
: 'CBI.Value',
1598 value: function(key
, val
) {
1599 this.keylist
= this.keylist
|| [];
1600 this.keylist
.push(String(key
));
1602 this.vallist
= this.vallist
|| [];
1603 this.vallist
.push(dom
.elem(val
) ? val
: String(val
!= null ? val
: key
));
1606 render: function(option_index
, section_id
, in_table
) {
1607 return Promise
.resolve(this.cfgvalue(section_id
))
1608 .then(this.renderWidget
.bind(this, section_id
, option_index
))
1609 .then(this.renderFrame
.bind(this, section_id
, in_table
, option_index
));
1612 renderFrame: function(section_id
, in_table
, option_index
, nodes
) {
1613 var config_name
= this.uciconfig
|| this.section
.uciconfig
|| this.map
.config
,
1614 depend_list
= this.transformDepList(section_id
),
1618 var title
= this.stripTags(this.title
).trim();
1619 optionEl
= E('div', {
1620 'class': 'td cbi-value-field',
1621 'data-title': (title
!= '') ? title
: null,
1622 'data-description': this.stripTags(this.description
).trim(),
1623 'data-name': this.option
,
1624 'data-widget': this.typename
|| (this.template
? this.template
.replace(/^.+\//, '') : null) || this.__name__
1626 'id': 'cbi-%s-%s-%s'.format(config_name
, section_id
, this.option
),
1627 'data-index': option_index
,
1628 'data-depends': depend_list
,
1629 'data-field': this.cbid(section_id
)
1633 optionEl
= E('div', {
1634 'class': 'cbi-value',
1635 'id': 'cbi-%s-%s-%s'.format(config_name
, section_id
, this.option
),
1636 'data-index': option_index
,
1637 'data-depends': depend_list
,
1638 'data-field': this.cbid(section_id
),
1639 'data-name': this.option
,
1640 'data-widget': this.typename
|| (this.template
? this.template
.replace(/^.+\//, '') : null) || this.__name__
1643 if (this.last_child
)
1644 optionEl
.classList
.add('cbi-value-last');
1646 if (typeof(this.title
) === 'string' && this.title
!== '') {
1647 optionEl
.appendChild(E('label', {
1648 'class': 'cbi-value-title',
1649 'for': 'widget.cbid.%s.%s.%s'.format(config_name
, section_id
, this.option
),
1650 'click': function(ev
) {
1651 var node
= ev
.currentTarget
,
1652 elem
= node
.nextElementSibling
.querySelector('#' + node
.getAttribute('for')) || node
.nextElementSibling
.querySelector('[data-widget-id="' + node
.getAttribute('for') + '"]');
1660 this.titleref
? E('a', {
1661 'class': 'cbi-title-ref',
1662 'href': this.titleref
,
1663 'title': this.titledesc
|| _('Go to relevant configuration page')
1664 }, this.title
) : this.title
));
1666 optionEl
.appendChild(E('div', { 'class': 'cbi-value-field' }));
1671 (optionEl
.lastChild
|| optionEl
).appendChild(nodes
);
1673 if (!in_table
&& typeof(this.description
) === 'string' && this.description
!== '')
1674 dom
.append(optionEl
.lastChild
|| optionEl
,
1675 E('div', { 'class': 'cbi-value-description' }, this.description
));
1677 if (depend_list
&& depend_list
.length
)
1678 optionEl
.classList
.add('hidden');
1680 optionEl
.addEventListener('widget-change',
1681 L
.bind(this.map
.checkDepends
, this.map
));
1683 dom
.bindClassInstance(optionEl
, this);
1688 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1689 var value
= (cfgvalue
!= null) ? cfgvalue
: this.default,
1690 choices
= this.transformChoices(),
1694 var placeholder
= (this.optional
|| this.rmempty
)
1695 ? E('em', _('unspecified')) : _('-- Please choose --');
1697 widget
= new ui
.Combobox(Array
.isArray(value
) ? value
.join(' ') : value
, choices
, {
1698 id
: this.cbid(section_id
),
1700 optional
: this.optional
|| this.rmempty
,
1701 datatype
: this.datatype
,
1702 select_placeholder
: this.placeholder
|| placeholder
,
1703 validate
: L
.bind(this.validate
, this, section_id
)
1707 widget
= new ui
.Textfield(Array
.isArray(value
) ? value
.join(' ') : value
, {
1708 id
: this.cbid(section_id
),
1709 password
: this.password
,
1710 optional
: this.optional
|| this.rmempty
,
1711 datatype
: this.datatype
,
1712 placeholder
: this.placeholder
,
1713 validate
: L
.bind(this.validate
, this, section_id
)
1717 return widget
.render();
1721 var CBIDynamicList
= CBIValue
.extend({
1722 __name__
: 'CBI.DynamicList',
1724 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1725 var value
= (cfgvalue
!= null) ? cfgvalue
: this.default,
1726 choices
= this.transformChoices(),
1727 items
= L
.toArray(value
);
1729 var widget
= new ui
.DynamicList(items
, choices
, {
1730 id
: this.cbid(section_id
),
1732 optional
: this.optional
|| this.rmempty
,
1733 datatype
: this.datatype
,
1734 placeholder
: this.placeholder
,
1735 validate
: L
.bind(this.validate
, this, section_id
)
1738 return widget
.render();
1742 var CBIListValue
= CBIValue
.extend({
1743 __name__
: 'CBI.ListValue',
1745 __init__: function() {
1746 this.super('__init__', arguments
);
1747 this.widget
= 'select';
1751 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1752 var choices
= this.transformChoices();
1753 var widget
= new ui
.Select((cfgvalue
!= null) ? cfgvalue
: this.default, choices
, {
1754 id
: this.cbid(section_id
),
1757 optional
: this.optional
,
1758 placeholder
: this.placeholder
,
1759 validate
: L
.bind(this.validate
, this, section_id
)
1762 return widget
.render();
1766 var CBIFlagValue
= CBIValue
.extend({
1767 __name__
: 'CBI.FlagValue',
1769 __init__: function() {
1770 this.super('__init__', arguments
);
1773 this.disabled
= '0';
1774 this.default = this.disabled
;
1777 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1778 var widget
= new ui
.Checkbox((cfgvalue
!= null) ? cfgvalue
: this.default, {
1779 id
: this.cbid(section_id
),
1780 value_enabled
: this.enabled
,
1781 value_disabled
: this.disabled
,
1782 validate
: L
.bind(this.validate
, this, section_id
)
1785 return widget
.render();
1788 formvalue: function(section_id
) {
1789 var elem
= this.getUIElement(section_id
),
1790 checked
= elem
? elem
.isChecked() : false;
1791 return checked
? this.enabled
: this.disabled
;
1794 textvalue: function(section_id
) {
1795 var cval
= this.cfgvalue(section_id
);
1798 cval
= this.default;
1800 return (cval
== this.enabled
) ? _('Yes') : _('No');
1803 parse: function(section_id
) {
1804 if (this.isActive(section_id
)) {
1805 var fval
= this.formvalue(section_id
);
1807 if (!this.isValid(section_id
))
1808 return Promise
.reject();
1810 if (fval
== this.default && (this.optional
|| this.rmempty
))
1811 return Promise
.resolve(this.remove(section_id
));
1813 return Promise
.resolve(this.write(section_id
, fval
));
1816 return Promise
.resolve(this.remove(section_id
));
1821 var CBIMultiValue
= CBIDynamicList
.extend({
1822 __name__
: 'CBI.MultiValue',
1824 __init__: function() {
1825 this.super('__init__', arguments
);
1826 this.placeholder
= _('-- Please choose --');
1829 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1830 var value
= (cfgvalue
!= null) ? cfgvalue
: this.default,
1831 choices
= this.transformChoices();
1833 var widget
= new ui
.Dropdown(L
.toArray(value
), choices
, {
1834 id
: this.cbid(section_id
),
1837 optional
: this.optional
|| this.rmempty
,
1838 select_placeholder
: this.placeholder
,
1839 display_items
: this.display_size
|| this.size
|| 3,
1840 dropdown_items
: this.dropdown_size
|| this.size
|| -1,
1841 validate
: L
.bind(this.validate
, this, section_id
)
1844 return widget
.render();
1848 var CBITextValue
= CBIValue
.extend({
1849 __name__
: 'CBI.TextValue',
1853 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1854 var value
= (cfgvalue
!= null) ? cfgvalue
: this.default;
1856 var widget
= new ui
.Textarea(value
, {
1857 id
: this.cbid(section_id
),
1858 optional
: this.optional
|| this.rmempty
,
1859 placeholder
: this.placeholder
,
1860 monospace
: this.monospace
,
1864 validate
: L
.bind(this.validate
, this, section_id
)
1867 return widget
.render();
1871 var CBIDummyValue
= CBIValue
.extend({
1872 __name__
: 'CBI.DummyValue',
1874 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1875 var value
= (cfgvalue
!= null) ? cfgvalue
: this.default,
1876 hiddenEl
= new ui
.Hiddenfield(value
, { id
: this.cbid(section_id
) }),
1877 outputEl
= E('div');
1880 outputEl
.appendChild(E('a', { 'href': this.href
}));
1882 dom
.append(outputEl
.lastChild
|| outputEl
,
1883 this.rawhtml
? value
: [ value
]);
1891 remove: function() {},
1892 write: function() {}
1895 var CBIButtonValue
= CBIValue
.extend({
1896 __name__
: 'CBI.ButtonValue',
1898 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1899 var value
= (cfgvalue
!= null) ? cfgvalue
: this.default,
1900 hiddenEl
= new ui
.Hiddenfield(value
, { id
: this.cbid(section_id
) }),
1901 outputEl
= E('div'),
1902 btn_title
= this.titleFn('inputtitle', section_id
) || this.titleFn('title', section_id
);
1904 if (value
!== false)
1905 dom
.content(outputEl
, [
1907 'class': 'cbi-button cbi-button-%s'.format(this.inputstyle
|| 'button'),
1908 'click': ui
.createHandlerFn(this, function(section_id
, ev
) {
1910 return this.onclick(ev
, section_id
);
1912 ev
.currentTarget
.parentNode
.nextElementSibling
.value
= value
;
1913 return this.map
.save();
1918 dom
.content(outputEl
, ' - ');
1927 var CBIHiddenValue
= CBIValue
.extend({
1928 __name__
: 'CBI.HiddenValue',
1930 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1931 var widget
= new ui
.Hiddenfield((cfgvalue
!= null) ? cfgvalue
: this.default, {
1932 id
: this.cbid(section_id
)
1935 return widget
.render();
1939 var CBIFileUpload
= CBIValue
.extend({
1940 __name__
: 'CBI.FileSelect',
1942 __init__: function(/* ... */) {
1943 this.super('__init__', arguments
);
1945 this.show_hidden
= false;
1946 this.enable_upload
= true;
1947 this.enable_remove
= true;
1948 this.root_directory
= '/etc/luci-uploads';
1951 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1952 var browserEl
= new ui
.FileUpload((cfgvalue
!= null) ? cfgvalue
: this.default, {
1953 id
: this.cbid(section_id
),
1954 name
: this.cbid(section_id
),
1955 show_hidden
: this.show_hidden
,
1956 enable_upload
: this.enable_upload
,
1957 enable_remove
: this.enable_remove
,
1958 root_directory
: this.root_directory
1961 return browserEl
.render();
1965 var CBISectionValue
= CBIValue
.extend({
1966 __name__
: 'CBI.ContainerValue',
1967 __init__: function(map
, section
, option
, cbiClass
/*, ... */) {
1968 this.super('__init__', [map
, section
, option
]);
1970 if (!CBIAbstractSection
.isSubclass(cbiClass
))
1971 throw 'Sub section must be a descendent of CBIAbstractSection';
1973 this.subsection
= cbiClass
.instantiate(this.varargs(arguments
, 4, this.map
));
1974 this.subsection
.parentoption
= this;
1977 load: function(section_id
) {
1978 return this.subsection
.load();
1981 parse: function(section_id
) {
1982 return this.subsection
.parse();
1985 renderWidget: function(section_id
, option_index
, cfgvalue
) {
1986 return this.subsection
.render();
1989 checkDepends: function(section_id
) {
1990 this.subsection
.checkDepends();
1991 return CBIValue
.prototype.checkDepends
.apply(this, [ section_id
]);
1994 write: function() {},
1995 remove: function() {},
1996 cfgvalue: function() { return null },
1997 formvalue: function() { return null }
2000 return baseclass
.extend({
2002 JSONMap
: CBIJSONMap
,
2003 AbstractSection
: CBIAbstractSection
,
2004 AbstractValue
: CBIAbstractValue
,
2006 TypedSection
: CBITypedSection
,
2007 TableSection
: CBITableSection
,
2008 GridSection
: CBIGridSection
,
2009 NamedSection
: CBINamedSection
,
2012 DynamicList
: CBIDynamicList
,
2013 ListValue
: CBIListValue
,
2015 MultiValue
: CBIMultiValue
,
2016 TextValue
: CBITextValue
,
2017 DummyValue
: CBIDummyValue
,
2018 Button
: CBIButtonValue
,
2019 HiddenValue
: CBIHiddenValue
,
2020 FileUpload
: CBIFileUpload
,
2021 SectionValue
: CBISectionValue