treewide: switch to shared isObject(), toArray() and sortedKeys() helpers
[project/luci.git] / modules / luci-base / htdocs / luci-static / resources / form.js
1 'use strict';
2 'require ui';
3 'require uci';
4
5 var scope = this;
6
7 var CBINode = Class.extend({
8 __init__: function(title, description) {
9 this.title = title || '';
10 this.description = description || '';
11 this.children = [];
12 },
13
14 append: function(obj) {
15 this.children.push(obj);
16 },
17
18 parse: function() {
19 var args = arguments;
20 this.children.forEach(function(child) {
21 child.parse.apply(child, args);
22 });
23 },
24
25 render: function() {
26 L.error('InternalError', 'Not implemented');
27 },
28
29 loadChildren: function(/* ... */) {
30 var tasks = [];
31
32 if (Array.isArray(this.children))
33 for (var i = 0; i < this.children.length; i++)
34 if (!this.children[i].disable)
35 tasks.push(this.children[i].load.apply(this.children[i], arguments));
36
37 return Promise.all(tasks);
38 },
39
40 renderChildren: function(tab_name /*, ... */) {
41 var tasks = [],
42 index = 0;
43
44 if (Array.isArray(this.children))
45 for (var i = 0; i < this.children.length; i++)
46 if (tab_name === null || this.children[i].tab === tab_name)
47 if (!this.children[i].disable)
48 tasks.push(this.children[i].render.apply(
49 this.children[i], this.varargs(arguments, 1, index++)));
50
51 return Promise.all(tasks);
52 },
53
54 stripTags: function(s) {
55 if (!s.match(/[<>]/))
56 return s;
57
58 var x = E('div', {}, s);
59 return x.textContent || x.innerText || '';
60 }
61 });
62
63 var CBIMap = CBINode.extend({
64 __init__: function(config /*, ... */) {
65 this.super('__init__', this.varargs(arguments, 1));
66
67 this.config = config;
68 this.parsechain = [ config ];
69 },
70
71 findElements: function(/* ... */) {
72 var q = null;
73
74 if (arguments.length == 1)
75 q = arguments[0];
76 else if (arguments.length == 2)
77 q = '[%s="%s"]'.format(arguments[0], arguments[1]);
78 else
79 L.error('InternalError', 'Expecting one or two arguments to findElements()');
80
81 return this.root.querySelectorAll(q);
82 },
83
84 findElement: function(/* ... */) {
85 var res = this.findElements.apply(this, arguments);
86 return res.length ? res[0] : null;
87 },
88
89 chain: function(config) {
90 if (this.parsechain.indexOf(config) == -1)
91 this.parsechain.push(config);
92 },
93
94 section: function(cbiClass /*, ... */) {
95 if (!CBIAbstractSection.isSubclass(cbiClass))
96 L.error('TypeError', 'Class must be a descendent of CBIAbstractSection');
97
98 var obj = cbiClass.instantiate(this.varargs(arguments, 1, this));
99 this.append(obj);
100 return obj;
101 },
102
103 load: function() {
104 return uci.load(this.parsechain || [ this.config ])
105 .then(this.loadChildren.bind(this));
106 },
107
108 parse: function() {
109 var tasks = [];
110
111 if (Array.isArray(this.children))
112 for (var i = 0; i < this.children.length; i++)
113 tasks.push(this.children[i].parse());
114
115 return Promise.all(tasks);
116 },
117
118 save: function() {
119 this.checkDepends();
120
121 return this.parse()
122 .then(uci.save.bind(uci))
123 .then(this.load.bind(this))
124 .then(this.renderContents.bind(this))
125 .catch(function(e) {
126 alert('Cannot save due to invalid values')
127 return Promise.reject();
128 });
129 },
130
131 reset: function() {
132 return this.renderContents();
133 },
134
135 render: function() {
136 return this.load().then(this.renderContents.bind(this));
137 },
138
139 renderContents: function() {
140 var mapEl = this.root || (this.root = E('div', {
141 'id': 'cbi-%s'.format(this.config),
142 'class': 'cbi-map',
143 'cbi-dependency-check': L.bind(this.checkDepends, this)
144 }));
145
146 L.dom.bindClassInstance(mapEl, this);
147
148 return this.renderChildren(null).then(L.bind(function(nodes) {
149 var initialRender = !mapEl.firstChild;
150
151 L.dom.content(mapEl, null);
152
153 if (this.title != null && this.title != '')
154 mapEl.appendChild(E('h2', { 'name': 'content' }, this.title));
155
156 if (this.description != null && this.description != '')
157 mapEl.appendChild(E('div', { 'class': 'cbi-map-descr' }, this.description));
158
159 L.dom.append(mapEl, nodes);
160
161 if (!initialRender) {
162 mapEl.classList.remove('flash');
163
164 window.setTimeout(function() {
165 mapEl.classList.add('flash');
166 }, 1);
167 }
168
169 this.checkDepends();
170
171 return mapEl;
172 }, this));
173 },
174
175 lookupOption: function(name, section_id) {
176 var id, elem, sid, inst;
177
178 if (name.indexOf('.') > -1)
179 id = 'cbid.%s'.format(name);
180 else
181 id = 'cbid.%s.%s.%s'.format(this.config, section_id, name);
182
183 elem = this.findElement('data-field', id);
184 sid = elem ? id.split(/\./)[2] : null;
185 inst = elem ? L.dom.findClassInstance(elem) : null;
186
187 return (inst instanceof CBIAbstractValue) ? [ inst, sid ] : null;
188 },
189
190 checkDepends: function(ev, n) {
191 var changed = false;
192
193 for (var i = 0, s = this.children[0]; (s = this.children[i]) != null; i++)
194 if (s.checkDepends(ev, n))
195 changed = true;
196
197 if (changed && (n || 0) < 10)
198 this.checkDepends(ev, (n || 10) + 1);
199 }
200 });
201
202 var CBIAbstractSection = CBINode.extend({
203 __init__: function(map, sectionType /*, ... */) {
204 this.super('__init__', this.varargs(arguments, 2));
205
206 this.sectiontype = sectionType;
207 this.map = map;
208 this.config = map.config;
209
210 this.optional = true;
211 this.addremove = false;
212 this.dynamic = false;
213 },
214
215 cfgsections: function() {
216 L.error('InternalError', 'Not implemented');
217 },
218
219 filter: function(section_id) {
220 return true;
221 },
222
223 load: function() {
224 var section_ids = this.cfgsections(),
225 tasks = [];
226
227 if (Array.isArray(this.children))
228 for (var i = 0; i < section_ids.length; i++)
229 tasks.push(this.loadChildren(section_ids[i])
230 .then(Function.prototype.bind.call(function(section_id, set_values) {
231 for (var i = 0; i < set_values.length; i++)
232 this.children[i].cfgvalue(section_id, set_values[i]);
233 }, this, section_ids[i])));
234
235 return Promise.all(tasks);
236 },
237
238 parse: function() {
239 var section_ids = this.cfgsections(),
240 tasks = [];
241
242 if (Array.isArray(this.children))
243 for (var i = 0; i < section_ids.length; i++)
244 for (var j = 0; j < this.children.length; j++)
245 tasks.push(this.children[j].parse(section_ids[i]));
246
247 return Promise.all(tasks);
248 },
249
250 tab: function(name, title, description) {
251 if (this.tabs && this.tabs[name])
252 throw 'Tab already declared';
253
254 var entry = {
255 name: name,
256 title: title,
257 description: description,
258 children: []
259 };
260
261 this.tabs = this.tabs || [];
262 this.tabs.push(entry);
263 this.tabs[name] = entry;
264
265 this.tab_names = this.tab_names || [];
266 this.tab_names.push(name);
267 },
268
269 option: function(cbiClass /*, ... */) {
270 if (!CBIAbstractValue.isSubclass(cbiClass))
271 throw L.error('TypeError', 'Class must be a descendent of CBIAbstractValue');
272
273 var obj = cbiClass.instantiate(this.varargs(arguments, 1, this.map, this));
274 this.append(obj);
275 return obj;
276 },
277
278 taboption: function(tabName /*, ... */) {
279 if (!this.tabs || !this.tabs[tabName])
280 throw L.error('ReferenceError', 'Associated tab not declared');
281
282 var obj = this.option.apply(this, this.varargs(arguments, 1));
283 obj.tab = tabName;
284 this.tabs[tabName].children.push(obj);
285 return obj;
286 },
287
288 renderUCISection: function(section_id) {
289 var renderTasks = [];
290
291 if (!this.tabs)
292 return this.renderOptions(null, section_id);
293
294 for (var i = 0; i < this.tab_names.length; i++)
295 renderTasks.push(this.renderOptions(this.tab_names[i], section_id));
296
297 return Promise.all(renderTasks)
298 .then(this.renderTabContainers.bind(this, section_id));
299 },
300
301 renderTabContainers: function(section_id, nodes) {
302 var config_name = this.uciconfig || this.map.config,
303 containerEls = E([]);
304
305 for (var i = 0; i < nodes.length; i++) {
306 var tab_name = this.tab_names[i],
307 tab_data = this.tabs[tab_name],
308 containerEl = E('div', {
309 'id': 'container.%s.%s.%s'.format(config_name, section_id, tab_name),
310 'data-tab': tab_name,
311 'data-tab-title': tab_data.title,
312 'data-tab-active': tab_name === this.selected_tab
313 });
314
315 if (tab_data.description != null && tab_data.description != '')
316 containerEl.appendChild(
317 E('div', { 'class': 'cbi-tab-descr' }, tab_data.description));
318
319 containerEl.appendChild(nodes[i]);
320 containerEls.appendChild(containerEl);
321 }
322
323 return containerEls;
324 },
325
326 renderOptions: function(tab_name, section_id) {
327 var in_table = (this instanceof CBITableSection);
328 return this.renderChildren(tab_name, section_id, in_table).then(function(nodes) {
329 var optionEls = E([]);
330 for (var i = 0; i < nodes.length; i++)
331 optionEls.appendChild(nodes[i]);
332 return optionEls;
333 });
334 },
335
336 checkDepends: function(ev, n) {
337 var changed = false,
338 sids = this.cfgsections();
339
340 for (var i = 0, sid = sids[0]; (sid = sids[i]) != null; i++) {
341 for (var j = 0, o = this.children[0]; (o = this.children[j]) != null; j++) {
342 var isActive = o.isActive(sid),
343 isSatisified = o.checkDepends(sid);
344
345 if (isActive != isSatisified) {
346 o.setActive(sid, !isActive);
347 changed = true;
348 }
349
350 if (!n && isActive)
351 o.triggerValidation(sid);
352 }
353 }
354
355 return changed;
356 }
357 });
358
359
360 var isEqual = function(x, y) {
361 if (x != null && y != null && typeof(x) != typeof(y))
362 return false;
363
364 if ((x == null && y != null) || (x != null && y == null))
365 return false;
366
367 if (Array.isArray(x)) {
368 if (x.length != y.length)
369 return false;
370
371 for (var i = 0; i < x.length; i++)
372 if (!isEqual(x[i], y[i]))
373 return false;
374 }
375 else if (typeof(x) == 'object') {
376 for (var k in x) {
377 if (x.hasOwnProperty(k) && !y.hasOwnProperty(k))
378 return false;
379
380 if (!isEqual(x[k], y[k]))
381 return false;
382 }
383
384 for (var k in y)
385 if (y.hasOwnProperty(k) && !x.hasOwnProperty(k))
386 return false;
387 }
388 else if (x != y) {
389 return false;
390 }
391
392 return true;
393 };
394
395 var CBIAbstractValue = CBINode.extend({
396 __init__: function(map, section, option /*, ... */) {
397 this.super('__init__', this.varargs(arguments, 3));
398
399 this.section = section;
400 this.option = option;
401 this.map = map;
402 this.config = map.config;
403
404 this.deps = [];
405 this.initial = {};
406 this.rmempty = true;
407 this.default = null;
408 this.size = null;
409 this.optional = false;
410 },
411
412 depends: function(field, value) {
413 var deps;
414
415 if (typeof(field) === 'string')
416 deps = {}, deps[field] = value;
417 else
418 deps = field;
419
420 this.deps.push(deps);
421 },
422
423 transformDepList: function(section_id, deplist) {
424 var list = deplist || this.deps,
425 deps = [];
426
427 if (Array.isArray(list)) {
428 for (var i = 0; i < list.length; i++) {
429 var dep = {};
430
431 for (var k in list[i]) {
432 if (list[i].hasOwnProperty(k)) {
433 if (k.charAt(0) === '!')
434 dep[k] = list[i][k];
435 else if (k.indexOf('.') !== -1)
436 dep['cbid.%s'.format(k)] = list[i][k];
437 else
438 dep['cbid.%s.%s.%s'.format(this.config, this.ucisection || section_id, k)] = list[i][k];
439 }
440 }
441
442 for (var k in dep) {
443 if (dep.hasOwnProperty(k)) {
444 deps.push(dep);
445 break;
446 }
447 }
448 }
449 }
450
451 return deps;
452 },
453
454 transformChoices: function() {
455 if (!Array.isArray(this.keylist) || this.keylist.length == 0)
456 return null;
457
458 var choices = {};
459
460 for (var i = 0; i < this.keylist.length; i++)
461 choices[this.keylist[i]] = this.vallist[i];
462
463 return choices;
464 },
465
466 checkDepends: function(section_id) {
467 var def = false;
468
469 if (!Array.isArray(this.deps) || !this.deps.length)
470 return true;
471
472 for (var i = 0; i < this.deps.length; i++) {
473 var istat = true,
474 reverse = false;
475
476 for (var dep in this.deps[i]) {
477 if (dep == '!reverse') {
478 reverse = true;
479 }
480 else if (dep == '!default') {
481 def = true;
482 istat = false;
483 }
484 else {
485 var res = this.map.lookupOption(dep, section_id),
486 val = res ? res[0].formvalue(res[1]) : null;
487
488 istat = (istat && isEqual(val, this.deps[i][dep]));
489 }
490 }
491
492 if (istat ^ reverse)
493 return true;
494 }
495
496 return def;
497 },
498
499 cbid: function(section_id) {
500 if (section_id == null)
501 L.error('TypeError', 'Section ID required');
502
503 return 'cbid.%s.%s.%s'.format(this.map.config, section_id, this.option);
504 },
505
506 load: function(section_id) {
507 if (section_id == null)
508 L.error('TypeError', 'Section ID required');
509
510 return uci.get(
511 this.uciconfig || this.map.config,
512 this.ucisection || section_id,
513 this.ucioption || this.option);
514 },
515
516 cfgvalue: function(section_id, set_value) {
517 if (section_id == null)
518 L.error('TypeError', 'Section ID required');
519
520 if (arguments.length == 2) {
521 this.data = this.data || {};
522 this.data[section_id] = set_value;
523 }
524
525 return this.data ? this.data[section_id] : null;
526 },
527
528 formvalue: function(section_id) {
529 var node = this.map.findElement('id', this.cbid(section_id));
530 return node ? L.dom.callClassMethod(node, 'getValue') : null;
531 },
532
533 textvalue: function(section_id) {
534 var cval = this.cfgvalue(section_id);
535
536 if (cval == null)
537 cval = this.default;
538
539 return (cval != null) ? '%h'.format(cval) : null;
540 },
541
542 validate: function(section_id, value) {
543 return true;
544 },
545
546 isValid: function(section_id) {
547 var node = this.map.findElement('id', this.cbid(section_id));
548 return node ? L.dom.callClassMethod(node, 'isValid') : true;
549 },
550
551 isActive: function(section_id) {
552 var field = this.map.findElement('data-field', this.cbid(section_id));
553 return (field != null && !field.classList.contains('hidden'));
554 },
555
556 setActive: function(section_id, active) {
557 var field = this.map.findElement('data-field', this.cbid(section_id));
558
559 if (field && field.classList.contains('hidden') == active) {
560 field.classList[active ? 'remove' : 'add']('hidden');
561 return true;
562 }
563
564 return false;
565 },
566
567 triggerValidation: function(section_id) {
568 var node = this.map.findElement('id', this.cbid(section_id));
569 return node ? L.dom.callClassMethod(node, 'triggerValidation') : true;
570 },
571
572 parse: function(section_id) {
573 var active = this.isActive(section_id),
574 cval = this.cfgvalue(section_id),
575 fval = active ? this.formvalue(section_id) : null;
576
577 if (active && !this.isValid(section_id))
578 return Promise.reject();
579
580 if (fval != '' && fval != null) {
581 if (this.forcewrite || !isEqual(cval, fval))
582 return Promise.resolve(this.write(section_id, fval));
583 }
584 else {
585 if (this.rmempty || this.optional) {
586 return Promise.resolve(this.remove(section_id));
587 }
588 else if (!isEqual(cval, fval)) {
589 console.log('This should have been catched by isValid()');
590 return Promise.reject();
591 }
592 }
593
594 return Promise.resolve();
595 },
596
597 write: function(section_id, formvalue) {
598 return uci.set(
599 this.uciconfig || this.map.config,
600 this.ucisection || section_id,
601 this.ucioption || this.option,
602 formvalue);
603 },
604
605 remove: function(section_id) {
606 return uci.unset(
607 this.uciconfig || this.map.config,
608 this.ucisection || section_id,
609 this.ucioption || this.option);
610 }
611 });
612
613 var CBITypedSection = CBIAbstractSection.extend({
614 __name__: 'CBI.TypedSection',
615
616 cfgsections: function() {
617 return uci.sections(this.uciconfig || this.map.config, this.sectiontype)
618 .map(function(s) { return s['.name'] })
619 .filter(L.bind(this.filter, this));
620 },
621
622 handleAdd: function(ev, name) {
623 var config_name = this.uciconfig || this.map.config;
624
625 uci.add(config_name, this.sectiontype, name);
626 this.map.save();
627 },
628
629 handleRemove: function(section_id, ev) {
630 var config_name = this.uciconfig || this.map.config;
631
632 uci.remove(config_name, section_id);
633 this.map.save();
634 },
635
636 renderSectionAdd: function(extra_class) {
637 if (!this.addremove)
638 return E([]);
639
640 var createEl = E('div', { 'class': 'cbi-section-create' }),
641 config_name = this.uciconfig || this.map.config;
642
643 if (extra_class != null)
644 createEl.classList.add(extra_class);
645
646 if (this.anonymous) {
647 createEl.appendChild(E('input', {
648 'type': 'submit',
649 'class': 'cbi-button cbi-button-add',
650 'value': _('Add'),
651 'title': _('Add'),
652 'click': L.bind(this.handleAdd, this)
653 }));
654 }
655 else {
656 var nameEl = E('input', {
657 'type': 'text',
658 'class': 'cbi-section-create-name'
659 });
660
661 L.dom.append(createEl, [
662 E('div', {}, nameEl),
663 E('input', {
664 'class': 'cbi-button cbi-button-add',
665 'type': 'submit',
666 'value': _('Add'),
667 'title': _('Add'),
668 'click': L.bind(function(ev) {
669 if (nameEl.classList.contains('cbi-input-invalid'))
670 return;
671
672 this.handleAdd(ev, nameEl.value);
673 }, this)
674 })
675 ]);
676
677 ui.addValidator(nameEl, 'uciname', true, 'blur', 'keyup');
678 }
679
680 return createEl;
681 },
682
683 renderContents: function(cfgsections, nodes) {
684 var section_id = null,
685 config_name = this.uciconfig || this.map.config,
686 sectionEl = E('div', {
687 'id': 'cbi-%s-%s'.format(config_name, this.sectiontype),
688 'class': 'cbi-section'
689 });
690
691 if (this.title != null && this.title != '')
692 sectionEl.appendChild(E('legend', {}, this.title));
693
694 if (this.description != null && this.description != '')
695 sectionEl.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description));
696
697 for (var i = 0; i < nodes.length; i++) {
698 if (this.addremove) {
699 sectionEl.appendChild(
700 E('div', { 'class': 'cbi-section-remove right' },
701 E('input', {
702 'type': 'submit',
703 'class': 'cbi-button',
704 'name': 'cbi.rts.%s.%s'.format(config_name, cfgsections[i]),
705 'value': _('Delete'),
706 'data-section-id': cfgsections[i],
707 'click': L.bind(this.handleRemove, this, cfgsections[i])
708 })));
709 }
710
711 if (!this.anonymous)
712 sectionEl.appendChild(E('h3', cfgsections[i].toUpperCase()));
713
714 sectionEl.appendChild(E('div', {
715 'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]),
716 'class': this.tabs
717 ? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node'
718 }, nodes[i]));
719
720 if (this.tabs)
721 ui.tabs.initTabGroup(sectionEl.lastChild.childNodes);
722 }
723
724 if (nodes.length == 0)
725 L.dom.append(sectionEl, [
726 E('em', _('This section contains no values yet')),
727 E('br'), E('br')
728 ]);
729
730 sectionEl.appendChild(this.renderSectionAdd());
731
732 L.dom.bindClassInstance(sectionEl, this);
733
734 return sectionEl;
735 },
736
737 render: function() {
738 var cfgsections = this.cfgsections(),
739 renderTasks = [];
740
741 for (var i = 0; i < cfgsections.length; i++)
742 renderTasks.push(this.renderUCISection(cfgsections[i]));
743
744 return Promise.all(renderTasks).then(this.renderContents.bind(this, cfgsections));
745 }
746 });
747
748 var CBITableSection = CBITypedSection.extend({
749 __name__: 'CBI.TableSection',
750
751 tab: function() {
752 throw 'Tabs are not supported by TableSection';
753 },
754
755 renderContents: function(cfgsections, nodes) {
756 var section_id = null,
757 config_name = this.uciconfig || this.map.config,
758 max_cols = isNaN(this.max_cols) ? this.children.length : this.max_cols,
759 has_more = max_cols < this.children.length,
760 sectionEl = E('div', {
761 'id': 'cbi-%s-%s'.format(config_name, this.sectiontype),
762 'class': 'cbi-section cbi-tblsection'
763 }),
764 tableEl = E('div', {
765 'class': 'table cbi-section-table'
766 });
767
768 if (this.title != null && this.title != '')
769 sectionEl.appendChild(E('h3', {}, this.title));
770
771 if (this.description != null && this.description != '')
772 sectionEl.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description));
773
774 tableEl.appendChild(this.renderHeaderRows(max_cols));
775
776 for (var i = 0; i < nodes.length; i++) {
777 var sectionname = this.stripTags((typeof(this.sectiontitle) == 'function')
778 ? String(this.sectiontitle(cfgsections[i]) || '') : cfgsections[i]).trim();
779
780 var trEl = E('div', {
781 'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]),
782 'class': 'tr cbi-section-table-row',
783 'data-sid': cfgsections[i],
784 'draggable': this.sortable ? true : null,
785 'mousedown': this.sortable ? L.bind(this.handleDragInit, this) : null,
786 'dragstart': this.sortable ? L.bind(this.handleDragStart, this) : null,
787 'dragover': this.sortable ? L.bind(this.handleDragOver, this) : null,
788 'dragenter': this.sortable ? L.bind(this.handleDragEnter, this) : null,
789 'dragleave': this.sortable ? L.bind(this.handleDragLeave, this) : null,
790 'dragend': this.sortable ? L.bind(this.handleDragEnd, this) : null,
791 'drop': this.sortable ? L.bind(this.handleDrop, this) : null,
792 'data-title': (sectionname && (!this.anonymous || this.sectiontitle)) ? sectionname : null
793 });
794
795 if (this.extedit || this.rowcolors)
796 trEl.classList.add(!(tableEl.childNodes.length % 2)
797 ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2');
798
799 for (var j = 0; j < max_cols && nodes[i].firstChild; j++)
800 trEl.appendChild(nodes[i].firstChild);
801
802 trEl.appendChild(this.renderRowActions(cfgsections[i], has_more ? _('More…') : null));
803 tableEl.appendChild(trEl);
804 }
805
806 if (nodes.length == 0)
807 tableEl.appendChild(E('div', { 'class': 'tr cbi-section-table-row placeholder' },
808 E('div', { 'class': 'td' },
809 E('em', {}, _('This section contains no values yet')))));
810
811 sectionEl.appendChild(tableEl);
812
813 sectionEl.appendChild(this.renderSectionAdd('cbi-tblsection-create'));
814
815 L.dom.bindClassInstance(sectionEl, this);
816
817 return sectionEl;
818 },
819
820 renderHeaderRows: function(max_cols) {
821 var has_titles = false,
822 has_descriptions = false,
823 anon_class = (!this.anonymous || this.sectiontitle) ? 'named' : 'anonymous',
824 trEls = E([]);
825
826 for (var i = 0, opt; i < max_cols && (opt = this.children[i]) != null; i++) {
827 if (opt.optional || opt.modalonly)
828 continue;
829
830 has_titles = has_titles || !!opt.title;
831 has_descriptions = has_descriptions || !!opt.description;
832 }
833
834 if (has_titles) {
835 var trEl = E('div', {
836 'class': 'tr cbi-section-table-titles ' + anon_class,
837 'data-title': (!this.anonymous || this.sectiontitle) ? _('Name') : null
838 });
839
840 for (var i = 0, opt; i < max_cols && (opt = this.children[i]) != null; i++) {
841 if (opt.optional || opt.modalonly)
842 continue;
843
844 trEl.appendChild(E('div', {
845 'class': 'th cbi-section-table-cell',
846 'data-type': opt.__name__
847 }));
848
849 if (opt.width != null)
850 trEl.lastElementChild.style.width =
851 (typeof(opt.width) == 'number') ? opt.width+'px' : opt.width;
852
853 if (opt.titleref)
854 trEl.lastElementChild.appendChild(E('a', {
855 'href': opt.titleref,
856 'class': 'cbi-title-ref',
857 'title': this.titledesc || _('Go to relevant configuration page')
858 }, opt.title));
859 else
860 L.dom.content(trEl.lastElementChild, opt.title);
861 }
862
863 if (this.sortable || this.extedit || this.addremove || has_more)
864 trEl.appendChild(E('div', {
865 'class': 'th cbi-section-table-cell cbi-section-actions'
866 }));
867
868 trEls.appendChild(trEl);
869 }
870
871 if (has_descriptions) {
872 var trEl = E('div', {
873 'class': 'tr cbi-section-table-descr ' + anon_class
874 });
875
876 for (var i = 0, opt; i < max_cols && (opt = this.children[i]) != null; i++) {
877 if (opt.optional || opt.modalonly)
878 continue;
879
880 trEl.appendChild(E('div', {
881 'class': 'th cbi-section-table-cell',
882 'data-type': opt.__name__
883 }, opt.description));
884
885 if (opt.width != null)
886 trEl.lastElementChild.style.width =
887 (typeof(opt.width) == 'number') ? opt.width+'px' : opt.width;
888 }
889
890 if (this.sortable || this.extedit || this.addremove || has_more)
891 trEl.appendChild(E('div', {
892 'class': 'th cbi-section-table-cell cbi-section-actions'
893 }));
894
895 trEls.appendChild(trEl);
896 }
897
898 return trEls;
899 },
900
901 renderRowActions: function(section_id, more_label) {
902 var config_name = this.uciconfig || this.map.config;
903
904 if (!this.sortable && !this.extedit && !this.addremove && !more_label)
905 return E([]);
906
907 var tdEl = E('div', {
908 'class': 'td cbi-section-table-cell nowrap cbi-section-actions'
909 }, E('div'));
910
911 if (this.sortable) {
912 L.dom.append(tdEl.lastElementChild, [
913 E('div', {
914 'title': _('Drag to reorder'),
915 'class': 'cbi-button drag-handle center',
916 'style': 'cursor:move'
917 }, '☰')
918 ]);
919 }
920
921 if (this.extedit) {
922 var evFn = null;
923
924 if (typeof(this.extedit) == 'function')
925 evFn = L.bind(this.extedit, this);
926 else if (typeof(this.extedit) == 'string')
927 evFn = L.bind(function(sid, ev) {
928 location.href = this.extedit.format(sid);
929 }, this, section_id);
930
931 L.dom.append(tdEl.lastElementChild,
932 E('input', {
933 'type': 'button',
934 'value': _('Edit'),
935 'title': _('Edit'),
936 'class': 'cbi-button cbi-button-edit',
937 'click': evFn
938 })
939 );
940 }
941
942 if (more_label) {
943 L.dom.append(tdEl.lastElementChild,
944 E('input', {
945 'type': 'button',
946 'value': more_label,
947 'title': more_label,
948 'class': 'cbi-button cbi-button-edit',
949 'click': L.bind(this.renderMoreOptionsModal, this, section_id)
950 })
951 );
952 }
953
954 if (this.addremove) {
955 L.dom.append(tdEl.lastElementChild,
956 E('input', {
957 'type': 'submit',
958 'value': _('Delete'),
959 'title': _('Delete'),
960 'class': 'cbi-button cbi-button-remove',
961 'click': L.bind(function(sid, ev) {
962 uci.remove(config_name, sid);
963 this.map.save();
964 }, this, section_id)
965 })
966 );
967 }
968
969 return tdEl;
970 },
971
972 handleDragInit: function(ev) {
973 scope.dragState = { node: ev.target };
974 },
975
976 handleDragStart: function(ev) {
977 if (!scope.dragState || !scope.dragState.node.classList.contains('drag-handle')) {
978 scope.dragState = null;
979 ev.preventDefault();
980 return false;
981 }
982
983 scope.dragState.node = L.dom.parent(scope.dragState.node, '.tr');
984 ev.dataTransfer.setData('text', 'drag');
985 ev.target.style.opacity = 0.4;
986 },
987
988 handleDragOver: function(ev) {
989 var n = scope.dragState.targetNode,
990 r = scope.dragState.rect,
991 t = r.top + r.height / 2;
992
993 if (ev.clientY <= t) {
994 n.classList.remove('drag-over-below');
995 n.classList.add('drag-over-above');
996 }
997 else {
998 n.classList.remove('drag-over-above');
999 n.classList.add('drag-over-below');
1000 }
1001
1002 ev.dataTransfer.dropEffect = 'move';
1003 ev.preventDefault();
1004 return false;
1005 },
1006
1007 handleDragEnter: function(ev) {
1008 scope.dragState.rect = ev.currentTarget.getBoundingClientRect();
1009 scope.dragState.targetNode = ev.currentTarget;
1010 },
1011
1012 handleDragLeave: function(ev) {
1013 ev.currentTarget.classList.remove('drag-over-above');
1014 ev.currentTarget.classList.remove('drag-over-below');
1015 },
1016
1017 handleDragEnd: function(ev) {
1018 var n = ev.target;
1019
1020 n.style.opacity = '';
1021 n.classList.add('flash');
1022 n.parentNode.querySelectorAll('.drag-over-above, .drag-over-below')
1023 .forEach(function(tr) {
1024 tr.classList.remove('drag-over-above');
1025 tr.classList.remove('drag-over-below');
1026 });
1027 },
1028
1029 handleDrop: function(ev) {
1030 var s = scope.dragState;
1031
1032 if (s.node && s.targetNode) {
1033 var config_name = this.uciconfig || this.map.config,
1034 ref_node = s.targetNode,
1035 after = false;
1036
1037 if (ref_node.classList.contains('drag-over-below')) {
1038 ref_node = ref_node.nextElementSibling;
1039 after = true;
1040 }
1041
1042 var sid1 = s.node.getAttribute('data-sid'),
1043 sid2 = s.targetNode.getAttribute('data-sid');
1044
1045 s.node.parentNode.insertBefore(s.node, ref_node);
1046 uci.move(config_name, sid1, sid2, after);
1047 }
1048
1049 scope.dragState = null;
1050 ev.target.style.opacity = '';
1051 ev.stopPropagation();
1052 ev.preventDefault();
1053 return false;
1054 },
1055
1056 handleModalCancel: function(modalMap, ev) {
1057 return Promise.resolve(L.ui.hideModal());
1058 },
1059
1060 handleModalSave: function(modalMap, ev) {
1061 return modalMap.save()
1062 .then(L.bind(this.map.load, this.map))
1063 .then(L.bind(this.map.reset, this.map))
1064 .then(L.ui.hideModal)
1065 .catch(function() {});
1066 },
1067
1068 renderMoreOptionsModal: function(section_id, ev) {
1069 var parent = this.map,
1070 title = parent.title,
1071 name = null,
1072 m = new CBIMap(this.map.config, null, null),
1073 s = m.section(CBINamedSection, section_id, this.sectiontype);
1074 s.tabs = this.tabs;
1075 s.tab_names = this.tab_names;
1076
1077 if (typeof(this.sectiontitle) == 'function')
1078 name = this.stripTags(String(this.sectiontitle(section_id) || ''));
1079 else if (!this.anonymous)
1080 name = section_id;
1081
1082 if (name)
1083 title += ' - ' + name;
1084
1085 for (var i = 0; i < this.children.length; i++) {
1086 var o1 = this.children[i];
1087
1088 if (o1.modalonly === false)
1089 continue;
1090
1091 var o2 = s.option(o1.constructor, o1.option, o1.title, o1.description);
1092
1093 for (var k in o1) {
1094 if (!o1.hasOwnProperty(k))
1095 continue;
1096
1097 switch (k) {
1098 case 'map':
1099 case 'section':
1100 case 'option':
1101 case 'title':
1102 case 'description':
1103 continue;
1104
1105 default:
1106 o2[k] = o1[k];
1107 }
1108 }
1109 }
1110
1111 //ev.target.classList.add('spinning');
1112 m.render().then(L.bind(function(nodes) {
1113 //ev.target.classList.remove('spinning');
1114 L.ui.showModal(title, [
1115 nodes,
1116 E('div', { 'class': 'right' }, [
1117 E('input', {
1118 'type': 'button',
1119 'class': 'btn',
1120 'click': L.bind(this.handleModalCancel, this, m),
1121 'value': _('Dismiss')
1122 }), ' ',
1123 E('input', {
1124 'type': 'button',
1125 'class': 'cbi-button cbi-button-positive important',
1126 'click': L.bind(this.handleModalSave, this, m),
1127 'value': _('Save')
1128 })
1129 ])
1130 ], 'cbi-modal');
1131 }, this)).catch(L.error);
1132 }
1133 });
1134
1135 var CBIGridSection = CBITableSection.extend({
1136 tab: function(name, title, description) {
1137 CBIAbstractSection.prototype.tab.call(this, name, title, description);
1138 },
1139
1140 handleAdd: function(ev) {
1141 var config_name = this.uciconfig || this.map.config,
1142 section_id = uci.add(config_name, this.sectiontype);
1143
1144 this.addedSection = section_id;
1145 this.renderMoreOptionsModal(section_id);
1146 },
1147
1148 handleModalSave: function(/* ... */) {
1149 return this.super('handleModalSave', arguments)
1150 .then(L.bind(function() { this.addedSection = null }, this));
1151 },
1152
1153 handleModalCancel: function(/* ... */) {
1154 var config_name = this.uciconfig || this.map.config;
1155
1156 if (this.addedSection != null) {
1157 uci.remove(config_name, this.addedSection);
1158 this.addedSection = null;
1159 }
1160
1161 return this.super('handleModalCancel', arguments);
1162 },
1163
1164 renderUCISection: function(section_id) {
1165 return this.renderOptions(null, section_id);
1166 },
1167
1168 renderChildren: function(tab_name, section_id, in_table) {
1169 var tasks = [], index = 0;
1170
1171 for (var i = 0, opt; (opt = this.children[i]) != null; i++) {
1172 if (opt.disable || opt.modalonly)
1173 continue;
1174
1175 if (opt.editable)
1176 tasks.push(opt.render(index++, section_id, in_table));
1177 else
1178 tasks.push(this.renderTextValue(section_id, opt));
1179 }
1180
1181 return Promise.all(tasks);
1182 },
1183
1184 renderTextValue: function(section_id, opt) {
1185 var title = this.stripTags(opt.title).trim(),
1186 descr = this.stripTags(opt.description).trim(),
1187 value = opt.textvalue(section_id);
1188
1189 return E('div', {
1190 'class': 'td cbi-value-field',
1191 'data-title': (title != '') ? title : opt.option,
1192 'data-description': (descr != '') ? descr : null,
1193 'data-name': opt.option,
1194 'data-type': opt.typename || opt.__name__
1195 }, (value != null) ? value : E('em', _('none')));
1196 },
1197
1198 renderRowActions: function(section_id) {
1199 return this.super('renderRowActions', [ section_id, _('Edit') ]);
1200 },
1201
1202 parse: function() {
1203 var section_ids = this.cfgsections(),
1204 tasks = [];
1205
1206 if (Array.isArray(this.children)) {
1207 for (var i = 0; i < section_ids.length; i++) {
1208 for (var j = 0; j < this.children.length; j++) {
1209 if (!this.children[j].editable || this.children[j].modalonly)
1210 continue;
1211
1212 tasks.push(this.children[j].parse(section_ids[i]));
1213 }
1214 }
1215 }
1216
1217 return Promise.all(tasks);
1218 }
1219 });
1220
1221 var CBINamedSection = CBIAbstractSection.extend({
1222 __name__: 'CBI.NamedSection',
1223 __init__: function(map, section_id /*, ... */) {
1224 this.super('__init__', this.varargs(arguments, 2, map));
1225
1226 this.section = section_id;
1227 },
1228
1229 cfgsections: function() {
1230 return [ this.section ];
1231 },
1232
1233 handleAdd: function(ev) {
1234 var section_id = this.section,
1235 config_name = this.uciconfig || this.map.config;
1236
1237 uci.add(config_name, this.sectiontype, section_id);
1238 this.map.save();
1239 },
1240
1241 handleRemove: function(ev) {
1242 var section_id = this.section,
1243 config_name = this.uciconfig || this.map.config;
1244
1245 uci.remove(config_name, section_id);
1246 this.map.save();
1247 },
1248
1249 renderContents: function(data) {
1250 var ucidata = data[0], nodes = data[1],
1251 section_id = this.section,
1252 config_name = this.uciconfig || this.map.config,
1253 sectionEl = E('div', {
1254 'id': ucidata ? null : 'cbi-%s-%s'.format(config_name, section_id),
1255 'class': 'cbi-section'
1256 });
1257
1258 if (typeof(this.title) === 'string' && this.title !== '')
1259 sectionEl.appendChild(E('legend', {}, this.title));
1260
1261 if (typeof(this.description) === 'string' && this.description !== '')
1262 sectionEl.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description));
1263
1264 if (ucidata) {
1265 if (this.addremove) {
1266 sectionEl.appendChild(
1267 E('div', { 'class': 'cbi-section-remove right' },
1268 E('input', {
1269 'type': 'submit',
1270 'class': 'cbi-button',
1271 'value': _('Delete'),
1272 'click': L.bind(this.handleRemove, this)
1273 })));
1274 }
1275
1276 sectionEl.appendChild(E('div', {
1277 'id': 'cbi-%s-%s'.format(config_name, section_id),
1278 'class': this.tabs
1279 ? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node'
1280 }, nodes));
1281
1282 if (this.tabs)
1283 ui.tabs.initTabGroup(sectionEl.lastChild.childNodes);
1284 }
1285 else if (this.addremove) {
1286 sectionEl.appendChild(
1287 E('input', {
1288 'type': 'submit',
1289 'class': 'cbi-button cbi-button-add',
1290 'value': _('Add'),
1291 'click': L.bind(this.handleAdd, this)
1292 }));
1293 }
1294
1295 L.dom.bindClassInstance(sectionEl, this);
1296
1297 return sectionEl;
1298 },
1299
1300 render: function() {
1301 var config_name = this.uciconfig || this.map.config,
1302 section_id = this.section;
1303
1304 return Promise.all([
1305 uci.get(config_name, section_id),
1306 this.renderUCISection(section_id)
1307 ]).then(this.renderContents.bind(this));
1308 }
1309 });
1310
1311 var CBIValue = CBIAbstractValue.extend({
1312 __name__: 'CBI.Value',
1313
1314 value: function(key, val) {
1315 this.keylist = this.keylist || [];
1316 this.keylist.push(String(key));
1317
1318 this.vallist = this.vallist || [];
1319 this.vallist.push(String(val != null ? val : key));
1320 },
1321
1322 render: function(option_index, section_id, in_table) {
1323 return Promise.resolve(this.cfgvalue(section_id))
1324 .then(this.renderWidget.bind(this, section_id, option_index))
1325 .then(this.renderFrame.bind(this, section_id, in_table, option_index));
1326 },
1327
1328 renderFrame: function(section_id, in_table, option_index, nodes) {
1329 var config_name = this.uciconfig || this.map.config,
1330 depend_list = this.transformDepList(section_id),
1331 optionEl;
1332
1333 if (in_table) {
1334 optionEl = E('div', {
1335 'class': 'td cbi-value-field',
1336 'data-title': this.stripTags(this.title).trim(),
1337 'data-description': this.stripTags(this.description).trim(),
1338 'data-name': this.option,
1339 'data-type': this.typename || (this.template ? this.template.replace(/^.+\//, '') : null) || this.__name__
1340 }, E('div', {
1341 'id': 'cbi-%s-%s-%s'.format(config_name, section_id, this.option),
1342 'data-index': option_index,
1343 'data-depends': depend_list,
1344 'data-field': this.cbid(section_id)
1345 }));
1346 }
1347 else {
1348 optionEl = E('div', {
1349 'class': 'cbi-value',
1350 'id': 'cbi-%s-%s-%s'.format(config_name, section_id, this.option),
1351 'data-index': option_index,
1352 'data-depends': depend_list,
1353 'data-field': this.cbid(section_id),
1354 'data-name': this.option,
1355 'data-type': this.typename || (this.template ? this.template.replace(/^.+\//, '') : null) || this.__name__
1356 });
1357
1358 if (this.last_child)
1359 optionEl.classList.add('cbi-value-last');
1360
1361 if (typeof(this.title) === 'string' && this.title !== '') {
1362 optionEl.appendChild(E('label', {
1363 'class': 'cbi-value-title',
1364 'for': 'cbid.%s.%s.%s'.format(config_name, section_id, this.option)
1365 },
1366 this.titleref ? E('a', {
1367 'class': 'cbi-title-ref',
1368 'href': this.titleref,
1369 'title': this.titledesc || _('Go to relevant configuration page')
1370 }, this.title) : this.title));
1371
1372 optionEl.appendChild(E('div', { 'class': 'cbi-value-field' }));
1373 }
1374 }
1375
1376 if (nodes)
1377 (optionEl.lastChild || optionEl).appendChild(nodes);
1378
1379 if (!in_table && typeof(this.description) === 'string' && this.description !== '')
1380 L.dom.append(optionEl.lastChild || optionEl,
1381 E('div', { 'class': 'cbi-value-description' }, this.description));
1382
1383 if (depend_list && depend_list.length)
1384 optionEl.classList.add('hidden');
1385
1386 optionEl.addEventListener('widget-change',
1387 L.bind(this.map.checkDepends, this.map));
1388
1389 L.dom.bindClassInstance(optionEl, this);
1390
1391 return optionEl;
1392 },
1393
1394 renderWidget: function(section_id, option_index, cfgvalue) {
1395 var value = (cfgvalue != null) ? cfgvalue : this.default,
1396 choices = this.transformChoices(),
1397 widget;
1398
1399 if (choices) {
1400 var placeholder = (this.optional || this.rmempty)
1401 ? E('em', _('unspecified')) : _('-- Please choose --');
1402
1403 widget = new ui.Combobox(Array.isArray(value) ? value.join(' ') : value, choices, {
1404 id: this.cbid(section_id),
1405 sort: this.keylist,
1406 optional: this.optional || this.rmempty,
1407 datatype: this.datatype,
1408 select_placeholder: this.placeholder || placeholder,
1409 validate: L.bind(this.validate, this, section_id)
1410 });
1411 }
1412 else {
1413 widget = new ui.Textfield(Array.isArray(value) ? value.join(' ') : value, {
1414 id: this.cbid(section_id),
1415 password: this.password,
1416 optional: this.optional || this.rmempty,
1417 datatype: this.datatype,
1418 placeholder: this.placeholder,
1419 validate: L.bind(this.validate, this, section_id)
1420 });
1421 }
1422
1423 return widget.render();
1424 }
1425 });
1426
1427 var CBIDynamicList = CBIValue.extend({
1428 __name__: 'CBI.DynamicList',
1429
1430 renderWidget: function(section_id, option_index, cfgvalue) {
1431 var value = (cfgvalue != null) ? cfgvalue : this.default,
1432 choices = this.transformChoices(),
1433 items = L.toArray(value);
1434
1435 var widget = new ui.DynamicList(items, choices, {
1436 id: this.cbid(section_id),
1437 sort: this.keylist,
1438 optional: this.optional || this.rmempty,
1439 datatype: this.datatype,
1440 placeholder: this.placeholder,
1441 validate: L.bind(this.validate, this, section_id)
1442 });
1443
1444 return widget.render();
1445 },
1446 });
1447
1448 var CBIListValue = CBIValue.extend({
1449 __name__: 'CBI.ListValue',
1450
1451 __init__: function() {
1452 this.super('__init__', arguments);
1453 this.widget = 'select';
1454 this.deplist = [];
1455 },
1456
1457 renderWidget: function(section_id, option_index, cfgvalue) {
1458 var choices = this.transformChoices();
1459 var widget = new ui.Select((cfgvalue != null) ? cfgvalue : this.default, choices, {
1460 id: this.cbid(section_id),
1461 size: this.size,
1462 sort: this.keylist,
1463 optional: this.rmempty || this.optional,
1464 validate: L.bind(this.validate, this, section_id)
1465 });
1466
1467 return widget.render();
1468 },
1469 });
1470
1471 var CBIFlagValue = CBIValue.extend({
1472 __name__: 'CBI.FlagValue',
1473
1474 __init__: function() {
1475 this.super('__init__', arguments);
1476
1477 this.enabled = '1';
1478 this.disabled = '0';
1479 this.default = this.disabled;
1480 },
1481
1482 renderWidget: function(section_id, option_index, cfgvalue) {
1483 var widget = new ui.Checkbox((cfgvalue != null) ? cfgvalue : this.default, {
1484 id: this.cbid(section_id),
1485 value_enabled: this.enabled,
1486 value_disabled: this.disabled,
1487 validate: L.bind(this.validate, this, section_id)
1488 });
1489
1490 return widget.render();
1491 },
1492
1493 formvalue: function(section_id) {
1494 var node = this.map.findElement('id', this.cbid(section_id)),
1495 checked = node ? L.dom.callClassMethod(node, 'isChecked') : false;
1496
1497 return checked ? this.enabled : this.disabled;
1498 },
1499
1500 textvalue: function(section_id) {
1501 var cval = this.cfgvalue(section_id);
1502
1503 if (cval == null)
1504 cval = this.default;
1505
1506 return (cval == this.enabled) ? _('Yes') : _('No');
1507 },
1508
1509 parse: function(section_id) {
1510 if (this.isActive(section_id)) {
1511 var fval = this.formvalue(section_id);
1512
1513 if (!this.isValid(section_id))
1514 return Promise.reject();
1515
1516 if (fval == this.default && (this.optional || this.rmempty))
1517 return Promise.resolve(this.remove(section_id));
1518 else
1519 return Promise.resolve(this.write(section_id, fval));
1520 }
1521 else {
1522 return Promise.resolve(this.remove(section_id));
1523 }
1524 },
1525 });
1526
1527 var CBIMultiValue = CBIDynamicList.extend({
1528 __name__: 'CBI.MultiValue',
1529
1530 __init__: function() {
1531 this.super('__init__', arguments);
1532 this.placeholder = _('-- Please choose --');
1533 },
1534
1535 renderWidget: function(section_id, option_index, cfgvalue) {
1536 var value = (cfgvalue != null) ? cfgvalue : this.default,
1537 choices = this.transformChoices();
1538
1539 var widget = new ui.Dropdown(L.toArray(value), choices, {
1540 id: this.cbid(section_id),
1541 sort: this.keylist,
1542 multiple: true,
1543 optional: this.optional || this.rmempty,
1544 select_placeholder: this.placeholder,
1545 display_items: this.display_size || this.size || 3,
1546 dropdown_items: this.dropdown_size || this.size || -1,
1547 validate: L.bind(this.validate, this, section_id)
1548 });
1549
1550 return widget.render();
1551 },
1552 });
1553
1554 var CBIDummyValue = CBIValue.extend({
1555 __name__: 'CBI.DummyValue',
1556
1557 renderWidget: function(section_id, option_index, cfgvalue) {
1558 var value = (cfgvalue != null) ? cfgvalue : this.default,
1559 hiddenEl = new ui.Hiddenfield(value, { id: this.cbid(section_id) }),
1560 outputEl = E('div');
1561
1562 if (this.href)
1563 outputEl.appendChild(E('a', { 'href': this.href }));
1564
1565 L.dom.append(outputEl.lastChild || outputEl,
1566 this.rawhtml ? value : [ value ]);
1567
1568 return E([
1569 outputEl,
1570 hiddenEl.render()
1571 ]);
1572 },
1573 });
1574
1575 var CBIButtonValue = CBIValue.extend({
1576 __name__: 'CBI.ButtonValue',
1577
1578 renderWidget: function(section_id, option_index, cfgvalue) {
1579 var value = (cfgvalue != null) ? cfgvalue : this.default;
1580
1581 if (value !== false)
1582 return E([
1583 E('input', {
1584 'type': 'hidden',
1585 'id': this.cbid(section_id)
1586 }),
1587 E('input', {
1588 'class': 'cbi-button cbi-button-%s'.format(this.inputstyle || 'button'),
1589 'type': 'submit',
1590 //'id': this.cbid(section_id),
1591 //'name': this.cbid(section_id),
1592 'value': this.inputtitle || this.title,
1593 'click': L.bind(function(ev) {
1594 ev.target.previousElementSibling.value = ev.target.value;
1595 this.map.save();
1596 }, this)
1597 })
1598 ]);
1599 else
1600 return document.createTextNode(' - ');
1601 }
1602 });
1603
1604 var CBIHiddenValue = CBIValue.extend({
1605 __name__: 'CBI.HiddenValue',
1606
1607 renderWidget: function(section_id, option_index, cfgvalue) {
1608 var widget = new ui.Hiddenfield((cfgvalue != null) ? cfgvalue : this.default, {
1609 id: this.cbid(section_id)
1610 });
1611
1612 return widget.render();
1613 }
1614 });
1615
1616 var CBISectionValue = CBIValue.extend({
1617 __name__: 'CBI.ContainerValue',
1618 __init__: function(map, section, option, cbiClass /*, ... */) {
1619 this.super('__init__', [map, section, option]);
1620
1621 if (!CBIAbstractSection.isSubclass(cbiClass))
1622 throw 'Sub section must be a descendent of CBIAbstractSection';
1623
1624 this.subsection = cbiClass.instantiate(this.varargs(arguments, 4, this.map));
1625 },
1626
1627 load: function(section_id) {
1628 return this.subsection.load();
1629 },
1630
1631 parse: function(section_id) {
1632 return this.subsection.parse();
1633 },
1634
1635 renderWidget: function(section_id, option_index, cfgvalue) {
1636 return this.subsection.render();
1637 },
1638
1639 checkDepends: function(section_id) {
1640 this.subsection.checkDepends();
1641 return this.super('checkDepends');
1642 },
1643
1644 write: function() {},
1645 remove: function() {},
1646 cfgvalue: function() { return null },
1647 formvalue: function() { return null }
1648 });
1649
1650 return L.Class.extend({
1651 Map: CBIMap,
1652 AbstractSection: CBIAbstractSection,
1653 AbstractValue: CBIAbstractValue,
1654
1655 TypedSection: CBITypedSection,
1656 TableSection: CBITableSection,
1657 GridSection: CBIGridSection,
1658 NamedSection: CBINamedSection,
1659
1660 Value: CBIValue,
1661 DynamicList: CBIDynamicList,
1662 ListValue: CBIListValue,
1663 Flag: CBIFlagValue,
1664 MultiValue: CBIMultiValue,
1665 DummyValue: CBIDummyValue,
1666 Button: CBIButtonValue,
1667 HiddenValue: CBIHiddenValue,
1668 SectionValue: CBISectionValue
1669 });