/*-----------------------------------------------------------------------------
Client-side support library for the weblib ASP framework.

Some scripts borrowed/adapted from Dean Edwards and Peter Paul Koch fantastic
javascript pages.

@author:		Martí Congost
@contact:		marti.congost@whads.com
@organization:	Whads/Accent SL
@since:			July 2007
-----------------------------------------------------------------------------*/

/*=============================================================================
Library core
=============================================================================*/
var weblib = {};

/*=============================================================================
Javascript 1.6 array extensions
=============================================================================*/
if (!Array.prototype.forEach) {
	Array.prototype.forEach = function (f, context /* optional */) {
		for (var i = 0; i < this.length; i++) {
			f.call(context, this[i], i, this);
		}
	}
}

if (!Array.prototype.every) {
	Array.prototype.every = function (f, context /* optional */) {
		for (var i = 0; i < this.length; i++) {
			if (!f.call(context, this[i], i, this)) {
				return false;
			}
		}
		return true;
	}
}

if (!Array.prototype.filter) {
	Array.prototype.filter = function (f, context /* optional */) {
		var items = [];
		for (var i = 0; i < this.length; i++) {
			var item = this[i];
			if (f.call(context, item, i, this)) {
				items.push(item);
			}
		}
		return items;
	}
}

if (!Array.prototype.map) {
	Array.prototype.map = function (f, context /* optional */) {
		var items = [];
		for (var i = 0; i < this.length; i++) {
			items.push(f.call(context, this[i], i, this));
		}
		return items;
	}
}

if (!Array.prototype.some) {
	Array.prototype.some = function (f, context /* optional */) {
		for (var i = 0; i < this.length; i++) {
			if (f.call(context, this[i], i, this)) {
				return true;
			}
		}
		return false;
	}
}

/*=============================================================================
String formatting
=============================================================================*/
String.__trimExpr = /^\s*|\s*$/g;

String.prototype.trim = function () {
	return this.replace(String.__trimExpr, "");
}

/*=============================================================================
DOM traversal and behavior binding
=============================================================================*/
weblib.dom = {};

weblib.dom.__bindings = [];

weblib.dom.createHTML = function (html) {
	var elem = document.createElement("div");
	elem.innerHTML = html;
	return elem;
}

weblib.dom.prepend = function (parent, child) {
	if (parent.firstChild) {
		parent.insertBefore(child, parent.firstChild);
	}
	else {
		parent.appendChild(child);
	}
}

weblib.dom.empty = function (parent) {
	while (parent.firstChild) {
		parent.removeChild(parent.firstChild);
	}
}

weblib.dom.getRoot = function () {
	if (!this.__root) {
		this.__root = this.get(document.body, "*");
	}
	return this.__root;
}

weblib.dom.get = function (
	node,
	pattern,
	params /* optional */) {

	var child = null;

	this.match(
		node,
		pattern,
		function () {
			child = this;
			return false;
		},
		params);

	return child;
}

weblib.dom.select = function (
	node,
	pattern,
	params /* optional */) {

	var nodes = [];

	this.match(
		node,
		pattern,
		function () { nodes.push(this);},
		params);

	return nodes;
}

weblib.dom.children = function (
	node,
	pattern) {
	
	return this.select(node, pattern, { shallow : true });
}	

weblib.dom.descend = function (
	node,
	action,
	params /* optional */) {

	if (!params) {
		params = {};
	}

	var rootExcluded = true;

	if (!(params && params.excludeRoot)) {
		rootExcluded = false;
		action.call(node);
	}
	
	params.excludeRoot = true;
	
	var shallow = params && params.shallow;

	for (var i = 0; i < node.childNodes.length; i++) {
		var child = node.childNodes[i];
		if (child.nodeType == 1) {
			if (action.call(child) === false) {
				return false;
			}
			if (!shallow) {
				if (this.descend(child, action, params) === false) {
					return false;
				}
			}
			if (params && params.after) {
				params.after.call(child);
			}
		}
	}

	params.excludeRoot = rootExcluded;

	if (!rootExcluded && params && params.after) {
		params.after.call(node);
	}
}

weblib.dom.match = function (
	node,
	pattern,
	action,
	params /* optional */) {
	
	var sel = this._selector(pattern);

	this.descend(
		node,
		function () { if (sel(this)) return action.call(this); },
		params);
}

