
var mayaFish = (function () {
	
	var pub = { };
	var priv = { };
	
	pub.conf = mayaFishConf;
	priv.conf = { '_documentLoaded': false };
	
	/** const */ pub.consts = mayaFishConf.consts;
	priv._translations = mayaFishConf.translations;
	
	pub.debug = function () { }
	
	pub.$id = function (id) {
		var el = document.getElementById(id);
		if (el == null) {
			pub.debug("Undefined element; id:" + id);
		}
		return el;
	}
	pub.$name = function (n) {
		var el = document.getElementByName(n);
		if (el == null) {
			pub.debug("Undefined element; name:" + n);
		}
		return el;
	}
	pub.$tags = function (tn) { return document.getElementsByTagName(tn); }
	pub.$tag = function (tn, pos) {
		var position = typeof pos == "undefined" ? 0 : parseInt(pos);
		var el = document.getElementsByTagName(tn)[position];
		if (el == null) {
			pub.debug("Undefined element; tag:" + tn + "; position:" + position);
		}
		return el;
	}
	
	pub.$select = function (q, from) {
		if (from) return dojo.query(q, from);
		else return dojo.query(q);
	}
	
	pub.modules = { };
	
	priv._defaultParams = function (pars, defpars) {
		if (pars == null) return defpars;
		pub.forEach(defpars, function (key, p) {
			if (pars[key] == null) pars[key] = p;
		});
		return pars;
	}
	
	pub.addCSS = function (file) {
		if (document.createStyleSheet) {
			document.createStyleSheet(file);
		} else {
			var newCSS = document.createElement('link');
			newCSS.setAttribute('rel', 'stylesheet');
			newCSS.setAttribute('type', 'text/css');
			newCSS.setAttribute('href', file);
			pub.$tag('head').appendChild(newCSS);
		}
	}
	
	pub.addJS = function (file) {
		if (priv.conf._documentLoaded) {
			var newJS = document.createElement('script');
			newJS.setAttribute('type', 'text/javascript');
			newJS.setAttribute('src', file);
			pub.$tag('head').appendChild(newJS);
		} else {
			document.write(unescape('%3Cscript type="text/javascript" src="' + file + '"%3E%3C/script%3E'));
		}
	}
	
	pub.addSlashes = function (str) {
		str = str.replace(/\\/g, '\\\\');
		str = str.replace(/\'/g, '\\\'');
		str = str.replace(/\"/g, '\\"');
		str = str.replace(/\0/g, '\\0');
		str = str.replace(/\n/g, '\\n');
		return str;
	}
	
	pub.stripSlashes = function (str) {
		str = str.replace(/\\'/g, '\'');
		str = str.replace(/\\"/g, '"');
		str = str.replace(/\\0/g, '\0');
		str = str.replace(/\\n/g, "\n");
		str = str.replace(/\\\\/g, '\\');
		return str;
	}
	
	pub.tryUntil = function (func, expr, tryOnExcept, wait) {
		if (!wait) wait = 100;
		try {
			if (expr()) func();
			else setTimeout(function() {
				pub.tryUntil(func, expr, tryOnExcept, wait + 10);
			}, wait + 10);
		} catch (e) {
			if (tryOnExcept) setTimeout(function() {
				pub.tryUntil(func, expr, tryOnExcept, wait + 10);
			}, wait + 10);
		}
	}
	
	pub.toJson = function (par) {
		var ret = '';
		if ((typeof par == 'undefined') || (par == null)) {
			ret += 'null';
		} else if ((typeof par == 'array') || (par instanceof Array)) {
			
			ret += '[';
			var first = true;
			for (var i = 0; i < par.length; i++) {
				if (first) first = false;
				else ret += ',';
				ret += pub.toJson(par[i]);
			}
			ret += ']';
			
		} else if (typeof par == 'object') {
			
			if (!par.____jsonEncoded) {
				par.____jsonEncoded = true;
				ret += '{';
				var first = true;
				for (var i in par) {
					if (i != '____jsonEncoded') {
						if (first) first = false;
						else ret += ',';
						ret += '"' + pub.addSlashes(i) + '":';
						ret += pub.toJson(par[i]);
					}
				}
				ret += '}';
				delete par.____jsonEncoded;
			} else {
				ret += 'null';
			}
			
		} else if (typeof par == 'number') {
			ret += par;
		} else if (typeof par == 'boolean') {
			ret += par ? 'true' : 'false';
		} else {
			ret += '"' + pub.addSlashes('' + par) + '"';
		}
		return ret;
	}
	
	pub.clone = function (par) {
		var ret;
		if ((typeof par == 'undefined') || (par == null)) {
			ret = null;
		} else if ((typeof par == 'array') || (par instanceof Array)) {
			
			ret = new Array();
			for (var i = 0; i < par.length; i++) {
				ret.push(pub.clone(par[i]));
			}
			
		} else if (typeof par == 'object') {
			
			if (!par.____cloned) {
				par.____cloned = true;
				ret = new Object();
				for (var i in par) {
					if (i != '____cloned') {
						ret[i] = pub.clone(par[i]);
					}
				}
				delete par.____cloned;
			} else {
				ret = null;
			}
			
		} else {
			ret = par;
		}
		return ret;
	}
	
	priv._breakForEach = false;
	pub.breakForEach = function () {
		priv._breakForEach = true;
	}
	/** const */ pub.BREAK = false;
	pub.forEach = function (arr, func/*(key, item)*/) {
		if ((typeof arr == 'array') || (arr instanceof Array)) {
			for (var i = 0; i < arr.length; ++i) {
				if ((func(i, arr[i]) === pub.BREAK) || priv._breakForEach) {
					priv._breakForEach = false;
					break;
				}
			}
		} else if (typeof arr == 'object') {
			for (var i in arr) {
				if ((func(i, arr[i]) === pub.BREAK) || priv._breakForEach) {
					priv._breakForEach = false;
					break;
				}
			}
		} else {
			pub.debug('mayaFish.forEach: first parameter not array, nor object');
		}
	}
	
	pub.inArray = function (arr, elem) {
		var found = false;
		pub.forEach(arr, function (key, item) {
			if (item == elem) {
				found = true;
				return pub.BREAK;
			}
		});
		return found;
	}
	
	pub.arrayInsert = function (arr, elem, beta /** beta(elem) = false|"after"|"before" */) {
		var ret = new Array();
		if ((typeof arr == 'array') || (arr instanceof Array)) {
			for (var i = 0; i < arr.length; i++) {
				var b = beta(arr[i]);
				if (b == "before") ret.push(elem);
				ret.push(pub.clone(arr[i]));
				if (b == "after") ret.push(elem);
			}
		}
		return ret;
	}
	
	pub.arrayRemove = function (arr, beta /** beta(elem) = true|false */) {
		var ret = new Array();
		var el = null;
		if ((typeof arr == 'array') || (arr instanceof Array)) {
			for (var i = 0; i < arr.length; i++) {
				if (beta(arr[i]) && (el == null))
					el = pub.clone(arr[i]);
				else
					ret.push(pub.clone(arr[i]));
			}
		}
		return {
			'array': ret,
			'elem': el
		};
	}
	
	pub.cEscape = function (str, chrs) {
		if (!chrs) chrs = "'\"";
		return ("" + str).replace(/\\/g, "\\\\").replace(new RegExp("([" + chrs + "])", "g"), "\\$1");
	}
	
	pub.min = function (m1, m2) { return (m1 < m2) ? m1 : m2; }
	pub.max = function (m1, m2) { return (m1 > m2) ? m1 : m2; }
	
	pub.color = {
		'Rgb': function (r, g, b) { this.r = r; this.g = g; this.b = b; }, /** r, g, b in [0.0, 1.0] */
		'Cmy': function (c, m, y) { this.c = c; this.m = m; this.y = y; }, /** c, m, y in [0.0, 1.0] */
		'Hsv': function (h, s, v) { this.h = h; this.s = s; this.v = v; }, /** h in [0.0, 360.0]; s, v in [0.0, 1.0] */
		'Hsl': function (h, s, l) { this.h = h; this.s = s; this.l = l; }, /** h in [0.0, 360.0]; s, l in [0.0, 1.0] */
		'rgb2cmy': function (c1 /** r, g, b */) {
			return new pub.color.Cmy(1 - c1.r, 1 - c1.g, 1 - c1.b);
		},
		'cmy2rgb': function (c1 /** r, g, b */) {
			return new pub.color.Rgb(1 - c1.c, 1 - c1.m, 1 - c1.y);
		},
		'rgb2hsv': function (c1 /** r, g, b */) {
			var themin = pub.min(c1.r, pub.min(c1.g, c1.b));
			var themax = pub.max(c1.r, pub.max(c1.g, c1.b));
			var delta = themax - themin;
			var c2 = new pub.color.Hsv(0, 0, themax);
			if (themax > 0) c2.s = delta / themax;
			if (delta > 0) {
				if (themax == c1.r && themax != c1.g)
					c2.h += (c1.g - c1.b) / delta;
				if (themax == c1.g && themax != c1.b)
					c2.h += (2 + (c1.b - c1.r) / delta);
				if (themax == c1.b && themax != c1.r)
					c2.h += (4 + (c1.r - c1.g) / delta);
				c2.h *= 60;
			}
			return c2;
		},
		'hsv2rgb': function (c1 /** h, s, v */){
			var c2 = new pub.color.Rgb(0, 0, 0);
			var sat = new pub.color.Rgb(0, 0, 0);
			
			while (c1.h < 0) c1.h += 360;
			while (c1.h > 360) c1.h -= 360;
			
			if (c1.h < 120) {
				sat.r = (120 - c1.h) / 60.0;
				sat.g = c1.h / 60.0;
				sat.b = 0;
			} else if (c1.h < 240) {
				sat.r = 0;
				sat.g = (240 - c1.h) / 60.0;
				sat.b = (c1.h - 120) / 60.0;
			} else {
				sat.r = (c1.h - 240) / 60.0;
				sat.g = 0;
				sat.b = (360 - c1.h) / 60.0;
			}
			sat.r = pub.min(sat.r, 1);
			sat.g = pub.min(sat.g, 1);
			sat.b = pub.min(sat.b, 1);
			
			c2.r = (1 - c1.s + c1.s * sat.r) * c1.v;
			c2.g = (1 - c1.s + c1.s * sat.g) * c1.v;
			c2.b = (1 - c1.s + c1.s * sat.b) * c1.v;
			
			return c2;
		},
		'rgb2hsl': function (c1 /** r, g, b */) {
			var themin = pub.min(c1.r, pub.min(c1.g, c1.b));
			var themax = pub.max(c1.r, pub.max(c1.g, c1.b));
			var delta = themax - themin;
			var c2 = new pub.color.Hsl(0, 0, (themin + themax) / 2);
			if (c2.l > 0 && c2.l < 1)
				c2.s = delta / (c2.l < 0.5 ? (2*c2.l) : (2-2*c2.l));
			if (delta > 0) {
				if (themax == c1.r && themax != c1.g)
					c2.h += (c1.g - c1.b) / delta;
				if (themax == c1.g && themax != c1.b)
					c2.h += (2 + (c1.b - c1.r) / delta);
				if (themax == c1.b && themax != c1.r)
					c2.h += (4 + (c1.r - c1.g) / delta);
				c2.h *= 60;
			}
			return c2;
		},
		'hsl2rgb': function (c1 /** h, s, l */ ){
			var c2 = new pub.color.Rgb(0, 0, 0);
			var sat = new pub.color.Rgb(0, 0, 0);
			var ctmp = new pub.color.Rgb(0, 0, 0);
			
			while (c1.h < 0) c1.h += 360;
			while (c1.h > 360) c1.h -= 360;
			
			if (c1.h < 120) {
				sat.r = (120 - c1.h) / 60.0;
				sat.g = c1.h / 60.0;
				sat.b = 0;
			} else if (c1.h < 240) {
				sat.r = 0;
				sat.g = (240 - c1.h) / 60.0;
				sat.b = (c1.h - 120) / 60.0;
			} else {
				sat.r = (c1.h - 240) / 60.0;
				sat.g = 0;
				sat.b = (360 - c1.h) / 60.0;
			}
			sat.r = pub.min(sat.r, 1);
			sat.g = pub.min(sat.g, 1);
			sat.b = pub.min(sat.b, 1);
			
			ctmp.r = 2 * c1.s * sat.r + (1 - c1.s);
			ctmp.g = 2 * c1.s * sat.g + (1 - c1.s);
			ctmp.b = 2 * c1.s * sat.b + (1 - c1.s);
			
			if (c1.l < 0.5) {
				c2.r = c1.l * ctmp.r;
				c2.g = c1.l * ctmp.g;
				c2.b = c1.l * ctmp.b;
			} else {
				c2.r = (1 - c1.l) * ctmp.r + 2 * c1.l - 1;
				c2.g = (1 - c1.l) * ctmp.g + 2 * c1.l - 1;
				c2.b = (1 - c1.l) * ctmp.b + 2 * c1.l - 1;
			}
			
			return c2;
		}
	}
	
	pub.color.Rgb.prototype.toCmy = function () { return pub.color.rgb2cmy(this) };
	pub.color.Rgb.prototype.toHsv = function () { return pub.color.rgb2hsv(this) };
	pub.color.Rgb.prototype.toHsl = function () { return pub.color.rgb2hsl(this) };
	pub.color.Cmy.prototype.toRgb = function () { return pub.color.cmy2rgb(this) };
	pub.color.Hsv.prototype.toRgb = function () { return pub.color.hsv2rgb(this) };
	pub.color.Hsl.prototype.toRgb = function () { return pub.color.hsl2rgb(this) };
	pub.color.Rgb.prototype.toString = function () {
		var rs = parseInt(this.r * 255).toString(16);
		var gs = parseInt(this.g * 255).toString(16);
		var bs = parseInt(this.b * 255).toString(16);
		if (rs.length == 1) rs = '0' + rs;
		if (gs.length == 1) gs = '0' + gs;
		if (bs.length == 1) bs = '0' + bs;
		return '#' + rs + gs + bs;
	}
	pub.color.Cmy.prototype.toString =
	pub.color.Hsv.prototype.toString =
	pub.color.Hsl.prototype.toString = function () {
		return this.toRgb().toString();
	}
	
	priv._makeParams = function (params) {
		if (typeof params == 'object') {
			var paramStr = '';
			for (par in params) {
				if (paramStr != '') paramStr += '&';
				if (params[par] instanceof Array) {
					for (var p2 = 0; p2 < params[par].length; ++p2) {
						if (p2 > 0) paramStr += '&';
						paramStr += '' + par + '[' + p2 + ']=' + params[par];
					}
				} else if (typeof params[par] == 'object') {
					var first = true;
					for (p2 in params[par]) {
						if (first) first = false;
						else paramStr += '&';
						paramStr += '' + par + '[' + p2 + ']=' + params[par];
					}
				} else {
					paramStr += '' + par + '=' + params[par];
				}
			}
			return paramStr;
		} else {
			return '' + params;
		}
	}
	
	pub.httpRequest = function (params /* url, [method], [params], [randomize], [parser], [contentType], [charset], [headers], [onSuccess], [onFail], [onFinish] */) {
		if (params.url) {
			var _xhr = new pub.types.Xhr();
			if (!_xhr.valid) throw new Exception('xmlHttpRequest not implemented');
			if (params.method) {
				params.method = params.method.toUpperCase();
				if (params.method != 'GET' && params.method != 'POST')
					params.method = 'GET';
			} else
				params.method = 'GET';
			
			params.params = priv._makeParams(params.params);
			
			if (typeof params.randomize == 'undefined' || params.randomize) {
				var randStr = '__randomize=' + parseInt(Math.random() * 100000000000);
				if (params.method == 'GET') {
					if (params.params) params.params += '&' + randStr;
					else params.params = randStr;
				} else {
					if (params.url.indexOf('?') >= 0)
						params.url += '&' + randStr;
					else
						params.url += '?' + randStr;
				}
			}
			
			if (params.method == 'GET' && params.params) {
				if (params.url.indexOf('?') >= 0)
					params.url += '&' + params.params;
				else
					params.url += '?' + params.params;
			}
			
			if (!pub.httpRequestParser[params.parser])
				params.parser = 'auto';
			
			_xhr.xhr.onreadystatechange = function () {
				if (_xhr.xhr.readyState == 4) {
					if (_xhr.xhr.status == 200) {
						if (!!params.onSuccess) {
							try {
								params.onSuccess(pub.httpRequestParser[params.parser](_xhr.xhr));
							} catch (e) {
								pub.debug(e.message);
							}
						}
					} else if (_xhr.xhr.status != 200 && !!params.onFail) {
						try {
							params.onFail(priv._xhr.xhr.status);
						} catch (e) {
							pub.debug(e.message);
						}
					}
					if (!!params.onFinish)
						try {
							params.onFinish();
						} catch (e) {
							pub.debug(e.message);
						}
				}
			}
			
			if (!params.charset) params.charset = 'UTF-8';
			_xhr.xhr.open(params.method, params.url, true);
			if (params.contentType) {
				if (params.contentType.search(/[;,] charset=[a-zA-Z0-8_-]+/i))
					_xhr.xhr.setRequestHeader('Content-Type', params.contentType);
				else if (0 > params.contentType.indexOf(";"))
					_xhr.xhr.setRequestHeader('Content-Type', params.contentType + '; charset=' + params.charset);
				else
					_xhr.xhr.setRequestHeader('Content-Type', params.contentType + ', charset=' + params.charset);
			} else if (params.method == 'POST')
				_xhr.xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=' + params.charset);
			
			if ((params.headers instanceof Array) && (params.headers.length > 0))
				for (var i = 0; i < params.headers.length; ++i) {
					var h = ('' + params.headers[i]).split(':', 2);
					if (h[1]) h[1] = h[1].replace(/^ +/, '');
					_xhr.xhr.setRequestHeader(h[0], h[1]);
				}
			
			if (params.method == 'POST')
				_xhr.xhr.send(params.params);
			else
				_xhr.xhr.send(null);
		} else {
			pub.debug('mayaFish.httpRequest: Missing url.');
		}
	}
	pub.httpRequestParser = {
		'text': function (x) { return x.responseText; },
		'object': function (x) { return x; },
		'json': function (x) {
			try {
				if ((typeof JSON != 'undefined') && (!!JSON.parse))
					return JSON.parse(x.responseText);
				else
					return eval('(' + x.responseText + ')');
			} catch(e) {
				return x.responseText;
			}
		},
		'eval': function (x) {
			try {
				return eval('(' + x.responseText + ')');
			} catch(e) {
				return e;
			}
		},
		'func': function (x) {
			try {
				return eval('((function(){' + x.responseText + '})())');
			} catch(e) {
				return e;
			}
		},
		'xml': function (x) {
			if (x.responseXML)
				return x.responseXML;
			else {
				var xml = new pub.types.XmlParser();
				if (xml.valid) {
					try {
						xml.xml.async = false;
						xml.xml.loadXML(x.responseText);
						return xml.xml;
					} catch (e) {
						return x.responseText;
					}
				} else {
					return x.responseText;
				}
			}
		},
		'auto': function (x) {
			var contentType = null;
			if (!(contentType = x.getResponseHeader('Content-type')))
				contentType = x.getResponseHeader('Content-Type');
			contentType = contentType.replace(/;.*$/, '');
			contentType = contentType.replace(/\/.*\+/, '/');
			contentType = contentType.replace('/x-', '/');
			switch (contentType) {
				case 'text/xml':
				case 'application/xml':
					return pub.httpRequestParser.xml(x);
				break;
				case 'text/json':
				case 'application/json':
					return pub.httpRequestParser.json(x);
				break;
				case 'text/javascript':
				case 'application/javascript':
					return pub.httpRequestParser.eval(x);
				break;
				case 'text/plain':
				default:
					return x.responseText;
				break;
			}
		}
	};
	
	priv.jsonRpc = { '_lastId': 0 };
	pub.jsonRpc = {
		'call': function (rpcParams /** [url|site], method, [params], [onSuccess], [onError], [notify], [onFinish] */) {
			if (!rpcParams.notify) priv.jsonRpc._lastId++;
			var rpcId = parseInt(priv.jsonRpc._lastId);
			pub.httpRequest({
				'url': rpcParams.url ?
					rpcParams.url : (
					rpcParams.site ?
					(rpcParams.site + '/jsonRpc') :
					pub.consts.SITE_URL + '/jsonRpc'),
				'method': 'post',
				'params': pub.toJson({
					'jsonrpc': '2.0',
					'method': rpcParams.method,
					'params': rpcParams.params ? rpcParams.params : null,
					'id': rpcParams.notify ? null : rpcId
				}),
				'contentType': 'application/json; charset=UTF-8',
				'parser': 'json',
				'onSuccess': function (res) {
					if (res && res.error) {
						if (res.error.code > 0) {
							try {
								rpcParams.onError(res.error);
							} catch (e) {
								pub.ui.alert('Error: <b>' + res.error.code + '</b><br />Message: <b>' + res.error.message + '</b>');
								pub.debug(e.message);
							}
						} else
							pub.ui.alert('Error: <b>' + res.error.code + '</b><br />Message: <b>' + res.error.message + '</b>');
					} else if (res && res.id && (!rpcParams.notify) && (res.id != rpcId)) {
						pub.ui.alert('Id mismatch error while JSON-RPC #' + rpcId);
					} else if (res && ('result' in res)) {
						try {
							rpcParams.onSuccess(res.result);
						} catch (e) {
							if (!rpcParams.notify) {
								pub.ui.alert('Runtime error while JSON-RPC #' + rpcId);
								pub.debug(e.message + ' (name:' + e.name + '; number:' + e.number + ')');
							}
						}
					} else {
						pub.ui.alert('Parse error while JSON-RPC #' + rpcId);
						pub.debug(res);
					}
				},
				'onFail': function (err) {
					pub.ui.alert('Http error (' + err + ') while JSON-RPC #' + rpcId);
				},
				'onFinish': ((rpcParams && !!rpcParams.onFinish) ? rpcParams.onFinish : null)
			});
		}
	};
	
	pub.translations = function (section, msg) {
		section = section.toLowerCase();
		if (!priv._translations[section]) {
			pub.debug('Undefined translation; section:' + section + 'text:' + msg);
			return msg;
		} else {
			msg = msg.toLowerCase();
			msg = msg.replace(/[^a-zA-Z0-9]/g, '_');
			msg = msg.replace(/_+/g, '_');
			msg = msg.replace(/^_/g, '');
			msg = msg.replace(/_$/g, '');
			return priv._translations[section][msg];
		}
	}
	
	pub.makeSymbol = function (str) {
		str = str.toLowerCase();
		str = str.replace(/[^a-zA-Z0-9]/g, '_');
		str = str.replace(/_+/g, '_');
		str = str.replace(/^_/g, '');
		str = str.replace(/_$/g, '');
		return str;
	}
	
	pub.makeId = function (str) {
		str = pub.makeSymbol(str);
		if (mayaFish.$id(str)) {
			var i = 1;
			var str2;
			do {
				str2 = str + '_' + i;
				i++;
			} while (mayaFish.$id(str2));
			return str2;
		} else
			return str;
	}
	
	priv._events = [ ];
	pub.event = function (eventName, obj, eventObj) {
		for (i in priv._events) {
			if (
					(i == parseInt(i)) &&
					(priv._events[i].name == eventName) &&
					(!obj || (priv._events[i].obj == obj)) &&
					(typeof priv._events[i].func == 'function')
				) try {
					if (!priv._events[i].func(eventObj) && obj)
						return false;
				} catch(e) { }
		}
		return true;
	};
	pub.addEvent = function (name, func, obj) {
		var max = priv._events.length;
		var _name = func ? name : name.name;
		var _obj = func ? obj : name.obj;
		var _func = func ? func : (name.func ? name.func : name['function']);
		priv._events[max] = {
			'name': _name,
			'func': _func,
			'obj': _obj
		};
		if (_obj) {
			try {
				if (!_obj[_name])
					_obj[_name] = function (event) {
						return pub.event(_name, _obj, event);
					}
			} catch(e) { }
		}
	};
	
	djConfig = { 'parseOnLoad': false, 'isDebug': pub.conf.debug ? true : false };
	pub.addJS(pub.consts.LIBS_URL + '/dojo/dojo.js');
	
	// pub.addJS(pub.consts.LIBS_URL + '/ext-2.0.2/adapter/ext/ext-base.js');
	// pub.addJS(pub.consts.LIBS_URL + '/ext-2.0.2/ext-all.js');
	
	pub.addEvent('onload', function () {
		priv.conf._documentLoaded = true;
		dojo.require('dojo.parser');
		if (typeof console != 'undefined' && !!console.log)
			pub.debug = function (msg) {
				console.log(msg);
			}
		if (typeof opera != 'undefined' && !!opera.postError)
			pub.debug = function (msg) {
				opera.postError(msg);
			}
	});
	
	priv._toIsoDate = function (dateObj) {
		return (dateObj instanceof Date ?
			'' + dateObj.getFullYear() +
			'-' + ((dateObj.getMonth() + 1) < 10 ? '0' : '') + (dateObj.getMonth() + 1) +
			'-' + (dateObj.getDate() < 10 ? '0' : '') + dateObj.getDate()
			: '');
	}
	
	priv._toIsoTime = function (dateObj) {
		return (dateObj instanceof Date ?
			'' + (dateObj.getHours() < 10 ? '0' : '') + dateObj.getHours() +
			':' + (dateObj.getMinutes() < 10 ? '0' : '') + dateObj.getMinutes() +
			':' + (dateObj.getSeconds() < 10 ? '0' : '') + dateObj.getSeconds()
			: '');
	}
	
	pub.toIsoDateTime = function (dateObj, separator) {
		if (!separator) separator = 'T';
		return (dateObj instanceof Date ?
			priv._toIsoDate(dateObj) + separator + priv._toIsoTime(dateObj)
		: '');
	}
	
	priv._parseIsoDate = function (dateStr) {
		var dates = dateStr.split('-');
		if (dates.length) {
			var dateObj = new Date();
			dateObj.setYear(dates[0]);
			dateObj.setMonth(dates[1] - 1);
			dateObj.setDate(dates[2]);
			return dateObj;
		} else
			return '';
	}
	
	priv._parseIsoTime = function (dateStr) {
		var dates = dateStr.split(':');
		if (dates.length) {
			var dateObj = new Date();
			dateObj.setHours(dates[0]);
			dateObj.setMinutes(dates[1]);
			dateObj.setSeconds(dates[2]);
			return dateObj;
		} else
			return '';
	}
	
	pub.parseIsoDateTime = function (dateStr) {
		var dates = dateStr.split(/[Tt ]+/);
		if (dates.length) {
			return priv._parseIsoDate(dates[0]) + priv._parseIsoTime(dates[1]);
		} else
			return '';
	}
	
	pub.addClass = function (htmlo, classn) {
		if (htmlo.className) {
			var classes = htmlo.className.split(' ');
			for (var i = 0; i < classes.length; i++) {
				if (classes[i] == classn) return;
			}
			htmlo.className = htmlo.className + ' ' + classn;
		} else {
			htmlo.className = classn;
		}
	}
	
	pub.removeClass = function (htmlo, classn) {
		if (htmlo.className) {
			var classes = htmlo.className.split(' ');
			var ncn = '';
			for (var i = 0; i < classes.length; i++) {
				if (classes[i] != classn) ncn = ncn + ' ' + classes[i];
			}
			htmlo.className = ncn;
		}
	}
	
	priv._lastGenerated = 0;
	pub.generateId = function () {
		priv._lastGenerated++;
		return "mayaFish_generated_" + priv._lastGenerated;
	}
	
	pub.ui = {
		/** const */ 'buttons': { 'OK': 1, 'CANCEL': 2, 'YES': 4, 'NO': 8 },
		'progress': function (msg, title, progress) {
			dojo.require('dijit.Dialog');
			dojo.require('dijit.form.Button');
			
			if (!title) title = pub.consts.WINDOW_TITLE;
			if (!msg) msg = pub.translations('common', 'Please wait');
			var dial = new dijit.Dialog({ 'title': title, 'onCancel': function () { return false; } });
			var msgDiv = document.createElement('div');
			
			//msgDiv.appendChild(document.createTextNode(msg));
			msgDiv.innerHTML = '<img src="' + pub.consts.ACTUAL_TEMPLATE_URL +
				'/images/common/icon_loading_16x16.gif" alt="..." /> ' + msg + ' ...';
			
			dial.attr('content', msgDiv);
			dojo.body().appendChild(dial.domNode);
			dial.show();
			dial.containerNode.style.height = 'auto';
			dial.closeButtonNode.style.display = 'none';
			
			if (progress) {
				try {
					progress();
				} catch (e) {
					pub.debug(e.message);
				}
				dial.destroy();
				return false;
			}
			else
				return function () { dial.destroy() };
		},
		'alert': function (msg, title, onClick, buttons) {
			dojo.require('dijit.Dialog');
			dojo.require('dijit.form.Button');
			
			if (!title) title = pub.consts.WINDOW_TITLE;
			var dial = new dijit.Dialog({ 'title': title });
			var msgDiv = document.createElement('div');
			var buttDiv = document.createElement('div');
			var divs = document.createElement('div');
			
			var btLabel = pub.translations('common', (parseInt(buttons) & pub.ui.buttons.YES) ? 'yes' : 'ok');
			
			//msgDiv.appendChild(document.createTextNode(msg));
			msgDiv.innerHTML = msg;
			divs.appendChild(msgDiv);
			
			(new dijit.form.Button({
				'label': btLabel,
				'onCancel': function () { return false; },
				'onClick': function () { dial.destroy(); if (onClick) try { onClick(); } catch(e) {}; } 
			})).placeAt(buttDiv);
			
			divs.appendChild(buttDiv);
			
			dial.attr('content', divs);
			dojo.body().appendChild(dial.domNode);
			dial.show();
			dial.containerNode.style.height = 'auto';
		},
		'popup': function (content, title) {
			dojo.require('dijit.Dialog');
			
			if (!title) title = pub.consts.WINDOW_TITLE;
			var dial = new dijit.Dialog({ 'title': title });
			var divs = document.createElement('div');
			
			//msgDiv.appendChild(document.createTextNode(msg));
			divs.innerHTML = content;
			
			dial.attr('content', divs);
			dojo.body().appendChild(dial.domNode);
			dial.show();
			dial.containerNode.style.height = 'auto';
			
			return function () { dial.destroy() };
		},
		'load': function (htmla, title, params) {
			dojo.require('dijit.Dialog');
			
			var t = htmla || this;
			var src = ('href' in t) ? t.href : ('' + htmla);
			var p = params || {};
			if (!('width' in p)) p.width = 600;
			if (!('height' in p)) p.height = 400;
			
			pub.httpRequest({
				'url': src,
				'method': 'get',
				'params': '',
				'randomize': false,
				'parser': 'text',
				'onSuccess': function (result) {
					var res = '';
					var m = [];
					result = ('' + result).replace(/[\n\r\t ]+/g, " ");
					
					if (!title) {
						if (m = result.match(/<head>.*<title>(.*)<\/title>.*<\/head>/i)) title = m[1];
						else title = pub.consts.WINDOW_TITLE;
					}
					
					if (m = result.match(/<body[^>]*>(.*)<\/body>/i)) res = m[1];
					else if (m = result.match(/<html[^>]*>(.*)<\/html>/i)) res = m[1];
					else res = result;
					
					var dial = new dijit.Dialog({ 'title': title });
					var div = document.createElement('div');
					div.setAttribute('style', 'position:relative;width:' + p.width + 'px;height:' + p.height + 'px;overflow:auto;');
					
					dial.attr('content', div);
					dojo.body().appendChild(dial.domNode);
					dial.show();
					
					div.innerHTML = res;
					div.style.width = p.width + 'px';
					div.style.height = p.height + 'px';
					div.style.overflow = 'auto';
				},
				'onFail': function (err) {
					pub.ui.alert('HTTP error :' + err + '<br />Could not load: <i>' + src + '</i>');
				}
			});
			
			return false;
		},
		'confirm': function (msg, title, onYes, onNo, buttons) {
			dojo.require('dijit.Dialog');
			dojo.require('dijit.form.Button');
			
			if (typeof buttons == "undefined")
				buttons = (pub.ui.buttons.YES | pub.ui.buttons.NO);
			
			if (!title) title = pub.consts.WINDOW_TITLE;
			
			var msgDiv = document.createElement('div');
			var buttDiv = document.createElement('div');
			var divs = document.createElement('div');
			
			var okLabel = pub.translations('common', (parseInt(buttons) & pub.ui.buttons.YES) ? 'yes' : 'ok');
			var cancelLabel = ((parseInt(buttons) & pub.ui.buttons.CANCEL) || (parseInt(buttons) & pub.ui.buttons.NO)) ?
				pub.translations('common', (parseInt(buttons) & pub.ui.buttons.NO) ? 'no' : 'cancel') : false;
			
			var dial = new dijit.Dialog({ 'title': title, 'onCancel': function () { return !!cancelLabel; } });
			
			msgDiv.appendChild(document.createTextNode(msg));
			divs.appendChild(msgDiv);
			
			(new dijit.form.Button({ 'label': okLabel, 'onClick': function () {
				dial.destroy(); try { onYes(); } catch(e) {};
			} })).placeAt(buttDiv);
			if (cancelLabel)
				(new dijit.form.Button({ 'label': cancelLabel, 'onClick': function () {
					dial.destroy(); try { onNo(); } catch(e) {};
				} })).placeAt(buttDiv);
			divs.appendChild(buttDiv);
			
			dial.attr('content', divs);
			dojo.body().appendChild(dial.domNode);
			dial.show();
			dial.containerNode.style.height = 'auto';
			if (!!cancelLabel) dial.closeButtonNode.style.display = 'none';
		},
		'prompt': function (msg, title, defaultVal, onEnter, buttons) {
			dojo.require('dijit.Dialog');
			dojo.require('dijit.form.Button');
			dojo.require('dijit.form.TextBox');
			
			if (typeof buttons == "undefined")
				buttons = (pub.ui.buttons.OK | pub.ui.buttons.CANCEL);
			
			if (!title) title = pub.consts.WINDOW_TITLE;
			
			var msgDiv = document.createElement('div');
			var buttDiv = document.createElement('div');
			var inpDiv = document.createElement('div');
			var divs = document.createElement('div');
			
			var okLabel = pub.translations('common', (parseInt(buttons) & pub.ui.buttons.YES) ? 'yes' : 'ok');
			var cancelLabel = ((parseInt(buttons) & pub.ui.buttons.CANCEL) || (parseInt(buttons) & pub.ui.buttons.NO)) ?
				pub.translations('common', (parseInt(buttons) & pub.ui.buttons.NO) ? 'no' : 'cancel') : false;
			
			var dial = new dijit.Dialog({ 'title': title, 'onCancel': function () { return !!cancelLabel; } });
			
			msgDiv.appendChild(document.createTextNode(msg));
			divs.appendChild(msgDiv);
			
			var inp = new dijit.form.TextBox();
			inp.attr('value', (!!defaultVal) ? ('' + defaultVal) : '');
			inp.placeAt(inpDiv);
			divs.appendChild(inpDiv);
			
			(new dijit.form.Button({ 'label': okLabel, 'onClick': function () {
				var val = inp.attr('value');
				dial.destroy();
				try { onEnter(val); } catch(e) {};
			} })).placeAt(buttDiv);
			if (cancelLabel)
				(new dijit.form.Button({ 'label': cancelLabel, 'onClick': function () {
					dial.destroy();
				} })).placeAt(buttDiv);
			divs.appendChild(buttDiv);
			
			dial.attr('content', divs);
			dojo.body().appendChild(dial.domNode);
			dial.show();
			dial.containerNode.style.height = 'auto';
			if (!!cancelLabel) dial.closeButtonNode.style.display = 'none';
		},
		'ask': function (form, title, onAnswer, buttons, scope) {
			dojo.require('dijit.Dialog');
			
			if (typeof buttons == "undefined")
				buttons = (pub.ui.buttons.OK | pub.ui.buttons.CANCEL);
			
			if (typeof scope == "undefined")
				scope = this;
			
			if (!title) title = pub.consts.WINDOW_TITLE;
			var dial = new dijit.Dialog({ 'title': title, 'onCancel': function () { return !!(parseInt(buttons) & pub.ui.buttons.CANCEL); } });
			var div = document.createElement('div');
			var f = document.createElement('form');
			if (!form.id) form.id = pub.generateId();
			f.setAttribute('id', form.id);
			if (form.type == 'form') {
				f.setAttribute('action', !!form.action ? form.action : '?');
				f.setAttribute('method', !!form.method ? form.method : 'get');
				if (!!form.enctype) {
					f.setAttribute('enctype', form.enctype);
					if (!!dojo.isIE) f.setAttribute('encoding', form.enctype);
				}
			}
			if (!!form.label) {
				var l = document.createElement('div');
				f.appendChild(l);
				l.innerHTML = '' + form.label;
				l.style.margin = '0 auto';
				l.style.textAlign = 'center';
			}
			var t = document.createElement('div');
			if (!dojo.isIE || dojo.isIE >= 8) {
				t.setAttribute('style', 'display: table; border: none;');
			}
			var radios = { };
			var arrays = { };
			pub.forEach(form.inputs, function (key, input) {
				if (input.type == 'hidden') {
					var hi = document.createElement('input');
					hi.setAttribute('id', form.id + '_' + input.name);
					hi.setAttribute('name', '' + input.name);
					hi.setAttribute('type', 'hidden');
					hi.setAttribute('value', '' + input.value);
					f.appendChild(hi);
				} else {
					var inputId = form.id + '_' + input.name;
					if (inputId.match(/\[.*\]$/)) {
						var k = inputId.match(/(.*)\[(.*)\]$/);
						if (k[2]) inputId = k[1] + '_' + k[2];
						else {
							if (!(k[1] in arrays)) arrays[k[1]] = 0;
							inputId = k[1] + '_' + arrays[k[1]];
							arrays[k[1]] = arrays[k[1]] + 1;
						}
					}
					if (input.type == 'radio') {
						if (!(input.name in radios)) radios[input.name] = 0;
						inputId = inputId + '_' + radios[input.name];
						radios[input.name] = radios[input.name] + 1;
					}
					var tr = document.createElement('div');
					if (!dojo.isIE || dojo.isIE >= 8) {
						tr.setAttribute('style', 'display: table-row;');
					}
					var td1 = document.createElement('span');
					var lab = document.createElement('label');
					lab.setAttribute('for', inputId);
					td1.appendChild(lab);
					lab.innerHTML = '' + input.label;
					var td2 = document.createElement('span');
					var inp = document.createElement('input');
					inp.setAttribute('id', inputId);
					inp.setAttribute('name', '' + input.name);
					inp.setAttribute('type', '' + input.type);
					if ('value' in input) inp.setAttribute('value', '' + input.value);
					if (!!input.accept) inp.setAttribute('accept', '' + input.accept);
					td2.appendChild(inp);
					if (input.type == 'checkbox' || input.type == 'radio') {
						if (!!input.checked) inp.setAttribute('checked', 'checked');
						if (!dojo.isIE || dojo.isIE >= 8) {
							td2.setAttribute('style', 'display: table-cell; text-align: right; padding-right: 10px;');
							td1.setAttribute('style', 'display: table-cell; text-align: left;');
						} else {
							td2.setAttribute('style', 'padding-right: 10px;');
						}
						tr.appendChild(td2);
						tr.appendChild(td1);
					} else {
						if (!dojo.isIE || dojo.isIE >= 8) {
							td1.setAttribute('style', 'display: table-cell; text-align: right;');
							td2.setAttribute('style', 'display: table-cell; text-align: left;');
						}
						tr.appendChild(td1);
						tr.appendChild(td2);
					}
					t.appendChild(tr);
				}
			});
			
			var okClick = null;
			if (form.type == 'script') {
				okClick = function () {
					var result = { };
					var radios = { };
					var arrays = { };
					pub.forEach(form.inputs, function (key, input) {
						var inputId = form.id + '_' + input.name;
						var inputKey = input.name;
						var inputKey2 = false;
						if (inputId.match(/\[.*\]$/)) {
							var k = inputId.match(/(.*)\[(.*)\]$/);
							if (k[2]) inputId = k[1] + '_' + k[2];
							else {
								if (!(k[1] in arrays)) arrays[k[1]] = 0;
								inputId = k[1] + (k[2] = '_' + arrays[k[1]]);
								arrays[k[1]] = arrays[k[1]] + 1;
							}
							inputKey = input.name.match(/(.*)\[.*\]$/)[1];
							inputKey2 = k[2];
							if (!(inputKey in result)) result[inputKey] = { };
							if (input.type == 'checkbox') {
								result[inputKey][inputKey2] = !!pub.$id(inputId).checked ? (!!pub.$id(inputId).value ? pub.$id(inputId).value : true) : false;
							} else if (input.type == 'radio') {
								var radioKey = inputKey + '_' + inputKey2;
								if (!(radioKey in radios)) radios[radioKey] = 0;
								inputId = inputId + '_' + radios[radioKey];
								radios[radioKey] = radios[radioKey] + 1;
								if (!!pub.$id(inputId).checked)
									result[inputKey][inputKey2] = pub.$id(inputId).value;
							} else {
								result[inputKey][inputKey2] = pub.$id(inputId).value;
							}
						} else {
							if (input.type == 'checkbox') {
								result[inputKey] = !!pub.$id(inputId).checked ? (!!pub.$id(inputId).value ? pub.$id(inputId).value : true) : false;
							} else if (input.type == 'radio') {
								if (!(inputKey in radios)) radios[inputKey] = 0;
								inputId = inputId + '_' + radios[inputKey];
								radios[inputKey] = radios[inputKey] + 1;
								if (!!pub.$id(inputId).checked)
									result[inputKey] = pub.$id(inputId).value;
							} else {
								result[inputKey] = pub.$id(inputId).value;
							}
						}
					});
					dial.destroy();
					onAnswer(result, scope);
				};
			} else {
				okClick = function () {
					if (onAnswer(form, scope)) f.submit();
					// setTimeout(function () { dial.destroy(); }, 666);
					return true;
				};
			}
			
			var tr = document.createElement('div');
			if (!dojo.isIE || dojo.isIE >= 8) {
				tr.setAttribute('style', 'display: table-row;');
			}
			var td1 = document.createElement('span');
			if (!dojo.isIE || dojo.isIE >= 8) {
				td1.setAttribute('style', 'display: table-cell;');
			}
			var okbut = new dijit.form.Button({ 'label': pub.translations('common', 'ok'), 'onClick': okClick });
			okbut.placeAt(td1);
			
			tr.appendChild(td1);
			var td2 = document.createElement('span');
			if (!dojo.isIE || dojo.isIE >= 8) {
				td2.setAttribute('style', 'display: table-cell;');
			}
			if ((parseInt(buttons) & pub.ui.buttons.CANCEL)) {
				var ccbut = new dijit.form.Button({ 'label': pub.translations('common', 'cancel'), 'onClick': function () {
					dial.destroy();
				} });
				ccbut.placeAt(td2);
			}
			tr.appendChild(td2);
			t.appendChild(tr);
			
			f.appendChild(t);
			div.appendChild(f);
			dial.attr('content', div);
			dojo.body().appendChild(dial.domNode);
			dial.show();
			dial.containerNode.style.height = 'auto';
			if (!(parseInt(buttons) & pub.ui.buttons.CANCEL))
				dial.closeButtonNode.style.display = 'none';
		},
		'toolTip': function (htmlo, label) {
			dojo.require('dijit.Tooltip');
			dijit.hideTooltip(htmlo);
			if (!!label) dijit.showTooltip('' + label, htmlo);
		},
		'createToolTip': function (htmlo, label) {
			dojo.require('dijit.Tooltip');
			if (!htmlo.id) htmlo.id = pub.generateId();
			if (label) {
				var tool = new dijit.Tooltip({
					'connectId': [htmlo.id],
					'label': label,
					'showDelay': 0
				});
				htmlo.parentNode.insertBefore(tool.domNode, htmlo.nextSibling);
			}
		},
		'input': {
			'validation': function (htmlo, params) {
				dojo.require('dijit.form.ValidationTextBox');
				htmlo.type = 'hidden';
				var ins = new dijit.form.ValidationTextBox({
					'value': htmlo.value ? htmlo.value : '',
					'intermediateChanges': true,
					'required': (params && typeof params.required != 'undefined') ? params.required : false,
					'regExp': (params && typeof params.regExp != 'undefined') ?
						params.regExp : ((params && params.isEmail) ? '[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+\\.[a-zA-Z]+' : '.*'),
					'invalidMessage': (params && typeof params.invalidMessage != 'undefined') ?
						params.invalidMessage :	(
						(params && params.isEmail) ?
						pub.translations('data', 'Invalid email format') :
						pub.translations('data', 'Invalid data format'))
				});
				if (params) {
					if (typeof params.maxlength != 'undefined')
						ins.maxlength = parseInt(params.maxlength);
				}
				ins.onChange = ins.onBlur = function () { htmlo.value = ins.attr('value'); }
				htmlo.parentNode.insertBefore(ins.domNode, htmlo.nextSibling);
				dojo.connect(ins.domNode, 'onclick', function () { if (htmlo.onclick) htmlo.onclick(); });
				htmlo.onchange = function () { ins.attr('value', htmlo.value); }
			},
			'number': function (htmlo, params) {
				dojo.require('dijit.form.NumberSpinner');
				htmlo.type = 'hidden';
				var ins = new dijit.form.NumberSpinner({
					'value': htmlo.value ? parseFloat(htmlo.value) : '',
					'intermediateChanges': true,
					'required': (params && typeof params.required != 'undefined') ? params.required : false,
					'constraints': {}
				});
				if (params) {
					if (typeof params.min != 'undefined')
						ins.constraints.min = parseFloat(params.min);
					if (typeof params.max != 'undefined')
						ins.constraints.max = parseFloat(params.max);
					if (typeof params.delta != 'undefined')
						ins.smallDelta = parseFloat(params.delta);
					if (typeof params.places != 'undefined')
						ins.constraints.places = parseFloat(params.places);
				}
				ins.onChange = ins.onBlur = function () { htmlo.value = isNaN(ins.attr('value')) ? '' : ins.attr('value'); }
				ins.onClick = function () { if (htmlo.onclick) htmlo.onclick(); }
				htmlo.parentNode.insertBefore(ins.domNode, htmlo.nextSibling);
				htmlo.onchange = function () { ins.attr('value', htmlo.value); }
			},
			'date': function (htmlo, params) {
				dojo.require('dijit.form.DateTextBox');
				htmlo.type = 'hidden';
				var ins = new dijit.form.DateTextBox({
					'value': priv._parseIsoDate(htmlo.value),
					'intermediateChanges': true,
					'required': (params && typeof params.required != 'undefined') ? params.required : false,
					'constraints': {}
				});
				if (params) {
					if (typeof params.min != 'undefined')
						ins.constraints.min = priv._parseIsoDate(params.min);
					if (typeof params.max != 'undefined')
						ins.constraints.max = priv._parseIsoDate(params.max);
				}
				ins.onChange = ins.onBlur = function () { htmlo.value = priv._toIsoDate(ins.attr('value')); }
				ins.onClick = function () { if (htmlo.onclick) htmlo.onclick(); }
				htmlo.parentNode.insertBefore(ins.domNode, htmlo.nextSibling);
				htmlo.onchange = function () { ins.attr('value', priv._parseIsoDate(htmlo.value)); }
			},
			'time': function (htmlo, params) {
				dojo.require('dijit.form.TimeTextBox');
				htmlo.type = 'hidden';
				var ins = new dijit.form.TimeTextBox({
					'value': priv._parseIsoTime(htmlo.value),
					'intermediateChanges': true,
					'required': (params && typeof params.required != 'undefined') ? params.required : false,
					'constraints': {}
				});
				if (params) {
					if (typeof params.min != 'undefined')
						ins.constraints.min = priv._parseIsoTime(params.min);
					if (typeof params.max != 'undefined')
						ins.constraints.max = priv._parseIsoTime(params.max);
				}
				ins.onChange = ins.onBlur = function () { htmlo.value = priv._toIsoTime(ins.attr('value')); }
				ins.onClick = function () { if (htmlo.onclick) htmlo.onclick(); }
				htmlo.parentNode.insertBefore(ins.domNode, htmlo.nextSibling);
				htmlo.onchange = function () { ins.attr('value', priv._parseIsoTime(htmlo.value)); }
			},
			'datetime': function (htmlo, params) {
				dojo.require('dijit.form.DateTextBox');
				dojo.require('dijit.form.TimeTextBox');
				htmlo.style.display = 'none';
				var val = htmlo.value.split(' ');
				var constr = false;
				if (params) {
					constr = {
						'required': (typeof params.required != 'undefined') ? params.required : false
					}
					if (params.min) {
						constr.minDate = priv._parseIsoDate(params.min);
					}
					if (params.max) {
						constr.maxDate = priv._parseIsoDate(params.max);
					}
				}
				var insDate = new dijit.form.DateTextBox({
					'value': val.length > 1 ? priv._parseIsoDate(val[0]) : '',
					'intermediateChanges': true,
					'required': constr ? constr.required : false,
					'constraints': {}
				});
				if (constr && constr.minDate)
					insDate.constraints.min = constr.minDate;
				if (constr && constr.maxDate)
					insDate.constraints.max = constr.maxDate;
				
				var insTime = new dijit.form.TimeTextBox({
					'value': val.length > 1 ? priv._parseIsoTime(val[1]) : '',
					'intermediateChanges': true,
					'required': constr ? constr.required : false,
					'constraints': {}
				});
				
				insDate.onChange = insDate.onBlur = insTime.onChange = insTime.onBlur =	function () {
					if ((insDate.attr('value') instanceof Date) || (insTime.attr('value') instanceof Date))
						htmlo.value = '' +
							(	priv._toIsoDate(insDate.attr('value')) ?
								priv._toIsoDate(insDate.attr('value')) :
								'0000-00-00') + ' ' +
							(	priv._toIsoTime(insTime.attr('value')) ?
								priv._toIsoTime(insTime.attr('value')) :
								'00:00:00');
					else
						htmlo.value = '';
				}
				insDate.onClick = insTime.onClick = function () { if (htmlo.onclick) htmlo.onclick(); }
				htmlo.parentNode.insertBefore(insTime.domNode, htmlo.nextSibling);
				htmlo.parentNode.insertBefore(insDate.domNode, htmlo.nextSibling);
				htmlo.onchange = function () {
					var valu = htmlo.value.split(' ');
					insDate.attr('value', valu.length > 1 ? priv._parseIsoDate(valu[0]) : '');
					insTime.attr('value', valu.length > 1 ? priv._parseIsoTime(valu[1]) : '');
				}
			},
			'colorPalette': function (htmlo, params) {
				dojo.require("dijit.form.Button");
				dojo.require("dijit.ColorPalette");
				htmlo.style.display = 'none';
				var cparams = {};
				if (params && params.palette)
					cparams.palette = params.palette;
				var colPal = new dijit.ColorPalette(cparams);
				var ins = new dijit.form.DropDownButton({
					'iconClass': 'noteIcon',
					'label': (params && params.label) ? params.label : 'Color',
					'dropDown': colPal
				});
				var look = document.createElement('span');
				look.style.position = 'relative';
				look.style.padding = '0 0.6em';
				look.style.backgroundColor = '#' + htmlo.value;
				colPal.onChange = colPal.onBlur = function () {
					htmlo.value = ('' + colPal.attr('value')).substr(1);
					look.style.backgroundColor = colPal.attr('value');
					if (htmlo.onchange) htmlo.onchange();
				}
				ins.onClick = function () { if (htmlo.onclick) htmlo.onclick(); }
				htmlo.parentNode.insertBefore(ins.domNode, htmlo.nextSibling);
				htmlo.parentNode.insertBefore(look, htmlo.nextSibling);
				htmlo.onchange = function () { colPal.attr('value', '#' + htmlo.value); }
			},
			'xColor': function (htmlo, params) {
				pub.addCSS(pub.consts.LIBS_URL + '/dojox/widget/ColorPicker/ColorPicker.css');
				dojo.require('dojox.widget.ColorPicker');
				var cpo = new dojox.widget.ColorPicker();
				cpo.value = '#' + htmlo.value;
				cpo.onChange = function () {
					htmlo.value = cpo.value.substring(1);
				}
				htmlo.parentNode.insertBefore(cpo.domNode, htmlo.nextSibling);
				cpo.domNode.style.display = 'none';
				cpo.domNode.style.position = 'absolute';
				cpo.domNode.style.top = (htmlo.offsetTop + 20) + 'px';
				cpo.domNode.style.left = htmlo.offsetLeft;
				cpo.domNode.style.zIndex = '999';
				htmlo.onfocus = function () {
					cpo.domNode.style.display = 'block';
				}
				htmlo.onblur = cpo.domNode.onblur = function () {
					htmlo._cpo = setTimeout(function() {
						cpo.domNode.style.display = 'none';
						htmlo._cpo = null;
					}, 333);
				}
				cpo.domNode.onfocus = cpo.domNode.onclick = function () {
					if (htmlo._cpo) clearTimeout(htmlo._cpo);
				}
			},
			'_htmlEditorInit': function (id, params, wait) {
				if (typeof tinyMCE == "object") {
					params.mode = 'exact';
					params.elements = id;
					if (!params.theme) params.theme = 'advanced';
					if (!params.plugins) params.plugins = 'paste,noneditable,contextmenu';
					if (params.emotions) params.plugins += ',emotions';
					if (!params.theme_advanced_buttons1)
						params.theme_advanced_buttons1 = 'bold,italic,underline,strikethrough,|,link,unlink,|,undo,redo';
					if (params.emotions) params.theme_advanced_buttons1 += ',|,emotions';
					if (!params.theme_advanced_buttons2)
						params.theme_advanced_buttons2 = '';
					if (!params.theme_advanced_buttons3)
						params.theme_advanced_buttons3 = '';
					if (!params.theme_advanced_toolbar_location)
						params.theme_advanced_toolbar_location = 'top';
					if (!params.theme_advanced_toolbar_align)
						params.theme_advanced_toolbar_align = 'left';
					if (!params.extended_valid_elements)
						params.extended_valid_elements = 'hr[class|width|size|noshade]';
					//if (!params.file_browser_callback)
					//	params.file_browser_callback = 'ajaxfilemanager';
					if (!params.document_base_url)
						params.document_base_url = pub.consts.SITE_URL;
					if (typeof params.paste_use_dialog == 'undefined')
						params.paste_use_dialog = false;
					if (typeof params.theme_advanced_resizing == 'undefined')
						params.theme_advanced_resizing = true;
					if (typeof params.theme_advanced_resize_horizontal == 'undefined')
						params.theme_advanced_resize_horizontal = true;
					if (typeof params.apply_source_formatting == 'undefined')
						params.apply_source_formatting = true;
					if (typeof params.force_br_newlines == 'undefined')
						params.force_br_newlines = true;
					if (typeof params.force_p_newlines == 'undefined')
						params.force_p_newlines = false;
					if (typeof params.relative_urls == 'undefined')
						params.relative_urls = true;
					if (typeof params.remove_script_host == 'undefined')
						params.remove_script_host = true;
					if (typeof params.remember_last_path == 'undefined')
						params.remember_last_path = false;
					if (!params.width) params.width = 350;
					if (!params.height) params.height = 150;
					tinyMCE.init(params);
				} else {
					setTimeout(function () { pub.ui.input._htmlEditorInit(id, params, wait + 125); }, wait + 125);
				}
			},
			'htmlEditor': function (htmlo, params) {
				if (!htmlo.id) htmlo.id = pub.generateId();
				pub.ui.input._htmlEditorInit(htmlo.id, params ? params : { }, 100);
			},
			'richEditor': function (htmlo, params) {
				dojo.require("dijit.Editor");
				htmlo.style.display = "none";
				if (!params) params = {};
				var cparams = {
					'styleSheets': pub.consts.LIBS_URL + '/dojo/resources/dojo.css'
				};
				if (!!params.plugins) {
					dojo.require("dijit._editor.plugins.AlwaysShowToolbar");
					dojo.require("dijit._editor.plugins.EnterKeyHandling");
					dojo.require("dijit._editor.plugins.TextColor");
					dojo.require("dijit._editor.plugins.LinkDialog");
					cparams.plugins = params.plugins;
				}
				var editor = new dijit.Editor(cparams);
				editor.attr('value', htmlo.value);
				editor.onChange = editor.onBlur = function () {
					htmlo.value = editor.attr('value');
					if (htmlo.onchange) htmlo.onchange();
				}
				htmlo.parentNode.insertBefore(editor.domNode, htmlo.nextSibling);
				if (params.width) editor.domNode.style.width = params.width;
				if (params.height) editor.domNode.style.height = params.height;
			}
		}
	};
	
	pub.sendForm = function (form, onLoadFunc) {
		var ifr = document.createElement('iframe');
		ifr.id = pub.generateId();
		ifr.setAttribute('style', 'display: none;');
		ifr.setAttribute('name', ifr.id);
		form.setAttribute('target', ifr.id);
		pub.$tag('body').appendChild(ifr);
		form.submit();
		ifr.onload = function () {
			var oDoc = (ifr.contentWindow || ifr.contentDocument);
			if (oDoc.document) oDoc = oDoc.document;
			if (!!onLoadFunc) onLoadFunc(oDoc.body.innerHTML);
			//pub.$tag('body').removeChild(ifr);
		};
	}
	
	priv._declareTreeEditor = function () {
		dojo.require('dijit.Tree');
		dojo.require('dijit.form.TextBox');
		dojo.declare('dijit._TreeEditor', dijit.Tree, {
			'editing': false,
			'postCreate': function () {
				this.inherited('postCreate', arguments);
				dojo.connect(this.domNode, 'ondblclick', this, this._onDblClick);
			},
			'_onKeyPress': function (e) {
				if (this.tree.editing) return;
				this.inherited(arguments);
			},
			'onClick': function (e) {
				if (this.tree.editing) return;
				this.inherited(arguments);
			},
			'_onDblClick': function (e) {
				var domElement = e.target;
				var nodeWidget = dijit.getEnclosingWidget(domElement);
				if (!nodeWidget || !nodeWidget.isTreeNode || this.editing) return;
				this.editing = true;
				var labelNode = nodeWidget.labelNode;
				
				var editor = new dijit.form.TextBox({
					'tree': this,
					'value': labelNode.innerHTML,
					'onChange': function () {
						var val = editor.attr('value');
						this.tree.model.store.setValue(nodeWidget.item, 'title', [val]);
						this.tree.onLabelChange(nodeWidget.item);
					},
					'onBlur': function () {
						this.tree.editing = false;
						labelNode.innerHTML = editor.attr('value');
						!!editor && editor.domNode.parentNode.removeChild(editor.domNode);
					},
					'onKeyDown': function (e) {
						if (e.keyCode == '13' || e.keyCode == '10') {
							e.target.blur();
						}
					}
				});
				labelNode.parentNode.insertBefore(editor.domNode, labelNode.nextSibling);
				labelNode.innerHTML = '';
				editor.domNode.focus();
			}
		});
	}
	
	priv._declareAdvancedTreeEditor = function () {
		dojo.require('dijit.Tree');
		dojo.require('dijit.form.TextBox');
		dojo.declare('dijit._AdvancedTreeEditor', dijit.Tree, {
			'editing': false,
			'postCreate': function () {
				this.inherited('postCreate', arguments);
				dojo.connect(this.domNode, 'ondblclick', this, this._onDblClick);
			},
			'onClick': function (e) {
				if (this.tree.editing) return;
				this.inherited(arguments);
			},
			'setNodeLabel': function (node, label) {
				this.tree.model.store.setValue(node.item, 'title', [label]);
				node.labelNode.innerHTML = '' + label;
			},
			'_onDblClick': function (e) {
				var domElement = e.target;
				var nodeWidget = dijit.getEnclosingWidget(domElement);
				if (!nodeWidget || !nodeWidget.isTreeNode || this.editing) return;
				this.editing = true;
				this.onDblClick(e, nodeWidget);
				var tree = this;
				setTimeout(function () {
					tree.editing = false;
				}, 500);
			}
		});
	}
	
	pub.types = {
		'Xhr': function () {
			try {
				this.xhr = new XMLHttpRequest();
				this.valid = true;
			} catch (e) { try {
				this.xhr = new ActiveXObject('Msxml2.XMLHTTP');
				this.valid = true;
			} catch (e) { try {
				this.xhr = new ActiveXObject('Microsoft.XMLHTTP');
				this.valid = true;
			} catch (e) {
				this.xhr = null;
				this.valid = false;
			} } }
		},
		'XmlParser': function () {
			/// xml.async
			/// xml.load (file)
			/// xml.loadXML (string)
			try {
				this.xml = new ActiveXObject('Microsoft.XMLDOM');
				this.valid = true;
			} catch(e) {
				try {
					this.xml = document.implementation.createDocument('', '', null);
					this.valid = true;
				} catch(e) {
					this.xml = null;
					this.valid = false;
				}
			}
		},
		'MenuBar': function (parent, id, label, onClick) {
			dojo.require('dijit.Menu');
			dojo.require('dijit.Toolbar');
			dojo.require('dijit.form.Button');
			
			this._id = id;
			this._parent = typeof (parent) == 'object' ? parent : null;
			this._root = parent ? false : true;
			
			var root = parent;
			while (root._parent) root = root._parent;
			
			if (this._root) {
				this._obj = new dijit.Toolbar();
				this.draw = function (htmlo) {
					this._obj.placeAt(htmlo);
				}
			} else {
				if (onClick) {
					if (this._parent._root) {
						this._obj = new dijit.form.Button({ 'id': mayaFish.makeId(id), 'label': label });
					} else {
						this._obj = new dijit.MenuItem({ 'id': mayaFish.makeId(id), 'label': label });
					}
					this._obj.onClick = onClick;
				} else {
					this._menu = new dijit.Menu({
						'class': 'menubar_' + mayaFish.makeSymbol(root._id)
					});
					if (this._parent._root) {
						this._obj = new dijit.form.DropDownButton({ 'id': mayaFish.makeId(id), 'label': label, 'dropDown': this._menu });
					} else {
						this._obj = new dijit.PopupMenuItem({ 'id': mayaFish.makeId(id), 'label': label, 'popup': this._menu });
					}
				}
				if (this._parent._root) {
					this._parent._obj.addChild(this._obj);
				} else {
					this._parent._menu.addChild(this._obj);
				}
			}
		},
		'Gallery': function (urlPrefix) {
			pub.addCSS(pub.consts.LIBS_URL + '/dojox/image/resources/image.css');
			dojo.require('dojox.image.Gallery');
			dojo.require('dojo.data.ItemFileReadStore');
			
			this._urlPrefix = '' + urlPrefix;
			
			this._data = {
				'identifier': 'id',
				'label': 'title',
				'items': []
			};
			
			this.addImage = function (thumb, large, image, title, id) {
				var max = 0;
				if (this._data.items && this._data.items.length)
					max = this._data.items.length;
				if (!id) id = max;
				
				this._data.items[max] = {
					'id': id,
					'title': title,
					'thumb': this._urlPrefix + thumb,
					'large': this._urlPrefix + large,
					'link': this._urlPrefix + image
				};
			};
			
			this.draw = function (htmlo) {
				this._dataStore = new dojo.data.ItemFileReadStore({ 'data': this._data });
				this._galleryWidget = new dojox.image.Gallery();
				this._galleryWidget.setDataStore(this._dataStore, {
					'query': {}
				}, {
					'imageThumbAttr': 'thumb',
					'imageLargeAttr': 'large'
				});
				this._galleryWidget.placeAt(htmlo);
				this._galleryWidget.startup();
			};
		},
		'LinearGradient': function (obj, x1, y1, x2, y2, colors, params) {
			dojo.require('dojo._base.Color');
			dojo.require('dojox.gfx._base');
			dojo.require('dojox.gfx.shape');
			dojo.require('dojox.gfx.path');
			dojo.require('dojox.gfx.arc');
			dojo.require('dojox.gfx');
			dojo.require('dojo.colors');
			
			var linear = {
			    'type': 'linear',
			    'x1': x1, 'y1': y1,
			    'x2': x2, 'y2': y2,
			    'colors': colors
			};
			
			var width = obj.offsetWidth;
			var height = obj.offsetHegiht;
			
			if (typeof params == 'object') {
				if (params.width) width = params.width;
				if (params.height) height = params.height;
			}
			
			var surface = dojox.gfx.createSurface(obj, width, height);
			var group = surface.createGroup();
			var rectParams = {
				'width':  width,
				'height': height
			};
			if (typeof params == 'object') {
				if (params.round) rectParams.r = params.round;
			}
			var rect = surface.createRect(rectParams);
			rect.setFill(linear);
			if (typeof params == 'object') {
				if (params.stroke) rect.setStroke(params.stroke);
			}
			group.add(rect);
		},
		'Tree': function (params /** [id], [forest], [ordered], [onRename] */) {
			dojo.require("dojo.data.ItemFileWriteStore");
			if (!!params.forest) dojo.require("dijit.tree.ForestStoreModel");
			else dojo.require("dijit.tree.TreeStoreModel");
			dojo.require("dijit.Tree");
			
			this.id = '' + params.id;
			if (!this.id) this.id = this.generateId();
			this._data = {
				'identifier': 'id',
				'label': 'title',
				'items': [ ]
			};
			
			this._forest = !!params.forest;
			this._ordered = !!params.ordered;
			this._onRename = !!params.onRename ? params.onRename : null;
			this._onDblClick = !!params.onDblClick ? params.onDblClick : null;
			
			this.addNode = function (pars /** parent, title, [id], [data], [position /node, at/] */) {
				var max = 0;
				if (this._data.items && this._data.items.length)
					max = this._data.items.length;
				if (!pars.id) pars.id = max;
				var pos = max;
				var nodeVals = {
					'id': pars.id,
					'title': pars.title,
					'type': 'leaf',
					'parent': pars.parent,
					'data': pars.data,
					'mayHaveChildren': typeof pars.mayHaveChildren == "undefined" ? true : !!pars.mayHaveChildren
				};
				
				if (pars.position) {
					this._data.items = pub.arrayInsert(this._data.items, nodeVals, function (item) {
						if (item.id == pars.position.node)
							return pars.position.at;
						else return false;
					});
				} else {
					this._data.items[pos] = nodeVals;
				}
				
				
				if (typeof pars.parent != "undefined" && pars.parent != null) {
					for (var i = 0; i < max; i++) {
						if (this._data.items[i] && (this._data.items[i].id == pars.parent)) {
							if (this._data.items[i].children) {
								if (pars.position) {
									this._data.items[i].children = pub.arrayInsert(
										this._data.items[i].children, { '_reference': pars.id }, function (child) {
											if (child._reference == pars.position.node)
												return pars.position.at;
											else return false;
										});
								} else {
									var imax = this._data.items[i].children.length;
									this._data.items[i].children[imax] = { '_reference': pars.id };
								}
							} else {
								this._data.items[i].children = [{ '_reference': pars.id }];
								if (this._data.items[i].type == 'leaf')
									this._data.items[i].type = 'spring';
							}
						}
					}
				} else {
					this._data.items[pos].type = 'top';
				}
				
				return pars.id;
			}
			
			this.removeNode = function (removeId) {
				var tree = this;
				var node = null;
				var data = {
					'identifier': 'id',
					'label': 'title',
					'items': [ ]
				};
				var added = [null];
				pub.forEach(this._data.items, function (key, item) {
					if (item.id != removeId && (typeof item.parent == 'undefined' || pub.inArray(added, item.parent))) {
						added.push(item.id);
						data.items.push({
							'id': item.id,
							'title': item.title,
							'type': item.type,
							'parent': item.parent,
							'data': item.data,
							'mayHaveChildren': item.mayHaveChildren
						});
						var id = item.id;
						var parent = item.parent;
						pub.forEach(data.items, function (key, item) {
							if (item.id == parent) {
								if (!data.items[key].children) data.items[key].children = [ ];
								data.items[key].children.push({ '_reference': id });
							}
						});
					}
				});
				this._data = data;
			}
			
			this.relocateNode = function (node /** id, parent, [to, at] */) {
				var tree = this;
				var data = pub.arrayRemove(this._data.items, function (item) {
					return item.id == node.id;
				});
				
				if (!!data.elem) {
					pub.forEach(data.array, function (key, item) {
						if (item.id == data.elem.parent) {
							data.array[key].children = pub.arrayRemove(data.array[key].children, function (child) {
								return child._reference == node.id;
							}).array;
						}
						if (item.id == node.parent) {
							if (!data.array[key].children) data.array[key].children = [ ];
							if (node.to && node.at) {
								data.array[key].children = pub.arrayInsert(data.array[key].children,
									{ '_reference': node.id }, function (child) {
										if (child._reference == node.to)
											return node.at;
										else return false;
									});
							} else
								data.array[key].children.push({ '_reference': node.id });
						}
					});
					if (node.to && node.at) {
						data.array = pub.arrayInsert(data.array, data.elem, function (el) {
							if (el.id == node.to) return node.at;
							else return false;
						});
					} else {
						data.array.push(data.elem);
					}
					this._data.items = data.array;
				}
			}
			
			this.draw = function (htmlo) {
				this._dataStore = new dojo.data.ItemFileWriteStore({ 'data': pub.clone(this._data) });
				
				if (this._forest) {
					this._model = new dijit.tree.ForestStoreModel({
						'store': this._dataStore,
						'childrenAttrs': ['children'],
						'query': { 'type': 'top' },
						'labelAttr': 'title',
						'typeAttr': 'type',
						'rootId': '0',
						'rootLabel': 'root',
						'mayHaveChildren': function (item) {
							return !!item.mayHaveChildren && !!item.mayHaveChildren[0];
						}
					});
				} else {
					this._model = new dijit.tree.TreeStoreModel({
						'store': this._dataStore,
						'childrenAttrs': ['children'],
						'query': { 'type': 'top' },
						'labelAttr': 'title',
						'typeAttr': 'type',
						'mayHaveChildren': function (item) {
							return !!item.mayHaveChildren && !!item.mayHaveChildren[0];
						}
					});
				}
				
				var treeParams = {
					'id': this.id,
					'model': this._model,
					'openOnClick': true
				};
				
				if (this._forest)
					treeParams.showRoot = false;
				
				if (!!this._onDblClick) {
					if (!dijit._AdvancedTreeEditor) priv._declareAdvancedTreeEditor();
					treeParams.onDblClick = this._onDblClick;
					this._treeWidget = new dijit._AdvancedTreeEditor(treeParams);
				} else if (!!this._onRename) {
					if (!dijit._TreeEditor) priv._declareTreeEditor();
					treeParams.onLabelChange = this._onRename;
					this._treeWidget = new dijit._TreeEditor(treeParams);
				} else {
					this._treeWidget = new dijit.Tree(treeParams);
				}
				
				this._treeWidget.placeAt(htmlo);
				this._treeWidget.startup();
			}
			
			this.remove = function () {
				this._treeWidget.destroy();
			}
			
			this._getItemByNode = function (mainBranch, node) {
				if (mainBranch.contentNode == node)
					return mainBranch.item;
				
				var branches = mainBranch.getChildren();
				var ret;
				for (var i = 0; i < branches.length; i++) {
					if (ret = this._getItemByNode(branches[i], node))
						return ret;
				}
				return false;
			};
			
			this.getItemByNode = function (node) {
				return this._getItemByNode(this._treeWidget.rootNode, node);
			};
			
			this.repair = function (mainBranch) {
				if (!mainBranch) {
					mainBranch = this._treeWidget.rootNode;
					mainBranch.item.type = 'top';
					mainBranch.item.parent = '';
				} else {
					mainBranch.item.type = 'spring';
				}
				var branches = mainBranch.getChildren();
				for (var i = 0; i < branches.length; i++) {
					branches[i].item.parent = mainBranch.item.id;
					if (!!params.forest && (mainBranch.item.id == '0'))
						branches[i].item.type = 'top';
					this.repair(branches[i]);
				}
				if (!branches)
					mainBranch.item.type = 'leaf';
			}
		}
		
	}
	
	priv.dnd = {
		'_inited': false,
		'_connected': [ ],
		'_areas': { },
		'_init': function () {
			dojo.subscribe('/dnd/drop', function (source, nodes, copy) {
				var target = dojo.dnd.manager().target;
				var sid = (source && source.node) ? source.node.id :
					((source && source.domNode) ? source.domNode.id : null);
				var tid = (target && target.node) ? target.node.id :
					((target && target.domNode) ? target.domNode.id : null);
				var n = [ ]; var p = { };
				if (source.tree) {
					pub.forEach(nodes, function (key, it) {
						n.push(dijit.getEnclosingWidget(it));
					});
				} else {
					n = nodes;
				}
				if (target.tree) {
					p.dropPosition = target.dropPosition;
					p.dropAt = dijit.getEnclosingWidget(target.targetAnchor);
				}
				var called = false;
				var dndAnswer = true;
				pub.forEach(priv.dnd._connected, function (key, it) {
					if (it.source == sid && it.target == tid) {
						called = true;
						dndAnswer = it.dndFunc(n, p);
						return pub.BREAK;
					}
				});
				if (!called) pub.debug('mayaFish.dnd: Uncovered dnd; from: ' + sid + '; to: ' + tid);
			});
			dojo.subscribe('/dnd/source/over', function (source) {
				var target = dojo.dnd.manager().target;
				var sid = (source && source.node) ? source.node.id :
					((source && source.domNode) ? source.domNode.id : null);
				var tid = (target && target.node) ? target.node.id :
					((target && target.domNode) ? target.domNode.id : null);
				if (!!priv.dnd._areas[sid] && !!priv.dnd._areas[tid])
					dojo.dnd.manager().canDrop(!pub.inArray(
						priv.dnd._areas[tid]._accept,
						priv.dnd._areas[sid]._provide
					));
			});
			priv.dnd._inited = true;
		}
	};
	
	pub.dnd = {
		'Area': function (o, params /** [targetOnly], [copy], [accept], [provide], [creator] */) {
			dojo.require('dojo.dnd.Source');
			if (!priv.dnd._inited) priv.dnd._init();
			
			this._isDnd = true;
			this._obj = o;
			if (!o.id) o.id = pub.generateId();
			this._id = o.id;
			this._targetOnly = params ? (!!params.targetOnly) : false;
			this._copy = params ? (!!params.copy) : false;
			this._accept = params && (params.accept instanceof Array) ? params.accept : ['text'];
			this._provide = params && !!params.provide ? '' + params.provide : 'text';
			this._creator = params && !!params.creator ? params.creator : function (str) {
				return { 'text': str, 'data': str };
			};
			this.blocked = false;
			this.block = function () { this._blocked = true; };
			this.release = function () { this._blocked = false; };
			
			var selfAccept = pub.inArray(this._accept, this._provide);
			var area = this;
			
			if (o && !!o._treeWidget) {
				dojo.require('dijit._tree.dndSource');
				
				this._isTree = true;
				var dndClass = this._targetOnly ? dijit._tree.dndTarget : dijit._tree.dndSource;
				this._dndSrc = new dndClass(o._treeWidget, {
					'accept': area._accept,
					'type': area._provide,
					'allowBetween': !!o._ordered,
					'betweenThreshold': o._ordered ? 5 : 0,
					'copyOnly': area._copy,
					'selfAccept': selfAccept,
					'checkAcceptance': function (source, nodes) {
						return !area.blocked && !!this.accept[source.type];
					},
					'deleteSelectedNodes': function () {
						this._removeSelection();
						return this;
					},
					'checkItemAcceptance': function (node, source, pos) {
						var titem = dijit.getEnclosingWidget(node).item;
						var snTop = o._forest || ((!!source.tree) ? dijit.getEnclosingWidget(source.anchor).item.type != 'top' : true);
						return !area.blocked && (
							!!titem.mayHaveChildren && !!titem.mayHaveChildren[0] || pos != 'over'
						) && snTop;
					}
				});
				
				if (o._forest) {
					o._model.onAddToRoot = function (item) {
						o._dataStore.setValue(item, 'type', ['top']);
						o._dataStore.setValue(item, 'parent', ['0']);
						o._model._requeryTop();
						o._treeWidget.buildRendering();
						return true;
					};
					
					o._model.onLeaveRoot = function (item) {
						o._dataStore.setValue(item, 'type', [(item.children &&
							item.children.length > 0) ? 'spring' : 'leaf']);
						o._model._requeryTop();
						o._treeWidget.buildRendering();
						return true;
					};
				}
				
			} else {
				var dndClass = this._targetOnly ? dojo.dnd.Target : dojo.dnd.Source;
				this._dndSrc = new dndClass(o, {
					'accept': area._accept,
					'type': area._provide,
					'copyOnly': area._copy,
					'selfAccept': selfAccept,
					'creator': function (item, hint) {
						var c = area._creator('' + item);
						var n = document.createElement('div'); n.innerHTML = c.text;
						var d = document.createElement('div'); d.innerHTML = c.data;
						return {
							'node': n,
							'data': d,
							'type': [area._provide]
						};
					},
					'deleteSelectedNodes': function () {
						this._removeSelection();
						return this;
					},
					'checkAcceptance': function (source, nodes) {
						return !area.blocked && !!this.accept[source.type];
					}
				});
			}
			this._dndSrc.startup();
			priv.dnd._areas[this._id] = this;
		},
		'connect': function (source, target, onDnd) {
			if (!source._isDnd) source = new pub.dnd.Area(source, { });
			if (!target._isDnd) target = new pub.dnd.Area(target, { });
			
			priv.dnd._connected.push({
				'source': source._id,
				'target': target._id,
				'dndFunc': onDnd
			});
		}
	};
	
	return pub;
})();

