var al = {
	version: '2.46'
}

/*** these (al.*) methods/objects use neither jQuery nor other 3rd-party library, 
so you can use them in frontends, too. ***/


// object class checking
Object.checkClass=function(obj, className)
{
	return !isEmpty(obj) && (obj.__className==className);
}

Object.isClass=function(obj)
{
	return typeof(obj)=='object' && isEmpty(obj.__className);
}


Number.prototype.__className='Number';
Number.isClass=function(obj)
{
	return Object.checkClass(obj, 'Number');
}

Boolean.prototype.__className='Boolean';
Boolean.isClass=function(obj)
{
	return Object.checkClass(obj, 'Boolean');
}

Function.prototype.__className='Function';
Function.isClass=function(obj)
{
	return Object.checkClass(obj, 'Function');
}

String.prototype.__className='String';
String.isClass=function(obj)
{
	return Object.checkClass(obj, 'String');
}

Array.prototype.__className='Array';
Array.isClass=function(obj)
{
	return Object.checkClass(obj, 'Array');
}



// get object property with prefix
Object.getProperty=function(obj, propName, prefix)
{
	if (prefix)
	{
		propName=prefix+propName.charAt(0).toUpperCase()+propName.substr(1);
	}
	
	return obj[propName];
}


Object.getMethod=function(obj, methodName, prefix)
{
	var method=Object.getProperty(obj, methodName, prefix);
	return Function.isClass(method) ? method : null; 
}


Object.applyProperties=function(obj, srcObj)
{
	al.Data.each(srcObj, function(value, key) {
		obj[key]=value;
	});
	
	return obj;
}


String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,'');
}


String.prototype.startsWith=function(str)
{
	return this.indexOf(str)==0;
}


String.prototype.endsWith=function(str)
{
	return this.lastIndexOf(str)+str.length==this.length;
}


String.prototype.contains=function(str)
{
	return this.indexOf(str)>=0;
}


String.prototype.pad=function(padding, cnt, after)
{
	after=Function.getArg(after, false);
	
	var str=this;
	for (var i=0;i<cnt;i++)
	{
		if (after)
		{
			str+=padding;
		}
		else
		{
			str=padding+str;
		}
	}
	
	return str;
}


//split str1<separator>str2 into 2 element array (finds only first/last occurrence of <separator>)
String.prototype.split2=function(separator, last)
{
	last=Function.getArg(last,false);
	var pos=last ? this.lastIndexOf(separator) : this.indexOf(separator);
	var arr=[];
	if (pos>0)
	{
		arr.push(this.substr(0,pos));
		arr.push(this.substr(pos+1));
	}
	else
	{
		arr.push(this);
	}
	
	return arr;
}


//verify, if string should be JSON sequence
String.prototype.isJSON=function()
{
	var str=this.trim();
	var c1=str.charAt(0);
	var c2=str.charAt(str.length-1);
	return str.length>1 && ((c1=='{' && c2=='}') || (c1=='[' && c2==']') || (c1=='"' && c2=='"') || (c1=="'" && c2=="'"));
}


// evaluate JSON and return data, if string represents JSON sequence
String.getJSON=function(str)
{
	return str.isJSON && str.isJSON() ? eval('('+str+')') : str;
}

String.prototype.getJSON=function()
{
	return String.getJSON(this);
}


// decimal-separator aware conversion function
String.prototype.toDecimal=function()
{
  var result={};
  
  strValue=this.trim();
  
  if (strValue.length==0)
  {
  	strValue='0';
	}
	else if (strValue!=strValue*1)
	{
		return null;
	}
	  
	if (strValue.contains(','))
	{ 
		strValue.replace(',','.');
		result.decSeparator=',';
	}
	else
	{
		result.decSeparator='.';
	}	
	
	result.value=parseFloat(strValue);
	if (isNaN(result.value))
	{
		return null;
	}
	
	return result;
}


// decimal-separator aware conversion function
String.fromDecimal=function(decimalValue)
{
	var strValue=decimalValue.value.toString();
	return strValue.replace('.', decimalValue.decSeparator);
}


String.prototype.escape=function(isUri)
{
	isUri=Function.getArg(isUri, false);
	return isUri ? encodeURI(this) : encodeURIComponent(this);
}


String.prototype.unescape=function(isUri)
{
	isUri=Function.getArg(isUri, false);
	return (isUri ? decodeUri(this) : decodeURIComponent(this)).replace(/\+/g, ' ');
}



Array.prototype.copy=function()
{
	return this.slice(0);
}


Array.prototype.spliceCopy=function()
{
	var result=this.copy();
	result.splice.apply(result, arguments);
	return result;
}


// make array from value
Array.make=function(value)
{
	return (Array.isClass(value) || !isSet(value)) ? value: [value];
}



// if argument is null or undefined, return default value, otherwise return argument or its new-value
Function.getArg=function(arg, defValue, newValue, checkNull)
{
	checkNull= isSet(checkNull) ? checkNull : true;
	if (!isSet(arg) || (checkNull && isNull(arg)))
	{
		return defValue;
	}
	else
	{
		return isSet(newValue) ? newValue : arg;
	}
}


// exception-free function call
Function.prototype.cleanCall=function()
{
	try 
	{
		return arguments.callee.apply(this, arguments);
	}
	catch(e)
	{
	}
}



al.isSet=function(v)
{
	try 
	{
		return v!==undefined;
	}
	catch(e)
	{
		return false;
	}
}
isSet=al.isSet;


al.isNull=function(v)
{
	return v===null;
}
isNull=al.isNull;


al.isEmpty=function(v, checkLength, lengthProp)
{
	checkLength=Function.getArg(checkLength, false);
	lengthProp=Function.getArg(lengthProp, 'length');
	return !isSet(v) || isNull(v) || (checkLength && (v=={} || v[lengthProp]==0)); 
}
isEmpty=al.isEmpty;



// debugging routines
al.Debug = {
	varDump: function(data, msg, showPrototype, levelCnt, level)
	{
		var LEVEL_INDENT='   ';
		
		msg=Function.getArg(msg,'');
		showPrototype=Function.getArg(showPrototype, false);
		levelCnt=Function.getArg(levelCnt, 4);
		level=Function.getArg(level, 0);
			
		var dataMsg='';
		if (!isSet(data))
		{
			dataMsg='undefined';
		}
		else if (String.isClass(data))
		{
			dataMsg='"'+data+'"';
		}
		else if (Object.isClass(data) || Array.isClass(data))
		{
			if (level>levelCnt-1)
			{
				dataMsg = Array.isClass(data) ? '<array>' : '<object>';
			}
			else
			{
				level++;
				if (data==null)
				{
					dataMsg='null';
				}
				else
				{
					var arr=[];
					if (al.Script.isJQuery() && data instanceof jQuery)
					{
						data.each(function(){
							arr.push(this.id ? this.id : '<none>');
						});
					}
					else
					{
						var objType = isSet(data.length) ? '[]' : '{}';
						
						var pad='\r\n'.pad(LEVEL_INDENT, level, true);
									
						dataMsg+= objType.charAt(0)+' ';
						al.Data.each(data, function(value, key){
							try
							{
								arr.push(pad + key + ': ' + varDump(value, false, showPrototype, levelCnt, level));
							}
							catch(e)
							{
								return false;
							};
						}, null, true, showPrototype);
					}
				
					dataMsg+=arr.join(',')+'\r\n'.pad(LEVEL_INDENT, level-1, true)+objType.charAt(1);
				}
			}
		}
		else 
		{
			if (data.toString)
			{
				dataMsg=data.toString();
			}
			else
			{
				dataMsg='<unknown>'
			}
		}
		
		if (String.isClass(msg))
		{
			alert(msg=='' ? dataMsg : msg+ ': '+ dataMsg);
		}
		return dataMsg;
	},
	
	
	domDump: function(domObject, msg)
	{
		return varDump(domObject, msg, true, al.DOM.isCollection(domObject) ? 2 : 1);
	}
}
varDump=al.Debug.varDump;
domDump=al.Debug.domDump;



al.Data = {
	executeQuery: function(query, queryContainer, defaultResult, userConfig)
	{
		return al.Data.parseQuery(query, queryContainer, defaultResult, userConfig).call();
	},
		
	parseQuery: function(query, queryContainer, defaultResult, userConfig)
	{
		var CONFIG = {
			negationOperator: '!',
			methodSeparator: '.',
			
			typeFilter: 'filter',
			typeEach: 'each',
			typeGet: 'get'
		}
		
		if (!query) 
		{
			return null;
		}	
		
		Object.applyProperties(CONFIG, userConfig);
				
		if (Function.isClass(query))
		{
			var callback=query;
		}
		else if (typeof(query)=='object')
		{
			queryContainer=Function.getArg(queryContainer, al.Data);
			
			var methods=[];
			al.Data.each(query, function(args, methodName) {
				if (methodName.startsWith(CONFIG.negationOperator))
				{
					methodName=methodName.substr(1);
					var negation=true;
				}
				else
				{
					var negation=false;
				}
				
				var arr=methodName.split2(CONFIG.methodSeparator, true);
				var obj=arr[0];
				var methodName=arr[1];
				
				if (!methodName)
				{
					methodName=obj;
					obj=null;				
				}
				
				if (obj)
				{
					obj=obj.substr(0,1).toUpperCase()+obj.substr(1);
				}
				
				var container = obj ? queryContainer[obj] : queryContainer;
			
				if (container)
				{
					var method=container[methodName];
						if (method)
						{
							methods.push({
										container:container, 
										method: method,
										args: args ? args : [], 
										negation: negation});
						}
				}
			});
			
			function callback(el, index, level)
			{
				var result=defaultResult;
			
				al.Data.each(methods, function(method, key)
				{
					var subResult=undefined;
					//subResult=this.method.apply(el, this.args.spliceCopy(this.args.length, 0, el, index, level));
					subResult=this.method.apply(el, this.args);
					
					if (this.container.__type==CONFIG.typeFilter)
					{
						if (this.negation ? subResult==true : subResult==false)
						{
							result=false;
							return false;
						}
						else if (isNull(subResult))
						{
							result=null;
							return false;
						}
					}
					else if (this.container.__type==CONFIG.typeGet)
					{
						result=subResult;
						return false;
					}
					else if (this.container.__type==CONFIG.typeEach)
					{
						if (subResult==false)
						{
							result=false;
							return false;
						}
					}
				});
				
				return result;
			}
		}
		else 
		{
			var callback=null;
		}
		
		return callback;
	},

	Filter: {__type: 'filter'},
	Each: {__type: 'each'},
	Get: {__type: 'get'},
	
	
	// iterate (with recursion, filtering & getting) through object
	iterate: function(obj, eachQuery, filterQuery, getQuery, recursion, includePrototype, lengthProp, level)
	{
		if (!isSet(obj))
		{
			return;
		}
			
		eachQuery=Function.getArg(al.Data.parseQuery(eachQuery), function(){});
		filterQuery=Function.getArg(al.Data.parseQuery(filterQuery, null, true), function(){return true;});
		getQuery=Function.getArg(al.Data.parseQuery(getQuery), function(){return this;});
		
		recursion=Function.getArg(recursion, false);
		includePrototype=Function.getArg(includePrototype, false);
		lengthProp=Function.getArg(lengthProp, 'length');
		level=Function.getArg(level, 0);
		
		function doIteration(obj, index)
		{		
			if (filterQuery.call(obj, obj, index, level))
			{
				var gotObj=getQuery.call(obj, obj, index, level);
				if (eachQuery.call(gotObj, gotObj, index, level)==false) 
				{
					return false;
				}
			}
			else
			{
				if (recursion && typeof(obj)=='object' && obj && al.Data.iterate((recursion==true) ? obj : obj[recursion], eachQuery, filterQuery, getQuery, recursion, includePrototype, lengthProp, level+1)==false) 
				{
					return false;
				}
			}
		}
		
		if (typeof(obj)=='object' && obj)
		{
			if (!isSet(obj[lengthProp]))
			{
				for (var i in obj)
				{
					
					var skip=false;
					
					if (!includePrototype) 
					{
						// skip prototyped properties
						for (var j in Object.prototype)
						{
							if (j==i) 
							{
								skip=true;
								break;
							}
						}
					}
					
					// do iteration
					if (!skip && doIteration(obj[i],i)==false) 
					{
						return false;
					}
				}
			}
			else
			{
				for (var i=0; i<obj[lengthProp]; i++)
				{
					if (doIteration(obj[i],i)==false) 
					{
						return false;
					}
				}
			}
		}
		else
		{
			if (doIteration(obj, 0)==false)
			{
				return false;
			}
		}
	},
	
		
	// do something for each (filtered) element of object
	each: function(obj, eachQuery, filterQuery, recursion, includePrototype, lengthProp)
	{
		al.Data.iterate(obj, eachQuery, filterQuery, null, recursion, includePrototype, lengthProp);
	},
	

	// get (filtered) set of elements from collection 
	get: function(obj, filterQuery, getQuery, recursion, includePrototype, iterator, lengthProp)
	{
		iterator=Function.getArg(iterator, al.Data.iterate);
		
		var result=[];
		iterator(obj, function(){result.push(this);}, filterQuery, getQuery, recursion, includePrototype, lengthProp);
		return result.length>0 ? result : null;
	},


	// get one (filtered) indexed element from collection
	getOne: function(obj, filterQuery, index, getQuery, recursion, includePrototype, iterator, lengthProp)
	{
		index=Function.getArg(index, 0);
		iterator=Function.getArg(iterator, al.Data.iterate);
		
		var currentIndex=-1;
		var result=null;
				
		return iterator(obj, function() {result=this; return false;},
			
			function(el, index, level) {
				currentIndex++;
				if (currentIndex>index)
				{
					return null;
				}
				else
				{
					return filterQuery.call(el, el, index, level);
				}
			}, 
		
			getQuery, recursion, includePrototype, lengthProp
		);
		
		return result;
	},
	
	
	// get unique elements from array
	getUnique: function(array, getQuery, recursion, includePrototype, iterator, lengthProp)
	{
		iterator=Function.getArg(iterator, al.Data.iterate);
		
		var removed=[];
		var unique=[];
		
		if (!Array.isClass(array))
		{
			return array;
		}
		
		// I.) iterate every element from array
		iterator(array,	function(){unique.push(this);}, 
			
			function(keyElement) {					
				var found=false;
				
				// II.) compare it with other (non-removed) elements
				iterator(array, 
				
						// III.) do it recursively
						function(searchRoot) {	
							iterator(searchRoot, 
								function(searchElement){found=true; return false;},	function(){return this==keyElement;}, 
								getQuery, recursion, includePrototype, lengthProp
							);
							
							if (found)
							{
								return false;
							}
						},
						
						function(){return this!=keyElement && !al.Data.isIn(removed, this)}, 
						
						getQuery, false, includePrototype, lengthProp
				);
				
				if (found)
				{
					removed.push(keyElement);
				}
				
				return !found;
			},
			
			getQuery, false, includePrototype, lengthProp
		);
			
		return unique;
	},


	indexOf: function(obj, element, getQuery, recursion, includePrototype, iterator, lengthProp)
	{
		iterator=Function.getArg(iterator, al.Data.iterate);
		
		var results={};
		var found=false;
		
		iterator(obj, function() {found=true; return false;}, function(el, index, level) {results[level]=index; return this==element;}, getQuery, recursion, includePrototype, lengthProp);
	
		result=[];
		al.Data.each(results, function() {result.push(this);});
		
		return found ? (result.length>1 ? result : result[0]) : false;
	},


	isIn: function(obj, element, getQuery, recursion, includePrototype, iterator, lengthProp)
	{
		return al.Data.indexOf(obj, element, getQuery, recursion, includePrototype, iterator, lengthProp)!==false;
	},
	
	
	tokenize: function(obj, valueGetter, elemGetter, filterQuery, getQuery, includePrototype, iterator, lengthProp)
	{
		var valueGetter=Function.getArg(valueGetter, '=');
		var elemGetter=Function.getArg(elemGetter, ',');
		
		if (String.isClass(valueGetter))
		{
			valueSeparator=valueGetter;
			valueGetter=function(){return valueSeparator+this;};
		}
		
		if (String.isClass(elemGetter))
		{
			elemSeparator=elemGetter;
			elemGetter=function(){return elemSeparator+this;};
		}
					
		iterator=Function.getArg(iterator, al.Data.iterate);
		
		var result='';
		var notFirst=false;
		iterator(obj, function(value, key) {
			result+=elemGetter.call(key+valueGetter.call(value))
		}, 
		
		filterQuery, getQuery, false, includePrototype);
		
		return result;
	}
}