weblib.dom.test = function (node, pattern) {
	return this._selector(pattern)(node);
}

weblib.dom._selector = function (pattern) {

	var selector;

	if (typeof(pattern) == "string") {

		// Everything
		if (pattern == "*") {
			selector = function (node) { return true; }
		}
		// Class selector
		else if (pattern.charAt(0) == ".") {
			var clsExpr = weblib.ui._classRegExp(pattern.substr(1));
			selector = function (node) {
				return clsExpr.test(node.className);
			}
		}
		// Id selector
		else if (pattern.charAt(0) == "#") {
			var id = pattern.substr(1);
			selector = function (node) {
				return node.id == id;
			}
		}
		// Attribute selector
		else if (pattern.charAt(0) == "["
		&& pattern.charAt(pattern.length - 1) == "]") {
			var attrib = pattern.substring(1, pattern.length - 1);
			selector = function (node) {
				return node.getAttribute(attrib);
			}
		}
		// Tag selector
		else {
			var tag = pattern.toLowerCase();
			selector = function (node) {
				return node.tagName.toLowerCase() == tag;
			}
		}
	}
	else if (pattern instanceof Function) {
		selector = pattern;
	}
	else {
		throw "Wrong pattern type: " + pattern;
	}
	
	selector.pattern = pattern;
	return selector;
}

weblib.dom.bind = function (pattern, behavior) {
	this.__bindings.push([this._selector(pattern), behavior]);
}

weblib.dom.initialize = function (node) {
		
	if (weblib.ui.hasClass(node, "modelContainer")) {
		return false;
	}

	weblib.state._initialize(node);

	var bindings = weblib.dom.__bindings;

	for (var i = 0; i < bindings.length; i++) {			
		var binding = bindings[i];
		if (binding[0](node)) {
			/*
			var dbgSign = document.createElement("div");
			dbgSign.appendChild(
				document.createTextNode(
					node.tagName
					+ (node.id ? "#" + node.id : "")
					+ (node.className ? "." + node.className : "")
					+ ": " + binding[0].pattern + ";"
				)
			);
			document.body.appendChild(dbgSign);
			*/
			binding[1].call(node);
		}
	}

	for (var i = 0; i < node.childNodes.length; i++) {
		var child = node.childNodes[i];
		if (child.nodeType == 1) {
			weblib.dom.initialize(child);
		}
	}

	weblib.events.notify(node, "init");
}

weblib.dom.bindParts = function (node, names) {
	for (var i = 0; i < names.length; i++) {
		var name = names[i];
		var part = weblib.dom.get(node, "." + name);
		if (part) {
			node["_" + name] = part;
			part._owner = node;
		}
	}
}

/*=============================================================================
Event handling
=============================================================================*/
weblib.events = {};

weblib.events.__id = 0;
weblib.events.__initDone = false;
weblib.events.__initHandlers = [];
weblib.events.__initTimer = null;
weblib.events.__dummy = {};

weblib.events.addInit = function (handler) {
	this.__initHandlers.push(handler);
}

weblib.events.__init = function () {
	with (weblib.events) {
		if (!__initDone) {
			__initDone = true;
			if (__initTimer) clearInterval(__initTimer);
			for (var i = 0; i < __initHandlers.length; i++) {
				__initHandlers[i]();
			}
		}
	}
}

/* for Mozilla/Opera9 */
if (document.addEventListener) {
	document.addEventListener("DOMContentLoaded", weblib.events.__init, false);
}

/* for Internet Explorer */
/*@cc_on @*/
/*@if (@_win32)
	document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
	var script = document.getElementById("__ie_onload");
	script.onreadystatechange = function() {
		if (this.readyState == "complete") {
			weblib.events.__init(); // call the onload handler
		}
	};
/*@end @*/

/* for Safari */
if (/WebKit/i.test(navigator.userAgent)) { // sniff
	weblib.events.__initTimer = setInterval(function() {
		if (/loaded|complete/.test(document.readyState)) {
			weblib.events.__init(); // call the onload handler
		}
	}, 10);
}

/* for other browsers */
window.onload = weblib.events.__init;

weblib.events.addInit(function () {
	weblib.dom.initialize(document.body);
});

