/*
Script: anobii.js
	Contains <LoadingEffect> Class.
	Contains <ContentPresentation> Class.
*/








/*
Class: LoadingEffect
	Functions related to AJAX or any kind of loading
*/
var LoadingEffect = new Class({

	options: {
		loadArea:		'default',
		wheelPos:		'default',
		wheelSize: 		'default',
		overlayColor: 	'#000',
		scroll:			false,
		scrollTo:		'default',
		toCoordinates:	'default',
		loadingText:	'Loading...'
	},
	
	initialize: function(options){
		this.setOptions(options);
		
		if (this.options.loadArea == 'default') {
			this.loadArea = document.body;
		} else {
			this.loadArea = this.options.loadArea;		
		}
		this.scroll = this.options.scroll;
		this.scrollTo = this.options.scrollTo;
		this.toCoordinates = this.options.toCoordinates;
		this.wheelSize = this.options.wheelSize;
		this.overlayColor = this.options.overlayColor;
		this.loadingText = this.options.loadingText;
		
		this.running = false;
		
		this.getTargetCoordinates();
		
		if (this.options.wheelPos == 'default') { this.wheelPos = 'center center';
		} else { this.wheelPos = '50% '+this.options.wheelPos+'px'; }
		
		this.overlay = new Element('div').setStyles({
			backgroundColor: this.overlayColor,
			textAlign: 'center',
			opacity: 0,
			zIndex: 5000
		});

		this.wheel = new Element('div');

		if ((this.options.loadArea == 'default') && (!Browser.Engine.trident4)) {
			this.overlay.setStyle('position', 'fixed');
		} else {
			this.overlay.setStyle('position', 'absolute');
		}
		
		this.loadingIndicator;
		this.loadingIndicatorExist = false;
		if (this.loadingIndicator = $('loadingIndicator')) {
			this.loadingIndicatorExist = true;
		} else {
			this.loadingIndicator = new Element('p').setProperty('id', 'loadingIndicator').set('text', this.loadingText);
		}
		
	},

	getTargetCoordinates: function() {
		if (this.options.loadArea == 'default') {
			if (Browser.Engine.trident4) {
				this.target = {
					width: this.loadArea.clientWidth,
					height: this.loadArea.clientHeight,
					left: 0,
					top: window.getScrollTop(),
					right: 0,
					bottom: 0
				};
			} else {
				this.target = {
					width: this.loadArea.clientWidth,
					height: this.loadArea.clientHeight,
					left: 0,
					top: 0,
					right: 0,
					bottom: 0
				};
			}
		} else {
			this.target = this.loadArea.getCoordinates();
		}	
	},

	//////////////////////////////////////////////////////////////////////////////////
	
	startLoading: function() {
		var thisObj = this;
		
		if (!this.running) { 
			this.running = true;
			if (this.scroll) {
				// Scroll to Load Area
				var scroll = new Fx.Scroll(window, {
					wait: false, duration: 1000
				});
				if (this.scrollTo != "default") {
					scroll.toElement(this.scrollTo);
				} else if (this.toCoodinates != "default") {
					scroll.start(this.toCoordinates.x, this.toCoordinates.y);
				} else {
					scroll.toElement(this.loadArea);
				}
			}
			// Insert Overlay
			this.overlay.setStyles({
				top: this.target.top + 'px',
				left: this.target.left + 'px',
				width: this.target.width + 'px',
				height: this.target.height + 'px'
			}).inject(document.body).get('tween', {property: 'opacity', duration: 200, transition: Fx.Transitions.linear}).start(0, 0.5);
			//this.overlay.inject(document.body).setOpacity(0.5);
			this.wheel.setStyle('backgroundPosition', this.wheelPos).inject(this.overlay);
			if (this.wheelSize == 'large') this.wheel.addClass('loadingWheelLarge');
			else this.wheel.addClass('loadingWheel');
			
			this.loadingIndicator.set('text', this.loadingText);
			if (!this.loadingIndicatorExist) this.loadingIndicator.inject(document.body);
			
			
			// Add Resize detection to window
			window.addEvent('resize', function(e) {
				thisObj.getTargetCoordinates();
				thisObj.overlay.setStyles({
					top: thisObj.target.top + 'px',
					left: thisObj.target.left + 'px',
					width: thisObj.target.width + 'px',
					height: thisObj.target.height + 'px'
				});
			});
		}
	},

	loadingCompleted: function() {
		if (this.overlay) {
			var overlay = this.overlay;
			overlay.get('tween', {property: 'opacity', duration: 100, transition: Fx.Transitions.linear}).start(0.5, 0).chain(function(){
				overlay.dispose();
			});
		}
		if ($('loadingIndicator')) this.loadingIndicator.dispose();
		
		window.removeEvent('resize');
		this.running = false;
		
	},


	//////////////////////////////////////////////////////////////////////////////////

	startLoadingLite: function() {
		
		if (!this.running) {
			var thisObj = this;
			this.running = true;
			// Set Area Opacity
			this.loadArea.setStyle('visibility', 'hidden');
		
			// Handle Scrolling
			if (this.scroll) {
				// Scroll to Load Area
				var scroll = new Fx.Scroll(window, {
					wait: false, duration: 1000
				});
				if (this.scrollTo != "default") {
					scroll.toElement(this.scrollTo);
				} else if (this.toCoodinates != "default") {
					scroll.start(this.toCoordinates.x, this.toCoordinates.y);
				} else {
					scroll.toElement(this.loadArea);
				}
			}
		
			// Insert Overlay
			this.overlay.setStyles({
				backgroundColor: 'transparent',
				opacity: '1',
				top: this.target.top + 'px',
				left: this.target.left + 'px',
				width: this.target.width + 'px',
				height: this.target.height + 'px'
			}).inject(document.body);
			this.wheel.setStyle('backgroundPosition', this.wheelPos).inject(this.overlay);
			if (this.wheelSize == 'large') this.wheel.addClass('loadingWheelLarge');
			else this.wheel.addClass('loadingWheelLite');

			this.loadingIndicator.set('text', this.loadingText);
			if (!this.loadingIndicatorExist) this.loadingIndicator.inject(document.body);
			
			// Add Resize detection to window
			window.addEvent('resize', function(e) {
				thisObj.getTargetCoordinates();
				thisObj.overlay.setStyles({
					top: thisObj.target.top + 'px',
					left: thisObj.target.left + 'px',
					width: thisObj.target.width + 'px',
					height: thisObj.target.height + 'px'
				});
			});
		}
	},

	loadingCompletedLite: function() {
		if (this.overlay) {
			this.loadArea.setStyle('visibility', 'visible');
			this.overlay.dispose();
		}
		if ($('loadingIndicator')) this.loadingIndicator.dispose();
		
		window.removeEvent('resize');
		this.running = false;
	}
	
});
LoadingEffect.implement(new Options, new Events);









/*
Class: ContentPresentation
	Functions related to effects for content presentation
*/
var ContentPresentation = new Class({
});
ContentPresentation.implement(new Options, new Events);


/*
Class: OverflowScroller
	Functions that scroll content
	Extended from ContentPresentation
*/