al.DOM = {
		Filter: {
			__type: 'filter',
			
			id: function(ids) {
				var id=this.id;
				
				if (!id)
				{
					return false;
				}
				
				var found=false;
				al.Data.each(ids, function(filterId) {
					if (filterId.endsWith('^'))
					{
						filterId=filterId.substr(0,filterId.length-1);
						if (id.startsWith(filterId))
						{
							found=true;
							return false;
						}
					}
					else
					{
						if (id==filterId)
						{
							found=true;
							return false;
						}
					}
				});
				
				return found;
			},
			
			tag: function(tagNames) {
				return al.Data.isIn(tagNames, al.DOM.Element.getTagName(this), function(){return this.toLowerCase()});
			},
		
			input: function() {
				var tagName=al.DOM.Element.getTagName(this);
				return tagName=='select' || tagName=='button' || tagName=='input' || tagName=='textarea';
			},
			
			button: function(special) {
				// special - include radios & checkboxes
				var tagName=al.DOM.Element.getTagName(this);
								
				if  (this.type)
				{
					var type=this.type.toLowerCase();
				}
				
				return  tagName=='button' || 
						tagName=='input' && (
							 type=='button' ||
							 type=='submit' ||
							 special && (type=='checkbox' || type=='radio'));
			},
			
			checked: function()	{return this.type && al.Data.isIn(['checkbox', 'radio'], this.type.toLowerCase()) && this.checked;},
						
			is: function(domObject){return al.DOM.isIn(domObject, this, false);},
			
			attr: function(name, value){return (!isSet(value) && !isEmpty(this.getAttribute(name))) || this.getAttribute(name)==value;}
		},
		
		
		Each: {
			__type: 'each',
			
			value: function(value){
				al.DOM.Element.setValue(this, value);
			},
			
			attr: function(attr, value){this.setAttribute(attr, value);},
			className: function(className){this.className=className;},
			
			remove: function(){al.DOM.Element.remove(this)},
			
			event: function(type, func, args){al.Events.add(this, type, func, args);},
			removeEvent: function(type, func, args){al.Events.remove(this, type, func, args);},
			callEvent: function(type){al.Events.execute(this, type);},
			
			show: function(show){al.DOM.Element.show(this, show)},
			enable: function(enable){this.disabled = !enable;},
						
			// add (remove) prefix to (from) attribute
			attrPrefix: function(attr, add, prefix)
			{
				attr=Function.getArg(attr,'id');
				add=Function.getArg(add,true);
				prefix=Function.getArg(prefix,'__');
				var val=this.getAttribute(attr);
				if (String.isClass(val))
				{
					var prefixed= val.startsWith(prefix);
					if (add)
					{
					 if (!prefixed)
						{
							this.setAttribute(attr, prefix+val);
						}
					}
					else if (prefixed) 
					{
						this.setAttribute(attr, val.substr(prefix.length));
					}
				}
			}
		},
		
		
		Get: {
			__type: 'get',
			
			// return "deepest" parent for element
			// for <body><div><img></div></body> - <body> is 2nd-parent of <img>
			parent: function(maxDeep, withChilds)
			{
				withChilds=Function.getArg(withChilds, false);
				var domObject=this;
				for (var i=0;i<maxDeep;i++)
				{
					var domParent=domObject.parentNode;
					if (domParent)
					{
						domObject=domParent;
					}
					else
					{
						break;					
					}
				}
				
				if (withChilds)
				{
					domObject=al.DOM.getAll(domObject);
					domObject.push(this);
				}
				
				return domObject;
			},
			
			document: function() {return al.DOM.Element.getDocument(this);}
		},
	
	
		makeObject: function(obj)
		{
			if (Array.isClass(obj))
			{
				obj.isDomCollection=true;
			}
			return obj;
		},
		
		
		isCollection: function(obj)
		{
			return obj && Boolean(obj.isDomCollection);
		},
		
		
		iterate: function(domObject, eachQuery, filterQuery, getQuery, recursion)
		{
			if (!domObject)
			{
				return;
			}
					
			eachQuery=al.Data.parseQuery(eachQuery, al.DOM);
			filterQuery=al.Data.parseQuery(filterQuery, al.DOM, true);
			
			var isSingleElem=!Array.isClass(domObject); 
			
			if (isSingleElem && isSet(domObject.length))
			{
				recursion=false;
			}
			else if (isEmpty(recursion))
			{
				recursion=isSingleElem;
			}
		
			if (recursion && isSingleElem)
			{
				domObject=domObject.childNodes;
			}
						
			return al.Data.iterate(domObject, eachQuery,
					function(el, index, level) {
				 		return al.Data.isIn([1], this.nodeType) && (isEmpty(filterQuery) || filterQuery.call(el, el, index, level));
					},				
					
					getQuery, recursion ? 'childNodes' : null);
		},
		
		each: function(domObject, eachQuery, filterQuery, recursion)
		{
			al.DOM.iterate(domObject, eachQuery, filterQuery, null, recursion);	
		},
	
		get: function(domObject, filterQuery, getQuery, recursion)
		{
			filterQuery=al.Data.parseQuery(filterQuery, al.DOM, true);
			getQuery=al.Data.parseQuery(getQuery, al.DOM);
			
			return al.DOM.makeObject(al.Data.get(domObject, filterQuery, getQuery, recursion, false, al.DOM.iterate));
		},
		
		getAll: function(domObject, filterQuery, getQuery)
		{
			return al.DOM.get(al.DOM.getUnique(domObject), filterQuery, getQuery, true);
		},
		
		getOne: function(domObject, filterQuery, index, getQuery)
		{
			filterQuery=al.Data.parseQuery(filterQuery, al.DOM, true);
			getQuery=al.Data.parseQuery(getQuery, al.DOM);
			
			return al.DOM.makeObject(al.Data.getOne(domObject, filterQuery, index, getQuery, al.DOM.get));
		},
		
		getUnique: function(domObject, getQuery, recursion)
		{
			return al.DOM.makeObject(al.Data.getUnique(domObject, getQuery, recursion, false, al.DOM.iterate));
		},
		
		isIn: function(domObject, domElem, getQuery, recursion)
		{
			if (!domObject)
			{
				return false;
			}
			else if (al.DOM.isCollection(domObject))
			{
				return al.Data.isIn(domObject, domElem, recursion, false, al.DOM.iterate);
			}
			else
			{
				return domObject==domElem;
			}
		}
}



al.DOM.Document = {
	isClass: function(obj)
	{
		// browser-independed checking of document class
		return isSet(obj.open) && isSet(obj.getElementById);
	},
	
	
	// apply styles from srcDocument
	applyStyles: function(domDocument, srcDocument)
	{
		srcDocument=Function.getArg(srcDocument, document);
		var styleContainer = domDocument.getElementsByTagName('head')[0];
		if (styleContainer)
		{
			al.Data.each(srcDocument.styleSheets, function()
			{
				var standard = !domDocument.createStyleSheet;
				
				if (standard)
				{
					var style=domDocument.createElement('style'); // NOTE: unable to do this using domDocument.importNode()
				} 
				
				if (this.href)
				{
					if (standard)
					{
						style.innerHTML='@import url('+this.href+');'
					}
					else
					{
						var style=domDocument.createStyleSheet(this.href);
					}
				}
				else if (this.cssText || this.innerHTML)
				{
					if (standard)
					{
						style.innerHTML=this.innerHTML;
					}
					else
					{
						var style=domDocument.createStyleSheet(this.cssText);
					}	
				}
				else 
				{
					return false;
				}
				
				if (standard)
				{
					styleContainer.appendChild(style);
				}
			});
		}
		
		return domDocument;
	},
	
	
	// total (unscrolled) height of document
	getTotalHeight: function(domDocument)
	{
		domDocument=Function.getArg(domDocument, document);
		if	(window.innerHeight && window.scrollMaxY )
		{
			return window.innerHeight + window.scrollMaxY;
		}
		else if (domDocument.body.scrollHeight > domDocument.body.offsetHeight)
		{
			return domDocument.body.scrollHeight;
		}
		else
		{
		return domDocument.body.offsetHeight + domDocument.body.offsetTop;
		}
	},
	
	
	// total (unscrolled) width of document
	getTotalWidth: function(domDocument)
	{
		domDocument=Function.getArg(domDocument, document);
		if	(window.innerWidth && window.scrollMaxX )
		{
			return window.innerWidth + window.scrollMaxX;
		}
		else if (domDocument.body.scrollWidth > domDocument.body.offsetWidth)
		{
			return domDocument.body.scrollWidth;
		}
		else
		{
		return domDocument.body.offsetWidth + domDocument.body.offsetLeft;
		}
	}
}



