/* A wrapper for Firebug console.* statements */
var dbug = {
	logged: [],
	timers: {},
	firebug: false,
	enabled: false,
	log: function(){
		dbug.logged.push(arguments);
	},
	nolog: function(msg){
		dbug.logged.push(arguments);
	},
	time: function(name){
		dbug.timers[name] = new Date().getTime();
	},
	timeEnd: function(name){
		if (dbug.timers[name]){
			var end = new Date().getTime() - dbug.timers[name];
			dbug.timers[name] = false;
			dbug.log('%s: %s', name, end);
		}else{
			dbug.log('no such timer: %s', name);
		}
	},
	enable: function(silent){
		if (dbug.firebug){
			try {
				dbug.enabled = true;
				dbug.log = function(){(console.debug || console.log).apply(console, arguments);};
				dbug.time = function(){console.time.apply(console, arguments);};
				dbug.timeEnd = function(){console.timeEnd.apply(console, arguments);};
				if (!silent) dbug.log('enabling dbug');
				for(var i = 0; i < dbug.logged.length; i++){
					dbug.log.apply(console, dbug.logged[i]);
				}
				dbug.logged = [];
			}
			catch(e){dbug.enable.delay(400);}
		}
	},
	disable: function(){
		if (dbug.firebug) dbug.enabled = false;
		dbug.log = dbug.nolog;
		dbug.time = function(){};
		dbug.timeEnd = function(){};
	},
	cookie: function(set){
		var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
		var debugCookie = value ? unescape(value[1]) : false;
		if ((!$defined(set) && debugCookie != 'true') || ($defined(set) && set)){
			dbug.enable();
			dbug.log('setting debugging cookie');
			var date = new Date();
			date.setTime(date.getTime() + (24 * 60 * 60 * 1000));
			document.cookie = 'jsdebug=true;expires=' + date.toGMTString() + ';path=/;';
		}else{
			dbug.disableCookie();
		}
	},
	disableCookie: function(){
		dbug.log('disabling debugging cookie');
		document.cookie = 'jsdebug=false;path=/;';
	}
};

(function(){
	var fb = typeof console != "undefined";
	var debugMethods = ['debug', 'info', 'warn', 'error', 'assert', 'dir', 'dirxml'];
	var otherMethods = ['trace', 'group', 'groupEnd', 'profile', 'profileEnd', 'count'];
	function set(methodList, defaultFunction){
		for(var i = 0; i < methodList.length; i++){
			dbug[methodList[i]] = (fb && console[methodList[i]]) ? console[methodList[i]] : defaultFunction;
		}
	};
	set(debugMethods, dbug.log);
	set(otherMethods, function(){});
})();

if (typeof console != "undefined" && console.warn){
	dbug.firebug = true;
	var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
	var debugCookie = value ? unescape(value[1]) : false;

	if (window.location.href.indexOf("jsdebug=true") > 0 || debugCookie == 'true') dbug.enable();
	if (debugCookie == 'true') dbug.log('debugging cookie enabled');
	if (window.location.href.indexOf("jsdebugCookie=true") > 0){
		dbug.cookie();
		if (!dbug.enabled) dbug.enable();
	}
	if (window.location.href.indexOf("jsdebugCookie=false") > 0) dbug.disableCookie();
}

/* Defines IframeShim, a class for obscuring select lists and flash objects in IE */
var IframeShim = new Class({
	Implements: [Options, Events],
	options: {
		name: '',
		className: 'iframeShim',
		display: false,
		zindex: null,
		margin: 0,
		offset: {
			x: 0,
			y: 0
		},
		browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
	},
	initialize: function (element, options){
		this.setOptions(options);
		if (this.options.offset && this.options.offset.top) this.options.offset.y = this.options.offset.top;
		if (this.options.offset && this.options.offset.left) this.options.offset.x = this.options.offset.left;
		this.element = $(element);
		this.makeShim();
		return;
	},
	makeShim: function(){
		this.shim = new Element('iframe');
		this.id = this.options.name || new Date().getTime() + "_shim";
		if (this.element.getStyle('z-Index').toInt() < 1 || isNaN(this.element.getStyle('z-Index').toInt())){
			this.element.setStyle('z-Index', 5);
		}

		var z = this.element.getStyle('z-Index') - 1;
		if ($chk(this.options.zindex) && this.element.getStyle('z-Index').toInt() > this.options.zindex){
			z = this.options.zindex;
		}

		this.shim.setStyles({
			'position': 'absolute',
			'zIndex': z,
			'border': 'none',
			'filter': 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
		}).setProperties({
			'src': 'javascript:void(0);',
			'frameborder': '0',
			'scrolling': 'no',
			'id': this.id
		}).addClass(this.options.className);

		this.element.store('shim', this);
		var inject = function(){
			this.shim.inject(this.element, 'after');
			if (this.options.display){
				this.show();
			}else{
				this.hide();
			}
			this.fireEvent('onInject');
		};

		if (this.options.browsers){
			if (Browser.Engine.trident && !IframeShim.ready){
				window.addEvent('load', inject.bind(this));
			}else{
				inject.run(null, this);
			}
		}
	},
	position: function(shim){
		if (!this.options.browsers || !IframeShim.ready) return this;

		var before = this.element.getStyles('display', 'visibility', 'position');
		this.element.setStyles({
			display: 'block',
			position: 'absolute',
			visibility: 'hidden'
		});

		var size = this.element.getSize();
		this.element.setStyles(before);
		if ($type(this.options.margin)){
			size.x = size.x - (this.options.margin * 2);
			size.y = size.y - (this.options.margin * 2);
			this.options.offset.x += this.options.margin;
			this.options.offset.y += this.options.margin;
		}

		this.shim.setStyles({
			'width': size.x,
			'height': size.y
		}).setPosition({
			relativeTo: this.element,
			offset: this.options.offset
		});
		return this;
	},
	hide: function(){
		if (this.options.browsers) this.shim.setStyle('display', 'none');
		return this;
	},
	show: function(){
		if (!this.options.browsers) return this;
		this.shim.setStyle('display', 'block');
		return this.position();
	},
	dispose: function(){
		if (this.options.browsers) this.shim.dispose();
		return this;
	}
});
window.addEvent('load', function(){
	IframeShim.ready = true;
});

/* Extends the Date native object to include methods useful in managing dates */
new Native({name: 'Date', initialize: Date, protect: true});
['now', 'parse', 'UTC'].each(function(method){
	Native.genericize(Date, method, true);
});

Date.$Methods = new Hash();
["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds", "Time", "TimezoneOffset", "Week", "Timezone", "GMTOffset", "DayOfYear", "LastMonth", "UTCDate", "UTCDay", "UTCFullYear", "AMPM", "UTCHours", "UTCMilliseconds", "UTCMinutes", "UTCMonth", "UTCSeconds"].each(function(method){
	Date.$Methods.set(method.toLowerCase(), method);
});

$each({
	ms: "Milliseconds",
	year: "FullYear",
	min: "Minutes",
	mo: "Month",
	sec: "Seconds",
	hr: "Hours"
}, function(value, key){
	Date.$Methods.set(key, value);
});

Date.implement({
	set: function(key, value){
		key = key.toLowerCase();
		var m = Date.$Methods;
		if (m.has(key)) this['set' + m.get(key)](value);
		return this;
	},
	get: function(key){
		key = key.toLowerCase();
		var m = Date.$Methods;
		if (m.has(key)) return this['get' + m.get(key)]();
		return null;
	},
	clone: function(){
		return new Date(this.get('time'));
	},
	increment: function(interval, times){
		return this.multiply(interval, times);
	},
	decrement: function(interval, times){
		return this.multiply(interval, times, false);
	},
	multiply: function(interval, times, increment){
		interval = interval || 'day';
		times = $pick(times, 1);
		increment = $pick(increment, true);
		var multiplier = increment ? 1 : -1;
		var month = this.format("%m").toInt() - 1;
		var year = this.format("%Y").toInt();
		var time = this.get('time');
		var offset = 0;
		switch (interval){
			case 'year':
				times.times(function(val){
					if (Date.isLeapYear(year + val) && month > 1 && multiplier > 0) val++;
					if (Date.isLeapYear(year + val) && month <= 1 && multiplier < 0) val--;
					offset += Date.$units.year(year + val);
				});
				break;
			case 'month':
				times.times(function(val){
					if (multiplier < 0) val++;
					var mo = month + (val * multiplier);
					var year = year;
					if (mo < 0){
						year--;
						mo = 12 + mo;
					}

					if (mo > 11 || mo < 0){
						year += (mo / 12).toInt() * multiplier;
						mo = mo % 12;
					}
					offset += Date.$units.month(mo, year);
				});
				break;
			default:
				offset = Date.$units[interval]() * times;
				break;
		}
		this.set('time', time + (offset * multiplier));
		return this;
	},
	isLeapYear: function(){
		return Date.isLeapYear(this.get('year'));
	},
	clearTime: function(){
		this.set('hr', 0);
		this.set('min',0);
		this.set('sec', 0);
		this.set('ms', 0);
		return this;
	},
	diff: function(d, resolution){
		resolution = resolution || 'day';
		if ($type(d) == 'string') d = Date.parse(d);
		switch (resolution){
			case 'year':
				return d.format("%Y").toInt() - this.format("%Y").toInt();
				break;
			case 'month':
				var months = (d.format("%Y").toInt() - this.format("%Y").toInt()) * 12;
				return months + d.format("%m").toInt() - this.format("%m").toInt();
				break;
			default:
				var diff = d.get('time') - this.get('time');
				if (diff < 0 && Date.$units[resolution]() > (-1 * (diff))){
					return 0;
				}else if (diff >= 0 && diff < Date.$units[resolution]()){
					return 0;
				}
				return ((d.get('time') - this.get('time')) / Date.$units[resolution]()).round();
		}
	},
	getWeek: function(){
		var day = (new Date(this.get('year'), 0, 1)).get('date');
		return Math.round((this.get('dayofyear') + (day > 3 ? day - 4 : day + 3)) / 7);
	},
	getTimezone: function(){
		return this.toString().replace(/^.*?([A-Z]{3}).[0-9]{4}.*$/, '$1').replace(/^.*?\(([A-Z])[a-z]+([A-Z])[a-z]+([A-Z])[a-z]+\)$/, '$1$2$3');
	},
	getGMTOffset: function(){
		var off = this.get('timezoneOffset');
		return ((off > 0) ? '-' : '+') + Math.floor(Math.abs(off) / 60).zeroise(2) + (off % 60).zeroise(2);
	},
	parse: function(str){
		this.set('time', Date.parse(str));
		return this;
	},
	format: function(f){
		f = f || "%x %X";
		if (!this.valueOf()) return 'invalid date';
		if (Date.$formats[f.toLowerCase()]) f = Date.$formats[f.toLowerCase()];
		var d = this;
		return f.replace(/\%([aAbBcdHIjmMpSUWwxXyYTZ])/g, function($1, $2){
			switch ($2){
				case 'a': return Date.$days[d.get('day')].substr(0, 3);
				case 'A': return Date.$days[d.get('day')];
				case 'b': return Date.$months[d.get('month')].substr(0, 3);
				case 'B': return Date.$months[d.get('month')];
				case 'c': return d.toString();
				case 'd': return d.get('date').zeroise(2);
				case 'H': return d.get('hr').zeroise(2);
				case 'I': return ((d.get('hr') % 12) || 12);
				case 'j': return d.get('dayofyear').zeroise(3);
				case 'm': return (d.get('mo') + 1).zeroise(2);
				case 'M': return d.get('min').zeroise(2);
				case 'p': return d.get('hr') < 12 ? 'AM' : 'PM';
				case 'S': return d.get('seconds').zeroise(2);
				case 'U': return d.get('week').zeroise(2);
				case 'W': throw new Error('%W is not supported yet');
				case 'w': return d.get('day');
				case 'x':
					var c = Date.$cultures[Date.$culture];
					return d.format('%' + c[0].substr(0,1) + c[3] + '%' + c[1].substr(0,1) + c[3] + '%' + c[2].substr(0,1).toUpperCase());
				case 'X': return d.format('%I:%M%p');
				case 'y': return d.get('year').toString().substr(2);
				case 'Y': return d.get('year');
				case 'T': return d.get('GMTOffset');
				case 'Z': return d.get('Timezone');
				case '%': return '%';
			}
			return $2;
		});
	},
	setAMPM: function(ampm){
		ampm = ampm.toUpperCase();
		if (this.format("%H").toInt() > 11 && ampm == "AM"){
			return this.decrement('hour', 12);
		}else if (this.format("%H").toInt() < 12 && ampm == "PM"){
			return this.increment('hour', 12);
		}
		return this;
	}
});

