2 var type = function(f
, l
)
12 L
.cbi
.validation
.message
= L
.tr(msg
);
15 compile: function(code
)
20 var types
= L
.cbi
.validation
.types
;
25 for (var i
= 0; i
< code
.length
; i
++)
33 switch (code
.charCodeAt(i
))
45 var label
= code
.substring(pos
, i
);
46 label
= label
.replace(/\\(.)/g, '$1');
47 label
= label
.replace(/^[ \t]+/g, '');
48 label
= label
.replace(/[ \t]+$/g, '');
50 if (label
&& !isNaN(label
))
52 stack
.push(parseFloat(label
));
54 else if (label
.match(/^(['"]).*\1$/))
56 stack
.push(label
.replace(/^(['"])(.*)\1$/, '$2'));
58 else if (typeof types
[label
] == 'function')
60 stack
.push(types
[label
]);
65 throw "Syntax error, unhandled token '"+label
+"'";
70 depth
+= (code
.charCodeAt(i
) == 40);
76 if (typeof stack
[stack
.length
-2] != 'function')
77 throw "Syntax error, argument list follows non-function";
79 stack
[stack
.length
-1] =
80 L
.cbi
.validation
.compile(code
.substring(pos
, i
));
93 var validation
= cbi_class
.validation
;
98 if (this.match(/^-?[0-9]+$/) != null)
101 validation
.i18n('Must be a valid integer');
105 'uinteger': function()
107 if (validation
.types
['integer'].apply(this) && (this >= 0))
110 validation
.i18n('Must be a positive integer');
116 if (!isNaN(parseFloat(this)))
119 validation
.i18n('Must be a valid number');
125 if (validation
.types
['float'].apply(this) && (this >= 0))
128 validation
.i18n('Must be a positive number');
134 if (L
.parseIPv4(this) || L
.parseIPv6(this))
137 validation
.i18n('Must be a valid IP address');
141 'ip4addr': function()
143 if (L
.parseIPv4(this))
146 validation
.i18n('Must be a valid IPv4 address');
150 'ip6addr': function()
152 if (L
.parseIPv6(this))
155 validation
.i18n('Must be a valid IPv6 address');
159 'netmask4': function()
161 if (L
.isNetmask(L
.parseIPv4(this)))
164 validation
.i18n('Must be a valid IPv4 netmask');
168 'netmask6': function()
170 if (L
.isNetmask(L
.parseIPv6(this)))
173 validation
.i18n('Must be a valid IPv6 netmask6');
179 if (this.match(/^([0-9.]+)\/(\d{1,2})$/))
180 if (RegExp
.$2 <= 32 && L
.parseIPv4(RegExp
.$1))
183 validation
.i18n('Must be a valid IPv4 prefix');
189 if (this.match(/^([a-fA-F0-9:.]+)\/(\d{1,3})$/))
190 if (RegExp
.$2 <= 128 && L
.parseIPv6(RegExp
.$1))
193 validation
.i18n('Must be a valid IPv6 prefix');
197 'ipmask4': function()
199 if (this.match(/^([0-9.]+)\/([0-9.]+)$/))
201 var addr
= RegExp
.$1, mask
= RegExp
.$2;
202 if (L
.parseIPv4(addr
) && L
.isNetmask(L
.parseIPv4(mask
)))
206 validation
.i18n('Must be a valid IPv4 address/netmask pair');
210 'ipmask6': function()
212 if (this.match(/^([a-fA-F0-9:.]+)\/([a-fA-F0-9:.]+)$/))
214 var addr
= RegExp
.$1, mask
= RegExp
.$2;
215 if (L
.parseIPv6(addr
) && L
.isNetmask(L
.parseIPv6(mask
)))
219 validation
.i18n('Must be a valid IPv6 address/netmask pair');
225 if (validation
.types
['integer'].apply(this) &&
226 (this >= 0) && (this <= 65535))
229 validation
.i18n('Must be a valid port number');
233 'portrange': function()
235 if (this.match(/^(\d+)-(\d+)$/))
240 if (validation
.types
['port'].apply(p1
) &&
241 validation
.types
['port'].apply(p2
) &&
242 (parseInt(p1
) <= parseInt(p2
)))
245 else if (validation
.types
['port'].apply(this))
250 validation
.i18n('Must be a valid port range');
254 'macaddr': function()
256 if (this.match(/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/) != null)
259 validation
.i18n('Must be a valid MAC address');
265 if (validation
.types
['hostname'].apply(this) ||
266 validation
.types
['ipaddr'].apply(this))
269 validation
.i18n('Must be a valid hostname or IP address');
273 'hostname': function()
275 if ((this.length
<= 253) &&
276 ((this.match(/^[a-zA-Z0-9]+$/) != null ||
277 (this.match(/^[a-zA-Z0-9_][a-zA-Z0-9_\-.]*[a-zA-Z0-9]$/) &&
278 this.match(/[^0-9.]/)))))
281 validation
.i18n('Must be a valid host name');
285 'network': function()
287 if (validation
.types
['uciname'].apply(this) ||
288 validation
.types
['host'].apply(this))
291 validation
.i18n('Must be a valid network name');
300 ? (v
.match(/^[a-fA-F0-9]{64}$/) != null)
301 : ((v
.length
>= 8) && (v
.length
<= 63)))
304 validation
.i18n('Must be a valid WPA key');
312 if (v
.substr(0,2) == 's:')
315 if (((v
.length
== 10) || (v
.length
== 26))
316 ? (v
.match(/^[a-fA-F0-9]{10,26}$/) != null)
317 : ((v
.length
== 5) || (v
.length
== 13)))
320 validation
.i18n('Must be a valid WEP key');
324 'uciname': function()
326 if (this.match(/^[a-zA-Z0-9_]+$/) != null)
329 validation
.i18n('Must be a valid UCI identifier');
333 'range': function(min
, max
)
335 var val
= parseFloat(this);
337 if (validation
.types
['integer'].apply(this) &&
338 !isNaN(min
) && !isNaN(max
) && ((val
>= min
) && (val
<= max
)))
341 validation
.i18n('Must be a number between %d and %d');
347 var val
= parseFloat(this);
349 if (validation
.types
['integer'].apply(this) &&
350 !isNaN(min
) && !isNaN(val
) && (val
>= min
))
353 validation
.i18n('Must be a number greater or equal to %d');
359 var val
= parseFloat(this);
361 if (validation
.types
['integer'].apply(this) &&
362 !isNaN(max
) && !isNaN(val
) && (val
<= max
))
365 validation
.i18n('Must be a number lower or equal to %d');
369 'rangelength': function(min
, max
)
373 if (!isNaN(min
) && !isNaN(max
) &&
374 (val
.length
>= min
) && (val
.length
<= max
))
378 validation
.i18n('Must be between %d and %d characters');
380 validation
.i18n('Must be %d characters');
384 'minlength': function(min
)
388 if (!isNaN(min
) && (val
.length
>= min
))
391 validation
.i18n('Must be at least %d characters');
395 'maxlength': function(max
)
399 if (!isNaN(max
) && (val
.length
<= max
))
402 validation
.i18n('Must be at most %d characters');
410 for (var i
= 0; i
< arguments
.length
; i
+= 2)
412 delete validation
.message
;
414 if (typeof(arguments
[i
]) != 'function')
416 if (arguments
[i
] == this)
420 else if (arguments
[i
].apply(this, arguments
[i
+1]))
425 if (validation
.message
)
426 msgs
.push(validation
.message
.format
.apply(validation
.message
, arguments
[i
+1]));
429 validation
.message
= msgs
.join( L
.tr(' - or - '));
437 for (var i
= 0; i
< arguments
.length
; i
+= 2)
439 delete validation
.message
;
441 if (typeof arguments
[i
] != 'function')
443 if (arguments
[i
] != this)
447 else if (!arguments
[i
].apply(this, arguments
[i
+1]))
452 if (validation
.message
)
453 msgs
.push(validation
.message
.format
.apply(validation
.message
, arguments
[i
+1]));
456 validation
.message
= msgs
.join(', ');
462 return validation
.types
['or'].apply(
463 this.replace(/^[ \t]*![ \t]*/, ''), arguments
);
466 'list': function(subvalidator
, subargs
)
468 if (typeof subvalidator
!= 'function')
471 var tokens
= this.match(/[^ \t]+/g);
472 for (var i
= 0; i
< tokens
.length
; i
++)
473 if (!subvalidator
.apply(tokens
[i
], subargs
))
479 'phonedigit': function()
481 if (this.match(/^[0-9\*#!\.]+$/) != null)
484 validation
.i18n('Must be a valid phone number digit');
494 cbi_class
.AbstractValue
= L
.ui
.AbstractWidget
.extend({
495 init: function(name
, options
)
499 this.dependencies
= [ ];
500 this.rdependency
= { };
502 this.options
= L
.defaults(options
, {
512 return this.ownerSection
.id('field', sid
|| '__unknown__', this.name
);
515 render: function(sid
, condensed
)
517 var i
= this.instance
[sid
] = { };
520 .addClass('luci2-field');
524 i
.top
.addClass('form-group');
526 if (typeof(this.options
.caption
) == 'string')
528 .addClass('col-lg-2 control-label')
529 .attr('for', this.id(sid
))
530 .text(this.options
.caption
)
534 i
.error
= $('<div />')
536 .addClass('luci2-field-error label label-danger');
538 i
.widget
= $('<div />')
539 .addClass('luci2-field-widget')
540 .append(this.widget(sid
))
546 i
.widget
.addClass('col-lg-5');
549 .addClass('col-lg-5')
550 .text((typeof(this.options
.description
) == 'string') ? this.options
.description
: '')
557 active: function(sid
)
559 return (this.instance
[sid
] && !this.instance
[sid
].disabled
);
562 ucipath: function(sid
)
565 config
: (this.options
.uci_package
|| this.ownerMap
.uci_package
),
566 section
: (this.options
.uci_section
|| sid
),
567 option
: (this.options
.uci_option
|| this.name
)
571 ucivalue: function(sid
)
573 var uci
= this.ucipath(sid
);
574 var val
= this.ownerMap
.get(uci
.config
, uci
.section
, uci
.option
);
576 if (typeof(val
) == 'undefined')
577 return this.options
.initial
;
582 formvalue: function(sid
)
584 var v
= $('#' + this.id(sid
)).val();
585 return (v
=== '') ? undefined : v
;
588 textvalue: function(sid
)
590 var v
= this.formvalue(sid
);
592 if (typeof(v
) == 'undefined' || ($.isArray(v
) && !v
.length
))
593 v
= this.ucivalue(sid
);
595 if (typeof(v
) == 'undefined' || ($.isArray(v
) && !v
.length
))
596 v
= this.options
.placeholder
;
598 if (typeof(v
) == 'undefined' || v
=== '')
601 if (typeof(v
) == 'string' && $.isArray(this.choices
))
603 for (var i
= 0; i
< this.choices
.length
; i
++)
604 if (v
=== this.choices
[i
][0])
605 return this.choices
[i
][1];
609 else if (v
=== false)
611 else if ($.isArray(v
))
617 changed: function(sid
)
619 var a
= this.ucivalue(sid
);
620 var b
= this.formvalue(sid
);
622 if (typeof(a
) != typeof(b
))
627 if (a
.length
!= b
.length
)
630 for (var i
= 0; i
< a
.length
; i
++)
636 else if ($.isPlainObject(a
))
643 if (!(k
in a
) || a
[k
] !== b
[k
])
654 var uci
= this.ucipath(sid
);
656 if (this.instance
[sid
].disabled
)
658 if (!this.options
.keep
)
659 return this.ownerMap
.set(uci
.config
, uci
.section
, uci
.option
, undefined);
664 var chg
= this.changed(sid
);
665 var val
= this.formvalue(sid
);
668 this.ownerMap
.set(uci
.config
, uci
.section
, uci
.option
, val
);
673 findSectionID: function($elem
)
675 return this.ownerSection
.findParentSectionIDs($elem
)[0];
678 setError: function($elem
, msg
, msgargs
)
680 var $field
= $elem
.parents('.luci2-field:first');
681 var $error
= $field
.find('.luci2-field-error:first');
683 if (typeof(msg
) == 'string' && msg
.length
> 0)
685 $field
.addClass('luci2-form-error');
686 $elem
.parent().addClass('has-error');
688 $error
.text(msg
.format
.apply(msg
, msgargs
)).show();
689 $field
.trigger('validate');
695 $elem
.parent().removeClass('has-error');
697 var $other_errors
= $field
.find('.has-error');
698 if ($other_errors
.length
== 0)
700 $field
.removeClass('luci2-form-error');
701 $error
.text('').hide();
702 $field
.trigger('validate');
711 handleValidate: function(ev
)
717 var val
= $elem
.val();
718 var vstack
= d
.vstack
;
720 if (vstack
&& typeof(vstack
[0]) == 'function')
722 delete validation
.message
;
724 if ((val
.length
== 0 && !d
.opt
))
726 rv
= d
.self
.setError($elem
, L
.tr('Field must not be empty'));
728 else if (val
.length
> 0 && !vstack
[0].apply(val
, vstack
[1]))
730 rv
= d
.self
.setError($elem
, validation
.message
, vstack
[1]);
734 rv
= d
.self
.setError($elem
);
740 var sid
= d
.self
.findSectionID($elem
);
742 for (var field
in d
.self
.rdependency
)
744 d
.self
.rdependency
[field
].toggle(sid
);
745 d
.self
.rdependency
[field
].validate(sid
);
748 d
.self
.ownerSection
.tabtoggle(sid
);
754 attachEvents: function(sid
, elem
)
758 opt
: this.options
.optional
762 for (var evname
in this.events
)
763 elem
.on(evname
, evdata
, this.events
[evname
]);
765 if (typeof(this.options
.datatype
) == 'undefined' && $.isEmptyObject(this.rdependency
))
769 if (typeof(this.options
.datatype
) == 'string')
772 evdata
.vstack
= L
.cbi
.validation
.compile(this.options
.datatype
);
775 else if (typeof(this.options
.datatype
) == 'function')
777 var vfunc
= this.options
.datatype
;
778 evdata
.vstack
= [ function(elem
) {
779 var rv
= vfunc(this, elem
);
781 validation
.message
= rv
;
782 return (rv
=== true);
786 if (elem
.prop('tagName') == 'SELECT')
788 elem
.change(evdata
, this.handleValidate
);
790 else if (elem
.prop('tagName') == 'INPUT' && elem
.attr('type') == 'checkbox')
792 elem
.click(evdata
, this.handleValidate
);
793 elem
.blur(evdata
, this.handleValidate
);
797 elem
.keyup(evdata
, this.handleValidate
);
798 elem
.blur(evdata
, this.handleValidate
);
801 elem
.addClass('luci2-field-validate')
802 .on('validate', evdata
, this.handleValidate
);
807 validate: function(sid
)
809 var i
= this.instance
[sid
];
811 i
.widget
.find('.luci2-field-validate').trigger('validate');
813 return (i
.disabled
|| i
.error
.text() == '');
816 depends: function(d
, v
, add
)
823 for (var i
= 0; i
< d
.length
; i
++)
825 if (typeof(d
[i
]) == 'string')
827 else if (d
[i
] instanceof L
.cbi
.AbstractValue
)
828 dep
[d
[i
].name
] = true;
831 else if (d
instanceof L
.cbi
.AbstractValue
)
834 dep
[d
.name
] = (typeof(v
) == 'undefined') ? true : v
;
836 else if (typeof(d
) == 'object')
840 else if (typeof(d
) == 'string')
843 dep
[d
] = (typeof(v
) == 'undefined') ? true : v
;
846 if (!dep
|| $.isEmptyObject(dep
))
849 for (var field
in dep
)
851 var f
= this.ownerSection
.fields
[field
];
853 f
.rdependency
[this.name
] = this;
858 if ($.isEmptyObject(dep
))
861 if (!add
|| !this.dependencies
.length
)
862 this.dependencies
.push(dep
);
864 for (var i
= 0; i
< this.dependencies
.length
; i
++)
865 $.extend(this.dependencies
[i
], dep
);
870 toggle: function(sid
)
872 var d
= this.dependencies
;
873 var i
= this.instance
[sid
];
878 for (var n
= 0; n
< d
.length
; n
++)
882 for (var field
in d
[n
])
884 var val
= this.ownerSection
.fields
[field
].formvalue(sid
);
885 var cmp
= d
[n
][field
];
887 if (typeof(cmp
) == 'boolean')
889 if (cmp
== (typeof(val
) == 'undefined' || val
=== '' || val
=== false))
895 else if (typeof(cmp
) == 'string' || typeof(cmp
) == 'number')
903 else if (typeof(cmp
) == 'function')
911 else if (cmp
instanceof RegExp
)
926 i
.top
.removeClass('luci2-field-disabled');
937 i
.top
.is(':visible') ? i
.top
.fadeOut() : i
.top
.hide();
938 i
.top
.addClass('luci2-field-disabled');
945 cbi_class
.CheckboxValue
= cbi_class
.AbstractValue
.extend({
946 widget: function(sid
)
948 var o
= this.options
;
950 if (typeof(o
.enabled
) == 'undefined') o
.enabled
= '1';
951 if (typeof(o
.disabled
) == 'undefined') o
.disabled
= '0';
953 var i
= $('<input />')
954 .attr('id', this.id(sid
))
955 .attr('type', 'checkbox')
956 .prop('checked', this.ucivalue(sid
));
959 .addClass('checkbox')
960 .append(this.attachEvents(sid
, i
));
963 ucivalue: function(sid
)
965 var v
= this.callSuper('ucivalue', sid
);
967 if (typeof(v
) == 'boolean')
970 return (v
== this.options
.enabled
);
973 formvalue: function(sid
)
975 var v
= $('#' + this.id(sid
)).prop('checked');
977 if (typeof(v
) == 'undefined')
978 return !!this.options
.initial
;
985 var uci
= this.ucipath(sid
);
987 if (this.instance
[sid
].disabled
)
989 if (!this.options
.keep
)
990 return this.ownerMap
.set(uci
.config
, uci
.section
, uci
.option
, undefined);
995 var chg
= this.changed(sid
);
996 var val
= this.formvalue(sid
);
1000 if (this.options
.optional
&& val
== this.options
.initial
)
1001 this.ownerMap
.set(uci
.config
, uci
.section
, uci
.option
, undefined);
1003 this.ownerMap
.set(uci
.config
, uci
.section
, uci
.option
, val
? this.options
.enabled
: this.options
.disabled
);
1010 cbi_class
.InputValue
= cbi_class
.AbstractValue
.extend({
1011 widget: function(sid
)
1013 var i
= $('<input />')
1014 .addClass('form-control')
1015 .attr('id', this.id(sid
))
1016 .attr('type', 'text')
1017 .attr('placeholder', this.options
.placeholder
)
1018 .val(this.ucivalue(sid
));
1020 return this.attachEvents(sid
, i
);
1024 cbi_class
.PasswordValue
= cbi_class
.AbstractValue
.extend({
1025 widget: function(sid
)
1027 var i
= $('<input />')
1028 .addClass('form-control')
1029 .attr('id', this.id(sid
))
1030 .attr('type', 'password')
1031 .attr('placeholder', this.options
.placeholder
)
1032 .val(this.ucivalue(sid
));
1034 var t
= $('<span />')
1035 .addClass('input-group-btn')
1036 .append(L
.ui
.button(L
.tr('Reveal'), 'default')
1037 .click(function(ev
) {
1039 var i
= b
.parent().prev();
1040 var t
= i
.attr('type');
1041 b
.text(t
== 'password' ? L
.tr('Hide') : L
.tr('Reveal'));
1042 i
.attr('type', (t
== 'password') ? 'text' : 'password');
1046 this.attachEvents(sid
, i
);
1049 .addClass('input-group')
1055 cbi_class
.ListValue
= cbi_class
.AbstractValue
.extend({
1056 widget: function(sid
)
1058 var s
= $('<select />')
1059 .addClass('form-control');
1061 if (this.options
.optional
&& !this.has_empty
)
1064 .text(L
.tr('-- Please choose --'))
1068 for (var i
= 0; i
< this.choices
.length
; i
++)
1070 .attr('value', this.choices
[i
][0])
1071 .text(this.choices
[i
][1])
1074 s
.attr('id', this.id(sid
)).val(this.ucivalue(sid
));
1076 return this.attachEvents(sid
, s
);
1079 value: function(k
, v
)
1085 this.has_empty
= true;
1087 this.choices
.push([k
, v
|| k
]);
1092 cbi_class
.MultiValue
= cbi_class
.ListValue
.extend({
1093 widget: function(sid
)
1095 var v
= this.ucivalue(sid
);
1096 var t
= $('<div />').attr('id', this.id(sid
));
1099 v
= (typeof(v
) != 'undefined') ? v
.toString().split(/\s+/) : [ ];
1102 for (var i
= 0; i
< v
.length
; i
++)
1106 for (var i
= 0; i
< this.choices
.length
; i
++)
1109 .addClass('checkbox')
1110 .append($('<input />')
1111 .attr('type', 'checkbox')
1112 .attr('value', this.choices
[i
][0])
1113 .prop('checked', s
[this.choices
[i
][0]]))
1114 .append(this.choices
[i
][1])
1121 formvalue: function(sid
)
1124 var fields
= $('#' + this.id(sid
) + ' > label > input');
1126 for (var i
= 0; i
< fields
.length
; i
++)
1127 if (fields
[i
].checked
)
1128 rv
.push(fields
[i
].getAttribute('value'));
1133 textvalue: function(sid
)
1135 var v
= this.formvalue(sid
);
1139 for (var i
= 0; i
< this.choices
.length
; i
++)
1140 c
[this.choices
[i
][0]] = this.choices
[i
][1];
1144 for (var i
= 0; i
< v
.length
; i
++)
1145 t
.push(c
[v
[i
]] || v
[i
]);
1147 return t
.join(', ');
1151 cbi_class
.ComboBox
= cbi_class
.AbstractValue
.extend({
1152 _change: function(ev
)
1155 var self
= ev
.data
.self
;
1157 if (s
.selectedIndex
== (s
.options
.length
- 1))
1159 ev
.data
.select
.hide();
1160 ev
.data
.input
.show().focus();
1161 ev
.data
.input
.val('');
1163 else if (self
.options
.optional
&& s
.selectedIndex
== 0)
1165 ev
.data
.input
.val('');
1169 ev
.data
.input
.val(ev
.data
.select
.val());
1172 ev
.stopPropagation();
1178 var val
= this.value
;
1179 var self
= ev
.data
.self
;
1181 ev
.data
.select
.empty();
1183 if (self
.options
.optional
&& !self
.has_empty
)
1186 .text(L
.tr('-- please choose --'))
1187 .appendTo(ev
.data
.select
);
1190 for (var i
= 0; i
< self
.choices
.length
; i
++)
1192 if (self
.choices
[i
][0] == val
)
1196 .attr('value', self
.choices
[i
][0])
1197 .text(self
.choices
[i
][1])
1198 .appendTo(ev
.data
.select
);
1201 if (!seen
&& val
!= '')
1205 .appendTo(ev
.data
.select
);
1209 .text(L
.tr('-- custom --'))
1210 .appendTo(ev
.data
.select
);
1212 ev
.data
.input
.hide();
1213 ev
.data
.select
.val(val
).show().blur();
1216 _enter: function(ev
)
1221 ev
.preventDefault();
1222 ev
.data
.self
._blur(ev
);
1226 widget: function(sid
)
1228 var d
= $('<div />')
1229 .attr('id', this.id(sid
));
1231 var t
= $('<input />')
1232 .addClass('form-control')
1233 .attr('type', 'text')
1237 var s
= $('<select />')
1238 .addClass('form-control')
1247 s
.change(evdata
, this._change
);
1248 t
.blur(evdata
, this._blur
);
1249 t
.keydown(evdata
, this._enter
);
1251 t
.val(this.ucivalue(sid
));
1254 this.attachEvents(sid
, t
);
1255 this.attachEvents(sid
, s
);
1260 value: function(k
, v
)
1266 this.has_empty
= true;
1268 this.choices
.push([k
, v
|| k
]);
1272 formvalue: function(sid
)
1274 var v
= $('#' + this.id(sid
)).children('input').val();
1275 return (v
== '') ? undefined : v
;
1279 cbi_class
.DynamicList
= cbi_class
.ComboBox
.extend({
1280 _redraw: function(focus
, add
, del
, s
)
1282 var v
= s
.values
|| [ ];
1285 $(s
.parent
).children('div.input-group').children('input').each(function(i
) {
1287 v
.push(this.value
|| '');
1290 $(s
.parent
).empty();
1295 v
.splice(focus
, 0, '');
1297 else if (v
.length
== 0)
1303 for (var i
= 0; i
< v
.length
; i
++)
1310 remove
: ((i
+1) < v
.length
)
1315 btn
= L
.ui
.button('–', 'danger').click(evdata
, this._btnclick
);
1317 btn
= L
.ui
.button('+', 'success').click(evdata
, this._btnclick
);
1321 var txt
= $('<input />')
1322 .addClass('form-control')
1323 .attr('type', 'text')
1326 var sel
= $('<select />')
1327 .addClass('form-control');
1330 .addClass('input-group')
1333 .append($('<span />')
1334 .addClass('input-group-btn')
1336 .appendTo(s
.parent
);
1338 evdata
.input
= this.attachEvents(s
.sid
, txt
);
1339 evdata
.select
= this.attachEvents(s
.sid
, sel
);
1341 sel
.change(evdata
, this._change
);
1342 txt
.blur(evdata
, this._blur
);
1343 txt
.keydown(evdata
, this._keydown
);
1348 if (i
== focus
|| -(i
+1) == focus
)
1355 var f
= $('<input />')
1356 .attr('type', 'text')
1358 .attr('placeholder', (i
== 0) ? this.options
.placeholder
: '')
1359 .addClass('form-control')
1360 .keydown(evdata
, this._keydown
)
1361 .keypress(evdata
, this._keypress
)
1365 .addClass('input-group')
1367 .append($('<span />')
1368 .addClass('input-group-btn')
1370 .appendTo(s
.parent
);
1376 else if (-(i
+1) == focus
)
1380 /* force cursor to end */
1386 evdata
.input
= this.attachEvents(s
.sid
, f
);
1397 _keypress: function(ev
)
1401 /* backspace, delete */
1404 if (ev
.data
.input
.val() == '')
1406 ev
.preventDefault();
1412 /* enter, arrow up, arrow down */
1416 ev
.preventDefault();
1423 _keydown: function(ev
)
1425 var input
= ev
.data
.input
;
1429 /* backspace, delete */
1432 if (input
.val().length
== 0)
1434 ev
.preventDefault();
1436 var index
= ev
.data
.index
;
1442 ev
.data
.self
._redraw(focus
, -1, index
, ev
.data
);
1450 ev
.data
.self
._redraw(NaN
, ev
.data
.index
, -1, ev
.data
);
1455 var prev
= input
.parent().prevAll('div.input-group:first').children('input');
1456 if (prev
.is(':visible'))
1459 prev
.next('select').focus();
1464 var next
= input
.parent().nextAll('div.input-group:first').children('input');
1465 if (next
.is(':visible'))
1468 next
.next('select').focus();
1475 _btnclick: function(ev
)
1477 if (!this.getAttribute('disabled'))
1481 var index
= ev
.data
.index
;
1482 ev
.data
.self
._redraw(-index
, -1, index
, ev
.data
);
1486 ev
.data
.self
._redraw(NaN
, ev
.data
.index
, -1, ev
.data
);
1493 widget: function(sid
)
1495 this.options
.optional
= true;
1497 var v
= this.ucivalue(sid
);
1500 v
= (typeof(v
) != 'undefined') ? v
.toString().split(/\s+/) : [ ];
1502 var d
= $('<div />')
1503 .attr('id', this.id(sid
))
1504 .addClass('cbi-input-dynlist');
1506 this._redraw(NaN
, -1, -1, {
1516 ucivalue: function(sid
)
1518 var v
= this.callSuper('ucivalue', sid
);
1521 v
= (typeof(v
) != 'undefined') ? v
.toString().split(/\s+/) : [ ];
1526 formvalue: function(sid
)
1529 var fields
= $('#' + this.id(sid
) + ' input');
1531 for (var i
= 0; i
< fields
.length
; i
++)
1532 if (typeof(fields
[i
].value
) == 'string' && fields
[i
].value
.length
)
1533 rv
.push(fields
[i
].value
);
1539 cbi_class
.DummyValue
= cbi_class
.AbstractValue
.extend({
1540 widget: function(sid
)
1543 .addClass('form-control-static')
1544 .attr('id', this.id(sid
))
1545 .html(this.ucivalue(sid
) || this.label('placeholder'));
1548 formvalue: function(sid
)
1550 return this.ucivalue(sid
);
1554 cbi_class
.ButtonValue
= cbi_class
.AbstractValue
.extend({
1555 widget: function(sid
)
1557 this.options
.optional
= true;
1559 var btn
= $('<button />')
1560 .addClass('btn btn-default')
1561 .attr('id', this.id(sid
))
1562 .attr('type', 'button')
1563 .text(this.label('text'));
1565 return this.attachEvents(sid
, btn
);
1569 cbi_class
.NetworkList
= cbi_class
.AbstractValue
.extend({
1572 return L
.network
.load();
1575 _device_icon: function(dev
)
1578 .attr('src', dev
.icon())
1579 .attr('title', '%s (%s)'.format(dev
.description(), dev
.name() || '?'));
1582 widget: function(sid
)
1584 var id
= this.id(sid
);
1585 var ul
= $('<ul />')
1587 .addClass('list-unstyled');
1589 var itype
= this.options
.multiple
? 'checkbox' : 'radio';
1590 var value
= this.ucivalue(sid
);
1593 if (!this.options
.multiple
)
1594 check
[value
] = true;
1596 for (var i
= 0; i
< value
.length
; i
++)
1597 check
[value
[i
]] = true;
1599 var interfaces
= L
.network
.getInterfaces();
1601 for (var i
= 0; i
< interfaces
.length
; i
++)
1603 var iface
= interfaces
[i
];
1606 .append($('<label />')
1607 .addClass(itype
+ ' inline')
1608 .append(this.attachEvents(sid
, $('<input />')
1609 .attr('name', itype
+ id
)
1610 .attr('type', itype
)
1611 .attr('value', iface
.name())
1612 .prop('checked', !!check
[iface
.name()])))
1613 .append(iface
.renderBadge()))
1617 if (!this.options
.multiple
)
1620 .append($('<label />')
1621 .addClass(itype
+ ' inline text-muted')
1622 .append(this.attachEvents(sid
, $('<input />')
1623 .attr('name', itype
+ id
)
1624 .attr('type', itype
)
1626 .prop('checked', $.isEmptyObject(check
))))
1627 .append(L
.tr('unspecified')))
1634 ucivalue: function(sid
)
1636 var v
= this.callSuper('ucivalue', sid
);
1638 if (!this.options
.multiple
)
1644 else if (typeof(v
) == 'string')
1647 return v
? v
[0] : undefined;
1654 if (typeof(v
) == 'string')
1655 v
= v
.match(/\S+/g);
1661 formvalue: function(sid
)
1663 var inputs
= $('#' + this.id(sid
) + ' input');
1665 if (!this.options
.multiple
)
1667 for (var i
= 0; i
< inputs
.length
; i
++)
1668 if (inputs
[i
].checked
&& inputs
[i
].value
!== '')
1669 return inputs
[i
].value
;
1676 for (var i
= 0; i
< inputs
.length
; i
++)
1677 if (inputs
[i
].checked
)
1678 rv
.push(inputs
[i
].value
);
1680 return rv
.length
? rv
: undefined;
1684 cbi_class
.DeviceList
= cbi_class
.NetworkList
.extend({
1685 handleFocus: function(ev
)
1687 var self
= ev
.data
.self
;
1688 var input
= $(this);
1690 input
.parent().prev().prop('checked', true);
1693 handleBlur: function(ev
)
1696 ev
.data
.self
.handleKeydown
.call(this, ev
);
1699 handleKeydown: function(ev
)
1701 if (ev
.which
!= 10 && ev
.which
!= 13)
1704 var sid
= ev
.data
.sid
;
1705 var self
= ev
.data
.self
;
1706 var input
= $(this);
1707 var ifnames
= L
.toArray(input
.val());
1709 if (!ifnames
.length
)
1712 L
.network
.createDevice(ifnames
[0]);
1714 self
._redraw(sid
, $('#' + self
.id(sid
)), ifnames
[0]);
1719 return L
.network
.load();
1722 _redraw: function(sid
, ul
, sel
)
1724 var id
= ul
.attr('id');
1725 var devs
= L
.network
.getDevices();
1726 var iface
= L
.network
.getInterface(sid
);
1727 var itype
= this.options
.multiple
? 'checkbox' : 'radio';
1732 for (var i
= 0; i
< devs
.length
; i
++)
1733 if (devs
[i
].isInNetwork(iface
))
1734 check
[devs
[i
].name()] = true;
1738 if (this.options
.multiple
)
1739 check
= L
.toObject(this.formvalue(sid
));
1746 for (var i
= 0; i
< devs
.length
; i
++)
1750 if (dev
.isBridge() && this.options
.bridges
=== false)
1753 if (!dev
.isBridgeable() && this.options
.multiple
)
1756 var badge
= $('<span />')
1758 .append($('<img />').attr('src', dev
.icon()))
1759 .append(' %s: %s'.format(dev
.name(), dev
.description()));
1761 //var ifcs = dev.getInterfaces();
1764 // for (var j = 0; j < ifcs.length; j++)
1765 // badge.append((j ? ', ' : ' (') + ifcs[j].name());
1767 // badge.append(')');
1771 .append($('<label />')
1772 .addClass(itype
+ ' inline')
1773 .append($('<input />')
1774 .attr('name', itype
+ id
)
1775 .attr('type', itype
)
1776 .attr('value', dev
.name())
1777 .prop('checked', !!check
[dev
.name()]))
1784 .append($('<label />')
1785 .attr('for', 'custom' + id
)
1786 .addClass(itype
+ ' inline')
1787 .append($('<input />')
1788 .attr('name', itype
+ id
)
1789 .attr('type', itype
)
1791 .append($('<span />')
1793 .append($('<input />')
1794 .attr('id', 'custom' + id
)
1795 .attr('type', 'text')
1796 .attr('placeholder', L
.tr('Custom device …'))
1797 .on('focus', { self
: this, sid
: sid
}, this.handleFocus
)
1798 .on('blur', { self
: this, sid
: sid
}, this.handleBlur
)
1799 .on('keydown', { self
: this, sid
: sid
}, this.handleKeydown
))))
1802 if (!this.options
.multiple
)
1805 .append($('<label />')
1806 .addClass(itype
+ ' inline text-muted')
1807 .append($('<input />')
1808 .attr('name', itype
+ id
)
1809 .attr('type', itype
)
1811 .prop('checked', $.isEmptyObject(check
)))
1812 .append(L
.tr('unspecified')))
1817 widget: function(sid
)
1819 var id
= this.id(sid
);
1820 var ul
= $('<ul />')
1822 .addClass('list-unstyled');
1824 this._redraw(sid
, ul
);
1831 if (this.instance
[sid
].disabled
)
1834 var ifnames
= this.formvalue(sid
);
1838 var iface
= L
.network
.getInterface(sid
);
1842 iface
.setDevices($.isArray(ifnames
) ? ifnames
: [ ifnames
]);
1847 cbi_class
.AbstractSection
= L
.ui
.AbstractWidget
.extend({
1850 var s
= [ arguments
[0], this.ownerMap
.uci_package
, this.uci_type
];
1852 for (var i
= 1; i
< arguments
.length
&& typeof(arguments
[i
]) == 'string'; i
++)
1853 s
.push(arguments
[i
].replace(/\./g, '_'));
1858 option: function(widget
, name
, options
)
1860 if (this.tabs
.length
== 0)
1861 this.tab({ id
: '__default__', selected
: true });
1863 return this.taboption('__default__', widget
, name
, options
);
1866 tab: function(options
)
1868 if (options
.selected
)
1869 this.tabs
.selected
= this.tabs
.length
;
1873 caption
: options
.caption
,
1874 description
: options
.description
,
1880 taboption: function(tabid
, widget
, name
, options
)
1883 for (var i
= 0; i
< this.tabs
.length
; i
++)
1885 if (this.tabs
[i
].id
== tabid
)
1893 throw 'Cannot append to unknown tab ' + tabid
;
1895 var w
= widget
? new widget(name
, options
) : null;
1897 if (!(w
instanceof L
.cbi
.AbstractValue
))
1898 throw 'Widget must be an instance of AbstractValue';
1900 w
.ownerSection
= this;
1901 w
.ownerMap
= this.ownerMap
;
1903 this.fields
[name
] = w
;
1909 tabtoggle: function(sid
)
1911 for (var i
= 0; i
< this.tabs
.length
; i
++)
1913 var tab
= this.tabs
[i
];
1914 var elem
= $('#' + this.id('nodetab', sid
, tab
.id
));
1917 for (var j
= 0; j
< tab
.fields
.length
; j
++)
1919 if (tab
.fields
[j
].active(sid
))
1926 if (empty
&& elem
.is(':visible'))
1933 validate: function(parent_sid
)
1935 var s
= this.getUCISections(parent_sid
);
1938 for (var i
= 0; i
< s
.length
; i
++)
1940 var $item
= $('#' + this.id('sectionitem', s
[i
]['.name']));
1942 $item
.find('.luci2-field-validate').trigger('validate');
1943 n
+= $item
.find('.luci2-field.luci2-form-error').not('.luci2-field-disabled').length
;
1949 load: function(parent_sid
)
1951 var deferreds
= [ ];
1953 var s
= this.getUCISections(parent_sid
);
1954 for (var i
= 0; i
< s
.length
; i
++)
1956 for (var f
in this.fields
)
1958 if (typeof(this.fields
[f
].load
) != 'function')
1961 var rv
= this.fields
[f
].load(s
[i
]['.name']);
1962 if (L
.isDeferred(rv
))
1966 for (var j
= 0; j
< this.subsections
.length
; j
++)
1968 var rv
= this.subsections
[j
].load(s
[i
]['.name']);
1969 deferreds
.push
.apply(deferreds
, rv
);
1976 save: function(parent_sid
)
1978 var deferreds
= [ ];
1979 var s
= this.getUCISections(parent_sid
);
1981 for (i
= 0; i
< s
.length
; i
++)
1983 if (!this.options
.readonly
)
1985 for (var f
in this.fields
)
1987 if (typeof(this.fields
[f
].save
) != 'function')
1990 var rv
= this.fields
[f
].save(s
[i
]['.name']);
1991 if (L
.isDeferred(rv
))
1996 for (var j
= 0; j
< this.subsections
.length
; j
++)
1998 var rv
= this.subsections
[j
].save(s
[i
]['.name']);
1999 deferreds
.push
.apply(deferreds
, rv
);
2006 teaser: function(sid
)
2008 var tf
= this.teaser_fields
;
2012 tf
= this.teaser_fields
= [ ];
2014 if ($.isArray(this.options
.teasers
))
2016 for (var i
= 0; i
< this.options
.teasers
.length
; i
++)
2018 var f
= this.options
.teasers
[i
];
2019 if (f
instanceof L
.cbi
.AbstractValue
)
2021 else if (typeof(f
) == 'string' && this.fields
[f
] instanceof L
.cbi
.AbstractValue
)
2022 tf
.push(this.fields
[f
]);
2027 for (var i
= 0; tf
.length
<= 5 && i
< this.tabs
.length
; i
++)
2028 for (var j
= 0; tf
.length
<= 5 && j
< this.tabs
[i
].fields
.length
; j
++)
2029 tf
.push(this.tabs
[i
].fields
[j
]);
2035 for (var i
= 0; i
< tf
.length
; i
++)
2037 if (tf
[i
].instance
[sid
] && tf
[i
].instance
[sid
].disabled
)
2040 var n
= tf
[i
].options
.caption
|| tf
[i
].name
;
2041 var v
= tf
[i
].textvalue(sid
);
2043 if (typeof(v
) == 'undefined')
2046 t
= t
+ '%s%s: <strong>%s</strong>'.format(t
? ' | ' : '', n
, v
);
2052 findAdditionalUCIPackages: function()
2056 for (var i
= 0; i
< this.tabs
.length
; i
++)
2057 for (var j
= 0; j
< this.tabs
[i
].fields
.length
; j
++)
2058 if (this.tabs
[i
].fields
[j
].options
.uci_package
)
2059 packages
.push(this.tabs
[i
].fields
[j
].options
.uci_package
);
2064 findParentSectionIDs: function($elem
)
2067 var $parents
= $elem
.parents('.luci2-section-item');
2069 for (var i
= 0; i
< $parents
.length
; i
++)
2070 rv
.push($parents
[i
].getAttribute('data-luci2-sid'));
2076 cbi_class
.TypedSection
= cbi_class
.AbstractSection
.extend({
2077 init: function(uci_type
, options
)
2079 this.uci_type
= uci_type
;
2080 this.options
= options
;
2083 this.subsections
= [ ];
2084 this.active_panel
= { };
2085 this.active_tab
= { };
2087 this.instance
= { };
2090 filter: function(section
, parent_sid
)
2095 sort: function(section1
, section2
)
2100 subsection: function(widget
, uci_type
, options
)
2102 var w
= widget
? new widget(uci_type
, options
) : null;
2104 if (!(w
instanceof L
.cbi
.AbstractSection
))
2105 throw 'Widget must be an instance of AbstractSection';
2107 w
.ownerSection
= this;
2108 w
.ownerMap
= this.ownerMap
;
2109 w
.index
= this.subsections
.length
;
2111 this.subsections
.push(w
);
2115 getUCISections: function(parent_sid
)
2117 var s1
= L
.uci
.sections(this.ownerMap
.uci_package
);
2120 for (var i
= 0; i
< s1
.length
; i
++)
2121 if (s1
[i
]['.type'] == this.uci_type
)
2122 if (this.filter(s1
[i
], parent_sid
))
2130 add: function(name
, parent_sid
)
2132 return this.ownerMap
.add(this.ownerMap
.uci_package
, this.uci_type
, name
);
2135 remove: function(sid
, parent_sid
)
2137 return this.ownerMap
.remove(this.ownerMap
.uci_package
, sid
);
2140 handleAdd: function(ev
)
2143 var name
= undefined;
2144 var self
= ev
.data
.self
;
2145 var sid
= self
.findParentSectionIDs(addb
)[0];
2147 if (addb
.prev().prop('nodeName') == 'INPUT')
2148 name
= addb
.prev().val();
2150 if (addb
.prop('disabled') || name
=== '')
2153 L
.ui
.saveScrollTop();
2155 self
.setPanelIndex(sid
, -1);
2156 self
.ownerMap
.save();
2158 ev
.data
.sid
= self
.add(name
, sid
);
2159 ev
.data
.type
= self
.uci_type
;
2160 ev
.data
.name
= name
;
2162 self
.trigger('add', ev
);
2164 self
.ownerMap
.redraw();
2166 L
.ui
.restoreScrollTop();
2169 handleRemove: function(ev
)
2171 var self
= ev
.data
.self
;
2172 var sids
= self
.findParentSectionIDs($(this));
2176 L
.ui
.saveScrollTop();
2179 ev
.parent_sid
= sids
[1];
2181 self
.trigger('remove', ev
);
2183 self
.ownerMap
.save();
2184 self
.remove(ev
.sid
, ev
.parent_sid
);
2185 self
.ownerMap
.redraw();
2187 L
.ui
.restoreScrollTop();
2190 ev
.stopPropagation();
2193 handleSID: function(ev
)
2195 var self
= ev
.data
.self
;
2197 var addb
= text
.next();
2198 var errt
= addb
.next();
2199 var name
= text
.val();
2201 if (!/^[a-zA-Z0-9_]*$/.test(name
))
2203 errt
.text(L
.tr('Invalid section name')).show();
2204 text
.addClass('error');
2205 addb
.prop('disabled', true);
2209 if (L
.uci
.get(self
.ownerMap
.uci_package
, name
))
2211 errt
.text(L
.tr('Name already used')).show();
2212 text
.addClass('error');
2213 addb
.prop('disabled', true);
2217 errt
.text('').hide();
2218 text
.removeClass('error');
2219 addb
.prop('disabled', false);
2223 handleTab: function(ev
)
2225 var self
= ev
.data
.self
;
2227 var sid
= self
.findParentSectionIDs($tab
)[0];
2229 self
.active_tab
[sid
] = $tab
.parent().index();
2232 handleTabValidate: function(ev
)
2234 var $pane
= $(ev
.delegateTarget
);
2235 var $badge
= $pane
.parent()
2236 .children('.nav-tabs')
2238 .eq($pane
.index() - 1) // item #1 is the <ul>
2239 .find('.badge:first');
2241 var err_count
= $pane
.find('.luci2-field.luci2-form-error').not('.luci2-field-disabled').length
;
2245 .attr('title', L
.trp('1 Error', '%d Errors', err_count
).format(err_count
))
2251 handlePanelValidate: function(ev
)
2253 var $elem
= $(this);
2255 .prevAll('.luci2-section-header:first')
2256 .children('.luci2-section-teaser')
2257 .find('.badge:first');
2259 var err_count
= $elem
.find('.luci2-field.luci2-form-error').not('.luci2-field-disabled').length
;
2263 .attr('title', L
.trp('1 Error', '%d Errors', err_count
).format(err_count
))
2269 handlePanelCollapse: function(ev
)
2271 var self
= ev
.data
.self
;
2273 var $items
= $(ev
.delegateTarget
).children('.luci2-section-item');
2275 var $this_panel
= $(ev
.target
);
2276 var $this_teaser
= $this_panel
.prevAll('.luci2-section-header:first').children('.luci2-section-teaser');
2278 var $prev_panel
= $items
.children('.luci2-section-panel.in');
2279 var $prev_teaser
= $prev_panel
.prevAll('.luci2-section-header:first').children('.luci2-section-teaser');
2281 var sids
= self
.findParentSectionIDs($prev_panel
);
2283 self
.setPanelIndex(sids
[1], $this_panel
.parent().index());
2287 .addClass('collapse');
2291 .children('span:last')
2293 .append(self
.teaser(sids
[0]));
2298 ev
.stopPropagation();
2301 handleSort: function(ev
)
2303 var self
= ev
.data
.self
;
2305 var $item
= $(this).parents('.luci2-section-item:first');
2306 var $next
= ev
.data
.up
? $item
.prev() : $item
.next();
2308 if ($item
.length
&& $next
.length
)
2310 var cur_sid
= $item
.attr('data-luci2-sid');
2311 var new_sid
= $next
.attr('data-luci2-sid');
2313 L
.uci
.swap(self
.ownerMap
.uci_package
, cur_sid
, new_sid
);
2315 self
.ownerMap
.save();
2316 self
.ownerMap
.redraw();
2319 ev
.stopPropagation();
2322 getPanelIndex: function(parent_sid
)
2324 return (this.active_panel
[parent_sid
|| '__top__'] || 0);
2327 setPanelIndex: function(parent_sid
, new_index
)
2329 if (typeof(new_index
) == 'number')
2330 this.active_panel
[parent_sid
|| '__top__'] = new_index
;
2333 renderAdd: function()
2335 if (!this.options
.addremove
)
2338 var text
= L
.tr('Add section');
2339 var ttip
= L
.tr('Create new section...');
2341 if ($.isArray(this.options
.add_caption
))
2342 text
= this.options
.add_caption
[0], ttip
= this.options
.add_caption
[1];
2343 else if (typeof(this.options
.add_caption
) == 'string')
2344 text
= this.options
.add_caption
, ttip
= '';
2346 var add
= $('<div />');
2348 if (this.options
.anonymous
=== false)
2351 .addClass('cbi-input-text')
2352 .attr('type', 'text')
2353 .attr('placeholder', ttip
)
2354 .blur({ self
: this }, this.handleSID
)
2355 .keyup({ self
: this }, this.handleSID
)
2359 .attr('src', L
.globals
.resource
+ '/icons/cbi/add.gif')
2360 .attr('title', text
)
2361 .addClass('cbi-button')
2362 .click({ self
: this }, this.handleAdd
)
2366 .addClass('cbi-value-error')
2372 L
.ui
.button(text
, 'success', ttip
)
2373 .click({ self
: this }, this.handleAdd
)
2380 renderRemove: function(index
)
2382 if (!this.options
.addremove
)
2385 var text
= L
.tr('Remove');
2386 var ttip
= L
.tr('Remove this section');
2388 if ($.isArray(this.options
.remove_caption
))
2389 text
= this.options
.remove_caption
[0], ttip
= this.options
.remove_caption
[1];
2390 else if (typeof(this.options
.remove_caption
) == 'string')
2391 text
= this.options
.remove_caption
, ttip
= '';
2393 return L
.ui
.button(text
, 'danger', ttip
)
2394 .click({ self
: this, index
: index
}, this.handleRemove
);
2397 renderSort: function(index
)
2399 if (!this.options
.sortable
)
2402 var b1
= L
.ui
.button('↑', 'info', L
.tr('Move up'))
2403 .click({ self
: this, index
: index
, up
: true }, this.handleSort
);
2405 var b2
= L
.ui
.button('↓', 'info', L
.tr('Move down'))
2406 .click({ self
: this, index
: index
, up
: false }, this.handleSort
);
2411 renderCaption: function()
2414 .addClass('panel-title')
2415 .append(this.label('caption') || this.uci_type
);
2418 renderDescription: function()
2420 var text
= this.label('description');
2424 .addClass('luci2-section-description')
2430 renderTeaser: function(sid
, index
)
2432 if (this.options
.collabsible
|| this.ownerMap
.options
.collabsible
)
2435 .attr('id', this.id('teaser', sid
))
2436 .addClass('luci2-section-teaser well well-sm')
2437 .append($('<span />')
2439 .append($('<span />'));
2445 renderHead: function(condensed
)
2451 .addClass('panel-heading')
2452 .append(this.renderCaption())
2453 .append(this.renderDescription());
2456 renderTabDescription: function(sid
, index
, tab_index
)
2458 var tab
= this.tabs
[tab_index
];
2460 if (typeof(tab
.description
) == 'string')
2463 .addClass('cbi-tab-descr')
2464 .text(tab
.description
);
2470 renderTabHead: function(sid
, index
, tab_index
)
2472 var tab
= this.tabs
[tab_index
];
2473 var cur
= this.active_tab
[sid
] || 0;
2475 var tabh
= $('<li />')
2477 .attr('id', this.id('nodetab', sid
, tab
.id
))
2478 .attr('href', '#' + this.id('node', sid
, tab
.id
))
2479 .attr('data-toggle', 'tab')
2480 .text((tab
.caption
? tab
.caption
.format(tab
.id
) : tab
.id
) + ' ')
2481 .append($('<span />')
2483 .on('shown.bs.tab', { self
: this, sid
: sid
}, this.handleTab
));
2485 if (cur
== tab_index
)
2486 tabh
.addClass('active');
2488 if (!tab
.fields
.length
)
2494 renderTabBody: function(sid
, index
, tab_index
)
2496 var tab
= this.tabs
[tab_index
];
2497 var cur
= this.active_tab
[sid
] || 0;
2499 var tabb
= $('<div />')
2500 .addClass('tab-pane')
2501 .attr('id', this.id('node', sid
, tab
.id
))
2502 .append(this.renderTabDescription(sid
, index
, tab_index
))
2503 .on('validate', this.handleTabValidate
);
2505 if (cur
== tab_index
)
2506 tabb
.addClass('active');
2508 for (var i
= 0; i
< tab
.fields
.length
; i
++)
2509 tabb
.append(tab
.fields
[i
].render(sid
));
2514 renderPanelHead: function(sid
, index
, parent_sid
)
2516 var head
= $('<div />')
2517 .addClass('luci2-section-header')
2518 .append(this.renderTeaser(sid
, index
))
2519 .append($('<div />')
2520 .addClass('btn-group')
2521 .append(this.renderSort(index
))
2522 .append(this.renderRemove(index
)));
2524 if (this.options
.collabsible
)
2526 head
.attr('data-toggle', 'collapse')
2527 .attr('data-parent', this.id('sectiongroup', parent_sid
))
2528 .attr('data-target', '#' + this.id('panel', sid
));
2534 renderPanelBody: function(sid
, index
, parent_sid
)
2536 var body
= $('<div />')
2537 .attr('id', this.id('panel', sid
))
2538 .addClass('luci2-section-panel')
2539 .on('validate', this.handlePanelValidate
);
2541 if (this.options
.collabsible
|| this.ownerMap
.options
.collabsible
)
2543 body
.addClass('panel-collapse collapse');
2545 if (index
== this.getPanelIndex(parent_sid
))
2546 body
.addClass('in');
2549 var tab_heads
= $('<ul />')
2550 .addClass('nav nav-tabs');
2552 var tab_bodies
= $('<div />')
2553 .addClass('form-horizontal tab-content')
2556 for (var j
= 0; j
< this.tabs
.length
; j
++)
2558 tab_heads
.append(this.renderTabHead(sid
, index
, j
));
2559 tab_bodies
.append(this.renderTabBody(sid
, index
, j
));
2562 body
.append(tab_bodies
);
2564 if (this.tabs
.length
<= 1)
2567 for (var i
= 0; i
< this.subsections
.length
; i
++)
2568 body
.append(this.subsections
[i
].render(false, sid
));
2573 renderBody: function(condensed
, parent_sid
)
2575 var s
= this.getUCISections(parent_sid
);
2576 var n
= this.getPanelIndex(parent_sid
);
2579 this.setPanelIndex(parent_sid
, n
+ s
.length
);
2580 else if (n
>= s
.length
)
2581 this.setPanelIndex(parent_sid
, s
.length
- 1);
2583 var body
= $('<ul />')
2584 .addClass('luci2-section-group list-group');
2586 if (this.options
.collabsible
)
2588 body
.attr('id', this.id('sectiongroup', parent_sid
))
2589 .on('show.bs.collapse', { self
: this }, this.handlePanelCollapse
);
2594 body
.append($('<li />')
2595 .addClass('list-group-item text-muted')
2596 .text(this.label('placeholder') || L
.tr('There are no entries defined yet.')))
2599 for (var i
= 0; i
< s
.length
; i
++)
2601 var sid
= s
[i
]['.name'];
2602 var inst
= this.instance
[sid
] = { tabs
: [ ] };
2604 body
.append($('<li />')
2605 .addClass('luci2-section-item list-group-item')
2606 .attr('id', this.id('sectionitem', sid
))
2607 .attr('data-luci2-sid', sid
)
2608 .append(this.renderPanelHead(sid
, i
, parent_sid
))
2609 .append(this.renderPanelBody(sid
, i
, parent_sid
)));
2615 render: function(condensed
, parent_sid
)
2617 this.instance
= { };
2619 var panel
= $('<div />')
2620 .addClass('panel panel-default')
2621 .append(this.renderHead(condensed
))
2622 .append(this.renderBody(condensed
, parent_sid
));
2624 if (this.options
.addremove
)
2625 panel
.append($('<div />')
2626 .addClass('panel-footer')
2627 .append(this.renderAdd()));
2632 finish: function(parent_sid
)
2634 var s
= this.getUCISections(parent_sid
);
2636 for (var i
= 0; i
< s
.length
; i
++)
2638 var sid
= s
[i
]['.name'];
2640 if (i
!= this.getPanelIndex(parent_sid
))
2641 $('#' + this.id('teaser', sid
)).children('span:last')
2642 .append(this.teaser(sid
));
2644 $('#' + this.id('teaser', sid
))
2647 for (var j
= 0; j
< this.subsections
.length
; j
++)
2648 this.subsections
[j
].finish(sid
);
2653 cbi_class
.TableSection
= cbi_class
.TypedSection
.extend({
2654 renderTableHead: function()
2656 var thead
= $('<thead />')
2658 .addClass('cbi-section-table-titles'));
2660 for (var j
= 0; j
< this.tabs
[0].fields
.length
; j
++)
2661 thead
.children().append($('<th />')
2662 .addClass('cbi-section-table-cell')
2663 .css('width', this.tabs
[0].fields
[j
].options
.width
|| '')
2664 .append(this.tabs
[0].fields
[j
].label('caption')));
2666 if (this.options
.addremove
!== false || this.options
.sortable
)
2667 thead
.children().append($('<th />')
2668 .addClass('cbi-section-table-cell')
2674 renderTableRow: function(sid
, index
)
2676 var row
= $('<tr />')
2677 .addClass('luci2-section-item')
2678 .attr('id', this.id('sectionitem', sid
))
2679 .attr('data-luci2-sid', sid
);
2681 for (var j
= 0; j
< this.tabs
[0].fields
.length
; j
++)
2683 row
.append($('<td />')
2684 .css('width', this.tabs
[0].fields
[j
].options
.width
|| '')
2685 .append(this.tabs
[0].fields
[j
].render(sid
, true)));
2688 if (this.options
.addremove
!== false || this.options
.sortable
)
2690 row
.append($('<td />')
2692 .addClass('text-right')
2693 .append($('<div />')
2694 .addClass('btn-group')
2695 .append(this.renderSort(index
))
2696 .append(this.renderRemove(index
))));
2702 renderTableBody: function(parent_sid
)
2704 var s
= this.getUCISections(parent_sid
);
2706 var tbody
= $('<tbody />');
2710 var cols
= this.tabs
[0].fields
.length
;
2712 if (this.options
.addremove
!== false || this.options
.sortable
)
2715 tbody
.append($('<tr />')
2717 .addClass('text-muted')
2718 .attr('colspan', cols
)
2719 .text(this.label('placeholder') || L
.tr('There are no entries defined yet.'))));
2722 for (var i
= 0; i
< s
.length
; i
++)
2724 var sid
= s
[i
]['.name'];
2725 var inst
= this.instance
[sid
] = { tabs
: [ ] };
2727 tbody
.append(this.renderTableRow(sid
, i
));
2733 renderBody: function(condensed
, parent_sid
)
2735 return $('<table />')
2736 .addClass('table table-condensed table-hover')
2737 .append(this.renderTableHead())
2738 .append(this.renderTableBody(parent_sid
));
2742 cbi_class
.NamedSection
= cbi_class
.TypedSection
.extend({
2743 getUCISections: function(cb
)
2746 var sl
= L
.uci
.sections(this.ownerMap
.uci_package
);
2748 for (var i
= 0; i
< sl
.length
; i
++)
2749 if (sl
[i
]['.name'] == this.uci_type
)
2755 if (typeof(cb
) == 'function' && sa
.length
> 0)
2756 cb
.call(this, sa
[0]);
2762 cbi_class
.SingleSection
= cbi_class
.NamedSection
.extend({
2765 this.instance
= { };
2766 this.instance
[this.uci_type
] = { tabs
: [ ] };
2769 .addClass('luci2-section-item')
2770 .attr('id', this.id('sectionitem', this.uci_type
))
2771 .attr('data-luci2-sid', this.uci_type
)
2772 .append(this.renderPanelBody(this.uci_type
, 0));
2776 cbi_class
.DummySection
= cbi_class
.TypedSection
.extend({
2777 getUCISections: function(cb
)
2779 if (typeof(cb
) == 'function')
2780 cb
.apply(this, [ { '.name': this.uci_type
} ]);
2782 return [ { '.name': this.uci_type
} ];
2786 cbi_class
.Map
= L
.ui
.AbstractWidget
.extend({
2787 init: function(uci_package
, options
)
2791 this.uci_package
= uci_package
;
2792 this.sections
= [ ];
2793 this.options
= L
.defaults(options
, {
2794 save: function() { },
2795 prepare: function() { }
2799 loadCallback: function()
2801 var deferreds
= [ L
.deferrable(this.options
.prepare
.call(this)) ];
2803 for (var i
= 0; i
< this.sections
.length
; i
++)
2805 var rv
= this.sections
[i
].load();
2806 deferreds
.push
.apply(deferreds
, rv
);
2809 return $.when
.apply($, deferreds
);
2815 var packages
= [ this.uci_package
];
2817 for (var i
= 0; i
< this.sections
.length
; i
++)
2818 packages
.push
.apply(packages
, this.sections
[i
].findAdditionalUCIPackages());
2820 for (var i
= 0; i
< packages
.length
; i
++)
2821 if (!L
.uci
.writable(packages
[i
]))
2823 this.options
.readonly
= true;
2827 return L
.uci
.load(packages
).then(function() {
2828 return self
.loadCallback();
2832 handleTab: function(ev
)
2834 ev
.data
.self
.active_tab
= $(ev
.target
).parent().index();
2837 handleApply: function(ev
)
2839 var self
= ev
.data
.self
;
2841 self
.trigger('apply', ev
);
2844 handleSave: function(ev
)
2846 var self
= ev
.data
.self
;
2848 self
.send().then(function() {
2849 self
.trigger('save', ev
);
2853 handleReset: function(ev
)
2855 var self
= ev
.data
.self
;
2857 self
.trigger('reset', ev
);
2861 renderTabHead: function(tab_index
)
2863 var section
= this.sections
[tab_index
];
2864 var cur
= this.active_tab
|| 0;
2866 var tabh
= $('<li />')
2868 .attr('id', section
.id('sectiontab'))
2869 .attr('href', '#' + section
.id('section'))
2870 .attr('data-toggle', 'tab')
2871 .text(section
.label('caption') + ' ')
2872 .append($('<span />')
2874 .on('shown.bs.tab', { self
: this }, this.handleTab
));
2876 if (cur
== tab_index
)
2877 tabh
.addClass('active');
2882 renderTabBody: function(tab_index
)
2884 var section
= this.sections
[tab_index
];
2885 var desc
= section
.label('description');
2886 var cur
= this.active_tab
|| 0;
2888 var tabb
= $('<div />')
2889 .addClass('tab-pane')
2890 .attr('id', section
.id('section'));
2892 if (cur
== tab_index
)
2893 tabb
.addClass('active');
2896 tabb
.append($('<p />')
2899 var s
= section
.render(this.options
.tabbed
);
2901 if (this.options
.readonly
|| section
.options
.readonly
)
2902 s
.find('input, select, button, img.cbi-button').attr('disabled', true);
2909 renderBody: function()
2911 var tabs
= $('<ul />')
2912 .addClass('nav nav-tabs');
2914 var body
= $('<div />')
2917 for (var i
= 0; i
< this.sections
.length
; i
++)
2919 tabs
.append(this.renderTabHead(i
));
2920 body
.append(this.renderTabBody(i
));
2923 if (this.options
.tabbed
)
2924 body
.addClass('tab-content');
2931 renderFooter: function()
2938 .addClass('panel panel-default panel-body text-right')
2939 .append($('<div />')
2940 .addClass('btn-group')
2941 .append(L
.ui
.button(L
.tr('Save & Apply'), 'primary')
2942 .click(evdata
, this.handleApply
))
2943 .append(L
.ui
.button(L
.tr('Save'), 'default')
2944 .click(evdata
, this.handleSave
))
2945 .append(L
.ui
.button(L
.tr('Reset'), 'default')
2946 .click(evdata
, this.handleReset
)));
2951 var map
= $('<form />');
2953 if (typeof(this.options
.caption
) == 'string')
2954 map
.append($('<h2 />')
2955 .text(this.options
.caption
));
2957 if (typeof(this.options
.description
) == 'string')
2958 map
.append($('<p />')
2959 .text(this.options
.description
));
2961 map
.append(this.renderBody());
2963 if (this.options
.pageaction
!== false)
2964 map
.append(this.renderFooter());
2971 for (var i
= 0; i
< this.sections
.length
; i
++)
2972 this.sections
[i
].finish();
2979 this.target
.hide().empty().append(this.render());
2984 section: function(widget
, uci_type
, options
)
2986 var w
= widget
? new widget(uci_type
, options
) : null;
2988 if (!(w
instanceof L
.cbi
.AbstractSection
))
2989 throw 'Widget must be an instance of AbstractSection';
2992 w
.index
= this.sections
.length
;
2994 this.sections
.push(w
);
2998 add: function(conf
, type
, name
)
3000 return L
.uci
.add(conf
, type
, name
);
3003 remove: function(conf
, sid
)
3005 return L
.uci
.remove(conf
, sid
);
3008 get: function(conf
, sid
, opt
)
3010 return L
.uci
.get(conf
, sid
, opt
);
3013 set: function(conf
, sid
, opt
, val
)
3015 return L
.uci
.set(conf
, sid
, opt
, val
);
3018 validate: function()
3022 for (var i
= 0; i
< this.sections
.length
; i
++)
3024 if (!this.sections
[i
].validate())
3035 if (self
.options
.readonly
)
3036 return L
.deferrable();
3038 var deferreds
= [ ];
3040 for (var i
= 0; i
< self
.sections
.length
; i
++)
3042 var rv
= self
.sections
[i
].save();
3043 deferreds
.push
.apply(deferreds
, rv
);
3046 return $.when
.apply($, deferreds
).then(function() {
3047 return L
.deferrable(self
.options
.save
.call(self
));
3053 if (!this.validate())
3054 return L
.deferrable();
3058 L
.ui
.saveScrollTop();
3061 return this.save().then(function() {
3062 return L
.uci
.save();
3063 }).then(function() {
3064 return L
.ui
.updateChanges();
3065 }).then(function() {
3067 }).then(function() {
3071 L
.ui
.loading(false);
3072 L
.ui
.restoreScrollTop();
3078 var packages
= [ this.uci_package
];
3080 for (var i
= 0; i
< this.sections
.length
; i
++)
3081 packages
.push
.apply(packages
, this.sections
[i
].findAdditionalUCIPackages());
3083 L
.uci
.unload(packages
);
3092 return self
.insertInto(self
.target
);
3095 insertInto: function(id
)
3098 self
.target
= $(id
);
3103 return self
.load().then(function() {
3104 self
.target
.empty().append(self
.render());
3108 L
.ui
.loading(false);
3113 cbi_class
.Modal
= cbi_class
.Map
.extend({
3114 handleApply: function(ev
)
3116 var self
= ev
.data
.self
;
3118 self
.trigger('apply', ev
);
3121 handleSave: function(ev
)
3123 var self
= ev
.data
.self
;
3125 self
.send().then(function() {
3126 self
.trigger('save', ev
);
3131 handleReset: function(ev
)
3133 var self
= ev
.data
.self
;
3135 self
.trigger('close', ev
);
3140 renderFooter: function()
3147 .addClass('btn-group')
3148 .append(L
.ui
.button(L
.tr('Save & Apply'), 'primary')
3149 .click(evdata
, this.handleApply
))
3150 .append(L
.ui
.button(L
.tr('Save'), 'default')
3151 .click(evdata
, this.handleSave
))
3152 .append(L
.ui
.button(L
.tr('Cancel'), 'default')
3153 .click(evdata
, this.handleReset
));
3158 var modal
= L
.ui
.dialog(this.label('caption'), null, { wide
: true });
3159 var map
= $('<form />');
3161 var desc
= this.label('description');
3163 map
.append($('<p />').text(desc
));
3165 map
.append(this.renderBody());
3167 modal
.find('.modal-body').append(map
);
3168 modal
.find('.modal-footer').append(this.renderFooter());
3185 return self
.load().then(function() {
3189 L
.ui
.loading(false);
3199 return Class
.extend(cbi_class
);