var OverflowScroller = new Class({

	Extends: ContentPresentation,
	
	initialize: function(wrapper, scrollItems, prev, next, itemsPerPage) {
		this.wrapper = wrapper;
		this.scrollItems = scrollItems;
		this.prev = prev;
		this.next = next;
		this.itemsPerPage = itemsPerPage;
		var thisObj = this;
		
		this.scrollEffect = new Fx.Scroll(this.wrapper, {
			wait: false,
			duration: 1000,
			transition: Fx.Transitions.Quad.easeInOut
		});
		this.numOfPages = Math.ceil(this.scrollItems.length / this.itemsPerPage);
		
		if (this.numOfPages <= 1) {
			this.next.setStyle('visibility', 'hidden');
			this.prev.setStyle('visibility', 'hidden');
		} else {
			this.prev.removeEvents();
			this.next.removeEvents();

			this.prev.setStyle('visibility', 'hidden');
			this.next.addEvent('click', function(e) {
				e = new Event(e); thisObj.updatePagination(2); e.stop();
			}).setStyle('visibility', 'visible');
		}
	},
		
	updatePagination: function(current) {
		this.prev.removeEvents();
		this.next.removeEvents();
		var thisObj = this;
	
		if (current <= 1) {
			this.prev.setStyle('visibility', 'hidden');
			this.next.addEvent('click', function(e) {
				e = new Event(e); thisObj.updatePagination(current+1); e.stop();
			}).setStyle('visibility', 'visible');
		} else if (current > this.numOfPages - 1) {
			this.next.setStyle('visibility', 'hidden');
			this.prev.addEvent('click', function(e) {
				e = new Event(e); thisObj.updatePagination(current-1); e.stop();
			}).setStyle('visibility', 'visible');
		} else {
			this.prev.addEvent('click', function(e) {
				e = new Event(e); thisObj.updatePagination(current-1); e.stop();
			}).setStyle('visibility', 'visible');
			this.next.addEvent('click', function(e) {
				e = new Event(e); thisObj.updatePagination(current+1); e.stop();
			}).setStyle('visibility', 'visible');
		}
		this.goToPage(current);
	},
	goToPage: function(target) {
		this.targetItem = target * this.itemsPerPage - this.itemsPerPage;
		this.scrollEffect.toElement(this.scrollItems[this.targetItem]);
	},


	goToElement: function(target) {
		var targetIndex = this.scrollItems.indexOf(target);
		this.updatePagination(Math.ceil((targetIndex+1) / 10));
		this.scrollEffect.toElement(this.scrollItems[target]);
	},
	goToElementPage: function(target) {
		var targetIndex = this.scrollItems.indexOf(target);
		this.updatePagination(Math.ceil((targetIndex+1) / 10));
	}
	

});
OverflowScroller.implement(new Options, new Events);





/*
Class: OverlayContent
	Functions that present pop-up content in an overlay
	Extended from ContentPresentation
*/

var OverlayContent = new Class({
	
	Extends: ContentPresentation,
	
	initialize: function(element) {
				
		var thisObj = this;
		this.overlayStyles;
		
		this.original = element;
		
		if (Browser.Engine.trident4) {
			this.overlayStyles = {
				position: 'absolute',
				zIndex: '990',
				top: window.getScrollTop() +'px',
				left: '0',
				width: window.getScrollWidth() +'px',
				height: window.getScrollHeight() +'px',
				textAlign: 'center'
			};
		} else {
			this.overlayStyles = {
				position: 'fixed',
				zIndex: '990',
				top: '0',
				left: '0',
				width: window.getScrollWidth() +'px',
				height: window.getScrollHeight() +'px',
				textAlign: 'center'
			};
		}

		this.createOverlay();
		
	},

	createOverlay: function() {
		
		var thisObj = this;
		
		this.overlay = new Element('div').setStyles(this.overlayStyles);
		this.overlay.inject(document.body);
		
		this.elementPopUp = new Element('div').addClass('popup_alert').inject(this.overlay, 'after');
		this.elementPopUp.setStyles({
			display: 'block',
			visibility: 'visible',
			position: 'absolute',
			zIndex: '999',
			margin: 'auto',
			opacity: 0
		});
		this.element = this.original.clone(true, true).inject(this.elementPopUp);
		this.element.removeClass('hide');

		this.elementPosX = (window.getScroll().x + (window.getSize().x - this.element.getSize().x)/2);
		this.elementPosY = (window.getScroll().y + (window.getSize().y - this.element.getSize().y)/2);
		if (this.elementPosY < 0) this.elementPosY = 0;
		if (this.elementPosX < 0) this.elementPosX = 0;
		this.elementPopUp.setStyles({
			top: this.elementPosY +'px',
			left: this.elementPosX - 10 +'px'
		});

		/*
		this.tempArray = new Array(this.overlay, this.element); 
		this.fx = new Fx.Elements(this.tempArray, {
			wait: false, duration: 600, transition: Fx.Transitions.Cubic.easeOut
		});
		this.fxBegins = {
			'0': { 'opacity': [0, 0.7] },
			'1': { 'opacity': [0, 1] }		
		};
		this.fxEnds = {
			'0': { 'opacity': [0.7, 0] },
			'1': { 'opacity': [1, 0] }	
		};
		this.fx.start(this.fxBegins);
		*/
	
		this.overlay.addEvent('click', function(e) {
			thisObj.closeOverlay();
		});
		
		this.fx = new Fx.Tween(this.elementPopUp, {
			wait: false, duration: 600, transition: Fx.Transitions.Cubic.easeOut
		});
		this.fx.start('opacity', 1);

	},
	
	closeOverlay: function() {
		var thisElementPopUp = this.elementPopUp;
		var thisOverlay = this.overlay;
		/*
		this.fx.start(this.fxEnds).chain(function() {
			thisElement.dispose();
			thisOverlay.dispose();
		});
		*/
		this.fx.start('opacity', 0).chain(function() {
			thisElementPopUp.dispose();
			thisOverlay.dispose();
		});
	},

	setupCancel: function() {
		var thisObj = this;
		// Cancel Button
		this.element.getElement('.cancel').addEvent('click', function(e) {
			e = new Event(e).stop();
			thisObj.closeOverlay();
		});
	}
	

});
OverlayContent.implement(new Options, new Events);








/*
Class: OverlayAlert
	Functions that present pop-up alert in an overlay
	Extended from ContentPresentation
	
	Usage:
	TRIGGER_ELEMENT.addEvents({
		'click': function(e) {
			e = new Event(e).stop();
			
			var ALERT_ELEMENT = $('MY_ALERT');
			var MY_ALERT = new OverlayAlert($('MY_ALERT'));

			MY_ALERT.setupCancel();			
			ALERT_ELEMENT.getElement('div.actions button').addEvent('click', function(e) {
				e = new Event(e).stop();
				confirmAlert.closeAlert();
				APPLY OTHER JS STUFF TO THE ALERT_ELEMENT
			});
		}
	});

*/