Date.prototype.compare = Date.prototype.diff;
Date.prototype.strftime = Date.prototype.format;
Date.$nativeParse = Date.parse;
$extend(Date, {$months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], $days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], $daysInMonth: function(monthIndex, year){
	if (Date.isLeapYear(year.toInt()) && monthIndex === 1){
		return 29;
	}
	return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][monthIndex];
},
$epoch: -1,
$era: -2,
$units: {
	ms: function(){return 1},
	second: function(){return 1000},
	minute: function(){return 60000},
	hour: function(){return 3600000},
	day: function(){return 86400000},
	week: function(){return 608400000},
	month: function(monthIndex, year){
		var d = new Date();
			return Date.$daysInMonth($pick(monthIndex, d.format("%m").toInt()), $pick(year, d.format("%Y").toInt())) * 86400000;
		},
		year: function(year){
			year = year || new Date().format("%Y").toInt();
			return Date.isLeapYear(year.toInt()) ? 31622400000 : 31536000000;
		}
	},
	$formats: {
		db: '%Y-%m-%d %H:%M:%S',
		compact: '%Y%m%dT%H%M%S',
		iso8601: '%Y-%m-%dT%H:%M:%S%T',
		rfc822: '%a, %d %b %Y %H:%M:%S %Z',
		'short': '%d %b %H:%M',
		'long': '%B %d, %Y %H:%M'
	},
	isLeapYear: function(yr){
		return new Date(yr, 1, 29).getDate() == 29;
	},
	parseUTC: function(value){
		var localDate = new Date(value);
		var utcSeconds = Date.UTC(localDate.get('year'), localDate.get('mo'), localDate.get('date'), localDate.get('hr'), localDate.get('min'), localDate.get('sec'));
		return new Date(utcSeconds);
	},
	parse: function(from){
		var type = $type(from);
		if (type == 'number') return new Date(from);
		if (type != 'string') return from;
		if (!from.length) return null;
		for (var i = 0, j = Date.$parsePatterns.length; i < j; i++){
			var r = Date.$parsePatterns[i].re.exec(from);
			if (r){
				try {
					return Date.$parsePatterns[i].handler(r);
				} catch(e){
					dbug.log('date parse error: ', e);
					return null;
				}
			}
		}
		return new Date(Date.$nativeParse(from));
	},
	parseMonth: function(month, num){
		var ret = -1;
		switch ($type(month)){
			case 'object':
				ret = Date.$months[month.get('mo')];
				break;
			case 'number':
				ret = Date.$months[month - 1] || false;
				if (!ret) throw new Error('Invalid month index value must be between 1 and 12:' + index);
				break;
			case 'string':
				var match = Date.$months.filter(function(name){
					return this.test(name);
				}, new RegExp('^' + month, 'i'));
				if (!match.length) throw new Error('Invalid month string');
				if (match.length > 1) throw new Error('Ambiguous month');
				ret = match[0];
		}
		return (num) ? Date.$months.indexOf(ret) : ret;
	},
	parseDay: function(day, num){
		var ret = -1;
		switch ($type(day)){
			case 'number':
				ret = Date.$days[day - 1] || false;
				if (!ret) throw new Error('Invalid day index value must be between 1 and 7');
				break;
			case 'string':
				var match = Date.$days.filter(function(name){
					return this.test(name);
				}, new RegExp('^' + day, 'i'));
				if (!match.length) throw new Error('Invalid day string');
				if (match.length > 1) throw new Error('Ambiguous day');
				ret = match[0];
		}
		return (num) ? Date.$days.indexOf(ret) : ret;
	},
	fixY2K: function(d){
		if (!isNaN(d)){
			var newDate = new Date(d);
			if (newDate.get('year') < 2000 && d.toString().indexOf(newDate.get('year')) < 0){
				newDate.increment('year', 100);
			}
			return newDate;
		}else{
			return d;
		}
	},
	$cultures: {
		'US': ['month', 'date', 'year', '/'],
		'GB': ['date', 'month', 'year', '/']
	},
	$culture: 'US',
	$language: 'enUS',
	$cIndex: function(unit){
		return Date.$cultures[Date.$culture].indexOf(unit) + 1;
	},
	$parsePatterns: [{
		re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})$/,
		handler: function(bits){
			var d = new Date();
			var culture = Date.$cultures[Date.$culture];
			d.set('year', bits[Date.$cIndex('year')]);
			d.set('month', bits[Date.$cIndex('month')] - 1);
			d.set('date', bits[Date.$cIndex('date')]);
			return Date.fixY2K(d);
		}
	}, {
		re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2}):(\d{1,2})(\w{2})$/,
		handler: function(bits){
			var d = new Date();
			d.set('year', bits[Date.$cIndex('year')]);
			d.set('month', bits[Date.$cIndex('month')] - 1);
			d.set('date', bits[Date.$cIndex('date')]);
			d.set('hr', bits[4]);
			d.set('min', bits[5]);
			d.set('ampm', bits[6]);
			return Date.fixY2K(d);
		}
	}]
});

Number.implement({
	zeroise: function(length){
		return String(this).zeroise(length);
	}
});

String.implement({
	repeat: function(times){
		var ret = [];
		for (var i = 0; i < times; i++) ret.push(this);
		return ret.join('');
	},
	zeroise: function(length){
		return '0' . repeat(length - this.length) + this;
	}
});

/* Extends the Hash native object to include getFromPath which allows a path notation to child elements */
Hash.implement({
	getFromPath: function(notation){
		var source = this.getClean();
		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
			if (!source) return;
			var prop = arguments[2] || arguments[1] || arguments[0];
			source = (prop in source) ? source[prop] : null;
			return match;
		});
		return source;
	},
	cleanValues: function(method){
		method = method || $defined;
		this.each(function(v, k){
			if (!method(v)) this.erase(k);
		}, this);
		return this;
	}
});

/* Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc) */
String.implement({
	stripTags: function(){
		return this.replace(/<\/?[^>]+>/gi, '');
	},
	parseQuery: function(encodeKeys, encodeValues){
		encodeKeys = $pick(encodeKeys, true);
		encodeValues = $pick(encodeValues, true);
		var vars = this.split(/[&;]/);
		var rs = {};
		if (vars.length) vars.each(function(val){
			var keys = val.split('=');
			if (keys.length && keys.length == 2){
				rs[(encodeKeys) ? encodeURIComponent(keys[0]) : keys[0]] = (encodeValues) ? encodeURIComponent(keys[1]) : keys[1];
			}
		});
		return rs;
	},
	tidy: function(){
		var txt = this.toString();
		$each({"[\xa0\u2002\u2003\u2009]": " ", "\xb7": "*", "[\u2018\u2019]": "'", "[\u201c\u201d]": '"', "\u2026": "...", "\u2013": "-", "\u2014": "--", "\uFFFD": "&raquo;"}, function(value, key){
			txt = txt.replace(new RegExp(key, 'g'), value);
		});
		return txt;
	},
	cleanQueryString: function(method){
		return this.split("&").filter(method||function(set){
			return $chk(set.split("=")[1]);
		}).join("&");
	}
});