weblib.events.add = function (elem, event, handler) {
	
	var hId = handler.__hId;
	if (!hId) hId = handler.__hId = ++weblib.ui.__id;

	if (!elem.__handlers) elem.__handlers = {};
	
	var eventHandlers = elem.__handlers[event];

	if (!eventHandlers) {
		eventHandlers = elem.__handlers[event] = {};
	
		var key = "on" + event;
		var launcher = elem[key];
		
		if (launcher != this.__handle) {
			elem[key] = this.__handle;
			if (launcher) {
				this.listen(elem, event, launcher);
			}
		}
	}

	eventHandlers[hId] = handler;
}

weblib.events.remove = function (elem, event, handler) {
	delete elem.__handlers[event][handler._hId];
}

weblib.events.notify = function (elem, event, evData) {
	var launcher = elem["on" + event];
	if (!evData) evData = this.__dummy;
	evData.type = event;
	if (launcher) launcher.call(elem, evData);
}

weblib.events.retain = function (elem, event) {
	this.add(elem, event, function (e) {
		e.stopPropagation();
	});
}

weblib.events.__handle = function (e) {
	
	if (!e) {
		e = window.event;
		e.preventDefault = weblib.events.__preventDefaultIE;
		e.stopPropagation = weblib.events.__stopPropagationIE;
	}
	
	var handlers = this.__handlers[e.type];
	var rvalue = true;

	for (var i in handlers) {
		if (handlers[i].call(this, e) === false) {
			rvalue = false;
		}
	}

	return rvalue;
}

weblib.events.__preventDefaultIE = function () {
	this.returnValue = false;
}

weblib.events.__stopPropagationIE = function () {
	this.cancelBubble = true;
}

/*=============================================================================
Standard behaviors
=============================================================================*/
weblib.controls = {};

/*=============================================================================
Data forwarding from the server
=============================================================================*/
weblib.state = {};
weblib.state.__data = {};

weblib.state.get = function (elem, key, defaultValue /* optional */) {
	var value = this.__getDataFor(elem)[key];
	return value !== undefined ? value : defaultValue;
}

weblib.state.set = function (elem, key, value) {	
	this.__getDataFor(elem)[key] = value;
}

weblib.state.__getDataFor = function (elem) {
	
	if (typeof(elem) != "string") {
		elem = elem.id;
	}
	
	var elemData = this.__data[elem];

	if (!elemData) {
		elemData = {};
		this.__data[elem] = elemData;
	}

	return elemData;
}

weblib.state._initialize = function (elem) {
	
	var state = this.__getDataFor(elem);

	for (var key in state) {
		elem["__" + key] = state[key];
	}
}

/*=============================================================================
User interface utilities
=============================================================================*/
weblib.ui = {};

weblib.ui._classRegExp = function (cls) {
	return new RegExp("(^|\\s)" + cls + "($|\\s)");
}

weblib.ui.hasClass = function (node, cls) {
	return (this._classRegExp(cls)).test(node.className);
}

weblib.ui.setClass = function (node, cls, enabled) {
	enabled ? this.addClass(node, cls) : this.removeClass(node, cls);
}

weblib.ui.addClass = function (node, cls) {
	if (!this.hasClass(node, cls)) {
		node.className += " " + cls;
	}
}

weblib.ui.removeClass = function (node, cls) {
	node.className = node.className.replace(this._classRegExp(cls), "");
}

weblib.ui.getMousePos = function (e) {
	
	if (!e) {
		e = window.event;
	}

	var pos = {};

	if ("pageX" in e) {
		pos.x = e.pageX;
		pos.y = e.pageY;
	}
	else if ("clientX" in e) 	{
		pos.x = e.clientX
			+ document.body.scrollLeft
			+ document.documentelem.scrollLeft;
		pos.y = e.clientY
			+ document.body.scrollTop
			+ document.documentelem.scrollTop;
	}

	return pos;
}

weblib.ui.getAbsPos = function (elem) {
	var x = 0;
	var y = 0;

	while (elem) {
		x += elem.offsetLeft;
		y += elem.offsetTop;
		elem = elem.offsetParent;
	}

	return { x: x, y: y };
}

weblib.ui.getScrollPos = function (elem) {
	
	var x = elem.offsetLeft + (document.documentElement.scrollLeft || 0);
	var y = elem.offsetTop + (document.documentElement.scrollTop || 0);

	while ((elem = elem.offsetParent)) {
		x += elem.offsetLeft + (elem.scrollLeft || elem.scrollX || 0);
		y += elem.offsetTop + (elem.scrollTop || elem.scrollY || 0);
	}

	return { x: x, y: y };
}