var OverlayAlert = new Class({
	
	Extends: ContentPresentation,
	
	options: {
		autoClose:		false
	},

	initialize: function(content, options) {
		this.setOptions(options);

		var thisObj = this;
		this.autoClose = this.options.autoClose;
		this.original = content;

		this.overlayStyles;
		if (Browser.Engine.trident4) {
			this.overlayStyles = {
				position: 'absolute',
				top: window.getScrollTop() +'px',
				width: window.getScrollWidth() +'px',
				height: window.getScrollHeight() +'px',
				zIndex: '700',
				textAlign: 'center'
			};
		} else {
			this.overlayStyles = {
				position: 'fixed',
				top: '0',
				left: '0',
				width: window.getScrollWidth() +'px',
				height: window.getScrollHeight() +'px',
				zIndex: '700',
				textAlign: 'center'
			};
		}

		this.createAlert();
		if (this.autoClose) (function() { thisObj.closeAlert() }).delay(3000);
		
	},
	
	createAlert: function() {
		var thisObj = this;


		this.overlay = new Element('div').setStyles(this.overlayStyles);
		this.overlay.inject(document.body).addEvent('click', function(e) { thisObj.closeAlert(); });
	
		this.popupAlert;
		if (document.getElement('div.popup_alert')) {
			this.popupAlert = document.getElement('div.popup_alert');
		} else {
			this.popupAlert = new Element('div').addClass('popup_alert').setOpacity(0);
			this.popupAlert.inject(this.overlay, 'after');
		}
		
		this.content = this.original.clone(true, true).removeClass('hide').addClass('alert_content');
		this.popupAlert.adopt(this.content);

		var leftPos = ((window.getWidth() - this.content.getCoordinates().width)/2);
		var topPos = ((window.getHeight() - this.content.getCoordinates().height)/2);
		var alertStyles;
		if (Browser.Engine.trident4) {
			alertStyles = {
				top: topPos + window.getScrollTop() + 'px',
				left: leftPos + window.getScrollLeft() + 'px',
				zIndex: '710'
			};
		} else {
			alertStyles = {
				top: topPos + 'px',
				left: leftPos + 'px',
				zIndex: '710'
			};
		}
		
		// Execute Fade-in effect
		this.popupAlert.setStyles(alertStyles).get('tween', {property: 'opacity', duration: 300, transition: Fx.Transitions.linear}).start(0, 1);
				
		return this.popupAlert;
	},

	closeAlert: function() {
		if (this.popupAlert) {
			var thisAlert = this.popupAlert;
			var thisOverlay = this.overlay;
			this.popupAlert.get('tween', {property: 'opacity', duration: 300, transition: Fx.Transitions.linear}).start(1, 0).chain(function(){
				thisAlert.dispose();
				thisOverlay.dispose();
			});
		}
		return this;
	},
	
	setupCancel: function() {
		var thisObj = this;
		// Cancel Button
		this.content.getElement('.cancel').addEvent('click', function(e) {
			e = new Event(e).stop();
			thisObj.closeAlert();
		});
		return this;
	}
	
});
OverlayAlert.implement(new Options, new Events);










/*
Class: ToggleElement
	Functions that setup trigger and element to be toggled
	Extended from ContentPresentation
*/

var ToggleElement = new Class({

	Extends: ContentPresentation,

	options: {
		scroll:			true,
		slide:			true,
		autoHeight:		false,
		toggleClass:	'active',
		toggleBody:		false,
		alsoDo:			null
	},
	initialize: function(trigger, content, options) {
		this.setOptions(options);

		var thisObj = this;

		this.trigger = trigger;
		this.content = content;
		this.slide = this.options.slide;
		this.scroll = this.options.scroll;
		this.autoHeight = this.options.autoHeight;
		this.toggleClass = this.options.toggleClass;
		this.toggleBody = this.options.toggleBody;
		this.bodyEventAdded = false;
		this.alsoDo = this.options.alsoDo;
		
		if (this.scroll) {
			this.scrollFx = new Fx.Scroll(window, {
				wait: false, duration: 1000, transition: Fx.Transitions.Quad.easeInOut
			});
		}
		
		if (this.slide) {
			
			this.content.removeClass('hide');
			
			this.slider = new Fx.Slide(thisObj.content, {
				wait: false, duration: 400, transition: Fx.Transitions.Cubic.easeOut
			}).hide();
		}
		
		
		this.trigger.addEvents({
			'click': function(e) {
				e = new Event(e).stop();
			
				if (thisObj.slide) {
					thisObj.slider.toggle().chain(function() {
						if (thisObj.scroll) thisObj.doScroll();
						if (thisObj.autoHeight) thisObj.content.getParent().setStyle('height', 'auto');
					});
				} else {
					thisObj.content.toggleClass('hide');
					if (thisObj.scroll) thisObj.doScroll();
				}
			
				if (thisObj.toggleBody) {
					if (!thisObj.bodyEventAdded) {
						thisObj.trigger.toggleClass(thisObj.toggleClass);
						thisObj.bodyEventAdded = true;
					}
					document.body.addEvent('mouseup', function(e) {
						thisObj.collapseIt();
						document.body.removeEvents('mouseup');
					});
				} else {
					thisObj.trigger.toggleClass(thisObj.toggleClass);		
					if (thisObj.alsoDo != null) thisObj.alsoDo();
				}
			},
			'mouseover': function(e) {
				thisObj.trigger.addClass('hover');
			},
			'mouseout': function(e) {
				thisObj.trigger.removeClass('hover');
			}			
		});
	},
	
	collapseIt: function() {
		var thisObj = this;
		if (thisObj.slide) {
			thisObj.slider.slideOut();
		} else {
			thisObj.content.addClass('hide');
		}
		thisObj.trigger.removeClass(thisObj.toggleClass);
		if (thisObj.alsoDo != null) thisObj.alsoDo();
	},

	
	doScroll: function() {
		var thisObj = this;
		var contentPos = {
			'top':		this.content.getTop(),
			'left':		this.content.getLeft(),
			'bottom':	this.content.getTop() + this.content.offsetHeight,
			'right':	this.content.getLeft() + this.content.offsetWidth
		};
		var windowPos = {
			'top':		window.getScrollTop(),
			'left':		window.getScrollLeft(),
			'bottom':	window.getHeight() + window.getScrollTop(),
			'right':	window.getWidth() + window.getScrollLeft()
		};
		
		if (contentPos.bottom > windowPos.bottom) {
			this.scrollFx.start(contentPos.left, windowPos.top + (contentPos.bottom - windowPos.bottom) + 10);
		}
	}

});
ToggleElement.implement(new Options, new Events);