/* Extends the Element native object to include methods useful in managing inputs */
Element.implement({
	tidy: function(){
		try {this.set('value', this.get('value').tidy());}
		catch(e){dbug.log('element.tidy error: %o', e);}
	},
	getTextInRange: function(start, end){
		return this.get('value').substring(start, end);
	},
	getSelectedText: function(){
		if (Browser.Engine.trident) return document.selection.createRange().text;
		return this.get('value').substring(this.getSelectionStart(), this.getSelectionEnd());
	},
	getSelectionStart: function(){
		if (Browser.Engine.trident){
			var offset = (Browser.Engine.trident4) ? 3 : 2;
			this.focus();
			var range = document.selection.createRange();
			if (range.compareEndPoints("StartToEnd", range) != 0) range.collapse(true);
			return range.getBookmark().charCodeAt(2) - offset;
		}
		return this.selectionStart;
	},
	getSelectionEnd: function(){
		if (Browser.Engine.trident){
			var offset = (Browser.Engine.trident4) ? 3 : 2;
			var range = document.selection.createRange();
			if (range.compareEndPoints("StartToEnd", range) != 0) range.collapse(false);
			return range.getBookmark().charCodeAt(2) - offset;
		}
		return this.selectionEnd;
	},
	getSelectedRange: function(){
		return {start: this.getSelectionStart(), end: this.getSelectionEnd()}
	},
	setCaretPosition: function(pos){
		if (pos == 'end') pos = this.get('value').length;
		this.selectRange(pos, pos);
		return this;
	},
	getCaretPosition: function(){
		return this.getSelectedRange().start;
	},
	selectRange: function(start, end){
		this.focus();
		if (Browser.Engine.trident){
			var range = this.createTextRange();
			range.collapse(true);
			range.moveStart('character', start);
			range.moveEnd('character', end - start);
			range.select();
			return this;
		}
		this.setSelectionRange(start, end);
		return this;
	},
	insertAtCursor: function(value, select){
		var start = this.getSelectionStart();
		var end = this.getSelectionEnd();
		this.set('value', this.get('value').substring(0, start) + value + this.get('value').substring(end, this.get('value').length));
		if ($pick(select, true)){
			this.selectRange(start, start + value.length);
		}else{
			this.setCaretPosition(start + value.length);
		}
		return this;
	},
	insertAroundCursor: function(options, select){
		options = $extend({before: '', defaultMiddle: 'SOMETHING HERE', after: ''}, options);
		value = this.getSelectedText() || options.defaultMiddle;
		var start = this.getSelectionStart();
		var end = this.getSelectionEnd();
		if (start == end){
			var text = this.get('value');
			this.set('value', text.substring(0, start) + options.before + value + options.after + text.substring(end, text.length));
			this.selectRange(start + options.before.length, end + options.before.length + value.length);
			text = null;
		}else{
			text = this.get('value').substring(start, end);
			this.set('value', this.get('value').substring(0, start) + options.before + text + options.after + this.get('value').substring(end, this.get('value').length));
			var selStart = start + options.before.length;
			if ($pick(select, true)){
				this.selectRange(selStart, selStart + text.length);
			}else{
				this.setCaretPosition(selStart + text.length);
			}
		}
		return this;
	}
});

Element.Properties.inputValue = {
	get: function(){
		switch(this.get('tag')){
			case 'select':
				vals = this.getSelected().map(function(op){
					var v = $pick(op.get('value'),op.get('text'));
					return (v == "") ? op.get('text') : v;
				});
				return this.get('multiple') ? vals : vals[0];
			case 'input':
				switch(this.get('type')){
					case 'checkbox':
						return this.get('checked') ? this.get('value') : false;
					case 'radio':
						var checked;
						if (this.get('checked')) return this.get('value');
						$(this.getParent('form') || document.body).getElements('input').each(function(input){
							if (input.get('name') == this.get('name') && input.get('checked')) checked = input.get('value');
						}, this);
						return checked || null;
				}
			case 'input':
			case 'textarea':
				return this.get('value');
			default:
				return this.get('inputValue');
		}
	},
	set: function(value){
		switch(this.get('tag')){
			case 'select':
				this.getElements('option').each(function(op){
					var v = $pick(op.get('value'), op.get('text'));
					if (v == "") v = op.get('text');
					op.set('selected', $splat(value).contains(v));
				});
				break;
			case 'input':
				if (['radio', 'checkbox'].contains(this.get('type'))){
					this.set('checked', $type(value) == "boolean" ? value : $splat(value).contains(this.get('value')));
					break;
				}
			case 'textarea':
			case 'input':
				this.set('value', value);
				break;
			default:
				this.set('inputValue', value);
		}
		return this;
	},
	erase: function(){
		switch(this.get('tag')){
			case 'select':
				this.getElements('option').each(function(op){
					op.erase('selected');
				});
				break;
			case 'input':
				if (['radio','checkbox'].contains(this.get('type'))){
					this.set('checked', false);
					break;
				}
			case 'input':
			case 'textarea':
				this.set('value', '');
				break;
			default:
				this.set('inputValue', '');
		}
		return this;
	}
};

