Skip to content
Snippets Groups Projects
gluon-web-model.js 11.23 KiB
/*
	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, update);
			}
		}

		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();
	})();
})();