/*
Class: AutoExpandTextArea
	Integrated from autoexpand.js

	Author: Scrivna
	WWW:	http://scrivna.com
	Description:	Will automagically resize any textarea... that's amazing
					So far it works on IE7, Firefox 2, Safari 3 and Opera 

	Usage:
	Simply use a classname of "autoExpand" on your textarea and include this file in your html
	If you are doing something a bit more fancy ie ajax, you can simply call autoExpand(document.getElementById('yourtextarea')) to auto expand that area
*/
var AutoExpandTextArea = new Class({

	initialize: function(){
		this.ae_debug = false;
		var thisObj = this;
			
		$$('body textarea.autoExpand').each(function(textarea, i) {
			thisObj.autoExpand(textarea);
		});
	},
	
	getStyle: function(el, style) {
		if(!document.getElementById) return;
		var value = el.style[this.toCamelCase(style)];
		if(!value) {
			if(document.defaultView) {
				value = document.defaultView.getComputedStyle(el, "").getPropertyValue(style);
			} else if(el.currentStyle) {
				value = el.currentStyle[this.toCamelCase(style)];
			}
		}
		if(value.length>0) {
			value = ((value.charAt(value.length-1,1) == "x") ? parseInt(value.substring(0,value.length-2)) : value);
		}
		return value;
	},
	
	toCamelCase: function(sInput) {
		var oStringList = sInput.split('-');
		if(oStringList.length == 1)   
			return oStringList[0];
		var ret = sInput.indexOf("-") == 0 ?
		   oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0];
		for(var i = 1, len = oStringList.length; i < len; i++){
			var s = oStringList[i];
			ret += s.charAt(0).toUpperCase() + s.substring(1)
		}
		return ret;
	},

	autoExpand: function(t) {
		var textarea = t;
		var __minHeight = textarea.offsetHeight;
		var a = parseInt(textarea.getStyle('min-height'));
		if (a>0) __minHeight = a;
	
		// adjust the textarea
		textarea.setStyles({
			'overflow': 'hidden',
			'overflowX': 'auto',		
			// set the width and height to the correct values
			'width': this.getStyle(textarea, 'width')+'px',
			'height': this.getStyle(textarea, 'height')+'px'
		});
		
		// create a new element that will be used to track the dimensions
		var dummy_id = Math.floor(Math.random()*99999) + '_dummy';
		
		// match the new elements style to the textarea
		var dummy = new Element('div').setProperty('id', dummy_id).inject(document.body);

		var copiedStyles = textarea.getStyles('font-family', 'font-weight', 'font-size', 'width', 'padding', 'margin');
		dummy.setStyles(copiedStyles);
		dummy.setStyles({
			'overflowX': 'auto',
			// hide the created div away
			'position': 'absolute',
			'top': '0px',
			'left': '-9999px'
		}).set('html', '&nbsp;42');
				
		var __lineHeight = dummy.offsetHeight;
		
		var checkExpand = function(){
			var html = textarea.value;
			html = html.replace(/\n/g, '<br>new');
			if (dummy.innerHTML != html)
			{
				dummy.innerHTML = html;
				var __dummyHeight = dummy.offsetHeight;
				var __textareaHeight = textarea.offsetHeight;
				ae_debug('Textarea: '+ __textareaHeight);
				ae_debug('Dummy: '+ __dummyHeight);
				if (__textareaHeight != __dummyHeight)
				{
					if (__dummyHeight > __minHeight)
					{
						textarea.style.height = (__dummyHeight+__lineHeight) + 'px';
					}
					else 
					{
						textarea.style.height = __minHeight+'px';
					}
				}
			}
		}
		
		var ae_startExpand = function()
		{
			interval = window.setInterval(function() {checkExpand()}, 500);
		}
		var ae_stopExpand = function()
		{
			clearInterval(interval);
		}
		
		var ae_debug = function(str)
		{
			if(ae_debug==true) document.getElementById('debug').innerHTML += '<br />'+str;
		}
		
		textarea.addEvents({
			'focus': ae_startExpand,
			'blur': ae_stopExpand
		});
		checkExpand();
	}


});
AutoExpandTextArea.implement(new Options, new Events);














/**
 * HistoryManager
 * 
 * Observes back/forward button usage and saves states
 * for registered modules into the hash. This allows to
 * bookmark specific states for an application.
 * 
 * @version		1.2
 * 
 * @see			Events, Options
 * 
 * @license		MIT License
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @updated		aNobii Ltd. <anobii.com>
 * @copyright	2007 Author
 */