/* Extends the Element native object to include methods useful in measuring dimensions */
Element.implement({
	expose: function(){
		if (this.getStyle('display') != 'none') return $empty;
		var before = {};
		var styles = {visibility: 'hidden', display: 'block', position:'absolute'};
		$each(styles, function(value, style){
			before[style] = this.style[style] || '';
		}, this);
		this.setStyles(styles);
		return (function(){this.setStyles(before);}).bind(this);
	},
	getDimensions: function(options){
		options = $merge({computeSize: false}, options);
		var dim = {};
		function getSize(el, options){
			return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
		};

		if (this.getStyle('display') == 'none'){
			var restore = this.expose();
			dim = getSize(this, options);
			restore();
		}else{
			try {dim = getSize(this, options);}
			catch(e){}
		}
		return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
	},
	getComputedSize: function(options){
		options = $merge({
			styles: ['padding', 'border'],
			plains: {height: ['top', 'bottom'], width: ['left', 'right']},
			mode: 'both'
		}, options);

		var size = {width: 0, height: 0};
		switch (options.mode){
			case 'vertical':
				delete size.width;
				delete options.plains.width;
				break;
			case 'horizontal':
				delete size.height;
				delete options.plains.height;
				break;
		};

		var getStyles = [];
		$each(options.plains, function(plain, key){
			plain.each(function(edge){
				options.styles.each(function(style){
					getStyles.push((style == "border") ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
				});
			});
		});

		var styles = this.getStyles.apply(this, getStyles);
		var subtracted = [];
		$each(options.plains, function(plain, key){
			size['total' + key.capitalize()] = 0;
			size['computed' + key.capitalize()] = 0;
			plain.each(function(edge){
				size['computed' + edge.capitalize()] = 0;
				getStyles.each(function(style, i){
					if (style.test(edge)){
						styles[style] = styles[style].toInt();
						if (isNaN(styles[style])) styles[style] = 0;
						size['total' + key.capitalize()] = size['total' + key.capitalize()] + styles[style];
						size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
					}

					if (style.test(edge) && key != style && (style.test('border') || style.test('padding')) && !subtracted.contains(style)){
						subtracted.push(style);
						size['computed' + key.capitalize()] = size['computed' + key.capitalize()] - styles[style];
					}
				});
			});
		});

		if ($chk(size.width)){
			size.width = size.width + this.offsetWidth + size.computedWidth;
			size.totalWidth = size.width + size.totalWidth;
			delete size.computedWidth;
		}

		if ($chk(size.height)){
			size.height = size.height + this.offsetHeight + size.computedHeight;
			size.totalHeight = size.height + size.totalHeight;
			delete size.computedHeight;
		}
		return $extend(styles, size);
	}
});

/* Extends the Element native object to include the pin method useful for fixed positioning for elements */
window.addEvent('domready', function(){
	var test = new Element('div').setStyles({position: 'fixed', top: 0, right: 0}).inject(document.body);
	var supported = (test.offsetTop === 0);
	test.dispose();
	Browser.supportsPositionFixed = supported;
});

Element.implement({
	pin: function(enable){
		if (this.getStyle('display') == 'none'){
			dbug.log('cannot pin ' + this + ' because it is hidden');
			return;
		}

		if (enable !== false){
			var p = this.getPosition();
			if (!this.get('pinned')){
				var pos = {
					top: (p.y - window.getScroll().y),
					left: (p.x - window.getScroll().x)
				};

				if (Browser.supportsPositionFixed){
					this.setStyle('position', 'fixed').setStyles(pos);
				}else{
					this.setStyles({
						position: 'absolute',
						top: p.y,
						left: p.x
					});

					window.addEvent('scroll', function(){
						if (this.get('pinned')){
							var to = {
								top: (pos.top.toInt() + window.getScroll().y),
								left: (pos.left.toInt() + window.getScroll().x)
							};
							this.setStyles(to);
						}
					}.bind(this));
				}
				this.set('pinned', true);
			}
		}else{
			var op;
			if (!Browser.Engine.trident){
				if (this.getParent().getComputedStyle('position') != 'static'){
					op = this.getParent();
				}else{
					op = this.getParent().getOffsetParent();
				}
			}

			var p = this.getPosition(op);
			this.set('pinned', false);
			var reposition = (Browser.supportsPositionFixed) ? {top: (p.y + window.getScroll().y), left: (p.x + window.getScroll().x)} : {top: (p.y), left: (p.x)};
			this.setStyles($merge(reposition, {position: 'absolute'}));
		}
		return this;
	},
	unpin: function(){
		return this.pin(false);
	},
	togglepin: function(){
		this.pin(!this.get('pinned'));
	}
});

/* Extends the Element native object to include methods useful positioning elements relative to others */
Element.implement({
	setPosition: function(options){
		$each(options||{}, function(v, k){
			if (!$defined(v)){
				delete options[k];
			}
		});

		options = $merge({
			relativeTo: document.body,
			position: {
				x: 'center',
				y: 'center'
			},
			edge: false,
			offset: {x:0, y:0},
			returnPos: false,
			relFixedPosition: false,
			ignoreMargins: false
		}, options);

		/* compute the offset of the parent positioned element if this element is in one */
		var parentOffset = {x: 0, y: 0};
		var parentPositioned = false;
		var putItBack = this.expose();
		var offsetParent = $(this.getOffsetParent());
		putItBack();
		if (offsetParent && offsetParent != this.getDocument().body){
			var putItBack = offsetParent.expose();
			parentOffset = offsetParent.getPosition();
			putItBack();
			parentPositioned = true;
			options.offset.x = options.offset.x - parentOffset.x;
			options.offset.y = options.offset.y - parentOffset.y;
		}

		function fixValue(option){
			if ($type(option) != "string") return option;
			option = option.toLowerCase();

			var val = {};
			if (option.test('left')){
				val.x = 'left';
			}else if(option.test('right')){
				val.x = 'right';
			}else{
				val.x = 'center';
			}

			if (option.test('upper') || option.test('top')){
				val.y = 'top';
			}else if (option.test('bottom')){
				val.y = 'bottom';
			}else{
				val.y = 'center';
			}
			return val;
		};

		options.edge = fixValue(options.edge);
		options.position = fixValue(options.position);
		if (!options.edge){
			if (options.position.x == 'center' && options.position.y == 'center'){
				options.edge = {x: 'center', y: 'center'};
			}else{
				options.edge = {x: 'left', y: 'top'};
			}
		}

		this.setStyle('position', 'absolute');
		var rel = $(options.relativeTo) || document.body;
		var top = (rel == document.body) ? window.getScroll().y : rel.getPosition().y;
		var left = (rel == document.body) ? window.getScroll().x : rel.getPosition().x;

		if (top < 0) top = 0;
		if (left < 0) left = 0;
		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border', 'margin']});
		if (options.ignoreMargins){
			options.offset.x = options.offset.x - dim['margin-left'];
			options.offset.y = options.offset.y - dim['margin-top'];
		}

		var pos = {};
		var prefY = options.offset.y.toInt();
		var prefX = options.offset.x.toInt();
		switch(options.position.x){
			case 'left':
				pos.x = left + prefX;
				break;
			case 'right':
				pos.x = left + prefX + rel.offsetWidth;
				break;
			default:
				pos.x = left + (((rel == document.body) ? window.getSize().x : rel.offsetWidth) / 2) + prefX;
				break;
		};

		switch(options.position.y){
			case 'top':
				pos.y = top + prefY;
				break;
			case 'bottom':
				pos.y = top + prefY + rel.offsetHeight;
				break;
			default:
				pos.y = top + (((rel == document.body) ? window.getSize().y : rel.offsetHeight) / 2) + prefY;
				break;
		};

		if (options.edge){
			var edgeOffset = {};
			switch(options.edge.x){
				case 'left':
					edgeOffset.x = 0;
					break;
				case 'right':
					edgeOffset.x = -dim.x - dim.computedRight - dim.computedLeft;
					break;
				default:
					edgeOffset.x = -(dim.x / 2);
					break;
			};

			switch(options.edge.y){
				case 'top':
					edgeOffset.y = 0;
					break;
				case 'bottom':
					edgeOffset.y = -dim.y - dim.computedTop - dim.computedBottom;
					break;
				default:
					edgeOffset.y = -(dim.y / 2);
					break;
			};
			pos.x = pos.x + edgeOffset.x;
			pos.y = pos.y + edgeOffset.y;
		}

		pos = {left: ((pos.x >= 0 || parentPositioned) ? pos.x : 0).toInt(), top: ((pos.y >= 0 || parentPositioned) ? pos.y : 0).toInt()};
		if (rel.getStyle('position') == "fixed" || options.relFixedPosition){
			pos.top = pos.top.toInt() + window.getScroll().y;
			pos.left = pos.left.toInt() + window.getScroll().x;
		}

		if (options.returnPos){
			return pos;
		}else{
			this.setStyles(pos);
		}
		return this;
	}
});

/* Extends the Element native object to include some shortcut methods */
Element.implement({
	isVisible: function(){
		return this.getStyle('display') != 'none';
	},
	toggle: function(){
		return this[this.isVisible() ? 'hide' : 'show']();
	},
	hide: function(){
		var d;
		try {
			if ('none' != this.getStyle('display')){
				d = this.getStyle('display');
			}
		}
		catch(e){}
		this.store('originalDisplay', d || 'block');
		this.setStyle('display', 'none');
		return this;
	},
	show: function(display){
		original = this.retrieve('originalDisplay') ? this.retrieve('originalDisplay') : this.get('originalDisplay');
		this.setStyle('display', (display || original || 'block'));
		return this;
	},
	swapClass: function(remove, add){
		return this.removeClass(remove).addClass(add);
	},
	fxOpacityOk: function(){
		return !Browser.Engine.trident4;
	}
});

/* Defines Fx.Move, a class that works with Element.Position to transition an element from one location to another */
Fx.Move = new Class({
	Extends: Fx.Morph,
	options: {
		relativeTo: document.body,
		position: 'center',
		edge: false,
		offset: {x: 0, y: 0}
	},
	start: function(destination){
		return this.parent(this.element.setPosition($merge(this.options, destination, {returnPos: true})));
	}
});

Element.Properties.move = {
	set: function(options){
		var morph = this.retrieve('move');
		if (morph) morph.cancel();
		return this.eliminate('move').store('move:options', $extend({link: 'cancel'}, options));
	},
	get: function(options){
		if (options || !this.retrieve('move')){
			if (options || !this.retrieve('move:options')) this.set('move', options);
			this.store('move', new Fx.Move(this, this.retrieve('move:options')));
		}
		return this.retrieve('move');
	}
};

Element.implement({
	move: function(options){
		this.get('move').start(options);
		return this;
	}
});

/* Adds a semi-transparent overlay over a dom element with a spinnin ajax icon */
var Waiter = new Class({
	Implements: [Options, Events, Chain],
	options: {
		baseHref: '',
		containerProps: {styles: {position: 'absolute', 'text-align': 'center'}, 'class': 'waiterContainer'},
		containerPosition: {},
		msg: false,
		msgProps: {styles: {'text-align': 'center', fontWeight: 'bold'}, 'class': 'waiterMsg'},
		img: {src: 'waiter.gif', styles: {width: 24, height: 24}, 'class': 'waiterImg'},
		layer: {styles: {width: 0, height: 0, position: 'absolute', zIndex: 999, display: 'none', opacity: 0.8, background: '#000'}, 'class': 'waitingDiv'},
		useIframeShim: true,
		fxOptions: {}
	},
	initialize: function(target, options){
		this.target = $(target) || $(document.body);
		this.setOptions(options);
		this.waiterContainer = new Element('div', this.options.containerProps).inject(document.body);
		if (this.options.msg){
			this.msgContainer = new Element('div', this.options.msgProps);
			this.waiterContainer.adopt(this.msgContainer);
			if (!$(this.options.msg)){
				this.msg = new Element('p').appendText(this.options.msg);
			}else{
				this.msg = $(this.options.msg);
			}
			this.msgContainer.adopt(this.msg);
		}

		if (this.options.img) this.waiterImg = $(this.options.img.id) || new Element('img').setStyle('opacity', 1).injectInside(this.waiterContainer);
		this.waiterOverlay = $(this.options.layer.id) || new Element('div').injectInside(document.body).adopt(this.waiterContainer);
		this.waiterOverlay.set(this.options.layer);
		try {
			if (this.options.useIframeShim) this.shim = new IframeShim(this.waiterOverlay, this.options.iframeShimOptions);
		}catch(e){
			dbug.log("Waiter attempting to use IframeShim but failed; did you include IframeShim? Error: ", e);
			this.options.useIframeShim = false;
		}
		this.waiterFx = this.waiterFx || new Fx.Elements($$(this.waiterContainer, this.waiterOverlay), this.options.fxOptions);
	},
	toggle: function(element, show){
		element = $(element) || $(this.active) || $(this.target);
		if (!$(element)) return this;
		if (this.active && element != this.active) return this.stop(this.start.bind(this, element));
		if ((!this.active || show) && show !== false){
			this.start(element);
		}else if(this.active && !show){
			this.stop();
		}
		return this;
	},
	reset: function(){
		this.waiterFx.cancel().set({0: {opacity: [0]}, 1: {opacity: [0]}});
	},
	start: function(element){
		this.reset();
		element = $(element) || $(this.target);
		if (this.options.img){
			this.waiterImg.set($merge(this.options.img, {
				src: this.options.baseHref + this.options.img.src
			}));
		}

		var start = function(){
			var dim = element.getComputedSize();
			this.active = element;
			this.waiterOverlay.setStyles({
				width: this.options.layer.width || dim.totalWidth,
				height: this.options.layer.height || dim.totalHeight,
				display: 'block'
			}).setPosition({
				relativeTo: element,
				position: 'upperLeft'
			});

			this.waiterContainer.setPosition({relativeTo: this.waiterOverlay});
			if (this.options.useIframeShim) this.shim.show();
			this.waiterFx.start({
				0: {opacity: [1]},
				1: {opacity: [this.options.layer.styles.opacity]}
			}).chain(function(){
				if (this.active == element) this.fireEvent('onShow', element);
				this.callChain();
			}.bind(this));
		}.bind(this);

		if (this.active && this.active != element){
			this.stop(start);
		}else{
			start();
		}
		return this;
	},
	stop: function(callback){
		if (!this.active){
			if ($type(callback) == "function") callback.attempt();
			return this;
		}

		this.waiterFx.cancel();
		this.waiterFx.clearChain();
		this.waiterFx.start({
			0: {opacity: [0]},
			1: {opacity: [0]}
		}).chain(function(){
			this.active = null;
			this.waiterOverlay.hide();
			if (this.options.useIframeShim) this.shim.hide();
			this.fireEvent('onHide', this.active);
			this.callChain();
			this.clearChain();
			if ($type(callback) == "function") callback.attempt();
		}.bind(this));
		return this;
	}
});

/* Provides a simple method for injecting a css style element into the DOM if it's not already present */
var StyleWriter = new Class({
	createStyle: function(css, id){
		window.addEvent('domready', function(){
			try {
				if ($(id) && id) return;
				var style = new Element('style', {id: id||''}).inject($$('head')[0]);
				if (Browser.Engine.trident){
					style.styleSheet.cssText = css;
				}else{
					style.set('text', css);
				}
			}catch(e){dbug.log('error: %s', e);}
		}.bind(this));
	}
});

/* Defines Modalizer: functionality to overlay the window contents with a semi-transparent layer that prevents interaction with page content until it is removed */
var Modalizer = new Class({
	defaultModalStyle: {display: 'block', position: 'fixed', top: 0, left: 0, 'z-index': 5000, 'background-color': '#333', opacity: 0.8},
	setModalOptions: function(options){
		this.modalOptions = $merge({width: (window.getScrollSize().x + 300), height: (window.getScrollSize().y + 300), elementsToHide: 'select', hideOnClick: false, modalStyle: {}, updateOnResize: true, layerId: 'modalOverlay', onModalHide: $empty, onModalShow: $empty}, this.modalOptions, options);
		return this;
	},
	toElement: function(){
		if (!this.modalOptions.layerId) this.setModalOptions();
		return $(this.modalOptions.layerId) || new Element('div', {id: this.modalOptions.layerId}).inject(document.body);
	},
	resize: function(){
		if ($(this)){
			$(this).setStyles({width: (window.getScrollSize().x + 300), height:(window.getScrollSize().y + 300)});
		}
	},
	setModalStyle: function (styleObject){
		this.modalOptions.modalStyle = styleObject;
		this.modalStyle = $merge(this.defaultModalStyle, {width: this.modalOptions.width, height: this.modalOptions.height}, styleObject);
		if ($(this)) $(this).setStyles(this.modalStyle);
		return(this.modalStyle);
	},
	modalShow: function(options){
		this.setModalOptions(options);
		$(this).setStyles(this.setModalStyle(this.modalOptions.modalStyle));
		if (Browser.Engine.trident4) $(this).setStyle('position', 'absolute');
		$(this).removeEvents('click').addEvent('click', function(){
			this.modalHide(this.modalOptions.hideOnClick);
		}.bind(this));

		this.bound = this.bound||{};
		if (!this.bound.resize && this.modalOptions.updateOnResize){
			this.bound.resize = this.resize.bind(this);
			window.addEvent('resize', this.bound.resize);
		}
		if ($type(this.modalOptions.onModalShow)  == "function") this.modalOptions.onModalShow();
		this.togglePopThroughElements(0);
		$(this).setStyle('display', 'block');
		return this;
	},
	modalHide: function(override, force){
		if (override === false) return false;
		this.togglePopThroughElements(1);
		if ($type(this.modalOptions.onModalHide) == "function") this.modalOptions.onModalHide();
		$(this).setStyle('display', 'none');
		if (this.modalOptions.updateOnResize){
			this.bound = this.bound || {};
			if (!this.bound.resize) this.bound.resize = this.resize.bind(this);
			window.removeEvent('resize', this.bound.resize);
		}
		return this;
	},
	togglePopThroughElements: function(opacity){
		if (Browser.Engine.trident4 || (Browser.Engine.gecko && Browser.Platform.mac)){
			$$(this.modalOptions.elementsToHide).each(function(sel){sel.setStyle('opacity', opacity);});
		}
	}
});

/* Creates a div within the page with the specified contents at the location relative to the element you specify; basically an in-page popup maker */
var StickyWin = new Class({
	Implements: [Options, Events, StyleWriter],
	options: {
		closeClassName: 'closeSticky',
		pinClassName: 'pinSticky',
		content: '',
		zIndex: 10000,
		className: '',
		width: false,
		height: false,
		timeout: -1,
		allowMultipleByClass: false,
		allowMultiple: false,
		showNow: true,
		useIframeShim: true,
		iframeShimSelector: ''
	},
	css: '.SWclearfix:after {content: "."; display: block; height: 0; clear: both; visibility: hidden;}' + '.SWclearfix {display: inline-table;}' + '* html .SWclearfix {height: 1%;}' + '.SWclearfix {display: block;}',
	initialize: function(options){
		this.options.inject = {target: document.body, where: 'bottom'};
		this.setOptions(options);
		this.id = this.options.id || 'StickyWin_' + new Date().getTime();
		this.makeWindow();
		if (this.options.content) this.setContent(this.options.content);
		if (this.options.timeout > 0){
			this.addEvent('onDisplay', function(){
				this.hide.delay(this.options.timeout, this)
			}.bind(this));
		}
		if (this.options.showNow) this.show();
		this.createStyle(this.css, 'StickyWinClearFix');
	},
	toElement: function(){
		return this.win;
	},
	makeWindow: function(){
		this.destroyOthers();
		if (!$(this.id)){
			this.win = new Element('div', {
				id: this.id
			}).addClass(this.options.className).addClass('StickyWinInstance').addClass('SWclearfix').setStyles({
				display: 'none',
				position: 'absolute',
				zIndex: this.options.zIndex
			}).inject(this.options.inject.target, this.options.inject.where).store('StickyWin', this);
		}else{
			this.win = $(this.id);
		}
		if (this.options.width && $type(this.options.width.toInt()) == "number") this.win.setStyle('width', this.options.width.toInt());
		if (this.options.height && $type(this.options.height.toInt()) == "number") this.win.setStyle('height', this.options.height.toInt());
		return this;
	},
	show: function(){
		this.fireEvent('onDisplay');
		this.showWin();
		if (this.options.useIframeShim) this.showIframeShim();
		this.visible = true;
		return this;
	},
	showWin: function(){
		this.win.setStyle('display', 'block');
		if (!this.positioned) this.position();
	},
	hide: function(suppressEvent){
		if (!suppressEvent) this.fireEvent('onClose');
		this.hideWin();
		if (this.options.useIframeShim) this.hideIframeShim();
		this.visible = false;
		return this;
	},
	hideWin: function(){
		this.win.setStyle('display', 'none');
	},
	destroyOthers: function(){
		if (!this.options.allowMultipleByClass || !this.options.allowMultiple){
			$$('div.StickyWinInstance').each(function(sw){
				if (!this.options.allowMultiple || (!this.options.allowMultipleByClass && sw.hasClass(this.options.className))){
					sw.dispose();
				}
			}, this);
		}
	},
	setContent: function(html){
		if (this.win.getChildren().length > 0) this.win.empty();
		if ($type(html) == "string"){
			this.win.set('html', html);
		}else if ($(html)){
			this.win.adopt(html);
		}

		this.win.getElements('.' + this.options.closeClassName).each(function(el){
			el.addEvent('click', this.hide.bind(this));
		}, this);

		this.win.getElements('.' + this.options.pinClassName).each(function(el){
			el.addEvent('click', this.togglepin.bind(this));
		}, this);
		return this;
	},
	position: function(){
		this.positioned = true;
		this.win.setPosition({relativeTo: this.options.relativeTo, position: this.options.position, offset: this.options.offset, edge: this.options.edge});
		if (this.shim) this.shim.position();
		return this;
	},
	pin: function(pin){
		if (!this.win.pin){
			dbug.log('you must include element.pin.js!');
			return this;
		}
		this.pinned = $pick(pin, true);
		this.win.pin(pin);
		return this;
	},
	unpin: function(){
		return this.pin(false);
	},
	togglepin: function(){
		return this.pin(!this.pinned);
	},
	makeIframeShim: function(){
		if (!this.shim){
			var el = (this.options.iframeShimSelector) ? this.win.getElement(this.options.iframeShimSelector) : this.win;
			this.shim = new IframeShim(el, {display: false, name: 'StickyWinShim'});
		}
	},
	showIframeShim: function(){
		if (this.options.useIframeShim){
			this.makeIframeShim();
			this.shim.show();
		}
	},
	hideIframeShim: function(){
		if (this.options.useIframeShim) this.shim.hide();
	},
	destroy: function(){
		if (this.win) this.win.dispose();
		if (this.options.useIframeShim) this.shim.dispose();
		if ($('modalOverlay')) $('modalOverlay').dispose();
	}
});

/* Extends StickyWin to create popups that fade in and out and can be dragged and resized */
var StickyWinFx = new Class({
	Extends: StickyWin,
	options: {fade: false, fadeDuration: 150, draggable: true, dragOptions: {}, dragHandleSelector: '.drag', resizable: false, resizeOptions: {}, resizeHandleSelector: ''},
	setContent: function(html){
		this.parent(html);
		if (this.options.draggable) this.makeDraggable();
		if (this.options.resizable) this.makeResizable();
		return this;
	},
	hideWin: function(){
		if (this.options.fade){
			this.fade(0);
		}else{
			this.parent();
		}
	},
	showWin: function(){
		if (this.options.fade){
			this.fade(1);
		}else{
			this.parent();
		}
	},
	fade: function(to){
		if (!this.fadeFx){
			this.win.setStyles({opacity: 0, display: 'block'});
			var opts = {property: 'opacity', duration: this.options.fadeDuration};
			if (this.options.fadeTransition) opts.transition = this.options.fadeTransition;
			this.fadeFx = new Fx.Tween(this.win, opts);
		}

		if (to > 0){
			this.win.setStyle('display', 'block');
			this.position();
		}
		this.fadeFx.clearChain();
		this.fadeFx.start(to).chain(function (){
			if (to == 0) this.win.setStyle('display', 'none');
		}.bind(this));
		return this;
	},
	makeDraggable: function(){
		dbug.log('you must include Drag.js, cannot make draggable');
	},
	makeResizable: function(){
		dbug.log('you must include Drag.js, cannot make resizable');
	}
});

/* Implements drag and resize functionality into StickyWinFx */
if (typeof Drag != "undefined"){
	StickyWinFx.implement({
		makeDraggable: function(){
			var toggled = this.toggleVisible(true);
			if (this.options.useIframeShim){
				this.makeIframeShim();
				var onComplete = (this.options.dragOptions.onComplete || $empty);
				this.options.dragOptions.onComplete = function(){
					onComplete();
					this.shim.position();
				}.bind(this);
			}

			if (this.options.dragHandleSelector){
				var handle = this.win.getElement(this.options.dragHandleSelector);
				if (handle){
					handle.setStyle('cursor', 'move');
					this.options.dragOptions.handle = handle;
				}
			}
			this.win.makeDraggable(this.options.dragOptions);
			if (toggled) this.toggleVisible(false);
		},
		makeResizable: function(){
			var toggled = this.toggleVisible(true);
			if (this.options.useIframeShim){
				this.makeIframeShim();
				var onComplete = (this.options.resizeOptions.onComplete || $empty);
				this.options.resizeOptions.onComplete = function(){
					onComplete();
					this.shim.position();
				}.bind(this);
			}

			if (this.options.resizeHandleSelector){
				var handle = this.win.getElement(this.options.resizeHandleSelector);
				if (handle) this.options.resizeOptions.handle = this.win.getElement(this.options.resizeHandleSelector);
			}
			this.win.makeResizable(this.options.resizeOptions);
			if (toggled) this.toggleVisible(false);
		},
		toggleVisible: function(show){
			if (!this.visible && Browser.Engine.webkit && $pick(show, true)){
				this.win.setStyles({display: 'block', opacity: 0});
				return true;
			}else if(!$pick(show, false)){
				this.win.setStyles({display: 'none', opacity: 1});
				return false;
			}
			return false;
		}
	});
}

/* This script extends StickyWin and StickyWinFx classes to add Modalizer functionality */
var StickyWinModal, StickyWinFxModal;
(function(){
	var modalWinBase = function(extend){
		return {
			Extends: extend,
			initialize: function(options){
				options = options || {};
				this.setModalOptions($merge(options.modalOptions || {}, {
					onModalHide: function(){this.hide(false);}.bind(this)
				}));
				this.parent(options);
			},
			show: function(showModal){
				if ($pick(showModal, true)){
					this.modalShow();
					this.win.getElements(this.modalOptions.elementsToHide).setStyle('opacity', 1);
				}
				this.parent();
			},
			hide: function(hideModal){
				if ($pick(hideModal, true)) this.modalHide();
				this.parent($pick(hideModal, true));
			}
		}
	};
	StickyWinModal = new Class(modalWinBase(StickyWin));
	StickyWinModal.implement(new Modalizer);
	StickyWinFxModal = (typeof StickyWinFx != "undefined") ? new Class(modalWinBase(StickyWinFx)) : $empty;
	try {StickyWinFxModal.implement(new Modalizer());}
	catch(e){}
})();

/* Creates an html holder for in-page popups using a default style */
StickyWin.ui = function(caption, body, options){
	options = $extend({width: 300, cornerHandle: false, cssClass: '', buttons: []}, options);
	caption = $pick(caption, '%caption%');
	body = $pick(body, '%body%');
	var container = new Element('div').setStyle('width', options.width).addClass('jqmDialog');
	if (options.cssClass) container.addClass(options.cssClass);

	/* title */
	var title = new Element('div').addClass('jqmdTC');
	if ($(caption)){
		title.adopt(caption);
	}else{
		title.set('html', caption);
	}

	/* content */
	var content = new Element('div').addClass('jqmdMSG');
	if ($(body)){
		content.adopt(body);
	}else{
		content.set('html', body);
	}

	/* dialog top */
	title.addClass('drag');
	container.adopt(new Element('div').addClass('jqmdTL').adopt(new Element('div').addClass('jqmdTR')).adopt(title));

	/* dialog bottom */
	container.adopt(new Element('div').addClass('jqmdBL').adopt(new Element('div').addClass('jqmdBR').adopt(new Element('div').addClass('jqmdBC').adopt(content))));

	/* dialog close button */
	container.adopt(new Element('div').addClass('jqmdX').addClass('closeSticky'));
	return container;
};

/* Creates a custom html holder for in-page popups using a default style (with dynamic forms) */
StickyWin.custom = function(caption, body, options){
	options = $extend({width: 300, cornerHandle: false, cssClass: '', fields: [], buttons: []}, options);
	caption = $pick(caption, '%caption%');
	body = $pick(body, '%body%');
	var container = new Element('div').addClass('customBox');
	if (options.cssClass) container.addClass(options.cssClass);

	/* title */
	var title = new Element('span');
	if ($(caption)){
		title.adopt(caption);
	}else{
		title.set('html', caption);
	}

	/* content */
	var content = new Element('div').addClass('customContent');
	if ($(body)){
		content.adopt(body);
	}else{
		content.set('html', body);
	}

	if (options.fields.length > 0){
		var customTable = new HtmlTable({properties: {'class': 'customTable'}});
		options.fields.each(function(cField){
			var nField;
			switch(cField.type){
				case 'text':
				case 'password':
				case 'hidden':
					nField = new Element('input', {'type': cField.type, 'name': cField.name, 'id': cField.id});
					break;
				case 'textarea':
					break;
				default:
					nField = new Element('input', {'type': cField.type, 'name': cField.name, 'id': cField.id});
					break;
			}

			var properties = cField.properties;
			customTable.push(['<b>' + cField.label + '</b>', nField.setProperties(properties)]);
		});
	}

	if (options.buttons.length > 0){
		var closeButtons = new Element('div').addClass('closeButtons');
		options.buttons.each(function(button){
			if (button.properties && button.properties.className){
				button.properties['class'] = button.properties.className;
				delete button.properties.className;
			}

			var properties = button.properties;
			new Element('a').addEvent('click', button.onClick || $empty).appendText(button.text).inject(closeButtons).setProperties(properties).addClass('button');
		});
	}

	container.adopt(new Element('div').addClass('customHeader').addClass('drag').adopt(title));
	container.adopt(content.adopt(new Element('div', {'id': 'customError'}).setStyle('color', '#ff0000')).adopt(customTable.table).adopt(closeButtons).adopt(new Element('div').setStyles({'margin-top': '1px'}).set('html', '&nbsp')));

	/* dialog close button */
	container.adopt(new Element('div').addClass('customClose').addClass('closeSticky'));
	return container;
};

/* Builds table elements with methods to add rows quickly */
var HtmlTable = new Class({
	Implements: [Options],
	options: {
		properties: {cellpadding: 0, cellspacing: 0, border: 0},
		rows: []
	},
	initialize: function(options){
		this.setOptions(options);
		this.table = new Element('table').setProperties(this.options.properties);
		this.table.store('HtmlTable', this);
		this.tbody = new Element('tbody').inject(this.table);
		this.options.rows.each(this.push.bind(this));
		["adopt", "inject", "wraps", "grab", "replaces", "empty", "dispose"].each(function(method){
			this[method] = this.table[method].bind(this.table);
		}, this);
	},
	toElement: function(){
		return this.table;
	},
	push: function(row){
		var tr = new Element('tr').inject(this.tbody);
		var tds = row.map(function (tdata){
			tdata = tdata || '';
			var td = new Element('td').inject(tr);
			if (tdata.properties) td.setProperties(tdata.properties);
			function setContent(content){
				if ($(content)){
					td.adopt($(content));
				}else{
					td.set('html', content);
				}
			};

			if ($defined(tdata.content)){
				setContent(tdata.content);
			}else{
				setContent(tdata);
			}
			return td;
		}, this);
		return {tr: tr, tds: tds};
	}
});

/* Custom tooltips with AJAX functionality */
var TipsX3 = new Class({
	Implements: [Events, Options],
	options: {
		onShow: function(tip){tip.setStyle('visibility', 'visible');},
		onHide: function(tip){tip.setStyle('visibility', 'hidden');},
		showDelay: 0,
		hideDelay: 0,
		offsets: {x: 16, y: 16},
		fixed: false,
		loadingText: 'Loading...',
		errTitle: 'Oops..',
		errText: 'There was a problem retrieving the tooltip.'
	},
	initialize: function(){
		var params = Array.link(arguments, {options: Object.type, elements: $defined});
		this.setOptions(params.options || null);
		this.tip = new Element('div').inject(document.body);
		this.tip.addClass('tooltips');
		this.container = new Element('div', {'class': 'container'}).inject(this.tip);
		this.tip.setStyles({position: 'absolute', top: 0, left: 0, visibility: 'hidden'});
		if (params.elements) this.attach(params.elements);
	},
	attach: function(elements){
		$$(elements).each(function(element){
			var title = element.retrieve('tip:title', element.get('title'));
			var text = element.retrieve('tip:text', element.get('rel') || element.get('href'));
			var tclass = element.retrieve('tip:class', element.get('rel') || 'usertool');
			if (title){
				if (title.test('^DOM:', 'i')){
					title = title.split(':')[1].trim();
					var dual = title.split('::');
					if (dual.length > 1){
						element.store('tip:title', dual[0].trim());
						element.store('tip:text', dual[1].trim());
					}else{
						element.store('tip:title', title);
						element.store('tip:text', '');
					}
				}

				if (title.test('^AJAX:', 'i')){
					element.store('tip:url', title.replace(/AJAX:/i, ''));
					element.store('tip:type', 'AJAX');
				}
			}
			var enter = element.retrieve('tip:enter', this.elementEnter.bindWithEvent(this, element));
			var leave = element.retrieve('tip:leave', this.elementLeave.bindWithEvent(this, element));
			element.addEvents({mouseenter: enter, mouseleave: leave});
			if (!this.options.fixed){
				var move = element.retrieve('tip:move', this.elementMove.bindWithEvent(this, element));
				element.addEvent('mousemove', move);
			}
			element.store('tip:native', element.get('title'));
			element.erase('title');
		}, this);
		return this;
	},
	detach: function(elements){
		$$(elements).each(function(element){
			element.removeEvent('mouseenter', element.retrieve('tip:enter') || $empty);
			element.removeEvent('mouseleave', element.retrieve('tip:leave') || $empty);
			element.removeEvent('mousemove', element.retrieve('tip:move') || $empty);
			element.eliminate('tip:enter').eliminate('tip:leave').eliminate('tip:move');
			var original = element.retrieve('tip:native');
			if (original) element.set('title', original);
		});
		return this;
	},
	elementEnter: function(event, element){
		$A(this.container.childNodes).each(Element.dispose);

		this.container.empty();
		if (element.retrieve('tip:title') || element.retrieve('tip:type') == 'AJAX'){
			this.titleElement = new Element('div', {'class': element.retrieve('tip:class') + '-title'}).inject(this.container);
			this.fill(this.titleElement, element.retrieve('tip:title'));
		}

		if (element.retrieve('tip:text') || element.retrieve('tip:type') == 'AJAX'){
			this.textElement = new Element('div', {'class': element.retrieve('tip:class') + '-text'}).inject(this.container);
			this.fill(this.textElement, element.retrieve('tip:text'));
		}

		/* get new content from ajax */
		if (element.retrieve('tip:type') == 'AJAX'){
			this.ajax = new Request({
				url: element.retrieve('tip:url'),
				method: 'get',
				onRequest: function(){
					this.fill(this.titleElement, this.options.loadingText);
					this.fill(this.textElement, '<div class="usertool-loading">&nbsp;</div>');
				}.bind(this),
				onSuccess: function(response){
					var dual = response.split('::');
					if (dual.length > 1){
						this.fill(this.titleElement, dual[0].trim());
						this.fill(this.textElement, dual[1].trim());
					}else{
						this.fill(this.titleElement, response);
						this.fill(this.textElement, '<div class="usertool-loading">&nbsp;</div>');
					}
				}.bind(this),
				onFailure: function(){
					this.fill(this.titleElement, this.options.errTitle);
					this.fill(this.textElement, this.options.errText);
				}.bind(this)
			}).send();
		}

		this.timer = $clear(this.timer);
		this.timer = this.show.delay(this.options.showDelay, this);
		this.position((!this.options.fixed) ? event : {page: element.getPosition()});
	},
	elementLeave: function(event){
		$clear(this.timer);
		this.timer = this.hide.delay(this.options.hideDelay, this);
	},
	elementMove: function(event){
		this.position(event);
	},
	position: function(event){
		var size = window.getSize(), scroll = window.getScroll();
		var tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight};
		var props = {x: 'left', y: 'top'};
		for (var z in props){
			var pos = event.page[z] + this.options.offsets[z];
			if ((pos + tip[z] - scroll[z]) > size[z]){
				pos = event.page[z] - this.options.offsets[z] - tip[z];
			}
			this.tip.setStyle(props[z], pos);
		}
	},
	fill: function(element, contents){
		(typeof contents == 'string') ? element.set('html', contents) : element.adopt(contents);
	},
	show: function(){
		this.fireEvent('show', this.tip);
	},
	hide: function(){
		this.fireEvent('hide', this.tip);
	}
});