weblib.ui.getClientSize = function () {
	return {
		width: 
			document.documentElement.clientWidth
			|| document.clientWidth
			|| document.body.clientWidth,
		height:
			document.documentElement.clientHeight
			|| document.clientHeight
			|| document.body.clientHeight
	}
}

weblib.ui.setVisible = function (elem, value) {
	elem.style.visibility = value ? "visible" : "hidden";
}

weblib.ui.setDisplayed = function (elem, value) {
	elem.style.display = value ? "block" : "none";
}

weblib.ui.pop = function (elem, pos, toggle /* optional */) {
	
	if (this.__currentPopup != elem) {
		this.hidePopup();
	}
	
	if (toggle && this.__currentPopup == elem) {
		this.hidePopup();
	}
	else {
		this.__currentPopup = elem;
	
		// Make sure the popup stays inside the document boundaries
		var x = pos.x;
		var y = pos.y;
		
		/*var ds = this.getClientSize();
		ds.width += document.documentElement.scrollLeft;
		ds.height += document.documentElement.scrollTop;

		var w = elem.offsetWidth;
		var h = elem.offsetHeight;

		if (x + w >= ds.width) x = ds.width - w - 1;
		if (y + h >= ds.height) y = ds.height - h - 1;*/

		with (elem.style) {
			visibility = "visible";
			position = "absolute";
			left = x + "px";
			top = y + "px";
		}
	
		weblib.events.notify(this.__currentPopup, "popup");
	}
}

weblib.ui.popAtCursor = function (
	elem,
	e,
	offset /* optional */,
	toggle /* optional */) {
	
	var pos = this.getMousePos(e);
	if (offset) {
		if (offset.x) pos.x += offset.x;
		if (offset.y) pos.y += offset.y;
	}

	this.pop(elem, position, toggle);
}

weblib.ui.popUnder = function (elem, target, toggle /* optional */) {
	
	var pos = weblib.ui.getAbsPos(target);
	pos.y += target.offsetHeight;

	var ds = this.getClientSize();
	ds.width += document.documentElement.scrollLeft;
	ds.height += document.documentElement.scrollTop;

	var w = elem.offsetWidth;
	var h = elem.offsetHeight;

	if (pos.x + w >= ds.width) pos.x -= w + 1;
	if (pos.y + h >= ds.height) pos.y -= h + target.offsetHeight + 1;
	
	this.pop(elem, pos, toggle);
}

weblib.ui.hidePopup = function () {
	if (this.__currentPopup) {
		this.__currentPopup.style.visibility = "hidden";
		weblib.events.notify(this.__currentPopup, "popupHidden");
		this.__currentPopup = null;
	}
}

weblib.events.addInit(function () {
	weblib.events.add(document, "click", function (e) {
		weblib.ui.hidePopup();
	});
});

weblib.dom.bind("input", function () {
	if (this.type == "text" || this.type == "password") {
		this.getValue = function () { return this.value; }
		this.setValue = function (value) { this.value = value; }
	}
	else if (this.type == "checkbox") {
		this.getValue = function () { return this.checked; }
		this.setValue = function (value) { this.checked = value; }		
	}
});

weblib.dom.bind("textarea", function () {
	this.getValue = function () { return this.value; }
	this.setValue = function (value) { this.value = value; }
});

weblib.dom.bind("select", function () {
	this.getValue = function () { return this.options[this.selectedIndex].value; }
	this.setValue = function (value) {
		for (var i = 0; i < this.options.length; i++) {
			if (options[i].value == value) {
				this.selectedIndex = i;
				break;
			}
		}
	}
});

/*=============================================================================
Text
=============================================================================*/
weblib.text = {};
weblib.text.__strings = {};

weblib.text.init = function (strings) {
	this.__strings = strings;
}

weblib.text.set = function (key, text) {
	this.__strings[key] = text;
}

weblib.text.get = function (key) {
	return this.__strings[key];
}

/*=============================================================================
String method
=============================================================================*/
String.prototype.padding = function (character, length) {
	var diff = length - this.length;
	var str = this;
	for (; diff > 0; diff--) {
		str = character + str;
	}
	return str;
}


