02e213a718
Register to 'reset' event on form element and make call to 'update' function
delayed in 'data-update' handler to allow the form values to update beforehand.
When using a form's 'reset' button, form field visibility was not updated.
This could lead to situations where a checkbox had to be toggled again
twice to display the detail text inputs. (Example taken from private
wifi package)
(cherry picked from commit bf090a8a83
)
540 lines
11 KiB
JavaScript
540 lines
11 KiB
JavaScript
/*
|
|
Copyright 2008 Steven Barth <steven@midlink.org>
|
|
Copyright 2008-2012 Jo-Philipp Wich <jow@openwrt.org>
|
|
Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
*/
|
|
|
|
/*
|
|
Build using:
|
|
|
|
uglifyjs javascript/gluon-web-model.js -o files/lib/gluon/web/www/static/gluon-web-model.js -c -m --support-ie8
|
|
*/
|
|
|
|
|
|
|
|
(function() {
|
|
var dep_entries = {};
|
|
|
|
function Int(x) {
|
|
return (/^-?\d+$/.test(x) ? +x : NaN);
|
|
}
|
|
|
|
function Dec(x) {
|
|
return (/^-?\d*\.?\d+?$/.test(x) ? +x : NaN);
|
|
}
|
|
|
|
var validators = {
|
|
|
|
'integer': function() {
|
|
return !isNaN(Int(this));
|
|
},
|
|
|
|
'uinteger': function() {
|
|
return (Int(this) >= 0);
|
|
},
|
|
|
|
'float': function() {
|
|
return !isNaN(Dec(this));
|
|
},
|
|
|
|
'ufloat': function() {
|
|
return (Dec(this) >= 0);
|
|
},
|
|
|
|
'ipaddr': function() {
|
|
return validators.ip4addr.apply(this) ||
|
|
validators.ip6addr.apply(this);
|
|
},
|
|
|
|
'ip4addr': function() {
|
|
var match;
|
|
if ((match = this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))) {
|
|
return (match[1] >= 0) && (match[1] <= 255) &&
|
|
(match[2] >= 0) && (match[2] <= 255) &&
|
|
(match[3] >= 0) && (match[3] <= 255) &&
|
|
(match[4] >= 0) && (match[4] <= 255);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
'ip6addr': function() {
|
|
if (this.indexOf('::') < 0)
|
|
return (this.match(/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i) != null);
|
|
|
|
if (
|
|
(this.indexOf(':::') >= 0) || this.match(/::.+::/) ||
|
|
this.match(/^:[^:]/) || this.match(/[^:]:$/)
|
|
)
|
|
return false;
|
|
|
|
if (this.match(/^(?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}$/i))
|
|
return true;
|
|
if (this.match(/^(?:[a-f0-9]{1,4}:){7}:$/i))
|
|
return true;
|
|
if (this.match(/^:(?::[a-f0-9]{1,4}){7}$/i))
|
|
return true;
|
|
|
|
return false;
|
|
},
|
|
|
|
'wpakey': function() {
|
|
var v = this;
|
|
|
|
if (v.length == 64)
|
|
return (v.match(/^[a-f0-9]{64}$/i) != null);
|
|
else
|
|
return (v.length >= 8) && (v.length <= 63);
|
|
},
|
|
|
|
'range': function(min, max) {
|
|
var val = Dec(this);
|
|
return (val >= +min && val <= +max);
|
|
},
|
|
|
|
'min': function(min) {
|
|
return (Dec(this) >= +min);
|
|
},
|
|
|
|
'max': function(max) {
|
|
return (Dec(this) <= +max);
|
|
},
|
|
|
|
'irange': function(min, max) {
|
|
var val = Int(this);
|
|
return (val >= +min && val <= +max);
|
|
},
|
|
|
|
'imin': function(min) {
|
|
return (Int(this) >= +min);
|
|
},
|
|
|
|
'imax': function(max) {
|
|
return (Int(this) <= +max);
|
|
},
|
|
|
|
'minlength': function(min) {
|
|
return ((''+this).length >= +min);
|
|
},
|
|
|
|
'maxlength': function(max) {
|
|
return ((''+this).length <= +max);
|
|
},
|
|
};
|
|
|
|
function compile(type) {
|
|
var v, match;
|
|
if ((match = type.match(/^([^\(]+)\(([^,]+),([^\)]+)\)$/)) && (v = validators[match[1]]) !== undefined) {
|
|
return function() {
|
|
return v.apply(this, [match[2], match[3]]);
|
|
}
|
|
} else if ((match = type.match(/^([^\(]+)\(([^,\)]+)\)$/)) && (v = validators[match[1]]) !== undefined) {
|
|
return function() {
|
|
return v.apply(this, [match[2]]);
|
|
}
|
|
} else {
|
|
return validators[type];
|
|
}
|
|
}
|
|
|
|
function checkvalue(target, ref) {
|
|
var t = document.getElementById(target);
|
|
var value;
|
|
|
|
if (t) {
|
|
if (t.type == "checkbox") {
|
|
value = t.checked;
|
|
} else if (t.value) {
|
|
value = t.value;
|
|
} else {
|
|
value = "";
|
|
}
|
|
|
|
return (value == ref);
|
|
} else {
|
|
t = document.getElementById(target + '.' + ref);
|
|
if (t)
|
|
return (t.type == "radio" && t.checked);
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
function check(deps) {
|
|
for (var i=0; i < deps.length; i++) {
|
|
var stat = true;
|
|
|
|
for (var j in deps[i]) {
|
|
stat = (stat && checkvalue(j, deps[i][j]));
|
|
}
|
|
|
|
if (stat)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function update() {
|
|
window.dispatchEvent(new Event('gluon-update'));
|
|
|
|
var state = false;
|
|
for (var id in dep_entries) {
|
|
var entry = dep_entries[id];
|
|
var node = document.getElementById(id);
|
|
var parent = document.getElementById(entry.parent);
|
|
|
|
if (node && node.parentNode && !check(entry.deps)) {
|
|
node.parentNode.removeChild(node);
|
|
node.dispatchEvent(new Event('gluon-hide'));
|
|
state = true;
|
|
} else if (parent && (!node || !node.parentNode) && check(entry.deps)) {
|
|
var next = undefined;
|
|
|
|
for (next = parent.firstChild; next; next = next.nextSibling) {
|
|
if (next.getAttribute && parseInt(next.getAttribute('data-index'), 10) > entry.index) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!next) {
|
|
parent.appendChild(entry.node);
|
|
} else {
|
|
parent.insertBefore(entry.node, next);
|
|
}
|
|
|
|
entry.node.dispatchEvent(new Event('gluon-show'));
|
|
state = true;
|
|
}
|
|
|
|
// hide optionals widget if no choices remaining
|
|
if (parent && parent.parentNode && parent.getAttribute('data-optionals'))
|
|
parent.parentNode.style.display = (parent.options.length <= 1) ? 'none' : '';
|
|
}
|
|
|
|
if (state) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
function bind(obj, type, callback, mode) {
|
|
if (!obj.addEventListener) {
|
|
obj.attachEvent('on' + type,
|
|
function() {
|
|
var e = window.event;
|
|
|
|
if (!e.target && e.srcElement)
|
|
e.target = e.srcElement;
|
|
|
|
return !!callback(e);
|
|
}
|
|
);
|
|
} else {
|
|
obj.addEventListener(type, callback, !!mode);
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
function init_dynlist(parent, attr) {
|
|
var prefix = attr.prefix;
|
|
|
|
function dynlist_redraw(focus, add, del) {
|
|
var values = [];
|
|
|
|
while (parent.firstChild) {
|
|
var n = parent.firstChild;
|
|
var i = +n.index;
|
|
|
|
if (i != del) {
|
|
if (n.nodeName.toLowerCase() == 'input')
|
|
values.push(n.value || '');
|
|
else if (n.nodeName.toLowerCase() == 'select')
|
|
values[values.length-1] = n.options[n.selectedIndex].value;
|
|
}
|
|
|
|
parent.removeChild(n);
|
|
}
|
|
|
|
if (add >= 0) {
|
|
focus = add + 1;
|
|
values.splice(add, 0, '');
|
|
} else if (!attr.optional && values.length == 0) {
|
|
values.push('');
|
|
}
|
|
|
|
for (var i = 1; i <= values.length; i++) {
|
|
var t = document.createElement('input');
|
|
t.id = prefix + '.' + i;
|
|
t.name = prefix;
|
|
t.value = values[i-1];
|
|
t.type = 'text';
|
|
t.index = i;
|
|
t.className = 'gluon-input-text';
|
|
|
|
if (attr.size)
|
|
t.size = attr.size;
|
|
if (attr.placeholder)
|
|
t.placeholder = attr.placeholder;
|
|
|
|
parent.appendChild(t);
|
|
|
|
if (attr.type)
|
|
validate_field(t, false, attr.type);
|
|
|
|
bind(t, 'keydown', dynlist_keydown);
|
|
bind(t, 'keypress', dynlist_keypress);
|
|
|
|
if (i == focus) {
|
|
t.focus();
|
|
} else if (-i == focus) {
|
|
t.focus();
|
|
|
|
/* force cursor to end */
|
|
var v = t.value;
|
|
t.value = ' '
|
|
t.value = v;
|
|
}
|
|
|
|
if (attr.optional || values.length > 1) {
|
|
var b = document.createElement('span');
|
|
b.className = 'gluon-remove';
|
|
|
|
parent.appendChild(b);
|
|
|
|
bind(b, 'click', dynlist_btnclick(false));
|
|
|
|
parent.appendChild(document.createElement('br'));
|
|
}
|
|
}
|
|
|
|
var b = document.createElement('span');
|
|
b.className = 'gluon-add';
|
|
|
|
parent.appendChild(b);
|
|
|
|
bind(b, 'click', dynlist_btnclick(true));
|
|
}
|
|
|
|
function dynlist_keypress(ev) {
|
|
ev = ev ? ev : window.event;
|
|
|
|
var se = ev.target ? ev.target : ev.srcElement;
|
|
|
|
if (se.nodeType == 3)
|
|
se = se.parentNode;
|
|
|
|
switch (ev.keyCode) {
|
|
/* backspace, delete */
|
|
case 8:
|
|
case 46:
|
|
if (se.value.length == 0) {
|
|
if (ev.preventDefault)
|
|
ev.preventDefault();
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
/* enter, arrow up, arrow down */
|
|
case 13:
|
|
case 38:
|
|
case 40:
|
|
if (ev.preventDefault)
|
|
ev.preventDefault();
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function dynlist_keydown(ev) {
|
|
ev = ev ? ev : window.event;
|
|
|
|
var se = ev.target ? ev.target : ev.srcElement;
|
|
|
|
var index = 0;
|
|
var prev, next;
|
|
|
|
if (se) {
|
|
if (se.nodeType == 3)
|
|
se = se.parentNode;
|
|
|
|
index = se.index;
|
|
|
|
prev = se.previousSibling;
|
|
while (prev && prev.name != prefix)
|
|
prev = prev.previousSibling;
|
|
|
|
next = se.nextSibling;
|
|
while (next && next.name != prefix)
|
|
next = next.nextSibling;
|
|
}
|
|
|
|
switch (ev.keyCode) {
|
|
/* backspace, delete */
|
|
case 8:
|
|
case 46:
|
|
var del = (se.nodeName.toLowerCase() == 'select')
|
|
? true : (se.value.length == 0);
|
|
|
|
if (del) {
|
|
if (ev.preventDefault)
|
|
ev.preventDefault();
|
|
|
|
var focus = se.index;
|
|
if (ev.keyCode == 8)
|
|
focus = -focus+1;
|
|
|
|
dynlist_redraw(focus, -1, index);
|
|
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
/* enter */
|
|
case 13:
|
|
dynlist_redraw(-1, index, -1);
|
|
break;
|
|
|
|
/* arrow up */
|
|
case 38:
|
|
if (prev)
|
|
prev.focus();
|
|
|
|
break;
|
|
|
|
/* arrow down */
|
|
case 40:
|
|
if (next)
|
|
next.focus();
|
|
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function dynlist_btnclick(add) {
|
|
return function(ev) {
|
|
ev = ev ? ev : window.event;
|
|
|
|
var se = ev.target ? ev.target : ev.srcElement;
|
|
var input = se.previousSibling;
|
|
while (input && input.name != prefix) {
|
|
input = input.previousSibling;
|
|
}
|
|
|
|
if (add) {
|
|
dynlist_keydown({
|
|
target: input,
|
|
keyCode: 13
|
|
});
|
|
} else {
|
|
input.value = '';
|
|
|
|
dynlist_keydown({
|
|
target: input,
|
|
keyCode: 8
|
|
});
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
dynlist_redraw(NaN, -1, -1);
|
|
}
|
|
|
|
function validate_field(field, optional, type) {
|
|
var check = compile(type);
|
|
if (!check)
|
|
return;
|
|
|
|
var validator = function() {
|
|
if (!field.form)
|
|
return;
|
|
|
|
field.className = field.className.replace(/ gluon-input-invalid/g, '');
|
|
|
|
var value = (field.options && field.options.selectedIndex > -1)
|
|
? field.options[field.options.selectedIndex].value : field.value;
|
|
|
|
if (!(((value.length == 0) && optional) || check.apply(value)))
|
|
field.className += ' gluon-input-invalid';
|
|
};
|
|
|
|
bind(field, "blur", validator);
|
|
bind(field, "keyup", validator);
|
|
bind(field, "gluon-revalidate", validator);
|
|
|
|
if (field.nodeName.toLowerCase() == 'select') {
|
|
bind(field, "change", validator);
|
|
bind(field, "click", validator);
|
|
}
|
|
|
|
validator();
|
|
}
|
|
|
|
function add(obj, dep, index) {
|
|
var entry = dep_entries[obj.id];
|
|
if (!entry) {
|
|
entry = {
|
|
"node": obj,
|
|
"parent": obj.parentNode.id,
|
|
"deps": [],
|
|
"index": index
|
|
};
|
|
dep_entries[obj.id] = entry;
|
|
}
|
|
entry.deps.push(dep)
|
|
}
|
|
|
|
(function() {
|
|
var nodes;
|
|
|
|
nodes = document.querySelectorAll('[data-depends]');
|
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
|
var index = parseInt(node.getAttribute('data-index'), 10);
|
|
var depends = JSON.parse(node.getAttribute('data-depends'));
|
|
if (!isNaN(index) && depends.length > 0) {
|
|
for (var alt = 0; alt < depends.length; alt++) {
|
|
add(node, depends[alt], index);
|
|
}
|
|
}
|
|
}
|
|
|
|
nodes = document.querySelectorAll('[data-update]');
|
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
|
var events = node.getAttribute('data-update').split(' ');
|
|
for (var j = 0, event; (event = events[j]) !== undefined; j++) {
|
|
bind(node, event, function () {setTimeout(update, 0);});
|
|
}
|
|
}
|
|
|
|
nodes = document.querySelectorAll('[data-type]');
|
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
|
validate_field(node, node.getAttribute('data-optional') === 'true',
|
|
node.getAttribute('data-type'));
|
|
}
|
|
|
|
nodes = document.querySelectorAll('[data-dynlist]');
|
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
|
var attr = JSON.parse(node.getAttribute('data-dynlist'));
|
|
|
|
init_dynlist(node, attr);
|
|
}
|
|
|
|
update();
|
|
})();
|
|
})();
|