/* extends Swiff for flash detection */
Swiff.extend({
	getVersion: function(){
		if (!$defined(Swiff.pluginVersion)){
			var version;
			if (navigator.plugins && navigator.mimeTypes.length){
				version = navigator.plugins["Shockwave Flash"];
				if (version && version.description) version = version.description;
			}else if (Browser.Engine.trident){
				version = $try(function(){
					return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version");
				});
			}
			Swiff.pluginVersion = (typeof version == 'string') ? parseInt(version.match(/\d+/)[0]) : 0;
		}
		return Swiff.pluginVersion;
	}
});

/* Autocompleter */
var Autocompleter = {};
var OverlayFix = IframeShim;

Autocompleter.Base = new Class({
	Implements: [Options, Events],
	options: {
		minLength: 1,
		markQuery: true,
		width: 'inherit',
		maxChoices: 10,
		injectChoice: null,
		customChoices: null,
		emptyChoices: null,
		visibleChoices: true,
		className: 'autocompleter-choices',
		zIndex: 42,
		delay: 400,
		observerOptions: {},
		fxOptions: {},
		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: false,
		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: function(){
		if ($(this.options.customChoices)){
			this.choices = this.options.customChoices;
		}else{
			this.choices = new Element('ul', {'class': this.options.className, 'styles': {'zIndex': 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 tokens = value.split(this.options.separatorSplit).filter(function(entry){
					return this.test(entry);
				}, /[^\s,]+/);
				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();
		if (this.options.visibleChoices){
			var scroll = document.getScroll(),
			size = document.getSize(),
			coords = this.choices.getCoordinates();
			if (coords.right > scroll.x + size.x) scroll.x = coords.right - size.x;
			if (coords.bottom > scroll.y + size.y) scroll.y = coords.bottom - size.y;
			window.scrollTo(Math.min(scroll.x, coords.left), Math.min(scroll.y, coords.top));
		}
	},
	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;
		if (this.selected) this.selected.removeClass('autocompleter-selected');
		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.query();
			}
		}
		return true;
	},
	fetchCached: function(){
		return false;
		if (!this.options.cache || !this.cached || !this.cached.length || this.cached.length >= this.options.maxChoices || this.queryValue) return false;
		this.update(this.filter(this.cached));
		return true;
	},
	update: function(tokens){
		this.choices.empty();
		this.cached = tokens;
		var type = tokens && $type(tokens);
		if (!type || (type == 'array' && !tokens.length) || (type == 'hash' && !tokens.getLength())){
			(this.options.emptyChoices || this.hideChoices).call(this);
		}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 (!this.selectMode) this.opted = this.element.value;
		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){
		return (tokens || this.tokens).filter(function(token){
			return this.test(token);
		}, new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i'));
	},
	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: function(el){
		return el.addEvents({'mouseover': this.choiceOver.bind(this, [el]), 'click': this.choiceSelect.bind(this, [el])});
	}
});

/* Autocompleter.Request */
Autocompleter.Request = {};
Autocompleter.Request.Base = new Class({
	Extends: Autocompleter.Base,
	options: {
		postData: {},
		ajaxOptions: {},
		postVar: 'value'
	},
	query: function(){
		var data = $unlink(this.options.postData) || {};
		data[this.options.postVar] = this.queryValue;
		var indicator = $(this.options.indicator);
		if (indicator) indicator.setStyle('display', '');
		var cls = this.options.indicatorClass;
		if (cls) this.element.addClass(cls);
		this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]);
		this.request.send({'data': data});
	},
	queryResponse: function(){
		var indicator = $(this.options.indicator);
		if (indicator) indicator.setStyle('display', 'none');
		var cls = this.options.indicatorClass;
		if (cls) this.element.removeClass(cls);
		return this.fireEvent('onComplete', [this.element, this.request]);
	}
});

Autocompleter.Request.JSON = new Class({
	Extends: Autocompleter.Request.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.Request.HTML = new Class({
	Extends: Autocompleter.Request.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;
				this.addChoiceEvents(choice.set('html', this.markQueryValue(value)));
			}, this);
			this.showChoices();
		}
	}
});