var HistoryManager = new Class ( {

	/**
	 * Default options - Can be overridden with setOptions
	 * 
	 * observeDelay: Duration for checking the state, default 100ms
	 * stateSeparator: Seperator for module-state join, default ';'
	 * iframeSrc: Scr for IE6/7 iframe, must exist on server!
	 * onStart: Fires on start
	 * onRegister: Fires on register
	 * onUnregister: Fires on unregister
	 * onUpdate: Fires when state changes from ...
	 * onStateChange: ... module changes
	 * onObserverChange: ... history change
	 */
	options: {
		observeDelay: 100,
		stateSeparator: ';',
		iframeSrc: 'blank.html',
		onStart: Class.empty,
		onRegister: Class.empty,
		onUnregister: Class.empty,
		onStart: Class.empty,
		onUpdate: Class.empty,
		onStateChange: Class.empty,
		onObserverChange: Class.empty
	},

	/**
	 * Default options for register
	 * 
	 * defaults: Default values array, initially empty.
	 * regexpParams: When regexp is a String, this is the second argument for new RegExp.
	 * skipDefaultMatch: default true; When true onGenerate is not called when current values are similar to the default values.
	 */
	dataOptions: {
		skipDefaultMatch: true,
		defaults: [],
		regexpParams: ''
	},

	/**
	 * Constructur - Class.initialize
	 * 
	 * Options:
	 *  - observeDelay: duration in ms, default 100 - BackBuddy observe the hash for changes periodical
	 *  - stateSeparator: char, default ';' - Separator for multiple module-states in the hash
	 *  - iframeSrc: string, default 'blank.html' - File for the iframe (IE6/7), must exist on the server!
	 *  - Events: onStart, onRegister, onStart, onUpdate, onStateChange, onObserverChange
	 * 
	 * @return	this
	 * 
	 * @param	{Object} options
	 */
	initialize: function(options) {
		if (this.modules) return this;
		this.setOptions(options);
		this.modules = $H({});
		this.count = history.length;
		this.states = [];
		this.states[this.count] = this.getHash();
		this.state = null;
		return this;
	},

	/**
	 * Start - Check hash and start observer
	 * 
	 * Call start after registering ALL modules. This start the observer,
	 * reads the state from the hash and calls onMatch for effected modules.
	 * 
	 * @return	this
	 * 
	 */
	start: function() {
		this.observe.periodical(this.options.observeDelay, this);
		this.started = true;
		this.observe();
		this.update();
		this.fireEvent('onStart', [this.state]);
		return this;
	},

	/**
	 * Registers a module
	 * 
	 * @return	{Object} Object with shortcuts for setValues, setValue, generate and unregister
	 * 
	 * @param	{String} Module key
	 * @param	{RegExp}/{String} Regular expression that matches the string updated from onGenerate
	 * @param	{Function} Will be called when the regexp matches, with the new values as argument.
	 * @param	{Function} Should return the string for the state string, values are first argument
	 * @param	{Array} default values, the input values given to onMatch and onGenerate will be complemented with these
	 * @param	{Object} (optional) options
	 */
	register: function(key, defaults, onMatch, onGenerate, regexp, options) {
		if (!this.modules) this.initialize();
		var data = $merge(this.dataOptions, options || {}, {
			defaults: defaults,
			onMatch: onMatch,
			onGenerate: onGenerate,
			regexp: regexp
		});
		data.regexp = data.regexp || key + '-([\\w_-]*)';
		if (typeof data.regexp == 'string') data.regexp = new RegExp(data.regexp, data.regexpParams);
		data.onGenerate = data.onGenerate || function(values) { return key + '-' + values[0]; };
		data.values = $A(data.defaults);
		this.modules.set(key, data);
		this.fireEvent('onUnregister', [key, data]);
		return {
			setValues: function(values) {
				return this.setValues(key, values);
			}.bind(this),
			setValue: function(index, value) {
				return this.setValue(key, index, value);
			}.bind(this),
			generate: function(values) {
				return this.generate(key, values);
			}.bind(this),
			unregister: function() {
				return this.unregister(key);
			}.bind(this)
		};
	},

	/**
	 * unregister - Removes an module from the
	 * 
	 * @param	{String} Module key
	 */
	unregister: function(key) {
		this.fireEvent('onRegister', [key]);
		this.modules.remove(key);
	},

	/**
	 * setValues - Set all values new, updates new state
	 * 
	 * @param	{String} Module key
	 * @param	{Object} Complete values
	 */
	setValues: function(key, values) {
		var data = this.modules.get(key);
		if (!data || data.values.isSimilar(values)) return this;
		data.values = values;
		this.update();
		return this;
	},

	/**
	 * setValue - Set one value, updates new state
	 * 
	 * @param	{String} Module key
	 * @param	{Number} Value index
	 * @param	{Object} Value
	 */
	setValue: function(key, index, value) {
		var data = this.modules.get(key);
		if (!data || data.values[index] == value) return this;
		data.values[index] = value;
		this.update();
		return this;
	},

	/**
	 * generate - Generates a hash from the given
	 * 
	 * @param	{String} Module key
	 * @param	{Number} Value index
	 * @param	{Object} Value
	 */
	generate: function(key, values) {
		var data = this.modules.get(key);
		var current = $A(data.values);
		data.values = values;
		var state = this.generateState();
		data.values = current;
		return '#' + state;
	},

	observe: function() {
		if (this.timeout) return;
		var state = this.getState();
		if (this.state == state) return;
		if ((Browser.Engine.trident || Browser.Engine.webkit419) && (this.state !== null)) this.setState(state, true);
		else this.state = state;
		this.modules.each(function(data, key) {
			var bits = state.match(data.regexp);
			if (bits) {
				bits.splice(0, 1);
				bits.complement(data.defaults);
				if (!bits.isSimilar(data.defaults)) data.values = bits;
			} else data.values = $A(data.defaults);
			data.onMatch(data.values, data.defaults);
		});
		this.fireEvent('onStateChange', [state]).fireEvent('onObserverChange', [state]);
	},

	generateState: function() {
		var state = [];
		this.modules.each(function(data, key) {
			if (data.skipDefaultMatch && data.values.isSimilar(data.defaults)) return;
			state.push(data.onGenerate(data.values));
		});
		return state.join(this.options.stateSeparator);
	},

	update: function() {
		if (!this.started) return this;
		var state = this.generateState();
		if ((!this.state && !state) || (this.state == state)) return this;
		this.setState(state);
		this.fireEvent('onStateChange', [state]).fireEvent('onUpdate', [state]);
		return this;
	},

	observeTimeout: function() {
		if (this.timeout) this.timeout = $clear(this.timeout);
		else this.timeout = this.observeTimeout.delay(200, this);
	},

	getHash: function() {
		var href = top.location.href;
		var pos = href.indexOf('#') + 1;
		return (pos) ? href.substr(pos) : '';
	},

	getState: function() {
		var state = this.getHash();
		if (this.iframe) {
			var doc = this.iframe.contentWindow.document;
			if (doc && doc.body.id == 'state') {
				var istate = doc.body.innerText;
				if (this.state == state) return istate;
				this.istateOld = true;
			} else return this.istate;
		}
		if (Browser.Engine.webkit419 && history.length != this.count) {
			this.count = history.length;
			return $pick(this.states[this.count - 1], state);
		}
		return state;
	},

	setState: function(state, fix) {
		state = $pick(state, '');
		if (Browser.Engine.webkit419) {
			if (!this.form) this.form = new Element('form', {method: 'get'}).injectInside(document.body);
			this.count = history.length;
			this.states[this.count] = state;
			this.observeTimeout();
			this.form.setProperty('action', '#' + state).submit();
		} else top.location.hash = state || '#';
		if (Browser.Engine.trident && (!fix || this.istateOld)) {
			if (!this.iframe) {
				this.iframe = new Element('iframe', {
					'src': this.options.iframeSrc,
					'styles': {'display': 'none'}
				}).injectInside(document.body);
				this.istate = this.state;
			}
			try {
				var doc = this.iframe.contentWindow.document;
				doc.open();
				doc.write('<html><body id="state">' + state + '</body></html>');
				doc.close();
				this.istateOld = false;
			} catch(e) {};
		}
		this.state = state;
	},

	extend: $extend
});

HistoryManager.implement(Events.prototype);
HistoryManager.implement(Options.prototype);


/**
 * Extends Array with 2 helpers: isSimilar(array) and complement(array)
 * 
 */
Array.prototype.isSimilar = function (array){
		return (this.toString() == array.toString());
}

Array.prototype.complement = function (array){
		for (var i = 0, j = this.length; i < j; i++) this[i] = $pick(this[i], array[i] || null);
		return this;
}







/**
 * Observer - Observe formelements for changes
 *
 * @version		1.0rc3
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */
var Observer = new Class({

	Implements: [Options, Events],

	options: {
		periodical: false,
		delay: 1000
	},

	initialize: function(el, onFired, options){
		this.setOptions(options);
		this.addEvent('onFired', onFired);
		this.element = $(el) || $$(el);
		this.value = this.element.get('value');
		if (this.options.periodical) this.timer = this.changed.periodical(this.options.periodical, this);
		else this.element.addEvent('keyup', this.changed.bind(this));
	},

	changed: function() {
		var value = this.element.get('value');
		if ($equals(this.value, value)) return;
		this.clear();
		this.value = value;
		this.timeout = this.onFired.delay(this.options.delay, this);
	},

	setValue: function(value) {
		this.value = value;
		this.element.set('value', value);
		return this.clear();
	},

	onFired: function() {
		this.fireEvent('onFired', [this.value, this.element]);
	},

	clear: function() {
		$clear(this.timeout || null);
		return this;
	}

});

var $equals = function(obj1, obj2) {
	return (obj1 == obj2 || JSON.encode(obj1) == JSON.encode(obj2));
};



/**
 * Autocompleter
 *
 * @version		1.1.1
 *
 * @todo: Caching, no-result handling!
 *
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */
var Autocompleter = {};