al.DOM.Element = {
	isClass: function(obj, pure)
	{
		// browser-independed checking of element class
		pure=Function.getArg(pure, false);
		return isSet(obj.appendChild) && isSet(obj.setAttribute)  ||  !pure && al.DOM.Document.isClass(obj);
	},
	
	
	// get element(s) from ID/name, element-object or array of IDs/names/element-objects
	get: function(element, single)
	{
		if (!element)
		{
			return null;
		}
	
		single=Function.getArg(single, false);
		var result=null;

		if (al.DOM.Element.isClass(element))
		{
			result=element;
		}
		else if (String.isClass(element))
		{
			var result=document.getElementById(element);
			if (!result)
			{ 
				result=document.getElementsByName(element);
			}
		}
		else if (Array.isClass(element))
		{
			var result=[];
			al.Data.each(element, function() {
				result.push(al.DOM.Element.get(this, single));
				if (single)
				{
					return false;
				}
			});
		}
	
		if (result)
		{	
			result=al.DOM.makeObject(result);
			return al.DOM.isCollection(result) && single ? result[0] : result;
		}
		else
		{
			return null;
		}
	},
	

	getDocument: function(domElem)
	{
		return domElem.ownerDocument;
	},
	
	
	getOffsetParent: function(domElem)
	{
		try {
			return domElem.offsetParent;
		} catch (e)
		{
			return al.DOM.Element.getDocument(domElem).body;
		}
	},
	
	
	getPageLeft: function(domElem)
	{
		var original = domElem;
		var returnValue=domElem.offsetLeft;

		while((domElem = domElem.offsetParent) != null)
		{
			if(al.DOM.Element.getTagName(domElem)!='html')
			{
				returnValue += domElem.offsetLeft; 
				if(al.Browser.isIE()) 
				{
					returnValue+=domElem.clientLeft;
				}
			}
		}
		 
		return returnValue;
	},
	
	
	getPageTop: function(domElem)
	{
		var original = domElem;
		var returnValue = domElem.offsetTop;

		while((domElem = domElem.offsetParent) != null)
		{
			if(al.DOM.Element.getTagName(domElem)!='html')
			{
				returnValue += domElem.offsetTop; 
				if(al.Browser.isIE()) 
				{
					returnValue+= domElem.clientTop;
				}
			}
		}
		 
		return returnValue;
	},
	
	
	getTagName: function(domElem)
	{
		return domElem.tagName ? domElem.tagName.toLowerCase() : null;
	},
	
	
	// apply position from srcElem
	applyPosition: function(domElem, srcElem, applySize)
	{
		applySize=Function.getArg(applySize, false);
		domElem.style.left=al.DOM.Element.getPageLeft(srcElem)+'px';
		domElem.style.top=al.DOM.Element.getPageTop(srcElem)+'px';
		if (applySize)
		{
			domElem.style.width=srcElem.offsetWidth+'px';
			domElem.style.height=srcElem.offsetHeight+'px';
		}
	},
	
	
	// set element's class
	// - if add is false, className is removed 
	// - if className is not specified, element.className is restored to its initial state
	setClass: function(domElem, className, add)
	{
		add=Function.getArg(add, true);
					
		if (domElem.className.charAt(0)!=' ')
		{
			domElem.className=' '+domElem.className;
		}

		if (!isSet(domElem.initClassName))
		{
			domElem.initClassName=domElem.className;
		}
		
		if (isSet(className))
		{
			var classPos=domElem.className.indexOf(' '+className);
			if (add)
			{
				if (classPos<0)
				{
					domElem.className+=' '+className;
				}
			}
			else
			{
				if (classPos>=0)
				{
					domElem.className=domElem.className.substr(0, classPos)+domElem.className.substr(classPos+className.length+1);
				}
			}
		}
		else
		{
			if (domElem.initClassName)
			{
				domElem.className=domElem.initClassName;
			}
			else
			{
				domElem.className='';
			}
		}
	},
	
	
	setValue: function(domElem, value)
	{
		value=Function.getArg(value, '');
		
		if (isSet(domElem.value)) 
		{
			domElem.value=value;
		} 
		else 
		{
			domElem.innerHTML=value;
		}
	},
	
	
	setAttributes: function(domElem, attrs)
	{
		al.Data.each(attrs, function(value, attr){domElem.setAttribute(attr, value);});
	},
	
	
	// set element's transparency from 0..100%
	setTransparency: function(domElem, transparency, ieDxTransform)
	{
		ieDxTransform=Function.getArg(ieDxTransform, false); // enable directX transformation in IE
		
		transparency=Math.max(Math.min(Function.getArg(transparency, 100),100),0);
		
		if(!domElem.style)
		{
			return;
		}
		
		if (al.Browser.isIE())
		{
			var opacity='opacity='+(100-transparency);
			domElem.style.filter = ieDxTransform ? 'progid:DXImageTransform.Microsoft.Alpha(style=0, '+opacity+')' : 'alpha('+opacity+')';
		}
		else
		{
			domElem.style.opacity=1-transparency/100;
		}
	},
	
	
	show: function(domElem, show)
	{
		show=Function.getArg(show, true);
		if (show)
		{
			domElem.style.display='';
			domElem.style.visibility='visible';
		}
		else
		{
			domElem.style.visibility='hidden';
			domElem.style.display='none';
		}
	},
	
		
	remove: function(domElem)
	{
		domElem.parentNode.removeChild(domElem);
	}
}
		

		
al.DOM.Input = {
	// save element value into cookie
	saveValue: function(domInput)
	{
		var cook=new al.Cookie(domInput.id, '/');
		cook.set(domInput.value);
	},
	
	// restore element value from cookie
	restoreValue: function(domInput)
	{
		var cook=new al.Cookie(domInput.id, '/');
		var elemValue=cook.get();
		if (!isEmpty(elemValue))
		{
			if (al.DOM.Element.getTagName(domInput)=='select')
			{
				al.DOM.Select.restoreValue(domInput, elemValue);
			}
			else
			{
				domInput.value=elemValue;
			}
		}
		
		// cook.set('', -1);
	}
}
		
		
al.DOM.Select = {
	// restore select value
	restoreValue: function(domCombo, comboValue)
	{
		al.Data.each(domCombo.options, function(option, key) {
			if (option.value==comboValue)
			{
				domCombo.selectedIndex=key;
				return false;
			}
		});
	},


	// add options to combo from options-array
	addOptions: function(domCombo, options, truncate)
	{
		truncate=Function.getArg(truncate,false);
		if (truncate)
		{
			domCombo.options.length=0;
		}

		al.Data.each(options, function(text, value) {
			domCombo.options.add(new Option(text,value));
		});
	},


	// get combo options into options-array
	getOptions: function(domCombo)
	{
		var opts={};
		var options=domCombo.options;
		if (options)
		{
			al.Data.each(options, function(option) {
				opts[option.value]=option.text;
			});
		}

		return opts;
	}
}


al.DOM.IFrame = {
	getDocument: function(domFrame)
	{
		return domFrame.contentWindow.document;
	},
	
	
	create: function(loadHandler, parent, attrs, srcFrame, show)
	{
		parent=Function.getArg(parent, document.body);
		show=Function.getArg(show, false);
				
		if (srcFrame)
		{
			var domFrame=srcFrame;
			al.DOM.IFrame.getDocument(domFrame).body.innerHTML='';
			al.DOM.Element.remove(domFrame);
		}
		else
		{
			var domFrame=al.DOM.Element.getDocument(parent).createElement('iframe');
			if (al.Browser.isIE())
			{
				// WORKAROUND: for IE onload problem
				var frameId='iframe_'+Math.random();
				parent.innerHTML+='<iframe id="'+frameId+'" onload="javascript: al.Events.execute(this, event);"></iframe>';
				domFrame=al.DOM.Element.getDocument(parent).getElementById(frameId);
			}
			else
			{
				var domFrame=al.DOM.Element.getDocument(parent).createElement('iframe');
			}
			al.Events.add(domFrame, 'load', loadHandler);
		}
			
		domFrame.allowTransparency=true;
		domFrame.frameBorder=0;
		domFrame.style.border='none';
		domFrame.style.width='0';
		domFrame.style.height='0';
		al.DOM.Element.show(domFrame, show);
		
		al.DOM.Element.setAttributes(domFrame, attrs);
		parent.appendChild(domFrame);
		return domFrame;
	},
	
	
	// adapt iframe width to document width
	adaptWidth: function(domFrame)
	{
			domFrame.style.width=al.DOM.Document.getTotalWidth(al.DOM.IFrame.getDocument(domFrame))+'px';
	},
	
	// adapt iframe height to document height
	adaptHeight: function(domFrame)
	{
		domFrame.style.height=al.DOM.Document.getTotalHeight(al.DOM.IFrame.getDocument(domFrame))+'px';
	}
}



al.URL = {
	create: function(urlRoot, params, skipPrefix)
	{
		var urlParams={};
		al.Data.each(params, function(value, name) {
			urlParams[name]=value;
		},
		
		function(value, name) {
			return isEmpty(skipPrefix) || !name.startsWith(skipPrefix);
		});
		
		var paramStr=al.Data.tokenize(urlParams, '=', '&');
		
		if (urlRoot.indexOf('?')<0)
		{
			urlRoot+='?';
		}
		
		return urlRoot+paramStr;
	}
}



al.Script = {
	isJQuery: function() 
	{
		return isSet(window.jQuery);
	},
	
	// jQuery and some 3rd-party libs have problems with Object prototyping
	allowObjectPrototype: function()
	{
		return !al.Script.isJQuery();
	}
} 



al.Browser= {
	isQuirksMode: function()
	{
		return document.compatMode && document.compatMode=='BackCompat';
	},
	
	
	isIE: function(version)
	{
		var browser=al.Browser.getNameVersion();
		return browser.name=='ie' && (isEmpty(version) || browser.version<=version);
	},
	
	
	getName: function()
	{
		var browser=al.Browser.getNameVersion();
		return browser.name;
	},
	
	
	// simple browser detection routine 
	getNameVersion: function()
	{
		var name='unknown';
		var version='';
		
		if (/MSIE (\d+\.\d+);/.test(navigator.userAgent))
		{ 
 			version=new Number(RegExp.$1);
 			name='ie';
 		}
 		else if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent))
		{
 			version=new Number(RegExp.$1);
 			name='ff';
 		}
 		else if (/Opera[\/\s](\d+\.\d+)/.test(navigator.userAgent))
		{ 
 			version=new Number(RegExp.$1);
 			name='opera';
 		}
 				
		return {name: name, version: version};
	}
}