/* Observe formelements for changes */
var Observer = new Class({
	Implements: [Options, Events],
	options: {
		periodical: false,
		delay: 1000
	},
	initialize: function(el, onFired, options){
		this.element = $(el) || $$(el);
		this.addEvent('onFired', onFired);
		this.setOptions(options);
		this.bound = this.changed.bind(this);
		this.resume();
	},
	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;
	},
	pause: function(){
		if (this.timer){
			$clear(this.timer);
		}else{
			this.element.removeEvent('keyup', this.bound);
		}
		return this.clear();
	},
	resume: function(){
		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.bound);
		}
		return this;
	}
});

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

/* extends the basic request class for use with the waiter class */
Request.LOAD = new Class({
	Extends: Request,
	options: {
		useWaiter: false,
		waiterOptions: {},
		waiterTarget: false
	},
	initialize: function(options){
		this._send = this.send;
		this.send = function(options){
			if (this.waiter){
				this.waiter.start().chain(this._send.bind(this, options));
			}else{
				this._send(options);
			}
			return this;
		};
		this.parent(options);

		if (this.options.useWaiter && this.options.waiterTarget){
			this.waiter = new Waiter(this.options.waiterTarget, this.options.waiterOptions);
			['onSuccess', 'onException', 'onCancel'].each(function(event){
				this.addEvent(event, this.waiter.stop.bind(this.waiter));
			}, this);
		}
	}
});