Autocompleter.Base = new Class({

	options: {
		minLength: 1,
		markQuery: true,
		width: 'inherit',
		maxChoices: 10,
		injectChoice: null,
		customChoices: null,
		className: 'autocompleter-choices',
		zIndex: 42,
		delay: 400,
		observerOptions: {},
		fxOptions: {},
		onOver: $empty,
		onSelect: $empty,
		onSelection: $empty,
		onShow: $empty,
		onHide: $empty,
		onBlur: $empty,
		onFocus: $empty,

		autoSubmit: false,
		overflow: false,
		overflowMargin: 25,
		selectFirst: false,
		filter: null,
		filterCase: false,
		filterSubset: false,
		forceSelect: false,
		selectMode: true,
		choicesMatch: null,

		multiple: false,
		separator: ', ',
		separatorSplit: /\s*[,;]\s*/,
		autoTrim: true,
		allowDupes: false,

		cache: true,
		relative: false
	},

	initialize: function(element, options) {
		this.element = $(element);
		this.setOptions(options);
		this.build();
		this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({
			'delay': this.options.delay
		}, this.options.observerOptions));
		this.queryValue = null;
		if (this.options.filter) this.filter = this.options.filter.bind(this);
		var mode = this.options.selectMode;
		this.typeAhead = (mode == 'type-ahead');
		this.selectMode = (mode === true) ? 'selection' : mode;
		this.cached = [];
	},

	/**
	 * build - Initialize DOM
	 *
	 * Builds the html structure for choices and appends the events to the element.
	 * Override this function to modify the html generation.
	 */
	build: function() {
		if ($(this.options.customChoices)) {
			this.choices = this.options.customChoices;
		} else {
			this.choices = new Element('ul', {
				'class': this.options.className,
				'styles': {
					'z-index': this.options.zIndex
				}
			}).inject(document.body);
			this.relative = false;
			if (this.options.relative) {
				this.choices.inject(this.element, 'after');
				this.relative = this.element.getOffsetParent();
			}
			this.fix = new OverlayFix(this.choices);
		}
		if (!this.options.separator.test(this.options.separatorSplit)) {
			this.options.separatorSplit = this.options.separator;
		}
		this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, $merge({
			'property': 'opacity',
			'link': 'cancel',
			'duration': 200
		}, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0);
		this.element.setProperty('autocomplete', 'off')
			.addEvent((Browser.Engine.trident || Browser.Engine.webkit) ? 'keydown' : 'keypress', this.onCommand.bind(this))
			.addEvent('click', this.onCommand.bind(this, [false]))
			.addEvent('focus', this.toggleFocus.create({bind: this, arguments: true, delay: 100}))
			.addEvent('blur', this.toggleFocus.create({bind: this, arguments: false, delay: 100}));
	},

	destroy: function() {
		if (this.fix) this.fix.destroy();
		this.choices = this.selected = this.choices.destroy();
	},

	toggleFocus: function(state) {
		this.focussed = state;
		if (!state) this.hideChoices(true);
		this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]);
	},

	onCommand: function(e) {
		if (!e && this.focussed) return this.prefetch();
		if (e && e.key && !e.shift) {
			switch (e.key) {
				case 'enter':
					if (this.element.value != this.opted) return true;
					if (this.selected && this.visible) {
						this.choiceSelect(this.selected);
						return !!(this.options.autoSubmit);
					}
					break;
				case 'up': case 'down':
					if (!this.prefetch() && this.queryValue !== null) {
						var up = (e.key == 'up');
						this.choiceOver((this.selected || this.choices)[
							(this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst')
						](this.options.choicesMatch), true);
					}
					return false;
				case 'esc': case 'tab':
					this.hideChoices(true);
					break;
			}
		}
		return true;
	},

	setSelection: function(finish) {
		var input = this.selected.inputValue, value = input;
		var start = this.queryValue.length, end = input.length;
		if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0;
		if (this.options.multiple) {
			var split = this.options.separatorSplit;
			value = this.element.value;
			start += this.queryIndex;
			end += this.queryIndex;
			var old = value.substr(this.queryIndex).split(split, 1)[0];
			value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length);
			if (finish) {
				var space = /[^\s,]+/;
				var tokens = value.split(this.options.separatorSplit).filter(space.test, space);
				if (!this.options.allowDupes) tokens = [].combine(tokens);
				var sep = this.options.separator;
				value = tokens.join(sep) + sep;
				end = value.length;
			}
		}
		this.observer.setValue(value);
		this.opted = value;
		if (finish || this.selectMode == 'pick') start = end;
		this.element.selectRange(start, end);
		this.fireEvent('onSelection', [this.element, this.selected, value, input]);
	},

	showChoices: function() {
		var match = this.options.choicesMatch, first = this.choices.getFirst(match);
		this.selected = this.selectedValue = null;
		if (this.fix) {
			var pos = this.element.getCoordinates(this.relative), width = this.options.width || 'auto';
			this.choices.setStyles({
				'left': pos.left,
				'top': pos.bottom,
				'width': (width === true || width == 'inherit') ? pos.width : width
			});
		}
		if (!first) return;
		if (!this.visible) {
			this.visible = true;
			this.choices.setStyle('display', '');
			if (this.fx) this.fx.start(1);
			this.fireEvent('onShow', [this.element, this.choices]);
		}
		if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead);
		var items = this.choices.getChildren(match), max = this.options.maxChoices;
		var styles = {'overflowY': 'hidden', 'height': ''};
		this.overflown = false;
		if (items.length > max) {
			var item = items[max - 1];
			styles.overflowY = 'scroll';
			styles.height = item.getCoordinates(this.choices).bottom;
			this.overflown = true;
		};
		this.choices.setStyles(styles);
		this.fix.show();
	},

	hideChoices: function(clear) {
		if (clear) {
			var value = this.element.value;
			if (this.options.forceSelect) value = this.opted;
			if (this.options.autoTrim) {
				value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator);
			}
			this.observer.setValue(value);
		}
		if (!this.visible) return;
		this.visible = false;
		this.observer.clear();
		var hide = function(){
			this.choices.setStyle('display', 'none');
			this.fix.hide();
		}.bind(this);
		if (this.fx) this.fx.start(0).chain(hide);
		else hide();
		this.fireEvent('onHide', [this.element, this.choices]);
	},

	prefetch: function() {
		var value = this.element.value, query = value;
		if (this.options.multiple) {
			var split = this.options.separatorSplit;
			var values = value.split(split);
			var index = this.element.getCaretPosition();
			var toIndex = value.substr(0, index).split(split);
			var last = toIndex.length - 1;
			index -= toIndex[last].length;
			query = values[last];
		}
		if (query.length < this.options.minLength) {
			this.hideChoices();
		} else {
			if (query === this.queryValue || (this.visible && query == this.selectedValue)) {
				if (this.visible) return false;
				this.showChoices();
			} else {
				this.queryValue = query;
				this.queryIndex = index;
				if (!this.fetchCached(this.queryValue)) this.query();
			}
		}
		return true;
	},

	fetchCached: function(queryValue) {
	//	return false;
		if (!this.options.cache
			|| !this.cached
		//	|| !this.cached.length
			|| this.cached.length >= this.options.maxChoices
			|| !this.queryValue
			|| !this.cached[queryValue]) return false;
		this.update(this.filter(this.cached[queryValue]));
		return true;
	},

	update: function(tokens) {
		this.choices.empty();
	//	this.cached = tokens;
		if (!tokens || !tokens.length) {
			this.hideChoices();
		} else {
			if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices;
			tokens.each(this.options.injectChoice || function(token){
				var choice = new Element('li', {'html': this.markQueryValue(token)});
				choice.inputValue = token;
				this.addChoiceEvents(choice).inject(this.choices);
			}, this);
			this.showChoices();
		}
	},

	choiceOver: function(choice, selection) {
		if (!choice || choice == this.selected) return;
		if (this.selected) this.selected.removeClass('autocompleter-selected');
		this.selected = choice.addClass('autocompleter-selected');
		this.fireEvent('onSelect', [this.element, this.selected, selection]);
		if (!selection) return;
		this.selectedValue = this.selected.inputValue;
		if (this.overflown) {
			var coords = this.selected.getCoordinates(this.choices), margin = this.options.overflowMargin,
				top = this.choices.scrollTop, height = this.choices.offsetHeight, bottom = top + height;
			if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0);
			else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom);
		}
		if (this.selectMode) this.setSelection();
	},

	choiceSelect: function(choice) {
		if (choice) this.choiceOver(choice);
		this.setSelection(true);
		this.queryValue = false;
		this.hideChoices();
	},

	filter: function(tokens) {
		var regex = new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i');
		return (tokens || this.tokens).filter(regex.test, regex);
	},

	/**
	 * markQueryValue
	 *
	 * Marks the queried word in the given string with <span class="autocompleter-queried">*</span>
	 * Call this i.e. from your custom parseChoices, same for addChoiceEvents
	 *
	 * @param		{String} Text
	 * @return		{String} Text
	 */
	markQueryValue: function(str) {
		return (!this.options.markQuery || !this.queryValue) ? str
			: str.replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>');
	},

	/**
	 * addChoiceEvents
	 *
	 * Appends the needed event handlers for a choice-entry to the given element.
	 *
	 * @param		{Element} Choice entry
	 * @return		{Element} Choice entry
	 */
	addChoiceEvents: function(el) {
		return el.addEvents({
			'mouseover': this.choiceOver.bind(this, [el]),
			'click': this.choiceSelect.bind(this, [el])
		});
	}
});