al.Load = {
	METHOD_GET: 'GET ',
	METHOD_POST: 'POST ',
	
	// load XML from string or url
	xml: function(xmlString)
	{
		var XML_HEADER='<?xml';
		
		if (!xmlString.contains(XML_HEADER))
		{
			al.Load.ajax(xmlString, function(){xmlString=this;}, false, true);
		}
		
		if (!xmlString.contains(XML_HEADER))
		{
			return null;
		}
	
		if (document.implementation && document.implementation.createDocument) 
		{
				var parser=new DOMParser();
				var xmlDoc=parser.parseFromString(xmlString, "text/xml");
		}
	  else
	  {
	  	try 
	  	{
		   	var xmlDoc = new ActiveXObject("MSXML2.DOMDocument");
		  }
		  catch(e)
			{
				return null;
			}
			
			xmlDoc.async=false;
		 	xmlDoc.loadXML(xmlString);
		}
		
		return xmlDoc;
	},
	
	
	ajax: function(url, callback, json, wait)
	{ 
		json=Function.getArg(json, true);
		wait=Function.getArg(wait, false);
		
		var usePost=false;
		if (url.startsWith(al.Load.METHOD_GET))
		{
			url=url.substr(al.Load.METHOD_GET.length).trim();
		}
		else if (url.startsWith(al.Load.METHOD_POST))
		{
			url=url.substr(al.Load.METHOD_POST.length).trim();
			usePost=true;
		}
		
		var result;
		function execute(data)
		{
			if (data) 
			{
				if (json)
				{
					data=data.getJSON();
					if (data=='')
					{
						return false;
					}
				}
			}

			result=callback.call(data, data);
		}
			
		
		function stateChange()
		{
			if (req.readyState == 4) {
				if (req.status == 200) {
					execute(req.responseText);
				}
			}
		}
		
		if (window.XMLHttpRequest) 
		{
			var req= new XMLHttpRequest();
		}
		else
		{
			try 
			{
				var req = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch(e)
			{
				return null;
			}
		}
				
		if (usePost)
		{
			var pos=url.indexOf('?');
			var params=url.substr(pos+1);
			url=url.substr(0,pos);
			req.open('POST', url, !wait)
			req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
		}
		else
		{
			var params=null;
			req.open('GET', url, !wait);
		}
			
		if (!wait) 
		{
			req.onreadystatechange = stateChange;
		}
		
		req.send(params);
		
		if (wait)
		{
			execute(req.responseText);
		}

		return result;
	}
}



// easy manipulation with events
// TODO: how to handle destroyed functions/objects?
al.Events = {
	AllowDOMContentLoaded: true,

	__handlers: {},
	
	_preProcessObject: function(obj)
	{
		return obj;
	},
	
	
	// if func is undefined -> take-over previous event handler into __handlers 
	add: function(obj, eventType, func, args)
	{
		obj=al.DOM.Element.get(obj, true);
		if (!obj)
		{ 
			return;
		}

		var onEvent='on'+eventType;
		eventType=eventType.toLowerCase();
		if (!al.Events.AllowDOMContentLoaded && eventType=='domcontentloaded')
		{
			eventType='load'; onEvent='onload';
		}
		
		obj=al.Events._preProcessObject(obj);
				
		if (al.Events.__handlers[eventType])
		{
			if (func)
			{
				var found=false;
				al.Data.each(al.Events.__handlers[eventType], 
						function() 
						{
							found=true;
							return false;
						},
						
						function() { 
							return this.obj==obj && this.func==func && this.args==args;
				});
				
				if (found)
				{
					return;
				}
			}
		}
		else
		{
			al.Events.__handlers[eventType]=[];
		}

		if (func)
		{
			al.Events.__handlers[eventType].push({obj: obj, func: func, 
														args: args ? args : []});
		}
		
		if (obj[onEvent] && !obj[onEvent].hooked)
		{
			al.Events.__handlers[eventType].push({obj: obj, func: obj[onEvent]});
		}
		
		if (!obj[onEvent] || !obj[onEvent].hooked)
		{
			obj[onEvent]=al.Events._execute;
			obj[onEvent].hooked=true;
		}
	},

	
	execute: function(obj, eventType, e)
	{
		obj=al.DOM.Element.get(obj, true);
		if (!obj)
		{
			return;
		}
		
		if (typeof(eventType)=='object')
		{
			e=eventType;
		}
		else
		{ 
			if (!e)
			{
				e={};
			}
			if (!e.target)
			{
				e.target=obj;
			}
			if (!e.type)
			{
				e.type=eventType;
			}
		}
		
		return al.Events._execute.call(obj, e);
	},
	
	
	_preProcessEvent: function(e)
	{
		if (!e)
		{
			// use IE window.event
			e=window.event; 
		}
		
		if (!e.target)
		{
			// use IE event.srcElement
			e.target=e.srcElement; 
		}
		
		// set event.which in IE
		if (!isSet(e.which) && isSet(e.button)) 
		{
			switch (e.button)
			{
				case 1: e.which=1;
				case 2: e.which=3;
				case 4:	e.which=2;
				default: e.which=1;
			}
		}
		
		// browser-independent positioning
		if (isSet(e.pageX)) {
			e.xPos = e.pageX;
			e.yPos = e.pageY;
		}
		else if (isSet(e.clientX)) {
			e.xPos = e.clientX + document.body.scrollLeft
				+ document.documentElement.scrollLeft;
			e.yPos = e.clientY + document.body.scrollTop
				+ document.documentElement.scrollTop;
		}
		
		return e;
	},
	
	
	_postProcessEvent: function(e)
	{
		if (!e.propagate)
		{
			e.cancelBubble=true;
			if (e.stopPropagation)
			{
				e.stopPropagation();
			}
		}
	},
	
	
	_execute: function(e)
	{
		var result=true;
		var obj=this;
		
		e=al.Events._preProcessEvent(e);
		e.propagate=true; // browser-independent propagation property (use instead of cancelBubble or stopPropagation())
				
		al.Data.each(al.Events.__handlers[e.type.toLowerCase()], 
			function()
			{
				var subResult=undefined;
				subResult=this.func.apply(obj, this.args.spliceCopy(0,0,e));
																										
				if (!isEmpty(subResult))
				{
					result=result && subResult;
				}
			},
			
			function() {
				return this.obj==obj;
		});
		
		
		al.Events._postProcessEvent(e);
		
		e.returnValue=result;
		return result;
	},

	
	remove: function(obj, eventType, func, args)
	{
		obj=al.DOM.Element.get(obj, true);
		if (!obj)
		{
			return;
		}

		eventType=eventType.toLowerCase();
		if (!al.Events.AllowDOMContentLoaded && eventType=='domcontentloaded')
		{
			eventType='load';
		}
		
		var deletedItems=[];
		al.Data.each(al.Events.__handlers, 
			function(handlers, handlersEvent) 
			{
				al.Data.each(handlers,
					function(handler, i)
					{
						deletedItems.push({type: handlersEvent, index: i});
					},
					function()
					{
						return (this.obj==obj) && (isEmpty(func) || this.func==func) && (isEmpty(args) || this.args==args);
					});
			},
			function(handlers, handlersEvent)
			{
				return (isEmpty(eventType) || handlersEvent==eventType);
		});
		
		al.Data.each(deletedItems, function(item) {al.Events.__handlers[item.type].splice(item.index,1);});
	}
}




al.Cookie=function(c_name, path)
{
	path=Function.getArg(path,'');
	this.set=function(value, expiredays)
	{
		var exdate=new Date();
		var hasDate = !isEmpty(expiredays);
		
		if (hasDate)
		{
			exdate.setDate(exdate.getDate()+expiredays);
		}
	
		document.cookie=c_name+ "=" +escape(value)+
		((hasDate) ? ";expires="+exdate.toGMTString() : "" )+ (path=='' ? '' : ';path='+path);
	}

	this.get=function()
	{
		if (document.cookie.length>0)
		  {
		  c_start=document.cookie.indexOf(c_name + "=");
		  if (c_start!=-1)
		    { 
		    c_start=c_start + c_name.length+1; 
		    c_end=document.cookie.indexOf(";",c_start);
		    if (c_end==-1) c_end=document.cookie.length;
		    return unescape(document.cookie.substring(c_start,c_end));
		    } 
		  }
		return null;
	}
}



al.Hacks = {
	enabled: true,
	
	// remove outline from buttons using "this.blur() hack"
	// aggresive -> includes other elements such as radio-buttons, check-boxes...
	removeOutline: function(rootElem, aggresive)
	{
		if (!al.Hacks.enabled)
		{
			return;
		}
		rootElem=Function.getArg(rootElem, document.body);
		
		al.DOM.each(rootElem, {'each.event':['focus', function(){this.blur()}]},
									{'filter.button': [aggresive]});
	},
	
	
	// handles IE resize-window bug (elements have bad position after window resize)
	// TODO: we lost input values
	ieWindowResize: function()
	{
		if (!al.Hacks.enabled || !al.Browser.isIE())
		{
			return;
		}

		var body=document.body;
		var clientWidth=body.clientWidth;
		var clientHeight=body.clientHeight;
		
		body.onresize= 
			function() {
				if (body.clientWidth==clientWidth && body.clientHeight==clientHeight)
				{
					location.reload();
				}
				else
				{
					clientWidth=body.clientWidth;
					clientHeight=body.clientHeight;
				}
			};
	},
	
	
	// workaround for IE6- 'div over select' problem
	ieOverlay: function(domElem, show)
	{
		if (al.Hacks.enabled && al.Browser.isIE('6'))
		{		
			show=Function.getArg(show, true);
			if (show)
			{
					domElem.style.zIndex=domElem.style.zIndex+1;

					var iframe = document.createElement("iframe");
					iframe.setAttribute("src", "");
				
					iframe.id='menu_iframe';
					iframe.style.position="absolute";
					iframe.style.left=0; //domElem.offsetLeft + 'px';
					iframe.style.top=0; //domElem.offsetTop + 'px';
					iframe.style.width=domElem.offsetWidth + 'px';
					iframe.style.height=domElem.offsetHeight + 'px';
					iframe.style.zIndex = -1;
	
					domElem.appendChild(iframe);
					domElem.style.visibility = "visible";

					domElem.ieOverlayFrame=iframe;
			}
			else
			{
				if(domElem)
				{
					if (domElem.ieOverlayFrame)
					{
						if(domElem.ieOverlayFrame.parentNode)
						{
	//					al.DOM.Element.getDocument(domElem).removeChild(domElem.ieOverlayFrame);
						}
					}
				}
			}
		}
	}
}
	


// universal modal/tip window
/* Possibilities: - pass HTML-contents directly
                  - pass contents as JSON-object or string representing JSON-object (optionally processed by Renderer)
                  - pass contents as AJAX-url, which returns JSON-object (optionally processed by Renderer)
                  - load contents directly from specified URL
                  - use element as contents
*/
al.Window=function(contents, renderer, linkedElem, prefix)
{
	var __this=this;
	var container;
	
	linkedElem=al.DOM.Element.get(linkedElem, true);
	prefix=Function.getArg(prefix, 'window');
	
	var containerId=prefix+'Container';
	var overlayerId=prefix+'Overlayer';
	var bodyId=prefix+'Body';
	
	var headerClass=prefix+'Header';
	var titleClass=prefix+'Title'; var titleId=titleClass;
	var closeClass=prefix+'Close'; var closeId=closeClass;
	
	var isUrl=false; var isAjax=false; var isElem=false; 
	
	// parse type of contents
	var thisDocument=document;
	if (al.DOM.Element.isClass(contents))
	{
		isElem=true;		
		thisDocument=al.DOM.Element.getDocument(contents);
	}
	else if (String.isClass(contents))
	{
		if (contents.isJSON())
		{
			contents=contents.getJSON();
		}
		if (contents.startsWith(al.Window.CONTENTS_URL))
		{
			contents=contents.substr(al.Window.CONTENTS_URL.length).trim();
			isUrl=true;
		}
		else if (contents.startsWith(al.Window.CONTENTS_AJAX))
		{
			contents=contents.substr(al.Window.CONTENTS_AJAX.length).trim();
			isAjax=true;
		}
		else if (contents.startsWith(al.Window.CONTENTS_ELEM))
		{
			contents=al.DOM.Element.get(contents.substr(al.Window.CONTENTS_ELEM.length).trim());
			isElem=true;
		}
	}
	

	function initOverlayer()
	{
		if (!container.overlayer)
		{
			var overlayer=thisDocument.createElement('div');
			thisDocument.body.appendChild(overlayer);
			container.overlayer=overlayer;
		}
		else
		{
			var overlayer=container.overlayer;
		} 
		overlayer.id=overlayerId;
		overlayer.style.position='absolute';
		overlayer.style.border='none';
		overlayer.style.left='0';
		overlayer.style.top='0';
		overlayer.style.zIndex=99;
		overlayer.style.width='100%';
		overlayer.style.height='100%';
		al.Hacks.ieOverlay(overlayer);
	}
	
	
	// set body style to defaults (due to imported css into iframe)
	function setBodyStyle()
	{
		container.frameBody.style.width='auto';
		container.frameBody.style.height='auto';
		container.frameBody.style.border='none';
		container.frameBody.style.margin='0';
		container.frameBody.style.padding='0';
		container.frameBody.style.background='none';
	}
	
	
	function frameOnLoad() 
	{
		if (container.frameLoaded)
		{
			return; // onload is executed multiple times on FF, I don't know why
		}
		else
		{
			container.frameLoaded=true;
		}

		if (!isUrl)
		{
			container.frameDocument=al.DOM.IFrame.getDocument(this);
			al.DOM.Document.applyStyles(container.frameDocument);
			container.frameBody=container.frameDocument.body;
			container.frameBody.id=bodyId;
		
			if (isElem)
			{
				var elem=container.frameDocument.importNode(container.content, true);
				al.DOM.Element.show(elem);
				container.frameBody.appendChild(elem);
			}
			else
			{
				container.frameBody.innerHTML+= container.content.data ? (renderer ? renderer.getBody(container.content) : container.content.data) : container.content;
			}
			
			setBodyStyle();			
		}

		this.style.height='100px';
		if (al.Browser.isIE())
		{
			this.style.width=container.clientWidth+'px';
		}
		else
		{
			this.style.width='100%'; // TODO: @todo.xls
		}
		al.DOM.Element.show(this);
		al.DOM.IFrame.adaptHeight(this);
	};
	
	
	function initFrame(src)
	{
		var attr={scrolling: 'no'};
		if (src)
		{
			attr.src=src;
		}
		
		container.frameLoaded=false;
		container.frame = al.DOM.IFrame.create(frameOnLoad, container, attr, container.frame);
	}
	
	
	function initWindow() 
	{
		container=thisDocument.getElementById(containerId);
		var windowCreated=isEmpty(container);
		if (windowCreated)
		{
			container = thisDocument.createElement('div');
			container.id=containerId;
		}
	
		__this.container=container; // make container-object accessible outside al.Window
		container.style.zIndex=100;
		
		if (linkedElem)
		{
			container.style.position='absolute';
			al.DOM.Element.applyPosition(container, linkedElem);
		}
		else
		{
			container.style.position='relative';
			initOverlayer();
		}
		
		al.Events.add(thisDocument, 'click', __this.hide);
	
		if (windowCreated) 
		{
			container.innerHTML='<table border="0" cellspacing="0" cellpadding="0" class="'+headerClass+'"><tr><td class="'+titleClass+'" id="'+titleId+'" style="width: 95%"></td><td style="text-align: left; padding-right: 2px; padding-top: 2px;"><a style="outline: none" class="'+closeClass+'" id="'+closeId+'">&nbsp;</a></td></tr></table>';
			al.Events.add(thisDocument.getElementById(closeId), 'click', __this.hide);
			al.Events.add(container, 'click', function(e) {e.propagate=false;});
			thisDocument.body.appendChild(container);
		}
		
		al.DOM.Element.show(container);
		if (container.overlayer)
		{
			al.DOM.Element.show(container.overlayer);
		}
	}


	this.remove=function()
	{
		if (container.overlayer)
		{
			thisDocument.body.removeChild(container.overlayer);
			al.Hacks.ieOverlay(container.overlayer, false);
		}
		thisDocument.body.removeChild(container);
	}
	
	
	this.hide=function(e)
	{
		var hide=true;
		
		if (e)
		{
			e.propagate=false;
			
			if (linkedElem)
			{
				al.DOM.each(linkedElem, function() {hide=false; return false;}, {'filter.is':[e.target]});
			}
		}
		
		if (hide)
		{
			al.Events.remove(thisDocument.body, 'click', __this.hide);
			al.DOM.Element.show(container, false);
			if (container.overlayer)
			{
				al.DOM.Element.show(container.overlayer, false);
				al.Hacks.ieOverlay(container.overlayer, false);
			}
		}
	}
				
			
	function showWindow(content) 
	{
		initWindow();
		
		if (!isUrl &&!isElem && content.title)
		{
			thisDocument.getElementById(titleId).innerHTML = renderer && renderer.getTitle ? renderer.getTitle(content) : content.title;
		}
		
		if (isUrl)
		{
			var src=content;
		}
		else if (content.url)
		{
			var src=content.url;
		}
		
		container.content=content;
		initFrame(src);	// frame is initialized after (optional) AJAX-load
	};
	
	
	this.show=function()
	{
		if (isAjax)
		{
			al.Load.ajax(contents, showWindow, true, true);
		}
		else
		{
			showWindow(contents);
		}
	}
	
	this.show();
}
al.Window.CONTENTS_URL='URL ';
al.Window.CONTENTS_AJAX='AJAX ';
al.Window.CONTENTS_ELEM='ELEM ';


al.Renderers = {
	ValueList: function()
	{
		this.getBody=function(data) {
			var body='<table border="0" cellpadding="0" cellspacing="0" class="valueList">';		
			al.Data.each(data.data, function(value, key) {
				body+='<tr><td class="valueListKey">'+key+':'+'</td><td class="valueListValue">'+value+'</td></tr>';
			});
			
			body+='</table>';
			return body; 
		}
	}
}
al.ValueListRenderer=al.Renderers.ValueList;



// !!! NOTE: default "deep" for parent finding is 1 (for simpler use with AWeb)
al.PageStateChanger=function(notifier, states, root, callback, userConfig)
{
	var CONFIG = {
		idPrefix: '__',
		
		objectSeparator: '.',
		maskSeparator: '.',
		deepSeparator: '!',
		assignSeparator: '=',
	
		opsMethodPrefix: 'op',
		valueMethodName: 'value',
		noneMethodName: 'none',
	
		deep: 1,
		
		allPseudoId: '__all',
		defaultStateName: '__default',
		cachedState: '__cached',
		
		opsContainers: [al.PageStateChanger.Ops, al],
		defaultOps: al.PageStateChanger.Ops.Basic
	}
	
	Object.applyProperties(CONFIG, userConfig);
			
	var isAjax = String.isClass(states) && !states.isJSON();
	if (!isAjax)
	{
		states=String.getJSON(states);
	}
	
	root=Function.getArg(al.DOM.get(document.body, {'filter.is':[al.DOM.Element.get(root)]}), document.body);
		
	var currentStateId;
	var statesCache={};
	var statesStack={};

	function setState(stateParam, state)
	{
		var result=null;
		
		if (!state)
		{
			return result;
		}
		
		if (isAjax && (isEmpty(state[CONFIG.cachedState]) || state[CONFIG.cachedState])) 
		{
			statesCache[stateParam.__uid]=state;
		}

		// process every element
		al.Data.each(state, function(elemOps, id) {
			// split id!deep
			var idSplitted=id.split2(CONFIG.deepSeparator);
			var domObject=null;
			id=idSplitted[0];
			var deep=idSplitted[1];
			
			if (isEmpty(deep))
			{
				deep=CONFIG.deep;
			}			
			
			if (id==CONFIG.allPseudoId)
			{
				// notifiers are removed from __all pseudo-id
				domObject=al.DOM.getAll(root, {'!filter.tag':[['script']], '!filter.is':[notifier.getElems()]});
			}
			else
			{
				// split id.mask
				var idSplitted=id.split2(CONFIG.maskSeparator);
				id=idSplitted[0];

				// find both prefixed/non-prefixed ids
				domObject=al.DOM.getAll(root, {'filter.id':[[id, CONFIG.idPrefix+id]]});
						
				if (idSplitted[1])
				{
					domObject=al.DOM.get(domObject, null, {'get.parent':[deep]});
					var mask=idSplitted[1];
					al.DOM.each(domObject, {'each.show':[false]});
					domObject=al.DOM.getAll(root, {'filter.id':[[id, CONFIG.idPrefix+id]], 'filter.attr':['mask', mask]});
				}
			}

			domObject=al.DOM.get(domObject, null, {'get.parent':[deep]});
		
			// process every operation
			al.Data.each(elemOps, function (elemOp, key) {
				// split elemOp=value 
				var elemOpSplitted=elemOp.split2(CONFIG.assignSeparator);
				elemOp=elemOpSplitted[0];
				var value=elemOpSplitted[1];
				if (value)
				{
					value=value.getJSON();
				}
				
				// split opsObject.opMethod
				elemOpSplitted=elemOp.split2(CONFIG.objectSeparator, true);
				opsObject=elemOpSplitted[0];
				opMethod=elemOpSplitted[1];
				
				if (!opMethod)
				{
					opMethod=opsObject;
					opsObject=CONFIG.defaultOps;
				}
				else if (opsObject.length==0)
				{
					opsObject=CONFIG.defaultOps;
				}
				else
				{
					try
					{
						eval('opsObject='+opsObject+';');
					}
					catch (e)
					{
						try {
							var ops=null;
							al.Data.each(CONFIG.opsContainers,
								function(){opsObject=ops; return false;},
								function(){return Boolean(ops=this[opsObject]);}
							);
						}
						catch(e)
						{
						}
					}
				}
	
				if (opsObject && opMethod.length>0)
				{
					if (opMethod!=CONFIG.noneMethodName)
					{
						if (!statesStack[id])
						{
							statesStack[id]={};
						}
						
						if (!statesStack[id][elemOp])
						{
							statesStack[id][elemOp]={};
						}
	
						var method=Object.getMethod(opsObject, opMethod, CONFIG.opsMethodPrefix);
						if (method)
						{
							result=method.call(domObject, value, stateParam, statesStack[id][elemOp]);
						}

						if (opMethod==CONFIG.valueMethodName && mask)
						{
							al.DOM.each(domObject, {'each.show':[]});
						}
					}
				}
			});
		});
		
		return result;
	}
	
	
	this.changeState=function(stateId, stateParam)
	{
		if (isEmpty(stateId))
		{
			if (isEmpty(currentStateId))
			{
				return false;
			}
			stateId=currentStateId;
		}
		
		if (isEmpty(stateParam))
		{
			stateParam='';
		}
		
		stateParam={stateId: stateId, stateParam: stateParam}; // parameters are passed to URL in form: &stateId=<stateParam.stateId> &stateParam=<stateParam.stateParam> &<other optional parameters from stateParam object>
		 																		 									 // NOTE: compatibility reasons
		if (callback)
		{
			Object.applyProperties(stateParam, callback.call(this, stateParam));
		}
		
		stateParam.__uid=al.Data.tokenize(stateParam, ':', '#');
		
		if (/*currentStateId!=stateId*/ true)
		{
			currentStateId=stateId;
			var url=stateParam.__url;
			
			if (isAjax || url)
			{
				var state=statesCache[stateParam.__uid];
					
				if (!state || url)
				{
					var ajaxUrl=al.URL.create(url ? url : states, stateParam, '__');
					return al.Load.ajax(ajaxUrl, function(state){return setState(stateParam, state)}, true, true);
				}
				else
				{
					return setState(stateParam, state);
				}
			}
			else
			{
				state=states[stateId];
				if (!state)
				{
					state=states[CONFIG.defaultStateName];
				}
				
				return setState(stateParam, state);
			}
		}
	}
	
	notifier.addHandler(this.changeState);
	notifier.init();
}


// *** input filler ***
al.PageStateChanger.InputFiller = {
	saveState: function()
	{
		al.DOM.Input.saveValue(this);
	},
	
	fillDefault: function(value)
	{
		this.value=value;
	},
	
	fillSelect: function(value, stateParam, stack)
	{
		var comboOptions=stack[this.id];
		
		if (!comboOptions)
		{
			stack[this.id]=al.DOM.Select.getOptions(this);
		}
		else 
		{
			this.options.length=0;
		}
		
		if (this.options.length==0)
		{
			al.DOM.Select.addOptions(this, comboOptions);
		}
	
		al.DOM.Select.addOptions(this, value);
		al.DOM.Input.restoreValue(this);
		
		// save combo state
		al.Events.add(this, 'change', al.PageStateChanger.InputFiller.saveState);
	}
}


// *** ops ***	
al.PageStateChanger.Ops = {};

al.PageStateChanger.Ops.Basic = {

	// set value of input element
	opValue: function(value, stateParam, stack)
	{
		domObject=al.DOM.getAll(this, {'filter.input':[]});
		al.DOM.each(domObject, function(elem, index) {
			var tagName=al.DOM.Element.getTagName(this);
			var method=Object.getMethod(al.PageStateChanger.InputFiller,tagName, 'fill');
			if (!method)
			{
				method=al.PageStateChanger.InputFiller.fillDefault;
			}
			
			method.call(elem, value, stateParam, stack);
		});
		
		al.DOM.each(domObject, {'each.callEvent':['change']});
	},
	
	// put HTML into element
	opHtml: function(value)
	{	
		al.DOM.each(this, function() {this.innerHTML=value;});
	},
	
	opShow: function(value)
	{
		value=Function.getArg(value, true);
		al.DOM.each(this, {'each.attrPrefix':['id', !value]});
		al.DOM.each(this, {'each.show':[value]});
	},
		
	opHide: function()
	{
		al.PageStateChanger.Ops.Basic.opShow.call(this,false);
	},
		
	opEnable: function(value)
	{
		value=Function.getArg(value, true);
		var domObject=al.DOM.getAll(this, {'filter.input':[]});
		al.DOM.each(domObject, {'each.enable':[value]});
		al.DOM.each(domObject, {'each.attrPrefix':['id', !value]});
	},
		
	opDisable: function()
	{
		al.PageStateChanger.Ops.Basic.opEnable.call(this, false);
	},
	
	opStyle: function(value)
	{
		al.DOM.each(this, {'each.attr':['style', value]});
	},
	
	opClass: function(value)
	{
		al.DOM.each(this, {'each.className': [value]});
	}
}
al.BasicOps=al.PageStateChanger.Ops.Basic;
al.BasicOps.opFill=al.PageStateChanger.Ops.Basic.opValue; // backward compatibility


// validator operations - alpha version (subject to change)
al.PageStateChanger.Ops.Validate = {
	dateFromStr: function(string)
	{
		var dateArr=string.split('.');
		return new Date(dateArr[2],dateArr[1]-1,dateArr[0]);
	},
	
	// check, if date in element (this) is less than or equal to date in element with ID=value.id 
	// (if not, display value.msg)
	opDateLE: function(value)
	{
		domObject=al.DOM.getAll(this, {'filter.input':[]});
		al.DOM.each(domObject, function() {
			var domDate1=this;
			var date1=al.PageStateChanger.Ops.Validate.dateFromStr(domDate1.value);
			
			var domDate2=document.getElementById(id);
			var date2=al.PageStateChanger.Ops.Validate.dateFromStr(domDate2.value);
			if (date2.getTime()<date1.getTime())
			{
				alert(value.msg);
				domDate1.focus();
				return false;
			}
		});
	}
}
al.ValidOps=al.PageStateChanger.Ops.Validate;


// extended operations
al.PageStateChanger.Ops.Extended = { 
	// calculate (evaluate) value and set it to element
	// $<id>$ is placeholder for getElementId(id).value
	// TODO: alpha version (subject to change)
	opEval: function(value)
	{
		var SEPAR_PLACEHOLDER='$';
		
		domObject=al.DOM.getAll(this, {'filter.input':[]});
		
		var evalArr=value.split(SEPAR_PLACEHOLDER);
		var evalValue='';
		al.Data.each(evalArr, function(val, key) {
			if (key%2!=0)
			{
				var elemValue=document.getElementById(val).value;
				if (elemValue=='')
				{
					evalValue='""';
					return false;
				}
				evalValue+=elemValue;
			}
			else 
			{
				evalValue+=val;
			}
		});
	
		try 
		{
			al.DOM.each(domObject, {'each.value':[eval(evalValue)]});
		}
		catch (e)
		{
			al.DOM.each(domObject, {'each.value':[]});
		}
	},
	
	// get length of element value
	opValLength: function(value)
	{
		var elemVal=document.getElementById(value).value;
		if (elemVal)
		{
			al.DOM.each(this, {'each.value':[elemVal.length]});
		}
	},
	
	// set cookie
	opCookie: function(value)
	{
		var cook=new al.Cookie(value.name, value.path);
		cook.set(value.value, value.expireDays);
	},
	
	// call js-method (with arguments)
	opCall: function(value, stateParam, stack)
	{
		var __this=this;
		al.Data.each(value, function(args, name) {
			var func=eval(name);	
			if (func)
			{
				func.apply(__this, args.spliceCopy(args.length, 0, stateParam, stack));
			}
		});
	}
}
al.ExtOps=al.PageStateChanger.Ops.Extended;



//*** notifiers ***
al.Notifiers = {
	Abstract: function(notifierOptions)
	{
		var __this=this;
		var handlers=[];
		
		this.notifierOptions=Function.getArg(notifierOptions, {});
		
		this.addHandler=function(callback)
		{
			if (Function.isClass(callback) && !al.Data.isIn(handlers, callback))
			{
				handlers.push(callback);
			}
		}
		
		this.removeHandler=function(callback)
		{
			var pos=al.Data.indexOf(handlers, callback);
			if (pos!==false)
			{
				handlers=handlers.splice(pos,1);
			}
		}
		
		this.notifyAll=function(notifyId, notifyParam)
		{
			notifyParam=Object(notifyParam);
			
			var result=true;
			var thisObj=this;
		
			al.Data.each(handlers, function(handler, key) {
				var res=handler.call(thisObj, notifyId, notifyParam);
				if (!isEmpty(res))
				{
					result=result && res;
				}
			});
			
			return result;
		}
		
		this.init=function()
		{
			if (__this.notifyOnInit)
			{
				__this.notifyOnInit.call(this);
			}
		}
	},
			
	
	AbstractToggler: function(toggleStates, notifierOptions)
	{
		// TODO
	},
	
	
	Element: function(domObject, notifierOptions)
	{
		al.Notifiers.Element.prototype=al.Notifiers.Abstract;
		al.Notifiers.Abstract.call(this, notifierOptions);

		var __this=this;
		
		this.elems=Array.make(al.DOM.Element.get(domObject));
					
		this.getElems=function()
		{
			return this.elems;
		}
	
		this.abstractInit=this.init;
		this.init=function()
		{
			al.DOM.each(this.elems, function(){__this.abstractInit.call(this)});
		}
		
		var abstractNotifyAll=this.notifyAll;
		this.notifyAll=function(e, notifyId, notifyParam)
		{
			if (isSet(__this.notifierOptions.propagateEvent))
			{
				e.propagate=__this.notifierOptions.propagateEvent;
			}
			
			notifyParam=Object(notifyParam);
			notifyParam.eventObject=e;
			return abstractNotifyAll.call(this, notifyId, notifyParam);
		}
	},
	
	
	OneShot: function(domObject, eventType, notifierOptions)
	{
		if (!domObject)
		{
			al.Notifiers.OneShot.prototype=al.Notifiers.Abstract;
			al.Notifiers.Abstract.call(this);
			this.notifyOnInit=function()
			{
				this.notifyAll.call(this, '');
			}
		}
		else
		{
			eventType=Function.getArg(eventType, 'click');
			al.Notifiers.OneShot.prototype=al.Notifiers.Element;
			al.Notifiers.Element.call(this,domObject, notifierOptions);
			
			var __this=this;
			al.DOM.each(this.elems, {'each.event':[eventType, function(e){__this.notifyAll.call(this, e, this.value);}]});
		}
	},

		
	Combo: function(domObject, notifierOptions)
	{
		al.Notifiers.Combo.prototype=al.Notifiers.Element;
		al.Notifiers.Element.call(this, domObject, notifierOptions);
		
		var __this=this;
		
		this.notify=function(e)
		{
			return __this.notifyAll.call(this, e, this.value, this.id);
		}
		
		this.notifyOnInit=this.notify;
	
		al.DOM.each(this.elems, {'each.event':['change', this.notify]});
	},
	
	
	Edit: function(domObject, notifierOptions)
	{
		al.Notifiers.Edit.prototype=al.Notifiers.Element;
		al.Notifiers.Element.call(this,domObject, notifierOptions);
			
		var __this=this;
		
		this.notify=function(e)
		{
			return __this.notifyAll.call(this, e, this.value, this.id);
		}
		
		this.notifyOnInit=this.notify;
	
		al.DOM.each(this.elems, {'each.event':['keyup', this.notify]});
	},
	
	
	Checkbox: function(domObject, notifierOptions)
	{
		al.Notifiers.Checkbox.prototype=al.Notifiers.Element;
		al.Notifiers.Element.call(this,domObject, notifierOptions);
		
		var __this=this;
		
		this.notify=function(e)
		{
			return __this.notifyAll.call(this, e, this.checked ? "1" : "0", this.id);
		}
		this.notifyOnInit=this.notify;
			
		al.DOM.each(this.elems, {'each.event':['click', this.notify]});
	},
	
	
	// toggle -> toggle between true/false state
	Button: function(domObject, toggleState, removeOldHandler, notifierOptions)
	{
		removeOldHandler=Function.getArg(removeOldHandler, false);
		al.Notifiers.Button.prototype=al.Notifiers.Element;
		al.Notifiers.Element.call(this, domObject, notifierOptions);
	
		var __this=this;
		
		if (!isEmpty(toggleState))
		{
			var cookie=new al.Cookie('toggle_'+domObject);
			var cook=cookie.get();
			var state= (isEmpty(cook)) ? !toggleState : (cook==0);
		}
		
		this.notify=function(e)
		{
			if (!isEmpty(toggleState))
			{
				state=!state;
				var result=__this.notifyAll.call(this, e, state ? 1 : 0, this.id);
				cookie.set(state ? 1 : 0);
			}
			else
			{
				var result=__this.notifyAll.call(this, e, this.value, this.id);
			}
					
			return result;
		}
		
		if (!isEmpty(toggleState))
		{
			this.notifyOnInit=this.notify;
		}
		
		if (removeOldHandler)
		{
			al.DOM.each(this.elems, function(){this.onclick=null;});
		}
		al.DOM.each(this.elems, {'each.event':['click', this.notify]});
	},

	
	// notify on mouse-events
	Mouse: function(domObject, eventType, button, notifierOptions)
	{
		al.Notifiers.Mouse.prototype=al.Notifiers.Element;
		al.Notifiers.Element.call(this, domObject, notifierOptions);
		
		var __this=this;
		
		eventType=Function.getArg(eventType, 'up');
		if (al.Data.isIn['down', 'up', 'over', 'out'], eventType)
		{
			eventType='mouse'+eventType;
		}
		
		button=Function.getArg(button, al.Notifiers.Mouse.Buttons.LEFT);
		
		this.notify=function(e)
		{
			if (button==e.which)
			{
				return __this.notifyAll.call(this, e, e.which, e.target.id);
			}
		}
		
		al.DOM.each(this.elems, {'each.event':[eventType, this.notify]});
	},
	
	
	// popup-menu notifier
	Popup: function(domObject, notifierOptions)
	{
		al.Notifiers.Popup.prototype=al.Notifiers.Element;
		al.Notifiers.Element.call(this, domObject, notifierOptions);
		
		var __this=this;
		
		this.notify=function(e)
		{
			if (al.Data.isIn(__this.elems, e.target))
			{
				return __this.notifyAll.call(e.target, e, e.which, e.target.id);
			}
		}
		
		al.DOM.iterate({'each.event':['contextmenu', this.notify]}, null, {'get.document':[]});
	}
}

al.Notifiers.Mouse.Buttons = {
	LEFT: 1,
	MIDDLE: 2,
	RIGHT: 3
}

// backward compatibility
al.OneShotNotifier=al.Notifiers.OneShot;
al.ComboNotifier=al.Notifiers.Combo;
al.ButtonNotifier=al.Notifiers.Button;
al.EditNotifier=al.Notifiers.Edit;
al.CheckboxNotifier=al.Notifiers.Checkbox;



al.showInfoWindow=function(linkedElem, contents, renderer, prefix)
{
	prefix=Function.getArg(prefix, 'info');
	if (String.isClass(contents) && !contents.isJSON())
	{
		contents=al.Window.CONTENTS_AJAX+contents;
	}
	return new al.Window(contents, renderer, linkedElem, prefix);
}



al.HidablePanel=function(panelBody, headerText, hidden, prefix)
{
	panelBody=al.DOM.Element.get(panelBody, true);
	prefix=Function.getArg(prefix, 'hid');
		
	var bodyId=panelBody.id;
	var imgId=prefix+'Image_'+bodyId;
	var hdrId=prefix+'Header_'+bodyId;
	var thisDocument=al.DOM.Element.getDocument(panelBody);
	
	// problems occurred using innerHTML '<tr>..</tr>' in IE6, so I had to use this code
	var table=thisDocument.createElement('table');	table.cellspacing=0; table.cellpadding=0;
	table.className=prefix+'Panel';
	
	var tbody=thisDocument.createElement('tbody');
	table.appendChild(tbody);
	
	var tr=thisDocument.createElement('tr');
	tbody.appendChild(tr);
	
	var img=thisDocument.createElement('td'); img.className=prefix+'PanelImage'; img.id=imgId;
	tr.appendChild(img);
	
	var header=thisDocument.createElement('td'); header.className=prefix+'PanelHeader';	header.id=hdrId;
	header.innerHTML=headerText;
	tr.appendChild(header);
		
	panelBody.parentNode.insertBefore(table, panelBody);

	var changer=new al.PageStateChanger(new al.Notifiers.Button(imgId, true, !hidden), 
			'{"1": {"'+imgId+'!0": ["class=\''+prefix+'PanelImage '+prefix+'PanelImageActive\'"], "'+hdrId+'!0": ["class=\''+prefix+'PanelHeader '+prefix+'PanelHeaderActive\'"], "'+bodyId+'!0": ["show", "ExtOps.cookie={\'name\': \'toggle_'+bodyId+'\', \'value\':1}"]},'+
			' "0":{"'+imgId+'!0": ["class=\''+prefix+'PanelImage\'"], "'+hdrId+'!0": ["class=\''+prefix+'PanelHeader\'"], "'+bodyId+'!0": ["hide", "ExtOps.cookie={\'name\': \'toggle_'+bodyId+'\', \'value\':0}"]}}', panelBody.parentNode);
}



al.PageStateSetter=function(states, rootIds, callback, userConfig)
{
	this.changer=new al.PageStateChanger(new al.Notifiers.OneShot(), '{"__default":'+state+'}', rootIds, callback, userConfig);
}



//pre-filled input -> e.g. for search-box with default text "Search", which disappears on click
//idButton - onclick action on this button will be allowed, only if input is edited (focused) by user
al.PrefilledInput=function(domElem, domButton, initialValue, styleClass)
{
	var ops='';
	domElem=al.DOM.Element.get(domElem);
		
	if (domButton)
	{
		domButton=al.DOM.Element.get(domButton);
		ops+=', "ExtOps.call={\'al.PrefilledInput.focused\':[\''+domButton.id+'\']}"';
		
		al.Events.add(domButton, 'click');
		domButton.onclick=function(){return false;};
	}
	
	if (styleClass)
	{
		ops+=', "class=\''+domElem.className+' '+styleClass+'\'"';
	}
	
	if (initialValue)
	{
		domElem.value=initialValue;
	}
	
	return new al.PageStateChanger(new al.Notifiers.OneShot(domElem, 'focus'),
		'{"__default": {"'+domElem.id+'!0": ["value=\'\'"'+ops+']}}');
}

al.PrefilledInput.focused=function(domButton) // TODO: fix
{
	al.DOM.Element.getDocument(domButton).onclick=al.Events._execute;
}



// simple (and dirty) filler for combo-boxes (don't use in new projects, included only for compatibility reasons)
// NOTE: previous version didn't use PageStateChanger
al.SimpleFiller=function(domElem, fillElem, data)
{
	domElem=al.DOM.Element.get(domElem);
	fillElem=al.DOM.Element.get(fillElem);

	fillElem.datax=data;
	fillElem.savedOptions=al.DOM.Select.getOptions(fillElem);

	var changer=new al.PageStateChanger(new al.Notifiers.Combo(domElem),
			'{"__default":{"'+fillElem.id+'!0":["ExtOps.call={\'astonlib.SimpleFiller.fill\':[]}"]}}');

	al.DOM.Input.restoreValue(fillElem);
	al.Events.add(fillElem, 'change', function(){al.DOM.Input.saveValue(this)});
}

al.SimpleFiller.fill=function(stateParam)
{
	var domElem=this[0];
	al.DOM.Select.addOptions(domElem, domElem.savedOptions, true);
	al.Data.each(domElem.datax, function(value) {
		if (value.key==stateParam.stateId)
		{
			var currentValue=value.value;
			domElem.options.add(new Option(currentValue[1], currentValue[0]));
		}
	});
}


// set button as default
al.DefaultButton=function(domButton)
{
	var domButton=al.DOM.Element.get(domButton, true);
	var form=domButton.form;
	
	function redirectToDefault(e)
	{
		if (e.keyCode==13)
		{
			e.propagate=false;
			domButton.click();
		}
	}
	
	al.DOM.each(form, {'each.event':['keypress', redirectToDefault]},
							{'filter.input':[], '!filter.is':[domButton]});
}



// simple & stupid JS menu highlighter (for server-side generated menus)
al.MenuHighlighter=function(domMenus, defaultClass, hoverClass, highlightClass, menuName)
{
	domMenus=al.DOM.Element.get(domMenus);
	menuName=Function.getArg(menuName, 'mainMenu');
	defaultClass=Function.getArg(defaultClass, 'item');
	hoverClass=Function.getArg(hoverClass, 'hover');
	highlightClass=Function.getArg(highlightClass, 'highlight');
	var cook=new al.Cookie(menuName);
	
	function saveItem()
	{
		cook.set(this.id);
	}
	
	var currentItem=cook.get();
	if (currentItem)
	{
		var elem=document.getElementById(currentItem);
		if (elem)
		{
			elem.className=defaultClass+' '+highlightClass;
			elem.onclick=null;
			elem.style.cursor='default';
		}
	}
	
	al.Data.each(domMenus, function() {
			al.Events.add(this, 'click', saveItem);
			al.Events.add(this, 'mouseover', function(){this.className=defaultClass+' '+hoverClass;});
			al.Events.add(this, 'mouseout', function(){this.className=defaultClass;});
			this.className=defaultClass;
		},
			
		function() {
			return this!=currentItem;
		}
	);
}



// Multi-level CSS-styleable menu
// TODO: dynamically resizeable menu
// TODO: integrate with MenuHighlighter (save menu 'selected' state)
al.Menu=function(container, items, style, prefix)
{
	var HIDE_TIMEOUT=20;
	
	var __this=this;

	if (container instanceof al.Notifiers.Abstract)
	{
		style=Function.getArg(style, al.Menu.Style.POPUP_MOUSE);
		prefix=Function.getArg(prefix, 'popup');
		var notifier=container; container=document.body;
		notifier.addHandler(function(notifyId, notifyParam){__this.popup.call(this, true, notifyParam.eventObject); return false;});
		notifier.init();
		al.Events.add(container, 'click', function(e){__this.popup.call(__this, false, e);});
	}
	else
	{
		style=Function.getArg(style, al.Menu.Style.HORIZONTAL);
		prefix=Function.getArg(prefix, 'main');
		container=Function.getArg(al.DOM.Element.get(container), document.body);
	}
	
	items=String.getJSON(items);
	
	if (al.Data.isIn([al.Menu.Style.POPUP_MOUSE, al.Menu.Style.POPUP_ELEMENT], style))
	{
		var popupElem;
		this.popup=function(show, e)
		{
			show=Function.getArg(show, true);
		
			if (show)
			{
				popupElem=this;
				popupElem.menu=__this.menu;
				popupElem.menu.parentItem=this;
				itemMouseOver.call(this, e);
			}
			else
			{
				if (popupElem)
				{
					itemMouseOut.call(popupElem, e);
				}
			}
		}
	}		
	
	function itemMouseOut(e)
	{
		if (!isSet(this.level) && itemMouseOut.caller!=__this.popup)
		{
			return;
		}

		var thisObj=this; // IE -> passing 'this' through setTimeout doesn't work
		
		function out(/*thisObj*/) {
			if (!thisObj.menu || !thisObj.menu.hovered)
			{
				al.DOM.Element.setClass(thisObj);
				if (thisObj.menu)
				{
					al.DOM.Element.show(thisObj.menu, false);
					al.Hacks.ieOverlay(thisObj.menu, false);
				}
			}
		}
		al.Hacks.ieOverlay(thisObj.menu, false);
		
		this.outTimeoutId=setTimeout(out, HIDE_TIMEOUT /*, this*/);
	}
	
	
	function itemMouseOver(e)
	{
		if (this.outTimeoutId)
		{
			clearTimeout(this.outTimeoutId);
			this.timeoutId=null;
		}
		
		al.DOM.Element.setClass(this, 'hover');
		
		if (this.menu)
		{
			if (style==al.Menu.Style.POPUP_MOUSE && itemMouseOver.caller==__this.popup)
			{
				var left=e.xPos;
				var top=e.yPos;
			}
			else
			{
				var top= al.Browser.isIE() && this.level==0 ? al.DOM.Element.getPageTop(this) : this.offsetTop;
				var left=al.Browser.isIE() && this.level==0 ? al.DOM.Element.getPageLeft(this) : this.offsetLeft;
				
				if (this.level==0 && style==al.Menu.Style.HORIZONTAL)
				{
					top+=this.offsetHeight;
					this.menu.style.width=this.offsetWidth+'px';
				}
				else
				{
					left+=this.offsetWidth;
					this.menu.style.height=this.offsetHeight+'px';
				}
			}
			
			this.menu.style.left=left+'px';
			this.menu.style.top=top+'px';
			this.menu.style.zIndex=100;
			al.Hacks.ieOverlay(this.menu);
			al.DOM.Element.show(this.menu);
		}
	}
	
	
	function menuMouseOver()
	{
		this.hovered=true;
		al.DOM.Element.setClass(this, 'hover');
	}
	
	
	function menuMouseOut()
	{
		this.hovered=false;
		itemMouseOut.call(this.parentItem);
		al.DOM.Element.setClass(this);
	}
	
	
	function createItem(item)
	{
		var menuItem=document.createElement('li');
		var menuItem2=document.createElement('div');
		var menuItem3=document.createElement('div');

		menuItem2.className='middleBox';		
		menuItem3.className='innerBox';
	
		menuItem.appendChild(menuItem2);
		menuItem2.appendChild(menuItem3);	

		menuItem2.caption=document.createElement('span'); menuItem2.caption.innerHTML=item.caption; menuItem2.caption.className='caption';
		menuItem3.appendChild(menuItem2.caption);
		
		al.Events.add(menuItem2, 'click', function(e){if (al.Data.isIn([this, this.caption], e.target)) {window.location=item.link;};});
		al.Events.add(menuItem, 'mouseover', itemMouseOver);
		al.Events.add(menuItem, 'mouseout', itemMouseOut);
		return menuItem;
	}
	
	function createMenu(items, level, parentItem)
	{
		level=Function.getArg(level,0);
		var menu=document.createElement('div');
		var ul=document.createElement('ul');
				
		if (parentItem || __this.popup)
		{
			menu.style.position='absolute';
			al.DOM.Element.show(menu, false);
			
			al.Events.add(menu, 'mouseover', menuMouseOver);
			al.Events.add(menu, 'mouseout', menuMouseOut);
			
			if (parentItem)
			{
				menu.parentItem=parentItem;
				al.DOM.Element.setClass(parentItem, 'expandable');
			}
		}
		else
		{
			al.DOM.Element.show(menu);
		}
		
		if (level==0)
		{
			menu.id=prefix+'Menu';
		}
		
		menu.className='level'+level;
		
		al.Data.each(items, function() {
			var menuItem=createItem(this, level);
			menuItem.level=level;
			if (this.items)
			{
				menuItem.menu=createMenu(this.items, level+1, menuItem);
				menuItem.appendChild(menuItem.menu);
			}
			ul.appendChild(menuItem);
		});
	
		menu.appendChild(ul);
		return menu;
	}
	
	container.appendChild(this.menu=createMenu(items));
}
al.Menu.Style = {
	VERTICAL: 1,
	HORIZONTAL: 2,
	POPUP_MOUSE: 4,
	POPUP_ELEMENT: 8
}



//fill elements from XML
al.XMLFiller=function(mapping, xml)
{
	var SEPAR_TAG=',';
	
	mapping=String.getJSON(mapping);
	
	this.fill=function(xml)
	{
		var xmlDoc=al.Load.xml(xml);

		if (xmlDoc)
		{	
			al.Data.each(mapping, function(mapping, elemName) {
				
				var value='';
				if (mapping.tags)
				{
					var tags=mapping.tags.split(SEPAR_TAG);
					al.Data.each(tags, function(tag, key) {
						var xmlElem=xmlDoc.getElementsByTagName(this.trim());
						if (xmlElem.length>0)
						{
							value+=mapping.escaped ? xmlElem[0].childNodes[0].nodeValue.unescape() : xmlElem[0].childNodes[0].nodeValue;
							if (String.isClass(mapping.separator) && key<tags.length-1)
							{
								value+=mapping.separator;
							}
						}
					});
				}
			
				var domElem=al.DOM.Element.get(elemName);
				if (domElem)
				{
					al.DOM.Element.setValue(domElem, value);
				}
			});
		}
	}
	
	if (xml)
	{
		this.fill(xml);
	}
}



// form, with JS-submitting and error-handling
// NOTE: uses 'trick' with iframe
al.Form=function(domForm, defaultButton, errorPrefix)
{
	var domForm=al.DOM.Element.get(domForm);
	errorPrefix=Function.getArg(errorPrefix, 'error_');
	
	var thisDocument=al.DOM.Element.getDocument(domForm);
	
	if (defaultButton)
	{
		this.defaultButton=new al.DefaultButton(defaultButton, name);
	}
	
	var __this=this;
	
	/*function getSubmitUrl()
	{
		var data={};
		al.DOM.each(domForm.elements, function() {data[this.name]=this.value.escape();}, {'filter.checked':[]});
		return domForm.action+'?'+al.Data.tokenize(data, '=', '&').substr(1); // TODO: modify after tokenize() correction
	}*/
	
		
	this.setErrors=function(errors)
	{
		al.Data.each(domForm.elements, function() {
			var elem=thisDocument.getElementById(errorPrefix+this.name);
			if (elem)
			{
				al.DOM.Element.setValue(elem);
				al.DOM.Element.show(elem, false);
			}
		});
			
		al.Data.each(errors, function(error, name) {
			var elem=thisDocument.getElementById(errorPrefix+name);
			if (elem || !error.optional)
			{
				al.DOM.Element.setValue(elem, error.message);
				al.DOM.Element.show(elem);
			}
			
			if (error.focus)
			{
				thisDocument.getElementsByName(name)[0].focus();
			}
		});
	}
		
	
	this.submit=function()
	{
		var doc=al.DOM.IFrame.getDocument(domForm.sandBox);
		var clonedForm=domForm.cloneNode(true);
		clonedForm.action=domForm.action;
		clonedForm.onsubmit=null;
		doc.body.innerHTML='';
		doc.body.appendChild(clonedForm);
		doc.forms[clonedForm.name].submit();
		return false;
	}
		
		
	function sandBoxLoaded()
	{
		var result=al.DOM.IFrame.getDocument(this).body.innerHTML;
		if (result.isJSON())
		{
			result=result.getJSON();
			if (result.errors)
			{
				var errors=result.errors;
			}
			else
			{
				var errors={};
			}
			__this.setErrors(errors);
			
			if (!result.errors && result.redirectUrl)
			{
				window.location=result.redirectUrl;
			}
		}
	}
	
	al.Events.add(domForm, 'submit', this.submit);
	domForm.sandBox=al.DOM.IFrame.create(sandBoxLoaded);
		
	this.setErrors({});
}	
	


// exchange calculator 
al.ExchangeCalc=function(homeCurrency, exRates, elems, messages)
{
	var CURRENCY_DECIMAL_COUNT=2;
	
	exRates=String.getJSON(exRates);
	
	var selType=al.DOM.Element.get(elems.typeSelect);
	var inpBuy=al.DOM.Element.get(elems.buyInput);
	var inpSell=al.DOM.Element.get(elems.sellInput);
	var selBuy=al.DOM.Element.get(elems.buySelect);
	var selSell=al.DOM.Element.get(elems.sellSelect);
	var btnBuy=al.DOM.Element.get(elems.buyButton);
	var btnSell=al.DOM.Element.get(elems.sellButton);
	
	selBuy.options.length=0;
	selSell.options.length=0;
	selBuy.options.add(new Option(homeCurrency, homeCurrency));
	selSell.options.add(new Option(homeCurrency, homeCurrency));
	
	al.Data.each(exRates, function (values, rate){
		selBuy.options.add(new Option(rate,rate));
		selSell.options.add(new Option(rate,rate));
	});
		
	function calculate()
	{
		var sell=(this==btnSell);
		var strValue = (sell) ? inpBuy.value : inpSell.value;
		
		var inValue=strValue.toDecimal();
		if (!inValue)
		{
			alert(strValue+messages.invalidNumber);
			return;
		}
	
		var type=selType.value;
		var buyRates=exRates[selBuy.value];
		var sellRates=exRates[selSell.value];
				
		var isChange=type.indexOf('chg');
		if (isChange==0)
		{
			var changeType=type.substr(isChange+3);
		}
		
		if (!buyRates)
		{
			if (selBuy.value==homeCurrency)
			{
				buyRate=1;
			}
			/*else
			{
				alert(messages.noRate);
				return;
			}*/
		}
		else
		{
				buyRate= changeType ? buyRates['buy'+changeType] : buyRates[type];
		}
		
		if (!sellRates)
		{
			if (selSell.value==homeCurrency)
			{
				sellRate=1;
			}
			/*else
			{
				alert(messages.noRate);
				return;
			}*/
		}
		else
		{
			sellRate= changeType ? sellRates['sell'+changeType] : sellRates[type];
		}
		
		if (!(buyRate && sellRate))
		{
			alert(messages.noRate);
			return;
		}
				
		var outValue={};
		var inRate = (sell) ? buyRate : sellRate;
		var outRate = (sell) ? sellRate : buyRate;
		
		outValue.value=(inValue.value*inRate/outRate).toFixed(CURRENCY_DECIMAL_COUNT);
		outValue.decimal=inValue.decimal;
		outValue=String.fromDecimal(outValue);
		
		if (sell)
		{
			inpSell.value=outValue;
		}
		else
		{
			inpBuy.value=outValue;
		}
	}
			
			
	function changeType()
	{
		var buyHome = (selBuy.value==homeCurrency);
		var sellHome = (selSell.value==homeCurrency);
		
		var types=[];
		
		// customer BUY means bank SELL
		// customer SELL means bank BUY
		if (buyHome)
		{
				types=['sellCash', 'sell'];
		}
		else {
			if (sellHome)
			{
				types=['buyCash', 'buy'];
			}
			else
			{
				types=['chgCash', 'chg'];
			}
		}
			
		types.push('midCash');
		types.push('mid');
		
		selType.options.length=0;
		al.Data.each(types, function(name) {
			selType.options.add(new Option(messages[name],name));
		});
	}
		
	btnBuy.onclick=calculate;
	btnSell.onclick=calculate;
	
	selBuy.onchange=changeType;
	selSell.onchange=changeType;
	
	changeType();
}


if (!astonlib)
{
	var astonlib={}; 
}


// methods depended on allowObjectPrototype()
if (al.Script.allowObjectPrototype())
{
	Object.prototype.getProperty=function(propName, prefix)
	{
		return Object.getProperty(this, propName, prefix);
	}
	
	
	Object.prototype.getMethod=function(methodName, prefix)
	{
		return Object.getProperty(this, methodName, prefix);
	}
	
	
	Object.prototype.applyProperties=function(srcObj)
	{
		return Object.applyProperties(this, srcObj);
	}
}


/******** Old AWeb/jQuery stuff *******/
if (al.Script.isJQuery())
{
	astonlib.Log = function(text)
	{
		$("<div class='log'>" + text + "</div>").appendTo("body");
	}
	
	astonlib.ajaxLoad = function(parent)
	{
		$(parent).find("a.ajaxLoad").each(function()
		{
			var a = this;
			$.ajax({ url: a.href, success: function(html){$(a).replaceWith(html);} });
		});
	}
	
	
	astonlib.SlidePanel = function(name)
	{
		name=(name==undefined)?'.slide_panel':'#'+name;
				
		var header = $(name+'>.panel_header');
		var body = $(name+'>.panel_body');
	
				
		this.show=function()
		{
			header.addClass('panel_header_active');
			body.slideSell('fast'); 
		}
			
		this.hide = function()
		{
			header.attr('class', 'panel_header');
			body.slideUp('fast'); 
		}
		
		this.show();	
	}
	
	
	astonlib.AccordionPanel = function(name, fixHeight) 
	{	
		var key = (name==undefined) ? ".accordion_panel" : "#"+name;
		var linkClick = function()
		{
			if($(this).attr("class")!="panel_header_active")
			{
				$(key+">.panel_header_active").attr("class", "panel_header");
				$(this).addClass("panel_header_active");
			    var hide_el = $(key+">.panel_body:visible");
			    $(this).next(".panel_body").slideSell("fast");
			    hide_el.slideUp("fast");
				$(key+">[error=1]").addClass("error");
			}
		};
		var fixLinkClick = function()
		{
			if($(this).attr("class")!="panel_header_active")
			{
				$(key+">.panel_header_active").attr("class", "panel_header");
				$(this).addClass("panel_header_active");
			    var hide_el = $(key+">.panel_body:visible");
			    var show_el = $(this).next(".panel_body");
			    var body_height = hide_el.height();
				show_el.css({ height: 0, overflow: 'hidden' }).show();
			    hide_el.animate({height:"hide"},{ step: function(now) { show_el.height(Math.ceil(body_height - now)); }});
				$(key+">[error=1]").addClass("error");
			}
		};
		
	    var headers = $(key+">.panel_header").click(fixHeight>0 ? fixLinkClick : linkClick);
	    if(fixHeight>0)
	    {
	    	$(key).height(fixHeight);
	    	var body_height = fixHeight;
	
	    	headers.each(function() { body_height -= $(this).height(); });
	    	$(key+">.panel_body").height(body_height);
	    }
	    $(key+">.panel_body:gt(0)").hide();
	    $(key+">.panel_header:eq(0)").addClass("panel_header_active");
	    
	    this.show = function(pos)
	    {
			headers.eq(pos).click();
	    };
	
	    this.setErrorPanel = function(pos, err)
	    {
			if(err) headers.eq(pos).attr("error", "1").addClass("error");
			else headers.eq(pos).removeAttr("error").removeClass("error");
	    };
	};
	
	astonlib.TabPanel = function(name)
	{
		var key = (name==undefined) ? ".tab_panel" : "#"+name;
		var tab_body = $(key).after('<div class="tab_body"></div>').next();
		var tabClick = function()
		{
			if($(this).attr("class")!="panel_header_active")
			{
				$(key+">.panel_header_active").attr("class", "panel_header");
				$(this).addClass("panel_header_active");
				tab_body.html($(this).next(".panel_body").html());
			}
			$(key+">[error=1]").addClass("error");
		};
	    $(key+">.panel_body").hide();
	    var headers = $(key+">.panel_header").click(tabClick);
	
	    this.show = function(pos)
	    {
			headers.eq(pos).click();
	    };
	
	    this.setErrorTab = function(pos, err)
	    {
			if(err) headers.eq(pos).attr("error", "1").addClass("error");
			else headers.eq(pos).removeAttr("error").removeClass("error");
	    };
		this.show(0);
	};
	
	astonlib.Combo = function(name, items)
	{
		var el = $("#"+name);
		var modal = $('<div class="combo_modal"></div>');
		modal.hide().css({position: 'absolute', 'z-index': 1000}).appendTo("body");
		for(var i in items)
		{
			var elitem = $("<div>"+items[i]+"</div>");
			elitem.click(function() { el.val($(this).text()); modal.hide(); });
			modal.append(elitem);
		}
		this.show = function()
		{
			var pos = el.offset();
			var width = Math.max(el.width(), modal.width());
			modal.css({top: pos.top+el.height()+6, left: pos.left, width: width});
			modal.show();
		};
		this.hide = function()
		{
			modal.hide();
		};
		this.filter = function()
		{
			var text = el.val();
			modal.children().each(function(){ if($(this).text().indexOf(text)==0) $(this).show(); else $(this).hide(); });
		};
	
		modal.mousedown(function(e)
		{
			var x = e.pageX - this.offsetLeft;
			var y = e.pageY - this.offsetTop;
			astonlib.log("x=y: "+x+"="+y);
			e.stopPropagation();
		});
	
		modal.mouseup(function(e)
		{
			e.stopPropagation();
		});
	
		modal.mouseover(function(e)
		{
			e.stopPropagation();
		});
	
		el.after("<span>xx</span>").next().toggle(this.show, this.hide);
		el.bind("keyup", this.filter);	
		el.bind("keyup", this.show);	
	};
	
	astonlib.ContentMenu = function(clname)
	{
		$('ul.content_menu').each(function()
		{
			var ul_el = $(this);
			var btn_el = $('<span class="button_content_menu"></span>').append(ul_el.find('>li:first').html()).insertBefore(ul_el);
			
			var modal = $('<div class="content_menu_panel"></div>');
			modal.hide().css({position: 'absolute', 'z-index': 1000}).appendTo('body');
			modal.append(ul_el);
			modal.find('a').each(function(){ this.isModalLink = true; });
	
			var clickBtn = function()
			{	
				if(modal.is(':hidden'))
				{
					$('div.content_menu_panel').hide();
					var pos = btn_el.offset();
					modal.css({top: pos.top+btn_el.height(), left: pos.left});
					modal.show();
				}
				else modal.hide();
				return false;
			};
	
			$(btn_el).click(clickBtn);
		});
		$(document).click(function(e) { if(e.target.isMenuItem!=true) $('div.content_menu_panel').hide(); });
	}
}

// add objects/methods to astonlib (workaround)
Object.applyProperties(astonlib, al);