Request.JSON = new Class({
	Extends: Request.JSON,
	options: {
		useWaiter: false,
		waiterOptions: {},
		waiterTarget: false
	},
	initialize: function(options){
		this._send = this.send;
		this.send = function(options){
			if (this.waiter){
				this.waiter.start().chain(this._send.bind(this, options));
			}else{
				this._send(options);
			}
			return this;
		};
		this.parent(options);

		if (this.options.useWaiter && this.options.waiterTarget){
			this.waiter = new Waiter(this.options.waiterTarget, this.options.waiterOptions);
			['onSuccess', 'onException', 'onCancel'].each(function(event){
				this.addEvent(event, this.waiter.stop.bind(this.waiter));
			}, this);
		}
	}
});

var DataGrid = new Class({
	Implements: [Events, Options],
	options: {
		url: '',
		method: 'post',
		query: '',
		perPage: 15,
		headerCaptions: [],
		aligns: [],
		widths: [],
		headerIds: [],
		width: 500
	},
	initialize: function(el, options){
		this.setOptions(options);
		this.el = $type(el) === 'string' ? $(el) : el;
		this.pages = 0;
		this.createElements();
	},
	createElements: function(){
		var structureHtml = '<table cellpadding="0" cellspacing="0"><thead><tr class="paging stophover"><td colspan="' + this.options.headerCaptions.length + '"><div style="padding-left: 10px; padding-right: 10px;"><table class="paging" width="100%"><tr><td style="width: 180px; text-align: left;" class="paginationDisplay"></td><td><div align="center"><table><tr><td class="paginationFirst"><span></span></td><td class="paginationPrev"><span></span></td><td class="pagination"><span></span></td><td class="paginationNext"><span></span></td><td class="paginationLast"><span></span></td></tr></table></div></td><td style="width: 100px; text-align: right; padding-bottom: 10px;">&nbsp;</td></tr></table></div></td></tr></thead><thead><tr></tr></thead><tbody></tbody><tfoot><tr class="paging stophover"><td colspan="' + this.options.headerCaptions.length + '"><div style="padding-left: 10px; padding-right: 10px;"><table class="paging" width="100%"><tr><td style="width: 180px; text-align: left;">&nbsp;</td><td><div align="center"><table><tr><td class="paginationFirst"></td><td class="paginationPrev"><span></span></td><td class="pagination"><span></span></td><td class="paginationNext"><span></span></td><td class="paginationLast"><span></span></td></tr></table></div></td><td style="width: 100px; text-align: right; padding-bottom: 10px;">&nbsp;</td></tr></table></div></td></tr></tfoot></table>';
		this.el.set('html', structureHtml);
		this.el.addClass('datagrid');
		this.el.setStyle('width', this.options.width);
		this.table = this.el.getChildren("table")[0];
		this.tableBody = this.table.getChildren("tbody")[0];
		this.tableHead = this.table.getChildren("thead")[1];
		this.pagf = this.table.getElements('.paginationFirst > span')[0];
		this.pagp = this.table.getElements('.paginationPrev > span')[0];
		this.pag = this.table.getElements('.pagination > span')[0];
		this.pagn = this.table.getElements('.paginationNext > span')[0];
		this.pagl = this.table.getElements('.paginationLast > span')[0];
		this.pagd = this.table.getElements('.paginationDisplay')[0];
		this.headersEl = [];
		this.createHeaders();
	},
	createHeaders: function(){
		var headerCaptions = this.options.headerCaptions;
		var headerIds = this.options.headerIds;
		var headerTr = this.tableHead.getChildren("tr")[0];
		headerCaptions.each(function(header, index){
			var headerTd = new Element('th', {id: headerIds[index], width: this.options.widths[index]}).setStyle('text-align', this.options.aligns[index]);
			headerTd.set('html', headerCaptions[index]);
			this.headersEl.push(headerTd);
			headerTd.inject(headerTr);
		}, this);

		this.page = 1;
		this.requestData(this.page);
	},
	requestData: function(page, arg){
		if (!arg) var arg = '';
		var jsonRequest = new Request.JSON({
			url: this.options.url,
			method: this.options.method,
			useWaiter: true,
			waiterTarget: this.tableBody,
			waiterOptions: {
				containerProps: {styles: {position: 'absolute', 'text-align': 'left'}},
				img: {src: '/images/loader-small.gif', styles: {width: 32, height: 32}},
				layer: {styles: {position: 'absolute', display: 'block', left: 0, top: 0, opacity: 0.5, background: '#fff'}},
				useIframeShim: true,
				fxOptions: {duration: 500}
			},
			onSuccess: function(resp){
				if (resp == null){
					this.pagd.set('html', "Invalid JSON result...");
				}else{
					this.parseData(resp.total, resp.page, resp.rows);
				}
			}.bind(this),
			onFailure: function(){
				this.pagd.set('html', "The request failed...");
			}.bind(this)
		}).send("page=" + page + "&perPage=" + this.options.perPage + '&bust=' + new Date().getTime() + '&' + this.options.query + arg);
		this.page = page;
	},
	parseData: function(total, page, rows){
		this.tableBody.empty();
		rows.each(function(row, index){
			var index = index + 1;
			var tr = new Element('tr');
			var cssClass = index % 2 == 0 ? 'evenRow' : 'oddRow';
			tr.addClass(cssClass);
			row.each(function(cell, index2){
				var td = new Element('td').setStyle('text-align', this.options.aligns[index2]);
				td.set('html', cell);
				td.inject(tr);
			}, this);
			tr.inject(this.tableBody);
		}, this);

		if (this.pages === 0) this.pages = Math.ceil(total / this.options.perPage);
		if (this.pages === 0) this.pages = 1;
		var recMax = this.options.perPage * page;
		recMax = recMax > total ? total : recMax;
		var recMin = (this.options.perPage * page) - (this.options.perPage - 1);
		recMin = (total == 0) ? '0' : recMin;
		this.pagd.set('html', "Displaying " + recMin + " - " + recMax + " of " + total + " records");
		this.paginate();
	},
	paginate: function(){
		if (this.pages == 1){
			return;
		}else{
			this.pagf.empty();
			this.pagp.empty();
			this.pag.empty();
			this.pagn.empty();
			this.pagl.empty();

			/* Link to first page */
			if (this.page >= 2){
				var firstLink = new Element('a', {'html': '<img src="/themes/css/' + Cookie.read('theme') + '/misc/blog_first.gif" style="border-width: 0px;" alt="" />', 'href': '#', 'events': {'click': function(){
					this.pageClicked("first");
					return false;
				}.bind(this)}});
				this.pagf.grab(firstLink);
			}else{
				this.pagf.set('html', '<img src="/themes/css/' + Cookie.read('theme') + '/misc/blog_first.gif" style="border-width: 0px;" alt="" />');
			}

			/* Link to previous page */
			if (this.page > 1){
				var prevLink = new Element('a', {'html': '<img src="/themes/css/' + Cookie.read('theme') + '/misc/blog_prev.gif" style="border-width: 0px;" alt="" />', 'href': '#', 'events': {'click': function(){
					this.pageClicked("prev");
					return false;
				}.bind(this)}});
				this.pagp.grab(prevLink);
			}else{
				this.pagp.set('html', '<img src="/themes/css/' + Cookie.read('theme') + '/misc/blog_prev.gif" style="border-width: 0px;" alt="" />');
			}

			/* Printing page numbers */
			if (this.page == 1){
				var page1Span = new Element('span', {'html': '1&nbsp;'}).setStyles({'font-weight': 'bold', 'color': '#ff0000'});
				this.pag.grab(page1Span);
			}else{
				var page1Link = new Element('a', {'html': '1&nbsp;', 'href': "#", 'events': {'click': function(){
					this.pageClicked("first");
					return false;
				}.bind(this)}});
				this.pag.grab(page1Link);
			}

			if (this.page > 2){
				this.pag.appendText('...');
				if (this.page == this.pages && this.pages > 3){
					var minusTwo = new Element('a', {'html': this.page - 2 + '&nbsp;', 'href': "#", 'events': {'click': function(){
						this.pageClicked(this.page - 2);
						return false;
					}.bind(this)}});
					this.pag.grab(minusTwo);
				}

				var minusOne = new Element('a', {'html': this.page - 1 + '&nbsp;', 'href': "#", 'events': {'click': function(){
					this.pageClicked(this.page - 1);
					return false;
				}.bind(this)}});
				this.pag.grab(minusOne);
			}

			if (this.page != 1 && this.page != this.pages) {
				var current = new Element('span', {'html': this.page + '&nbsp;'}).setStyles({'font-weight': 'bold', 'color': '#ff0000'});
				this.pag.grab(current);
			}

			if (this.page < this.pages - 1){
				var plusOne = new Element('a', {'html': this.page + 1 + '&nbsp;', 'href': "#", 'events': {'click': function(){
					this.pageClicked(this.page + 1);
					return false;
				}.bind(this)}});
				this.pag.grab(plusOne);

				if (this.page == 1 && this.pages > 3){
					var plusTwo = new Element('a', {'html': this.page + 2 + '&nbsp;', 'href': "#", 'events': {'click': function(){
						this.pageClicked(this.page + 2);
						return false;
					}.bind(this)}});
					this.pag.grab(plusTwo);
				}
				this.pag.appendText('...');
			}

			if (this.page == this.pages){
				var lastPageSpan = new Element('span', {'html': this.pages + '&nbsp;'}).setStyles({'font-weight': 'bold', 'color': '#ff0000'});
				this.pag.grab(lastPageSpan);
			}else{
				var lastPageLink = new Element('a', {'html': this.pages + '&nbsp;', 'href': "#", 'events': {'click': function(){
					this.pageClicked("last");
					return false;
				}.bind(this)}});
				this.pag.grab(lastPageLink);
			}

			/* Link to next page */
			if (this.page < this.pages){
				var nextLink = new Element('a', {'html': '<img src="/themes/css/' + Cookie.read('theme') + '/misc/blog_next.gif" style="border-width: 0px;" alt="" />', 'href': '#', 'events': {'click': function(){
					this.pageClicked("next");
					return false;
				}.bind(this)}});
				this.pagn.grab(nextLink);
			}else{
				this.pagn.set('html', '<img src="/themes/css/' + Cookie.read('theme') + '/misc/blog_next.gif" style="border-width: 0px;" alt="" />');
			}

			/* Link to last page */
			if (this.page < this.pages){
				var lastLink = new Element('a', {'html': '<img src="/themes/css/' + Cookie.read('theme') + '/misc/blog_last.gif" style="border-width: 0px;" alt="" />', 'href': '#', 'events': {'click': function(){
					this.pageClicked("last");
					return false;
				}.bind(this)}});
				this.pagl.grab(lastLink);
			}else{
				this.pagl.set('html', '<img src="/themes/css/' + Cookie.read('theme') + '/misc/blog_last.gif" style="border-width: 0px;" alt="" />');
			}
		}
	},
	pageClicked: function(page){
		if ($type(page) === "string"){
			if (page === "next" && this.page < this.pages){
				this.requestData(this.page + 1);
			}else if(page === "prev" && this.page > 1){
				this.requestData(this.page - 1);
			}else if(page === "first" && this.page != 1){
				this.requestData(1);
			}else if(page === "last" && this.page != this.pages){
				this.requestData(this.pages);
			}
		}else{
			if (page > 0 && page <= this.pages){
				this.requestData(page);
			}
		}
	},
	reloadActivePage: function(){
		this.requestData(this.page);
	},
	reloadPage: function(arg){
		this.requestData(1, arg);
	}
});

/* observes back/forward button usage and saves states for registered modules into the hash, this allows to bookmark specific states for an application */

/* tabs interface */