Autocompleter.Base.implement(new Events);
Autocompleter.Base.implement(new Options);

Autocompleter.Local = new Class({

	Extends: Autocompleter.Base,

	options: {
		minLength: 0,
		delay: 200
	},

	initialize: function(element, tokens, options) {
		this.parent(element, options);
		this.tokens = tokens;
	},

	query: function() {
		this.update(this.filter());
	}

});

Autocompleter.Ajax = {};

Autocompleter.Ajax.Base = new Class({

	Extends: Autocompleter.Base,

	options: {
		postVar: 'value',
		postData: {},
		ajaxOptions: {},
		onRequest: $empty,
		onComplete: $empty
	},

	initialize: function(element, options) {
		this.parent(element, options);
		var indicator = $(this.options.indicator);
		if (indicator) {
			this.addEvents({
				'onRequest': indicator.show.bind(indicator),
				'onComplete': indicator.hide.bind(indicator)
			}, true);
		}
	},

	query: function(){
		var data = $unlink(this.options.postData);
		data[this.options.postVar] = this.queryValue;
		this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]);
		this.request.send({'data': data});
	},

	/**
	 * queryResponse - abstract
	 *
	 * Inherated classes have to extend this function and use this.parent(resp)
	 *
	 * @param		{String} Response
	 */
	queryResponse: function() {
		this.fireEvent('onComplete', [this.element, this.request, this.response]);
	}

});

Autocompleter.Ajax.Json = new Class({

	Extends: Autocompleter.Ajax.Base,

	initialize: function(el, url, options) {
		this.parent(el, options);
		this.request = new Request.JSON($merge({
			'url': url,
			'link': 'cancel'
		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
	},

	queryResponse: function(response) {
		this.parent();
		this.update(response);
	}

});

Autocompleter.Ajax.Xhtml = new Class({

	Extends: Autocompleter.Ajax.Base,

	initialize: function(el, url, options) {
		this.parent(el, options);
		this.request = new Request.HTML($merge({
			'url': url,
			'link': 'cancel',
			'update': this.choices
		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
	},

	queryResponse: function(tree, elements) {
		this.parent();
		if (!elements || !elements.length) {
			this.hideChoices();
		} else {
			this.choices.getChildren(this.options.choicesMatch).each(this.options.injectChoice || function(choice) {
				var value = choice.innerHTML;
				choice.inputValue = value;

				if (!this.cached[this.queryValue]) this.cached[this.queryValue] = [];
				this.cached[this.queryValue].push(value);

				this.addChoiceEvents(choice.set('html', this.markQueryValue(value)));
			}, this);
			this.showChoices();
		}

	}

});


var OverlayFix = new Class({

	initialize: function(el) {
		if (Browser.Engine.trident) {
			this.element = $(el);
			this.relative = this.element.getOffsetParent();
			this.fix = new Element('iframe', {
				'frameborder': '0',
				'scrolling': 'no',
				'src': 'javascript:false;',
				'styles': {
					'position': 'absolute',
					'border': 'none',
					'display': 'none',
					'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
				}
			}).inject(this.element, 'after');
		}
	},

	show: function() {
		if (this.fix) {
			var coords = this.element.getCoordinates(this.relative);
			delete coords.right;
			delete coords.bottom;
			this.fix.setStyles($extend(coords, {
				'display': '',
				'zIndex': (this.element.getStyle('zIndex') || 1) - 1
			}));
		}
		return this;
	},

	hide: function() {
		if (this.fix) this.fix.setStyle('display', 'none');
		return this;
	},

	destroy: function() {
		this.fix = this.fix.destroy();
	}

});

/**
 * @todo Clean that up or check if they exist already
 */
Element.implement({

	getOffsetParent: function() {
		var body = this.getDocument().body;
		if (this == body) return null;
		if (!Browser.Engine.trident) return $(this.offsetParent);
		var el = this;
		while ((el = el.parentNode)){
			if (el == body || Element.getComputedStyle(el, 'position') != 'static') return $(el);
		}
		return null;
	},

	getCaretPosition: function() {
		if (!Browser.Engine.trident) return this.selectionStart;
		this.focus();
		var work = document.selection.createRange();
		var all = this.createTextRange();
		work.setEndPoint('StartToStart', all);
		return work.text.length;
	},

	selectRange: function(start, end) {
		if (Browser.Engine.trident) {
			var range = this.createTextRange();
			range.collapse(true);
			range.moveEnd('character', end);
			range.moveStart('character', start);
			range.select();
		} else {
			this.focus();
			this.setSelectionRange(start, end);
		}
		return this;
	}

});









/////////////////////////////////////////////////////////////////////////////
/*
Class: RatingStars
	Functions related to effects for rating stars
*/
var RatingStars = new Class({
	Implements: Options,
		
	initialize: function(stars, options){
		this.setOptions(options);
		var thisObj = this;
		var starsArray = stars.getElements('a');
		var starTips = stars.getElement('.ratingTip');
		
		starsArray.each(function(star, j) {
			star.addEvents({
				'mouseover': function(e) {
					e = new Event(e).stop();
					starsArray.each(function(starHover, k) {
						if (k <= j) starHover.addClass('hover')
						else starHover.addClass('unrated')
					});
//					for (counter=0; counter<j; counter++) {
//						starsArray[counter].addClass('hover');
//					}
					starTips.set('text', star.getProperty('rel'));
				},
				'mouseout': function(e) {
					e = new Event(e).stop();
					starsArray.each(function(starOut, k) {
						if (k <= j) starOut.removeClass('hover')
						else starOut.removeClass('unrated')
					});
//					for (counter=0; counter<j; counter++) {
//						starsArray[counter].removeClass('hover');
//					}	
					starTips.set('text', '');
				},
				'click': function(e) {
					e = new Event(e).stop();
					thisObj.onClickDo(j+1);
					
					starsArray.each(function(starRated, k) {
						starRated.removeClass('rated');
					});
					for (counter=0; counter<=j; counter++) {
						starsArray[counter].addClass('rated');
					}
				}
			});
		});
		
	},
	onClickDo: function(rating) {
	}
	
	
});
RatingStars.implement(new Options, new Events);






    







/*
Function: trim
*/
var trim = function(s) {
	while(s.substring(0,1)==' ') {
		s=s.substring(1,s.length);
	}
	while(s.substring(s.length-1,s.length)==' '){
		s=s.substring(0,s.length-1);
	}
	return s;
};



/*
Function: insertAtCursor
	Function to insert passed in text/value into textarea or input field
*/	
var insertAtCursor = function(myField, myValue) {
	//IE support
	if (document.selection) {
		myField.focus();
		sel = document.selection.createRange();
		sel.text = myValue;
	} //MOZILLA/NETSCAPE support
	else if (myField.selectionStart || myField.selectionStart == '0') {
		var startPos = myField.selectionStart;
		var endPos = myField.selectionEnd;
		myField.value = myField.value.substring(0, startPos)
		+ myValue
		+ myField.value.substring(endPos, myField.value.length);
	} else {
		myField.value += myValue;
	}
};


























window.addEvent('domready', function() {

// SETUP lOCATION
var locationCurrent;
if (locationCurrent = $('location_current')) {
	var locationSelect = $('location_select');
	locationCurrent.addEvent('click', function(e) {
		e = new Event(e).stop();
		locationCurrent.addClass('hide');
		locationSelect.removeClass('hide');
	});
	locationSelect.getElement('.current').addEvent('click', function(e) {
		e = new Event(e).stop();
		locationCurrent.removeClass('hide');
		locationSelect.addClass('hide');	
	});
}



// SET UP GLOBAL SEARCH FIELD
if ($('new_search_block')) {
	var searchFieldMini = $('search_dormant').getElement('input');
	var searchFieldFull = $('search_engine_word');

	searchFieldMini.addEvent('focus', function(e) {
		searchFieldMini.getParent().addClass('hide');
		$('search_input_wrap').removeClass('hide').addClass('show');
		searchFieldFull.focus();
	});

	$('search_link_contract').addEvent('click', function(e) {
		e = new Event(e).stop();
		searchFieldMini.getParent().removeClass('hide');
		$('search_input_wrap').removeClass('show').addClass('hide');
	});
/*
	  onSubmit="change_id('loading_layer', 'show');
	            change_id('search_engine_button', 'hide');               
	            change_id('loading_search_engine', 'loading');               
	            search_general('search_engine_fid', 'search_engine_type', 'search_engine_word', 'search_block', 0);         
	            return false;"
*/

	var globalSearchGeneral = function() {
	    search_general('search_engine_fid', 'search_engine_type', 'search_engine_word', 'search_block', 0);
	}
	if ($('search_engine')) {
		$('search_engine').addEvent('submit', function(e) {
			e = new Event(e).stop();
			$('search_engine_button').addClass('hide');    
		    $('loading_search_engine').removeClass('hide').addClass('loading');
		    globalSearchGeneral();
		});
		new SearchValidation($('search_engine_class'), $('search_engine'), globalSearchGeneral);
	}
}


/* Function to apply Previous Page Link */
var prevPageLinks = $$('.prevPageLink');
if (prevPageLinks != "") {
	prevPageLinks.each(function(link, i) {
		link.addEvent('click', function(e) {
			e = new Event(e).stop();
			window.history.go(-1);
		});
	})
}







// SETUP FOOTER

if ($('invite_friend_new')) {

var sendInviteForm = $('send_invite');
sendInviteForm.addEvent('submit', function(e) {
	e = new Event(e).stop();
	change_id('loading_layer', 'show');
	change_id('invite_friend_button', 'hide');
	change_id('loading_invite_friend', 'loading');
	change_id('invite_instruction', 'invisible');
	invite('invite_fid', 'invite_friend_email', 'invite_friend', '0');
});


sendInviteForm.getElement('h4').addEvents({
	'mouseover':	function(e) { this.addClass('mouseover'); },
	'mouseout':		function(e) { this.removeClass('mouseover'); },
	'click':		function(e) {
							change_id('invite_friend_input', 'input_wrap');
							change_id('invite_friend_alert', 'hide');
							change_id('invite_friend_error', 'hide');
							change_id('invite_friend_faq', 'hide', 'options');
							div_control('invite_friend_wrap');
							focus_control('invite_friend_wrap', 'invite_friend_email');
					}
});

sendInviteForm.getElements('h4 a').each(function(link, i) {
	link.addEvent('click', function(e) { e = new Event(e).stop(); });
});

/*
$('invite_friend_faq').getElement('a').addEvent('click', function(e) {
	e = new Event(e).stop();
	openwin('/anobi/invite_long.php', 600, 750, 'yes', '0');
});
*/

$('invite_friend_email').addEvents({
	'focus': 		function(e) { this.addClass('focused'); },
	'blur':			function(e) { this.removeClass('focused').addClass('text_input'); },
	'keyup':		function(e) { control_button('invite_friend_email', 'invite_friend_button'); },
	'keydown':		function(e) { control_button('invite_friend_email', 'invite_friend_button'); },
	'keypress':		function(e) { control_button('invite_friend_email', 'invite_friend_button'); },
	'mouseup':		function(e) { control_button('invite_friend_email', 'invite_friend_button'); },
	'mousedown':	function(e) { control_button('invite_friend_email', 'invite_friend_button'); }
});

}


if ($('language_other_option').getElement('a.reselect_language_values')) {
	$('language_option').getElement('a').addEvent('click', function(e) {
		e = new Event(e).stop();
		change_id('language_other_option', '');
	});
	var reSelectLangValues = $('language_other_option').getElement('a.reselect_language_values');
	var reSelectPID = reSelectLangValues.getProperty('id');
	var reSelectPage = reSelectLangValues.getProperty('href');
	$('reselect_language').addEvent('change', function(e) {
		change_id('loading_layer', 'show');
		change_language(reSelectPage, reSelectPID);
	});
}

$$('.bubble').each(function(bubble, i) {
	var closeBubble;
	if (closeBubble = bubble.getElement('.close')) {
		closeBubble.addEvent('click', function(e) {
			e = new Event(e).stop();
			bubble.addClass('hide');
		});
	}
});


});
