/*
---

script: RS.js

description: The RiteSoft (RS) namespace declaration and utility functions. Required by all the other scripts.

copyright: Copyright (c) 2009-2010 [Ritetek Enterprises, Inc.](http://ritetek.com/).

authors: Mark Hudson and the Ritesoft Development Team (http://ritetek.com/)

inspiration:
- Most inclusions of this library are either extensions of or heavily rely on the various classes and functions provided by the Core and More libaries of the [MooTools Framework](http://mootools.net/) Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/), [MIT License](http://opensource.org/licenses/mit-license.php)
- Some of the extensions to the Native Classes are based around quasi-SQL queries and grouping/sorting as well as collection-based operations to help order data and simplify XHR queries to an API-style service.

provides: [RS, RS.Form.base]

...
*/

var RS = {$version: "1b"};
$extend(RS, new Log);

// Default options for various classes

RS.defaultOptions = RS.options = {
	mask: {
		"class": "rs-mask"
	},
	spinner: {
		"class": "rs-spinner",
		fxOptions: {
			duration: 230,
			link: "cancel",
			transition: "cubic:in:out"
		}
	},
	reveal: {
		mode: "both",
		duration: 529,
		link: "chain",
		transition: "cubic:in:out"
	}
};

// Basic utility functions

RS.fireEventProxy = function() {
	return this.type && this.bind && $type(this.bind.fireEvent) === "function" ? this.bind.fireEvent(this.type, arguments, this.delay || 0) : null;
};

RS.objectify = function(keys, values) {
	return values.map(function(val) { return val.associate(keys); });
};

RS.displayMessage = function(html, cls, inject, fade) {
	cls = cls.trim().toLowerCase();
	fade = $type(fade) === "number" ? fade : (fade ? 3 : null);
	
	inject = $splat(inject);
	inject[0] = document.id(inject[0]) || document.getElement(inject[0]) || document.body;
	inject[1] = $defined(inject[1]) ? inject[1] : "bottom";
	
	var msg = new Element("p", {"class": ("message " + cls).trim(), "html": html}).inject(inject[0], inject[1]);
	
	if($type(fade) === "number") {
		(function() { new Fx.Tween(msg, {property: 'opacity'}).start(0).chain(function() { msg.destroy(); }); }).delay(fade * 1000);
	}
	
	return msg;
};

RS.clearMessages = function(container, cls, fade) {
	container = document.id(container) || document.getElement(container) || document.body;
	messages = container.getElements("p.message" + (cls ? "." + cls : ""));
	
	if(fade) {
		messages.each(function(msg) { new Fx.Tween(msg, {property: 'opacity'}).start(0).chain(function() { msg.destroy(); }); });
	}
	else {
		messages.destroy();
	}
};

// Native Extensions

String.implement({
	// This is a javascript port of the [CodeIgniter](http://codeigniter.com/) url_title function located in the base url_helper file
	slugify: function(separator, lowercase) {
		var trans, str = this;
		
		separator = separator || "-";
		
		trans = [
			["&\\#\\d+?;", ""],
			["&\\S+?;", ""],
			["\\s+", separator],
			["[^a-z0-9\\-\\._]", ""],
			[separator + "+", separator],
			[separator + "$", separator],
			["^" + separator, separator]
		];
		
		str = str.stripTags();
		
		trans.each(function(t) {
			str = str.replace(new RegExp(t[0], "gi"), t[1]);
		});
		
		if(lowercase) {
			str = str.toLowerCase();
		}
		
		return str.trim();
	}
});

Date.extend({
	"from": function(date) {
		switch($type(date)) {
			case "number": date = new Date(date); break;
			case "string": date = Date.parse(date); break;
			case "date": date = date.clone(); break;
		}
		
		return $type(date) == "date" && date.isValid() ? date : null;
	}
});

Function.implement({
	// The same as Function.pass however it automatically prevents the default action of the event passed if one exists
	passPrevent: function(args, bind) {
		var self = this;
		return function(event) {
			if($type(event) == "event") event.preventDefault();
			return self.run(args, bind);
		};
	},
	// Provieds Function.passPrevent functionality for Function.bind
	bindPrevent: function(bind, args) {
		var self = this;
		return function(event) {
			if($type(event) == "event") event.preventDefault();
			return self.run(args, bind);
		};
	}
});

Hash.implement({
	// TODO: clarify/enhance the "clean" functionality
	restructure: function(structure, clean) {
		var restructured, structType = $type(structure);
		
		switch(structType) {
			case "object": structure = $H(structure); // FALLTHROUGH: structure will now be a hash
			case "hash": // FALLTHROUGH: the only difference between arrays and hashes will be the clean function name
			case "array":
				restructured = structure.map(function(struct) { return this.restructure(struct, clean); }, this);
				
				if(clean) {
					restructured = restructured[structType == "array" ? "clean" : "cleanValues"]();
				}
				if(structType == "object") {
					restructured = restructured.getClean();
				}
			break;
			case "string":
				restructured = this.getFromPath(structure);
				
				if($type(clean) == "function") {
					restructured = clean.run(restructured, structure, this);
				}
			break;
			default: restructured = null;
		}
		
		return restructured;
	},
	
	getr: function(notation, fallback) {
		return $pick(this.getFromPath(notation), fallback);
	},
	
	setr: function(notation, value) {
		var obj = this, props = [], key;
		
		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
			props.push(arguments[2] || arguments[1] || arguments[0]);
			return match;
		});
		
		if(props.length == 0) {
			return this;
		}
		
		key = props.pop();
		
		while((prop = props.shift())) {
			obj = obj.hasOwnProperty(prop) && $type(obj[prop]) == "object" ? obj[prop] : (obj[prop] = {});
		}
		
		if( ! obj[key] || obj.hasOwnProperty(key)) {
			obj[key] = value;
		}
		
		return this;
	},
	
	eraser: function(notation) {
		var obj = this, props = [], key;
		
		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
			props.push(arguments[2] || arguments[1] || arguments[0]);
			return match;
		});
		
		if(props.length == 0) {
			return this;
		}
		
		key = props.pop();
		
		while((prop = props.shift())) {
			obj = obj.hasOwnProperty(prop) && $type(obj[prop]) == "object" ? obj[prop] : (obj[prop] = {});
		}
		
		if(obj.hasOwnProperty(key)) {
			delete obj[key];
		}
		
		return this;
	}
});

["restructure", "getr", "setr", "eraser"].each(function(method) {
	Hash.Cookie.implement(name, function(){
		var value = Hash.prototype[method].apply(this.hash, arguments);
		if (this.options.autoSave) this.save();
		return value;
	});
});

Array.implement({
	sqlSort: function() {
		var args = $A(arguments), srt = [], _sort, _field, _dir, _type, typeKeywords;
		
		typeKeywords = {
			"date": ["date", "time", "datetime"],
			"num": ["int", "num", "numeric", "number", "float", "double"]
		};
		
		if(args.length == 0) {
			return this;
		}
		
		if($type(args[0]) != "object") {
			for(var i = 0; i < args.length; i+=3) {
				_field = args[i];
				_type = "str";
				_dir = 1;
				
				if($type(_field) != "string") {
					continue;
				}
				
				if(i < args.length - 1) {
					if(i < args.length - 2 && ["down", "desc", "descending", -1].contains(args[i+2])) {
						_dir = -1;
					}
					
					if($type(args[i+1]) == "string") {
						if(["date", "time", "datetime"].contains(args[i+1].toLowerCase())) {
							_type = "date";
						}
						else if(["int", "num", "numeric", "number", "float", "double"].contains(args[i+1].toLowerCase())) {
							_type = "num";
						}
					}
				}
				
				srt.push([_field, _type, _dir]);
			}
		}
		else {
			// Do something for object (how should it be formatted?)
			// What if there are arrays, etc.?
		}
		
		if(srt.length == 0) {
			return this;
		}
		
		// Re-assigning a hash this many times is probably inefficient; need to just do the sort manually...
		this.sort(function(a1, b1) {
			var a = $H(a1), b = $H(b1), _a, _b, comp, logged = RS.logged;
			
			if( ! $type(a)) {
				RS.log("!a", a, srt);
				return null;
			}
			if( ! $type(b)) {
				RS.log("!b", b, srt);
				return null;
			}
			
			for(var i = 0; i < srt.length; i++) {
				_a = a.getFromPath(srt[i][0]);
				_b = b.getFromPath(srt[i][0]);
				comp = 0;
				
				if(srt[i][1] == "str") {
					_a = _a == null ? "" : _a.toString();
					_b = _b == null ? "" : _b.toString();
					
					if(_a < _b) {
						_a = 0;
						_b = 1;
					}
					else if(_a > _b) {
						_a = 1;
						_b = 0;
					}
					else {
						_a = _b = 0;
					}
				}
				else if(srt[i][1] == "date") {
					_a = Date.from(_a);
					_b = Date.from(_b);
				}
				
				comp = _a - _b;
				
				if(comp != 0) {
					return comp * srt[i][2];
				}
			}
			
			return 0;
		});
		
		return this;
	},
	
	restructure: function(structure, clean) {
		return this.map(function(obj) { return $H(obj).restructure(structure, clean); });
	},
	
	group: function(groups, structure, clean) {
		var grouped = {}, key;
		groups = $splat(groups).flatten().filter(String.type);
		
		if(groups.length == 0) {
			return structure == null ? this : this.restructure(structure, clean);
		}
		
		key = groups.shift();
		
		this.each(function(obj) {
			var k = $H(obj).getFromPath(key);
			
			if(k != null) {
				k = k.toString();
				if( ! $defined(grouped[k])) {
					grouped[k] = [];
				}
				grouped[k].push(obj);
			}
		});
		
		return $H(grouped).map(function(group) { return group.group(groups, structure, clean); }).getClean();
	},
	
	// Takes an array of "paths" used to build an object from either the array itself or each sub-array if the second argument is true
	// The function is recursive such that if there are arrays in the paths argument, the corresponding values in this array will be expanded
	// Ex:
	// this: [18, "Mark", "Hudson", ["PHP", "Javascript", "SQL"], "blue", [["Justin", "Lauria"], ["Erik", "Guthrie"]]]
	// paths: ["user_id", "first_name", "last_name", "interests[]", "fav_color", ["friends[].first_name", "friends[].last_name"]
	// result: {user_id: 18, first_name: "Mark", last_name: "Hudson", interests: ["PHP", "Javascript", "SQL"], fav_color: "blue", friends: [{first_name: "Justin", last_name: "Lauria"}, {first_name: "Erik", last_name: "Guthrie"}]}
	expand: function(paths, multiple) {
		multiple = $pick(multiple, false);
		
		paths.each(function(path) {
			
		});
		
	},
	
	// Goes through each value in the array (each should be an object) and compacts each one such that the returned value will the an array of
	// "paths" for each value in the array.  If the object does not contain the given path, null will be automatically inserted because an empty
	// spot in the array will throw off the path array
	compact: function() {
		
	}
});

Element.implement({
	addClasses: function() {
		$A(arguments).flatten().each(this.addClass, this);
		return this;
	},
	removeClasses: function() {
		$A(arguments).flatten().each(this.removeClass, this);
		return this;
	}
});

Elements.implement({
	addClasses: function() {
		$A(arguments).flatten().each(this.addClass, this);
		return this;
	},
	removeClasses: function() {
		$A(arguments).flatten().each(this.removeClass, this);
		return this;
	}
});

// Initializes RS.Form and gives it the *temporary* replaceCheckboxes function that really needs to be its own class...

RS.Form = {
	replaceCheckboxes: function(container, valKey) {
		var checkAll, checkNone;
		
		container = document.id(container) || document.getElement(container);
		
		if( ! container) {
			return null;
		}
		
		checkAll = container.getElement(".checkAll");
		checkNone = container.getElement(".checkNone");
		
		container.getElements("input[type=checkbox]").each(function(chk) {
			var html = "", parent = chk.getParent(), key = $pick(valKey, chk.get("name"), "value"), val = chk.value, checked = chk.checked;
			
			chk.dispose();
			html = parent.get("html");
			
			chk = new Element("a", {"href": "#", "class": "rs-checkbox", "html": html}).replaces(parent).store("checked", checked).store(key, val);
			if(checked) {
				chk.addClass("checked");
			}
			
			parent.destroy();
		});
		
		if(checkAll) {
			checkAll = new Element("a", {"href": "#", "class": "checkAll", "html": $pick(checkAll.value, checkAll.get("html"))}).replaces(checkAll);
			checkAll.addEvent("click", function(e) {
				e.preventDefault();
				container.getElements(".rs-checkbox").addClass("checked").store("checked", true);
			});
		}
		if(checkNone) {
			checkNone = new Element("a", {"href": "#", "class": "checkNone", "html": $pick(checkNone.value, checkNone.get("html"))}).replaces(checkNone);
			checkNone.addEvent("click", function(e) {
				e.preventDefault();
				container.getElements(".rs-checkbox").removeClass("checked").store("checked", false);
			});
		}
		
		container.addEvents({
			"mouseover:relay(.rs-checkbox)": function(e, chk) { chk.addClass("hover"); },
			"mouseout:relay(.rs-checkbox)": function(e, chk) { chk.removeClass("hover"); },
			"click:relay(.rs-checkbox)": function(e, chk) {
				e.preventDefault();
				
				if(chk.retrieve("checked")) {
					chk.removeClass("checked").store("checked", false).fireEvent("uncheck");
					container.fireEvent("uncheck", [chk]);
				}
				else {
					chk.addClass("checked").store("checked", true).fireEvent("check");
					container.fireEvent("check", [chk]);
				}
			}
		});
		
		$extend(container, {
			getChecked: function(key) {
				return this.getElements(".rs-checkbox").filter(function(chk) { return chk.retrieve("checked"); }).map(function(chk) { return $type(key) == "string" ? chk.retrieve(key) : chk; });
			}.bind(container),
			setChecked: function() {
				var chks = [], args = Array.link(arguments, {key: String.type, values: Array.type});
			
				if($type(args.key) != "string") {
					args.key = "value";
				}
				if($type(args.values) != "array") {
					args.values = [];
				}
			
				this.getElements(".rs-checkbox").each(function(chk) {
					if(args.values.contains(chk.retrieve(args.key))) {
						chk.addClass("checked").store("checked", true);
					}
					else {
						chk.removeClass("checked").store("checked", false);
					}
				});
				
				return this;
			}.bind(container)
		});
		
		return container;
	},
	
	linkDatePickers: function(start, end, offset, format) {
		if($type(start) != "element" || $type(end) != "element") {
			return false;
		}
		
		offset = new Number(offset);
		offset = isNaN(offset) ? 0 : offset.toInt();
		
		format = $type(format) == "string" ? format : "%Y-%m-%d";
		
		start.addEvent("change", function() {
			var date = Date.from(start.get("value"));
			end.set("value", date ? date.increment("day", offset).format(format) : start.get("value")).fireEvent("change");
		});
		
		return true;
	}
};

// Flags on the document and window for "load state" checks

window.addEvents({
	"domready": function() { window._domready = document._domready = true; },
	"loaded": function() { window._loaded = document._loaded = true; }
});


/*
---

script: AutoComplete.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.AutoComplete = new Class({
	Implements: [Events, Options, Class.Occlude],
	property: "rs-autocomplete",
	Binds: ["reposition", "show", "hide", "injectChoice", "insertChoice", "cleanInput", "processRequest", "processResponse", "update", "getSearchString", "populate", "clearCache", "toElement"],
	
	options: {
		minLength: 3,
		maxChoices: 10,
		width: null,
		delay: 529,
		
		repositionOnShow: false,
		autoClean: true,
		cache: true,
		multiple: false,
		separator: ", ",
		
		classContainer: "rs-autocomplete",
		classSpinner: "rs-autocomplete-spinner",
		classNoChoices: "noChoices",
		msgSpinner: "Searching...",
		msgNoChoices: "Your search yielded no results.",
		
		injectChoice: null,
		positionContainer: null,
		processRequest: null,
		processResponse: null
	},
	
	cache: {},
	
	initialize: function(element, options) {
		this.element = document.id(element) || document.getElement(element);
		
		if(this.occlude()) {
			return this.occluded;
		}
		
		this.setOptions(options);
		
		this.container = new Element("div", {"class": this.options.classContainer, "styles": {"position": "absolute"}}).inject(document.body).hide();
		document.addEvent("click", function(e) {
			var el = document.id(e.target), parents, parentIsContainer = false;
			
			if(el != this.element) {
				parents = el.getParents("div." + this.options.classContainer);
				parents.each(function(parent) {
					if(parent == this.container) {
						parentIsContainer = true;
					}
				});
				
				if( ! parentIsContainer) {
					this.hide();
				}
			}
		}.bind(this));
		
		this.container.setStyle("width", $chk(this.options.width) ? this.options.width : this.element.getSize().x);
		
		this.choiceList = new Element("ul").inject(this.container);
		this.choiceList.addEvents({
			"click:relay(li)": function(e) {
				var choiceElement = document.id(e.target);
				
				while(choiceElement.getParent() != this.choiceList) {
					choiceElement = choiceElement.getParents("li")[0];
				}
				
				this.insertChoice(this.selectedChoice);
			}.bind(this),
			"mouseover:relay(li)": function(e) {
				var choiceElement = document.id(e.target);
				
				if(choiceElement.getParent() != this.choiceList || choiceElement == this.selectedChoice) {
					return null;
				}
				
				this.selectedChoice.removeClass("selected");
				this.selectedChoice = choiceElement.addClass("selected");
			}.bind(this)
		});
		
		this.keyEvents = new Keyboard({
			eventType: "keyup",
			preventDefault: true,
			events: {
				"enter": function(e) {
					e.preventDefault();
					this.insertChoice();
				}.bind(this),
				"esc": function(e) {
					e.preventDefault();
					this.hide();
				}.bind(this),
				"up": function(e) {
					var previousChoice = this.selectedChoice.getPrevious();
					
					e.preventDefault();
					
					if(previousChoice) {
						this.selectedChoice.removeClass("selected");
						this.selectedChoice = previousChoice.addClass("selected");
					}
				}.bind(this),
				"down": function(e) {
					var nextChoice = this.selectedChoice.getNext();
					
					e.preventDefault();
					
					if(nextChoice) {
						this.selectedChoice.removeClass("selected");
						this.selectedChoice = nextChoice.addClass("selected");
					}
				}.bind(this)
			}
		});
		
		this.element.addEvent("keyup", function(e) {
			var query;
			
			if(e.key === "tab") {
				this.hide();
			}
			else if( ! ["up", "down", "enter", "left", "right", "esc"].contains(e.key)) {
				if(this.delayTimer) {
					$clear(this.delayTimer);
				}
				
				query = this.getSearchString();
				
				if(query.length >= this.options.minLength) {
					this.delayTimer = this.update.delay(this.options.delay, this, [query]);
				}
			}
		}.bind(this));
		
		if(this.options.autoClean) {
			this.element.addEvents({"focus": this.cleanInput, "blur": this.cleanInput});
		}
		
		this.noChoicesMessage = new Element("p", {"class": this.options.classNoChoices, "html": this.options.msgNoChoices}).inject(this.container).hide();
		this.spinner = new Spinner(this.container, {"class": this.options.classSpinner, "html": this.options.msgSpinner});
		
		this.reposition();
		this.hide();
	},
	
	reposition: function() {
		var elCoords;
		
		if($type(this.options.positionContainer) === "function") {
			this.options.positionContainer(this.container);
		}
		else {
			elCoords = this.element.getCoordinates();
			this.container.setStyles({
				"top": elCoords.bottom,
				"left": elCoords.left,
				"width": this.options.width || elCoords.width
			});
		}
		
		this.spinner.position();
	},
	
	update: function(query) {
		if(this.open) {
			this.keyEvents.deactivate();
			this.spinner.show();
		}
		else {
			this.show();
		}
		
		if( ! query) {
			query = this.getSearchString();
		}
		
		this.processRequest(query);
	},
	
	getSearchString: function() {
		var str, start, end, caretPos;
		
		if(this.options.multiple) {
			caretPos = this.element.getCaretPosition();
			
			start = this.element.value.substr(0, caretPos).lastIndexOf(this.options.separator);
			start = start === -1 ? 0 : start + this.options.separator.length;
			
			end = this.element.value.indexOf(this.options.separator, caretPos);
			end = end === -1 ? this.element.value.length : end;
			
			str = this.element.value.substring(start, end);
		}
		else {
			str = this.element.value;
		}
		
		return str.trim();
	},
	
	populate: function(choices, query, subQueries) {
		var max = (this.options.maxChoices && this.options.maxChoices >= 0 ? this.options.maxChoices : choices.length).limit(0, choices.length);
		
		this.noChoicesMessage.hide();
		this.choiceList.hide().empty();
		
		if(max === 0) {
			this.noChoicesMessage.hide()
		}
		else {
			this.choiceList.show();
			
			for(var i = 0; i < max; i++) {
				this.injectChoice(choices[i], query, subQueries);
			}
			
			this.selectedChoice = this.choiceList.getFirst();
			this.selectedChoice.addClass("selected");
		}
		
		this.keyEvents.activate();
		this.spinner.hide();
		
		this.fireEvent("populate", [choices]);
	},
	
	show: function() {
		if(this.options.repositionOnShow) {
			this.reposition();
		}
		
		this.container.show();
		this.spinner.show();
		
		this.open = true;
		this.fireEvent("show", [this.element, this.container]);
	},
	
	hide: function() {
		if(this.delayTimer) {
			$clear(this.delayTimer);
		}
		if(this.request) {
			this.request.cancel();
			this.request = null;
		}
		
		this.keyEvents.deactivate();
		this.query = this.selectedChoice = null;
		
		this.container.hide();
		this.spinner.hide();
		this.noChoicesMessage.hide();
		this.choiceList.empty();
		
		this.open = false;
		this.fireEvent("hide", [this.element, this.container]);
	},
	
	injectChoice: function(choice, query, subQueries) {
		if(this.options.injectChoice) {
			choice = this.options.injectChoice(choice, query, subQueries);
		}
		else {
			choice = new Element("li", {html: (choice.toString() || "").replace(this.query, '<span class="searched">' + this.query + '</span>')}).store("insertValue", choice);
		}
		
		choice.inject(this.choiceList);
	},
	
	insertChoice: function(choiceElement) {
		var insertValue, curIndex, values;
		
		if($type(choiceElement) !== "element") {
			choiceElement = this.selectedChoice;
		}
		
		insertValue = choiceElement.retrieve("insertValue") || choiceElement.get("text").clean();
		
		if(this.options.multiple) {
			curIndex = this.element.value.substr(0, this.element.getCaretPosition()).split(this.options.separator).length - 1; console.log(curIndex);
			values = this.element.value.split(this.options.separator); console.log(values);
			values.splice(curIndex, 1, insertValue); console.log(values);
			values = values.map(function(val) { return val.trim(); }); console.log(values);
			
			this.element.value = values.join(this.options.separator); console.log(this.element.value);
			this.element.setCaretPosition(this.element.value.indexOf(insertValue) + insertValue.length + this.options.separator.length);
		}
		else {
			this.element.value = insertValue;
			document.id(this.element).setCaretPosition("end");
		}
		
		this.fireEvent("insert", [choiceElement, insertValue, this.element]);
		this.hide();
	},
	
	cleanInput: function() {
		var cleaned = this.element.value;
		
		if(this.options.multiple) {
			cleaned = cleaned.split(this.options.separator);
			cleaned = cleaned.map(function(val) { return val.trim(); }).filter(function(val) { return val.length > 0; });
			cleaned = cleaned.length == 0 ? "" : cleaned.join(this.options.separator) + this.options.separator;
		}
		else {
			cleaned = cleaned.trim();
		}
		
		this.element.value = cleaned;
	},
	
	clearCache: function() {
		this.cache = {};
	},
	
	processRequest: function(query) {
		if(query.length >= this.options.minLength) {
			this.query = query;
			
			if(this.cache[query]) {
				this.populate.run(this.cache[query]);
			}
			else {
				if(this.request) {
					this.request.cancel();
				}
				
				this.request = this.options.processRequest(query, this.processResponse);
			}
		}
		else {
			this.hide();
		}
	},
	
	processResponse: function(result) {
		var choices, query = this.query, subQueries = null;
		
		if($type(this.options.processResponse) === "function") {
			result = this.options.processResponse(result, query, subQueries);
		}
		
		if($type(result) === "object" && (result.choices || result.results)) {
			choices = result.choices || result.results;
			
			if(result.query) {
				query = result.query;
			}
			if(result.queries || result.sub_queries || result.subQueries) {
				subQueries = result.queries || result.sub_queries || result.subQueries;
			}
		}
		else {
			choices = result;
		}
		
		if(this.options.cache) {
			this.cache[this.query] = [choices, query, subQueries];
		}
		
		if(this.request) {
			this.populate(choices, query, subQueries);
			this.request = null;
		}
	},
	
	toElement: function() {
		return this.element;
	}
});


/*
---

script: Field.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

//? Possibly add revert (undo populate/reset) function with stack functionality
RS.Form.Field = new Class({
	Implements: [Events, Options],
	
	options: {
		el: null, //i can be a css selector, an element, or the id of an element
		container: null, //i this is for use in passing nested field lists to a fieldset constructor; it is not required
		type: null, //i "text", "hidden", "textarea", "value" (this will just store the value in the instance instead of on/in an element), "checkbox", "checkboxes", "radio", ?"datepicker"?, "select", ?"mce"?
		value: null, //i "value" (string), "checked" (bool), "selected" (array of elements), "xyz" (tries el.get then el.retrieve on "xyz"); note that if the type is checkboxes then value and checked will return arrays of type string and bool respectively
		defaultValue: null, //* need to map out special cases i.e. checkboxes, radios, select, etc.
		initializeDefault: true, //* call populate with default value (whether provided or calculated) on initialize
		nullValue: null //i if serialization results in this value, then null will be returned instead
	},
	
	initialize: function(options) {
		if(options instanceof RS.Form.Fieldset || options instanceof RS.Form.Field) {
			return options;
		}
		
		this.setOptions(options);
		var container = ! this.options.container ? document : document.id(this.options.container) || document.getElement(this.options.container) || document;
		
		// Check/set el
		if( ! this.options.el) {
			this.el = null;
		}
		else {
			this.el = [];
			
			$splat(this.options.el).each(function(el, i) {
				var elid = container.getElementById(el);
				
				if(elid) {
					this.el.include(elid);
				}
				else {
					this.el.combine(container.getElements(el));
				}
			}.bind(this));
			
			if(this.el.length === 0) {
				this.el = null;
			}
		}
		
		// Check/set type
		if( ! this.options.type) {
			if( ! this.el) {
				this.options.type = "value";
				this.value = null;
			}
			else {
				var elTag = this.el[0].get("tag");
				
				if(elTag == "select" || elTag == "textarea") {
					this.el = this.el[0];
					this.options.type = elTag;
				}
				else if(elTag == "input") {
					var elType = this.el[0].get("type");
					
					if(elType == "text" || elType == "password" || elType == "hidden") {
						this.el = this.el[0];
						this.options.type = elType == "hidden" ? "hidden" : "text";
					}
					else if(elType == "radio") {
						this.options.type = "radio";
					}
					else if(elType == "checkbox") {
						if(this.el.length > 1) {
							this.options.type = "checkboxes";
						}
						else {
							this.options.type = "checkbox";
							this.el = this.el[0];
						}
					}
					else {
						//! Input of type reset, submit, button, or something else...what do we do???
						//? Possibly treat these as text/hidden types (unexpected behaviour is usually not good)
					}
				}
				else {
					if(this.el.length == 1) {
						this.options.type = "element";
						this.el = this.el[0];
					}
					else {
						this.options.type = "elements";
						this.el = new Elements(this.el);
					}
					//! We have an el that is not an input or a select or a textarea...just call it an element(s) and move on
				}
			}
		}
		else {
			if(this.options.type.match(/^text|datepicker|hidden|textarea|checkbox|select|element$/)) {
				this.el = this.el[0];
			}
			else if(this.options.type == "value") {
				this.el = null; //i just make sure
			}
			else if(this.options.type.match(/^elements|_?checkboxes|radio|value$/)){
				this.el = new Elements(this.el);
				//! the supplied type is not a valid type...somebody messed up
			}
		}
		
		// Check/set value
		if($type(this.options.value) != "string" || this.options.value === "") {
			this.options.value = "value";
		}
		else if(this.options.value.match(/^html|text|opacity$/)) {
			this.options.value = "@" + this.options.value;
		}
		else if(this.options.value.toLowerCase() == "mooeditable") {
			this.options.value = "mooeditable";
			if( ! this.el.retrieve("MooEditable")) {
				new MooEditable(this.el, $pick(this.options.MooEditable, {}));
			}
		}
		
		if( ! this.options.value.match(/^value|checked|selected|mooeditable|@.*|#.*$/)) {
			this.options.value = "#" + this.options.value;
		}
		
		// Check/set default
		if(this.options.type == "value") {
			this.value = this.options.defaultValue;
		}
		else if(this.options.defaultValue === null) {
			this.options.defaultValue = this.serialize();
		}
		
		if(this.options.initializeDefault) {
			this.populate(this.options.defaultValue);
		}
		
		this.fireEvent("initialize", this);
	},
	//? Should this function somehow determine for radios, checkboxes, and selects whether or not it should just check/select the elements or whether it should actually set the value (if it is retrieve or something like that)... ->
	//? I think for now it should just select/check the items since that is generally what is going to be done (maybe some kind of flag or something as an additional argument or something that could do otherwise) but I think ->
	//? That setting values for those types of elements is outside the desired scope of this particular class.  Also, what happens for the other elemnts? Needs testing with different "real-world" examples. Basically to ->
	//? implement the other method (that is population of data for those specfic fields) some recursion will be needed or something like that.
	//! I'm not sure serialize will work completely because if the newValue passed in is booleans then different behavior is expected (by the user); Add some kind of if clause to determine if value passed in is single/array boolean
	//! Possibly need to just make this recursive just like serialize (not sure yet)
	populate: function(newValue) {
		if(this.options.type == "value") {
			this.value = newValue;
		}
		else if(this.options.type == "select") {
			newValue = $splat(newValue);
			
			if( ! $splat(this.serialize()).every(function(serial) { newValue.contains(serial); })) {
				this.el.getElements("option").each(function(opt) { opt.selected = newValue.contains(this.serialize(opt)); }, this);
				this.el.fireEvent("change");
			}
		}
		else if(this.options.type == "checkbox") {
			this.el.checked = $type(newValue) == "boolean" ? newValue : (newValue == this.serialize(this.el));
		}
		else if(this.options.type == "checkboxes") {
			if(newValue === true || newValue == "all") {
				this.el.each(function(el) { el.checked = true; });
			}
			else if(newValue === false || newValue === null || newValue == "none") {
				this.el.each(function(el) { el.checked = false; });
			}
			else {
				newValue = $splat(newValue);
				this.el.each(function(el) {
					el.checked = newValue.contains(this.serialize(el));
				}.bind(this));
			}
		}
		else if(this.options.type == "radio") {
			this.el.each(function(el) {
				el.checked = newValue == this.serialize(el);
			}, this);
		}
		else {
			if(this.options.value == "value") {
				this.el.value = newValue;
				this.el.fireEvent("change");
			}
			else if(this.options.value == "mooeditable") {
				this.el.value = newValue;
				$try(function() { this.el.retrieve("MooEditable").setContent(newValue); }.bind(this));
				this.el.fireEvent("change");
			}
			else if(this.options.value.charAt(0) == "@") {
				this.el.set(this.options.value.substr(1), newValue);
			}
			else if(this.options.value.charAt(0) == "#") {
				this.el.store(this.options.value.substr(1), newValue);
			}
		}
		
		this.fireEvent("populate", [this, newValue]);
		return this;
	},
	
	reset: function() {
		this.populate(this.options.defaultValue);
		
		this.fireEvent("reset", this);
		return this;
	},
	
	serialize: function(el) {
		if(el === null) {
			return null;
		}
		
		el = el || this.el;
		var serialized = null;
		
		if(this.options.type === "value") {
			serialized = this.value;
		}
		else if(this.options.type === "checkboxes" && $type(el) === "array") {
			serialized = this.options.value === "checked" ? el.map(function(chk) { return chk.checked; }) : el.filter(function(chk) { return chk.checked; }).map(this.serialize.bind(this));
		}
		else if(this.options.type === "radio" && $type(el) === "array") {
			serialized = this.serialize(el.filter(function(r) { return r.checked; })[0] || null);
		}
		else if(this.options.type === "select" && el.get("tag") === "select" && el.multiple === true) {
			serialized = el.getChildren().map(this.serialize.bind(this));
		}
		else {
			if($type(this.options.value) === "function") {
				serialized = this.options.value.run(el);
			}
			if(this.options.value === "value") {
				serialized = $defined(el.value) ? el.value : el.get("value");
			}
			else if(this.options.value === "mooeditable") {
				this.el.retrieve("MooEditable").saveContent();
				serialized = this.el.value;
			}
			else if(this.options.value.charAt(0) === "@") {
				serialized = el.get(this.options.value.substr(1));
			}
			else if(this.options.value.charAt(0) === "#") {
				serialized = el.retrieve(this.options.value.substr(1));
			}
			else if(this.options.value === "checked") {
				serialized = el.checked;
			}
			else if(this.options.value === "selected") {
				serialized = el.selected;
			}
		}
		
		if(serialized === this.options.nullValue) {
			serialized = null;
		}
		
		return serialized;
	},
	
	setDefault: function(defaultValue) {
		this.options.defaultValue = defaultValue;
		return this;
	},
	
	toElement: function() {
		return this.el;
	},
	
	destroy: function() {
		if(this.el !== null) {
			this.el.destroy();
		}
		
		this.fireEvent("destroy");
		return null;
	}
});


/*
---

script: Fieldset.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.Form.Fieldset = new Class({
	Implements: [Events, Options],
	Binds: ["getField", "getFieldNames", "getFields", "addField", "addFields", "removeField", "submit", "populate", "reset", "serialize", "toQueryString", "toElement", "destroy"],
	
	options: {
		el: null, //? document.body
		container: null, //i set to false if nesting fieldsets and the parent element should NOT be used as a limit on where to look for the element
		fields: {}, //* can be nested and the fieldset will contain a fieldset for the given fieldname (populate/serialize/reset functions will work recursively)
		replaceExistingFields: false, //i replaces existing fields with new ones if addField or addFields is called with an existing field name
		destroyFields: false, //* not sure if destruction is handled correctly
		overrideFormSubmit: "replace", //i if el is a form, override default submit action; accepted values: "prevent", "stop", "replace"; if this evaluates to true and is not one of these, then "prevent" will be used
		overrideFormReset: "replace" //i same as overrideFormSubmit but with the reset action
	},
	
	initialize: function(options) {
		if(options instanceof RS.Form.Fieldset) {
			return options;
		}
		
		this.setOptions(options);
		
		this.container = ! this.options.container ? document : document.id(this.options.container) || document.getElement(this.options.container) || document;
		
		if(this.options.el) {
			this.el = this.container.getElementById(this.options.el) || this.container.getElement(this.options.el);
		}
		
		if( ! this.el) {
			this.el = null;
		}
		else if(this.el.get("tag") === "form"){
			if(this.options.overrideFormSubmit) {
				this.el.addEvent("submit", function(e) {
					if(this.options.overrideFormSubmit === "stop") {
						e.stop();
					}
					else {
						e.preventDefault();
					}
					
					if(this.options.overrideFormSubmit === "replace") {
						this.submit();
					}
				}.bind(this));
			}
			if(this.options.overrideFormReset) {
				this.el.addEvent("reset", function(e) {
					if(this.options.overrideFormReset === "stop") {
						e.stop();
					}
					else {
						e.preventDefault();
					}
					
					if(this.options.overrideFormReset === "replace") {
						this.reset();
					}
				}.bind(this));
			}
		}
		
		this.fields = new Hash();
		this.addFields(this.options.fields);
		
		this.fireEvent("initialize", this);
	},
	
	getField: function(fieldName) {
		return this.fields.get(fieldName);
	},
	
	getFieldNames: function() {
		return this.fields.getKeys();
	},
	
	getFields: function() {
		return this.fields.getClean();
	},
	
	addField: function(fieldName, field) {
		if( ! this.options.replaceExistingFields && this.fields.has(fieldName)) {
			return this;
		}
		
		if($type(field) === "string") {
			field = {"el": field};
		}
		else if($type(field) !== "object") {
			field = {"el": null, "type": "value", "value": field};
		}
		
		if( ! (field instanceof RS.Form.Fieldset) && ! (field instanceof RS.Form.Field)) {
			if(field.container !== false) {
				field.container = this.el || this.container;
			}
			
			if(field.fields) {
				field = new RS.Form.Fieldset($merge({"replaceExistingFields": this.options.replaceExistingFields, "destroyFields": this.options.destroyFields}, field));
			}
			else {
				field = new RS.Form.Field(field);
			}
		}
		
		this.fields.set(fieldName, field);
		this.fireEvent("fieldAdded", [field, fieldName, this]); //* pass field on event
		
		return this;
	},
	
	addFields: function(fields) {
		if($type(fields) === "object") {
			$each(fields, function(field, name) {
				this.addField(name, field);
			}, this);
		}
		
		return this;
	},
	
	removeField: function(fieldName, destroyField) {
		if(this.fields.has(fieldName)) {
			if(destroyField || this.options.destroyFields) {
				var field = this.fields.get(fieldName);
				if(field.destroy) {
					field.destroy();
				}
			}
			
			this.fields.erase(fieldName);
			this.fireEvent("fieldRemoved", fieldName);
		}
		
		return this;
	},
	
	removeFields: function(fields) {
		$splat(fields).each(this.removeField.bind(this));
		
		return this;
	},
	
	submit: function() {
		this.fireEvent("submit", this.serialize());
		return this;
	},
	
	//* add functionality to only reset certain fields (fields could be an object that matches the structure of serialize and the function would recurse respectively)
	//* if fields was an array then those fields would be reset
	reset: function(fields) {
		this.fields.each(function(field) {
			if(field.reset) {
				field.reset();
			}
		});
		
		this.fireEvent("reset");
		return this;
	},
	
	populate: function(values) {
		$each(values, function(value, fieldName) {
			var field = this.fields.get(fieldName);
			if(field && field.populate) {
				field.populate(value);
			}
		}, this);
		
		this.fireEvent("populate", values);
		return this;
	},
	
	serialize: function(queryString) {
		var serialized = this.fields.map(function(field) { return field.serialize ? field.serialize() : field; });
		return queryString ? serialized.toQueryString() : serialized.getClean();
	},
	
	toQueryString: function() {
		return this.serialize(true);
	},
	
	toElement: function() {
		return this.el;
	},
	
	destroy: function() {
		this.fields.each(function(field, fieldName) {
			field.destroy();
		}, this);
		this.fields.empty();
		
		if(this.el) {
			this.el.destroy();
		}
		
		this.fireEvent("destroy");
		return null;
	}
});


/*
---

script: Pagination.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.Pagination = new Class({
	Implements: [Events, Options],
	Binds: ["changePage", "changePageSize", "buildPageSizeSelect", "update", "reset", "build"],
	options: {
		pageSize: 10,
		pageSizes: null,
		totalItems: 0,
		page: 1,
		inject: [document.body, "bottom"],
		offsetDisplay: "{start} ... {end} / {total}",
		showRefreshButton: true
	},
	nav: {},
	
	initialize: function(options) {
		this.build();
		this.reset(options);
	},
	
	reset: function() {
		var _options = this.options, options = {}, doUpdate = false;
		
		if(arguments.length === 1 && $type(arguments[0]) === "object") {
			options = arguments[0];
		}
		else if(arguments.length === 2) {
			options[arguments[0]] = arguments[1];
		}
		else {
			return null;
		}
		
		this.setOptions(options);
		
		if(this.options.pageSizes != _options.pageSizes || ( ! _options.offsetDisplay && this.options.offsetDisplay) || ( ! _options.showRefreshButton && this.options.showRefreshButton)) {
			this.build();
		}
		
		if(this.options.totalItems != this.totalItems) {
			this.totalItems = this.options.totalItems.toInt();
			doUpdate = true;
		}
		if(this.options.page != this.page) {
			this.page = this.options.page.toInt();
			doUpdate = true;
		}
		if(this.options.pageSize != this.pageSize) {
			this.pageSize = this.options.pageSize.toInt();
			doUpdate = true;
		}
		
		if(doUpdate) {
			this.update();
		}
	},
	
	changePage: function(page) {
		if($type(page) === "event") {
			page = this.currentPageInput.value.toInt()
		}
		if( ! $defined(page) || $type(page) === "event") {
			page = this.currentPageInput.value.toInt();
		}
		
		var offset = (page + "").match(/^([\+\-])([0-9]+)$/);
		
		if(offset && offset.length === 3) {
			page = this.page + (offset[1] === "+" ? 1 : -1) * offset[2].toInt();
		}
		else if(page === "first") {
			page = 1;
		}
		else if(page === "last") {
			page = this.lastPage;
		}
		else if(page == "next") {
			page = this.page + 1;
		}
		else if(page == "previous") {
			page = this.page - 1;
		}
		
		if($type(page) !== "number") {
			return false;
		}
		
		page = page.toInt().limit(1, this.lastPage);
		
		if(page === this.page) {
			return null;
		}
		
		this.page = page;
		this.fireEvent("changePage", [this.page]);
		
		return page;
	},
	
	changePageSize: function(pageSize) {
		if($type(pageSize) !== "number") {
			pageSize = this.pageSizeSelect.value;
		}
		
		pageSize = pageSize.toInt();
		
		if(pageSize === this.pageSize) {
			return null;
		}
		
		//this.page = Math.floor(this.page * this.pageSize / pageSize).limit(1, Math.ceil(this.totalItems / pageSize).limit(1, this.totalItems));
		this.page = 1;
		this.pageSize = pageSize;
		this.fireEvent("changePageSize", pageSize);
		
		this.update();
		
		return pageSize;
	},
	
	buildPageSizeSelect: function(pageSizes) {
		if( ! pageSizes) {
			pageSizes = this.options.pageSizes;
		}
		if( ! pageSizes) {
			return null;
		}
		
		this.pageSizeSelect = new Element("select");
		this.pageSizeSelect.addEvent("change", this.changePageSize);
		
		pageSizes.each(function(pageSize) {
			pageSize = pageSize.toInt();
			if( ! $chk(pageSize)) {
				return false;
			}
			
			var opt = new Element("option", {"value": pageSize, "text": pageSize}).inject(this.pageSizeSelect);
			if((this.pageSize && pageSize == this.pageSize) || (this.options.pageSize && pageSize == this.options.pageSize)) {
				opt.selected = true;
			}
		}.bind(this));
		
		return true;
	},
	
	update: function() {
		this.offsetStart = (this.page - 1) * this.pageSize + 1;
		this.offsetEnd = (this.page * this.pageSize).limit(1, this.totalItems);
		this.lastPage = Math.ceil(this.totalItems / this.pageSize) || 1;
		
		if(this.page === 1) {
			this.firstButton.addClass("disabled");
			this.previousButton.addClass("disabled");
		}
		else {
			this.firstButton.removeClass("disabled");
			this.previousButton.removeClass("disabled");
		}
		
		if(this.page === this.lastPage) {
			this.lastButton.addClass("disabled");
			this.nextButton.addClass("disabled");
		}
		else {
			this.lastButton.removeClass("disabled");
			this.nextButton.removeClass("disabled");
		}
		
		this.currentPageInput.value = this.page;
		this.lastPageDisplay.set("text", this.lastPage);
		
		if(this.options.offsetDisplay) {
			this.offsetDisplay.set("text", this.options.offsetDisplay.substitute({"start": this.offsetStart, "end": this.offsetEnd, "total": this.totalItems}));
		}
		
		this.fireEvent("update", [this.serialize()]);
	},
	
	build: function() {
		if(this.container) {
			this.container.destroy();
		}
		
		this.container = new Element("div", {"class": "pagination"});
		
		// Page Size Select
		if($type(this.options.pageSizes) === "array" && this.options.pageSizes.length > 0) {
			if(this.buildPageSizeSelect()) {
				this.container.grab(this.pageSizeSelect);
			}
		}
		
		// First / Previous Buttons
		this.firstButton = new Element("a", {"class": "first", "href": "#", "text": "first"}).inject(this.container);
		this.firstButton.addEvent("click", function(e) { e.preventDefault(); this.changePage("first"); }.bind(this));
		
		this.previousButton = new Element("a", {"class": "previous", "href": "#", "text": "previous"}).inject(this.container);
		this.previousButton.addEvent("click", function(e) { e.preventDefault(); this.changePage("previous"); }.bind(this));
		
		// Current Page input + total page display
		this.currentPageInput = new Element("input", {"type": "text"});
		this.currentPageInput.addEvents({
			"change": this.changePage,
			"keyup": function(e) {
				if(e.key === "enter") {
					this.changePage();
					document.id(e.target).blur();
					e.stop();
				}
			}.bind(this)
		});
		
		this.lastPageDisplay = new Element("span").inject(new Element("span", {"text": "/"}).inject(new Element("div", {"class": "currentPage"}).grab(this.currentPageInput).inject(this.container)));
		
		// Next / Last Buttons
		this.nextButton = new Element("a", {"class": "next", "href": "#", "text": "next"}).inject(this.container);
		this.nextButton.addEvent("click", function(e) { e.preventDefault(); this.changePage("next"); }.bind(this));
		
		this.lastButton = new Element("a", {"class": "last", "href": "#", "text": "last"}).inject(this.container);
		this.lastButton.addEvent("click", function(e) { e.preventDefault(); this.changePage("last"); }.bind(this));
		
		// Refresh Button
		if(this.options.showRefreshButton) {
			this.refreshButton = new Element("a", {"class": "refresh", "href": "#", "text": "refresh"}).inject(this.container);
			this.refreshButton.addEvent("click", function(e) { e.preventDefault(); this.fireEvent("refresh", [this.serialize()]); }.bind(this));
		}
		
		// Offset Display
		if(this.options.offsetDisplay) {
			this.offsetDisplay = new Element("span", {"class": "offsetDisplay"}).inject(this.container);
		}
		
		return true;
	},
	
	serialize: function() {
		return {page: this.page, pageSize: this.pageSize, offsetStart: this.offsetStart, offsetEnd: this.offsetEnd, totalItems: this.totalItems};
	},
	
	toElement: function() {
		return this.container;
	}
});


/*
---

script: Table.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.Table = new Class({
	Extends: HtmlTable,
	Implements: [Options, Events],
	Binds: ["_buildColResizeHandles", "_adjustColResizeHandles", "_colResizeStart", "_colResizeComplete", "_colResizeDrag", "_updateSortOrder", "populate", "getRequestData", "processRequest", "processResponse", "requestSuccess", "requestError", "update", "serialize", "_updateTableHasSelected"],
	
	options: {
		// Title Bar which includes the title and the toggle button
		title: null,
		showToggleButton: false,
		
		// Buttons to include in the button bar below the title bar
		buttons: null,
		
		// Column model for populating
		columns: null,
		
		// HtmlTable.Zebra && HtmlTable.Selectable extensions
		sortKey: null,
		sortDir: null,
		zebra: true,
		selectable: true,
		allowMultiSelect: false,
		useKeyboard: true,
		
		// Column Resizing
		minColWidth: 46,
		colResizeSnap: 0,
		
		// RS.Pagination
		pagination: true,
		
		// Request Stuff - only necessary if no function processRequest is not provided
		requestUrl: "",
		requestType: "",
		
		// Various Messages and options
		msgSpinner: "Loading...",
		spinnerFxOptions: {
			duration: 460,
			link: "chain",
			transition: "cubic:in:out"
		},
		
		// Various Classes for elements
		classWrapper: "rs-table",
		classSpinner: "rs-table-spinner",
		classTitle: "title",
		classButtonContainer: "rs-navBar",
		classTable: "table",
		classZebra: "odd",
		classRowSelected: "selected",
		classRowHovered: "hover",
		classSelectable: "selectable",
		
		// Functions used to get and process data for table population
		getRequestData: null,
		processRequest: null,
		processResponse: null
	},
	
	sortOrder: {},
	
	initialize: function(element, options) {
		this.wrapper = new Element("div");
		
		if($type(element) === "array") {
			this.parent(null, options);
			this.element.inject(this.wrapper.inject(document.id(element[0]) || document.getElement(element[0]), element[1] || "bottom"));
		}
		else {
			this.parent(element, options);
			this.wrapper.wraps(this.element);
		}
		
		// Wrapper
		this.wrapper.addClass(this.options.classWrapper);
		this.spinner = new Spinner(this.wrapper, {"class": this.options.classSpinner, "message": this.options.msgSpinner, "fxOptions": this.options.spinnerFxOptions});
		
		// Buttons
		if(this.options.buttons) {
			this.buttonContainer = new Element("div", {"class": this.options.classButtonContainer}).inject(this.wrapper);
			
			this.options.buttons.each(function(btn) {
				var button;
				
				if(btn === "separator") {
					button = new Element("span", {"class": "separator"}).inject(this.buttonContainer);
				}
				else {
					button = new Element("a", {"href": "#", "text": btn.label}).inject(this.buttonContainer);
					
					if(btn["class"]) {
						button.addClass(btn["class"]);
					}
					if(btn.onClick) {
						button.addEvent("click", function(e) { e.preventDefault(); btn.onClick() });
					}
				}
				
				button.inject(this.buttonContainer);
			}.bind(this));
		}
		
		// Title
		if(this.options.title) {
			this.titleContainer = new Element("h3", {"class": this.options.classTitle}).inject(this.buttonContainer, "top");
			
			if($type(this.options.title) === "string") {
				this.titleContainer.set("html", this.options.title);
			}
			
			// Toggle Table Button
			if(this.options.showToggleButton) {
				this.toggleButton = new Element("a", {"href": "#", "class": "toggle", "text": "toggle"}).inject(this.titleContainer);
				this.toggleButton.addEvent("click", function(e) {
					e.preventDefault();
					this.titleContainer.getAllNext().toggle();
				}.bind(this));
			}
		}
		
		// Table
		this.sortOrder = {};
		if($type(this.options.sortOrder) == "object" && $H(this.options.sortOrder).getLength()) {
			this.sortOrder = this.options.sortOrder;
		}
		else if(this.options.sortKey) {
			this.sortOrder[this.options.sortKey] = this.options.sortDir === "desc" ? "desc" : "asc";
		}
		
		this.element.inject(this.wrapper).addClass(this.options.classTable);
		this.setHeaders(this.options.columns.map(function(col) { return col.label; }));
		this.element.getElements("th").each(function(th, i) {
			var opts = this.options.columns[i];
			
			th.store("key", opts.key);
			th.store("hidden", !! opts.hidden || false);
			
			if(opts["class"]) {
				th.addClass(opts["class"]);
			}
			if(opts.sortable) {
				th.addClass("sortable");
				th.store("sortable", opts.sortable);
			}
			if(opts.align) {
				th.setStyle("text-align", opts.align);
			}
			if(opts.width) {
				th.setStyle("width", (opts.width == "auto" || opts.width >= this.options.minColWidth) ? opts.width : this.options.minColWidth);
			}
			
			if(this.sortOrder[opts.key]) {
				th.addClass("sort-" + this.sortOrder[opts.key]);
			}
		}, this);
		this.element.addEvents({
			"click:relay(th, th span)": this._updateSortOrder/*,
			"mouseover:relay(th)": function(e) { document.id(e.target).addClass("hover"); },
			"mouseout:relay(th)": function(e) { document.id(e.target).removeClass("hover"); }*/
		});
		
		// Pagination
		if(this.options.pagination) {
			this.pagination = new RS.Pagination($type(this.options.pagination) === "object" ? this.options.pagination : {});
			document.id(this.pagination).inject(this.wrapper);
			
			// Repopulate table when anything happens with the pagination
			["refresh", "changePage", "changePageSize"].each(function(evt) { this.pagination.addEvent(evt, this.update); }, this);
		}
		
		this.addEvent("rowFocus", this._updateTableHasSelected, true);
		this.addEvent("rowUnfocus", this._updateTableHasSelected, true);
		
		this.update();
	},
	
	getRequestData: function() {
		return $type(this.options.getRequestData) === "function" ? this.options.getRequestData(this.serialize()) : this.serialize();
	},
	
	processRequest: function() {
		var requestOptions;
		this.requesting = true;
		this.spinner.resize().show();
		
		requestOptions = {
			url: this.options.requestUrl,
			type: this.options.requestType,
			onSuccess: this.requestSuccess,
			onError: this.requestError,
			data: this.getRequestData()
		};
		
		return $type(this.options.processRequest) === "function" ? this.options.processRequest(requestOptions) : new Request(requestOptions).send();
	},
	
	processResponse: function(result) {
		if($type(this.options.processResponse) === "function") {
			result = this.options.processResponse(result);
		}
		
		if($type(result) !== "object") {
			result = JSON.decode(result) || {total: 0, keys: [], data: []};
		}
		
		this.pagination.totalItems = result.total;
		this.pagination.update();
		
		this.populate(result.data || result.rows || result.records || []);
	},
	
	requestSuccess: function() {
		this.requesting = false;
		this.fireEvent("requestSuccess", arguments);
		
		this.processResponse.run(arguments, this);
	},
	
	requestError: function() {
		this.requesting = false;
		this.spinner.hide();
		
		this.fireEvent("requestError", arguments);
	},
	
	update: function() {
		this.pagination.update();
		this.fireEvent("update");
		this.processRequest();
	},
	
	populate: function(data) {
		var rowData, keys = [], col_aligns = [], columns = this.element.getElements("th"), cells = [];
		
		for(var i = 0, l = columns.length; i < l; i++) {
			if( ! columns[i].retrieve("hidden")) {
				keys.push(columns[i].retrieve("key"));
				col_aligns.push(columns[i].getStyle("text-align"));
			}
		}
		
		this.empty();
		this.spinner.resize();
		for(var i = 0, l = data.length; i < l; i++) {
			cells = [];
			rowData = $H(data[i]);
			
			for(var j = keys.length; j--;) {
				cells.unshift({content: rowData.getFromPath(keys[j]) || " ", style: "text-align: " + col_aligns[j]});
			}
			
			this.push(cells).tr.store("data", rowData.getClean());
		}
		this.spinner.resize();
		
		this.spinner.hide();
		this._updateTableHasSelected();
		this._adjustColResizeHandles();
		this.fireEvent("populate", data);
	},
	
	setHeaders: function(headers) {
		this.parent(headers);
		this.element.getElements("th").each(function(th) { th.set("html", "<span>" + th.get("html") + "</span>"); });
		this._buildColResizeHandles();
	},
	
	hideColumn: function(i) {
		this.element.getElement("th:nth-child(" + i + ")").hide();
		this.element.getElements("td:nth-child(" + i + ")").hide();
	},
	
	showColumn: function(i) {
		this.element.getElement("th:nth-child(" + i + ")").show();
		this.element.getElements("td:nth-child(" + i + ")").show();
	},
	
	_buildColResizeHandles: function() {
		return null;
		
		this.wrapper.getElements("div.colResizeHandle").destroy();
		
		this.element.getElements("th").each(function(th) {
			var handleCoords, colCoords = th.getCoordinates(this.wrapper), handle;
			
			if(colCoords.width < this.options.minColWidth) {
				th.setStyle("width", this.options.minColWidth);
				colCoords = th.getCoordinates(this.wrapper);
			}
			
			handle = new Element("div", {
				"class": "colResizeHandle",
				"events": {
					mouseover: function() { handle.addClass("hover"); },
					mouseout: function() { handle.removeClass("hover"); }
				}
			}).inject(this.wrapper);
			
			handleCoords = handle.getCoordinates(this.wrapper);
			
			var drag = new Drag(handle, {
				snap: this.options.colResizeSnap,
				onBeforeStart: this._colResizeStart,
				onDrag: this._colResizeDrag,
				onComplete: this._colResizeComplete
			});
			
			handle.store("col", th);
			handle.store("drag", drag);
			th.store("handle", handle);
		}, this);
		
		this._adjustColResizeHandles();
	},
	
	serialize: function() {
		return $merge(this.pagination.serialize(), {sortOrder: this.sortOrder});
	},
	
	_updateSortOrder: function(e) {
		var el = document.id(e.target), key;
		
		if(el.get("tag") === "span") {
			el = el.getParent();
		}
		
		key = el.retrieve("key");
		
		if( ! el.retrieve("sortable")) {
			return null;
		}
		
		if(this.sortOrder[key]) {
			if(this.sortOrder[key] === "asc") {
				this.sortOrder[key] = "desc";
				el.removeClass("sort-asc").addClass("sort-desc");
			}
			else {
				this.sortOrder[key] = "asc";
				el.removeClass("sort-desc").addClass("sort-asc");
			}
		}
		else {
			this.sortOrder = {};
			this.sortOrder[key] = "asc";
			
			this.element.getElement("th.sort-asc, th.sort-desc").removeClass("sort-desc").removeClass("sort-asc");
			el.addClass("sort-asc");
		}
		
		this.fireEvent("changeSort", this.sortOrder);
		this.update();
		
		return this;
	},
	
	selectAll: function(status) {
		this.parent(status);
		this._updateTableHasSelected();
	},
	
	selectNone: function() {
		this.parent();
		this._updateTableHasSelected();
	},
	
	_adjustColResizeHandles: function() {
		return null;
		
		this.element.getElements("th").each(function(th) {
			var colCoords = th.getCoordinates(this.wrapper);
			th.retrieve("handle").setStyle("height", this.element.getSize().y).setPosition({x: colCoords.right - 1, y: colCoords.top - 1});
		}.bind(this));
	},
	
	_colResizeStart: function(handle) {
		var handleCoords = handle.getCoordinates(this.wrapper), colCoords = handle.retrieve("col").getCoordinates(this.wrapper);
		
		handle.addClass("dragging");
		handle.retrieve("drag").options.limit = {x: [colCoords.left + this.options.minWidth, 12167], y: [handleCoords.top, handleCoords.top]};
	},
	
	_colResizeComplete: function(handle) {
		handle.removeClass("dragging");
		this._adjustColResizeHandles();
	},
	
	_colResizeDrag: function(handle) {
		var col = handle.retrieve("col");
		
		console.log("handle left: %n, col left: %n", handle.getCoordinates(this.wrapper).left, col.getCoordinates(this.wrapper).left);
		
		col.setStyle("width", handle.getCoordinates(this.wrapper).left - col.getCoordinates(this.wrapper).left - 1);
	},
	
	_updateTableHasSelected: function() {
		if(this.element.getElements("tr." + this.options.classRowSelected).length > 0 && ! this.wrapper.hasClass("hasSelected")) {
			this.wrapper.addClass("hasSelected");
		}
		else {
			this.wrapper.removeClass("hasSelected");
		}
	}
});


/*
---

script: RecordViewer.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.RecordViewer = new Class({
	Implements: [Events, Options, Class.Occlude],
	Binds: ["show", "hide", "toggle", "populate", "clear"],
	property: "rs-recordViewer",
	
	options: {
		populate: $empty,
		defaultShow: false,
		clearOnHide: true
	},
	
	initialize: function(element, options) {
		this.element = document.id(element) || document.getElement(element);
		if(this.occlude()) {
			return this.occluded;
		}
		this.setOptions(options);
		
		this.element.addEvent("click:relay(input.close, button.close, a.close, input.hide, button.hide, a.hide)", function(e) { e.preventDefault(); this.hide(); }.bind(this));
		
		this[this.options.defaultShow ? "show" : "hide"]();
	},
	
	show: function(record) {
		if($type(record) === "object") {
			this.populate(record);
		}
		
		if(this.hidden) {
			this.element.show();
			this.hidden = false;
		}
		
		this.fireEvent("show", [this.element]);
	},
	
	hide: function() {
		var record = this.record;
		
		this.element.hide();
		if(this.options.clearOnHide) {
			this.clear();
		}
		
		this.hidden = true;
		this.fireEvent("hide", [this.element, record]);
	},
	
	toggle: function() {
		this[this.hidden ? "show" : "hide"]();
	},
	
	populate: function(record) {
		if($type(this.options.populate) === "function") {
			this.options.populate.run([this.element, record]);
			this.record = record;
		}
		
		this.fireEvent("populate", [record]);
	},
	
	clear: function() {
		var record = this.record;
		this.record = null;
		
		this.fireEvent("clear", [record]);
	},
	
	toElement: function() {
		return this.element;
	}
});


/*
---

script: RecordsController.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.RecordsController = new Class({
	Implements: [Events, Options],
	Binds: ["showForm", "hideForm", "setFormTitle", "submitForm", "processFormResult", "hideMessages", "positionFormContainer"],
	
	options: {
		table: [null, {}],
		
		sessionVar: null,
		
		classFormMask: "rs-mask",
		classFormSpinner: "rs-spinner",
		msgFormSpinner: "Submitting...",
		formTitle: "",
		processFormData: $lambda,
		prepareFormData: $lambda,
		formOffsetTop: 46,
		formOffsetLeft: "50%",
		form: {
			fields: {},
			replaceExistingFields: false,
			destroyFields: false,
			overrideFormSubmit: "replace",
			overrideFormReset: "prevent"
		},
		
		recordViewer: {element: null, options: {}},
		
		hideFormOnSuccess: true,
		hideRecordViewerOnSuccess: false,
		refreshTableOnSuccess: true,
		updateRecordViewerOnSuccess: false,
		
		messageInject: null,
		msgFormResult: {
			"error": {},
			"success": {}
		}
	},
	
	saveSessionVar: $empty,
	getSessionVar: $empty,
	
	initialize: function(options, session) {
		var maskPin;
		
		this.setOptions(options);
		
		// Session
		if(session && this.options.sessionVar) {
			this.getSessionVar = function(k, def) {
				return $pick(session.getFromPath(this.options.sessionVar + "." + k), def, null);
			}.bind(this);
			this.setSessionVar = function(k, v) {
				var sessionObj = $H($pick(session.get(this.options.sessionVar), {}));
				sessionObj = sessionObj.set(k, v).getClean();
				
				session.set(this.options.sessionVar, sessionObj);
				
				return sessionObj;
			}.bind(this);
		}
		
		// Scroller (when record viewer opens/closes)
		this.prevScrollPosition = window.getScroll();
		this.scroll = new Fx.Scroll(window);
		
		// Record Viewer
		this.recordViewer = new RS.RecordViewer(this.options.recordViewer.element, this.options.recordViewer.options);
		this.recordViewer.addEvent("hide", function() {
			this.table.selectNone();  // Temporary, need to link recordViewer(s) to their selected row(s) and hide the specific rows
			this.table.wrapper.removeClass("recordActive");
			
			this.hideMessages();
			this.formSpinner.hide();
			
			this.form.reset();
			
			this.mask.hide();
			
			this.scroll.start(this.prevScrollPosition.x, this.prevScrollPosition.y);
		}.bind(this), true);
		this.recordViewer.addEvent("show", function() {
			this.prevScrollPosition = window.getScroll();
			this.scroll.toElement(this.recordViewer);
			
			this.table.wrapper.addClass("recordActive");
			this.mask.show();
			
			if(this.activeRecord == null) {
				this.tabs.show(1);
				this.tabs.hideNav();
			}
		}.bind(this), true);
		this.recordViewer.addEvent("populate", function(record) {
			record = $type(record) === "object" ? ($type(this.options.prepareFormData) === "function" ? this.options.prepareFormData.run(record) : record) : null;
			
			if(record !== null) {
				this.form.populate(record);
				this.tabs.showNav();
				this.tabs.show(0);
			}
			
			this.activeRecord = record;
		}.bind(this), true);
		this.recordViewer.addEvent("clear", function() {
			this.activeRecord = null;
		}.bind(this), true);
		
		// "Create New" Button
		if($chk(this.options.btnCreateNew)) {
			this.btnCreateNew = document.getElement(this.options.btnCreateNew).addEvent("click", this.recordViewer.show.pass([false]));
		}
		
		// Nav Bar
		this.rvNavBar = document.id(this.recordViewer).getElement(".rs-navBar");
		this.rvNavBar.getElement("a.closeRecord").addEvent("click", this.recordViewer.hide);
		
		// Table
		$extend(this.options.table.options, {
			sortOrder: this.getSessionVar("rc_so", {}),
			pagination: $merge(this.options.table.options.pagination, this.getSessionVar("rc_p", {}))
		})
		
		this.table = new RS.Table(this.options.table.element, this.options.table.options);
		this.table.addEvent("rowFocus", function(row) {
			this.recordViewer.show(row.retrieve("data"));
		}.bind(this));
		
		if(session) {
			this.table.addEvent("changeSort", function(sortOrder) {
				this.setSessionVar("rc_so", sortOrder);
			}.bind(this));
			
			this.table.pagination.addEvents({
				changePage: function(page) {
					this.setSessionVar("rc_p", $H(this.getSessionVar("rc_p", {})).set("page", page).getClean());
				}.bind(this),
				changePageSize: function(pageSize) {
					this.setSessionVar("rc_p", $H(this.getSessionVar("rc_p", {})).set("pageSize", pageSize).getClean());
					this.setSessionVar("rc_p", $H(this.getSessionVar("rc_p", {})).set("page", this.table.pagination.page).getClean());
				}.bind(this)
			});
		}
		
		// Form
		this.form = new RS.Form.Fieldset(this.options.form);
		//this.formContainer = new Element("div", {"class": "rs-form-container"}).inject(document.body).hide().grab(document.id(this.form));
		this.formSpinner = new Spinner(document.id(this.form), {"class": this.options.classFormSpinner, "message": this.options.msgFormSpinner});
		this.mask = new Mask(document.body, {"class": this.options.classFormMask, maskMargins: true, useIframeShim: true});
		
		this.mask.addEvents({
			"position": function() { this.element.setStyles({"width": "100%", "height": "300%"}); },
			"show": function() { this.element.setStyles({"width": "100%", "height": "300%"}); }
		});
		
		this.form.addEvent("submit", this.submitForm);
		document.id(this.form).addEvent("click:relay(input.close, button.close, a.close, input.hide, button.hide, a.hide)", function(e) { e.preventDefault(); this.recordViewer.hide(); }.bind(this));
		
		// Messages
		this.messages = new Hash();
		
		if( ! this.options.messageInject) {
			this.options.messageInject = [document.id(this.form), "bottom"];
		}
		$each(this.options.msgFormResult, function(messages, type) {
			$each(messages, function(message, key) {
				this.messages.set(type + "." + key, RS.displayMessage(message, type, this.options.messageInject).hide());
			}, this);
		}, this);
		
		// Tabs
		this.tabs = new RS.Tabs(this.recordViewer);
	},
	
	/*showForm: function(record) {
		var recType = $type(record);
		
		// if a non-object that evaluates to true or nothing is passed AND recordViewer has a record, use that
		if(recType !== "object" && (recType === "undefined" || record) && $type(this.activeRecord) === "object") {
			record = this.activeRecord;
		}
		else {
			record = null;
		}
		
		if($type(record) === "object") {
			this.form.populate($type(this.options.prepareFormData) === "function" ? this.options.prepareFormData(record) : record);
		}
		
		
		
		this.mask.position().show();
		this.formContainer.show();
		
		this.fireEvent("formShow", [record]);
		
		this.positionFormContainer();
	},*/
	
	positionFormContainer: function(show) {
		var container = this.formContainer.getDimensions(), doc = document.getCoordinates();
		
		this.formContainer.setStyles({
			"left": this.options.formOffsetLeft || ((doc.width - container.width) / 2).limit(0, doc.width),
			"top": this.options.formOffsetTop || ((doc.height - container.height) / 2).limit(0, doc.height)
		});
	},
	
	/*hideForm: function() {
		this.hideMessages();
		this.formContainer.hide();
		this.mask.hide();
		this.formSpinner.hide();
		
		this.form.reset();
		
		this.fireEvent("formHide", [this.form]);
	},*/
	
	setFormTitle: function(title) {
		this.formContainer.getElement("div.titleBar h2").set("text", title);
	},
	
	submitForm: function(data) {
		this.hideMessages();
		this.formSpinner.position().show();
		
		this.formData = $type(this.options.processFormData) === "function" ? this.options.processFormData(data, this.activeRecord) : data;
		
		this.fireEvent("formSubmit", [this.formData, this.processFormResult]); // callback required to show messages etc.
	},
	
	processFormResult: function(success, msgs, errorFields) {
		var msgType = "success";
		
		this.formSpinner.hide();
		
		if($type(this.options.prepareFormResult) === "function") {
			success = this.options.prepareFormResult.run(arguments); //console.log(success);
			msgs = success.msgs;
			errorFields = success.errorFields;
			success = success.success;
		}
		
		if( ! success) {
			msgType = "error";
			
			if($type(errorFields) === "array" && errorFields.length > 0) {
				var addErrorClasses = function(fieldset) {
					fieldset.fields.each(function(field, name) {
						if(field instanceof RS.Form.Fieldset) {
							addErrorClasses(field);
						}
						else if(errorFields.contains(name) && field.el) {
							field = document.id(field);
							switch($type(field)) {
								case "element": field.addClass("error"); break;
								case "array": field.each(function(f) { f.addClass("error"); }); break;
							}
						}
					});
				};
				
				addErrorClasses(this.form);
			}
		}
		
		$splat(msgs).each(function(msg) {
			msg = this.messages.get(msgType + "." + msg);
			if(msg) {
				msg.show();
			}
		}, this);
		
		if(success) {
			if(this.options.refreshTableOnSuccess) {
				this.table.update();
			}
			
			if(this.options.hideRecordViewerOnSuccess) {
				this.recordViewer.hide();
			}
			else if(this.options.updateRecordViewerOnSuccess && ! this.recordViewer.hidden) {
				this.recordViewer.populate(this.formData);
			}
			
			if(this.options.hideFormOnSuccess) {
				this.recordViewer.hide();
			}
		}
		
		this.fireEvent("formResult", [success, msgs, errorFields, this.formData]);
		this.formData = null;
	},
	
	hideMessages: function() {
		this.messages.each(function(msg) { msg.hide(); });
		
		var removeErrorClasses = function(fieldset) {
			fieldset.fields.each(function(field, name) {
				if(field instanceof RS.Form.Fieldset) {
					removeErrorClasses(field);
				}
				else if(field.el) {
					field = document.id(field);
					switch($type(field)) {
						case "element": field.removeClass("error"); break;
						case "array": field.each(function(f) { f.removeClass("error"); }); break;
					}
				}
			})
		};
		
		removeErrorClasses(this.form);
	}
});


/*
---

script: Calendar.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.Calendar = new Class({
	Implements: [Options, Events, Class.Occlude],
	Binds: ["build", "render", "positionMonthHeaders", "attach", "detach", "dateMousedown", "dateMouseup", "dateClick", "selectRange", "deselectRange", "dateMouseover", "dateMouseout", "renderOverlay", "setStartDate", "setEndDate", "setDateRange", "addOverlays", "addOverlay", "removeOverlays", "removeOverlay", "getOverlays", "clearOverlays", "getDateRange", "getCellsInRange"],
	property: "rs-calendar",
	
	options: {
		// startDate == start
		// endDate == end
		
		continuous: 2,
		dayHeaders: "letter",
		monthHeaders: false,
		cellDateFormat: "%d",
		acronymDateFormat: "%b %d, %y",
		
		clearOverlaysOnRender: true,
		
		classContainer: "calendarContainer", // div
		classTitle: "", // div/h4
		classCalendar: "rs-calendar", // div/ul
		classContinuous: "continuous-#", // div/ul
		
		titleFormat: "{start} - {end}",
		titleDateFormat: "%B %d, %Y",
		
		classMonthHeader: "monthTitle",
		monthHeaderDateFormat: "%B %Y",
		
		classHeader: "days", // div/ul/li[0]
		classHeaderWeek: "week", // div/ul/li[0]/ul[n]
		classDayHeader: "", // div/ul/li[0]/ul[n]/li[0-6]
		classDayHeaderContent: "day", // div/ul/li[0]/ul[n]/li[0-6]/span
		
		classBody: "dates", // div/ul/li[1]
		classDate: "", // div/ul/li[1]/ul/li[n]
		classDateHover: "hover", // div/ul/li[1]/ul/li[n]
		classDateAcronym: "", // div/ul/li[1]/ul/li[n]/acronym
		classDateContent: "date", // div/ul/li[1]/ul/li[n]/acronym/span
		classEvenMonth: "even-month", // div/ul/li[1]/ul/li[(0~30)n]
		classOddMonth: "", // div/ul/li[1]/ul/li[(0~30)(n-1)]
		classOverlay: "", // div/ul/li[1]/ul/li[o]
		classOverlayStart: "start", // div/ul/li[1]/ul/li[o[0]]
		classOverlayEnd: "end", // div/ul/li[1]/ul/li[o[n]]
		classMonthFirstDay: "firstDay", // div/ul/li[1]/ul/li[~30n]
		classMonthLastDay: "lastDay", // div/ul/li[1]/ul/li[~30n]
		classFirstDayOffset: "day-#", // div/ul/li[1]/ul/li[0]
		classToday: "today", // div/ul/li[1]/ul/li[t]
		
		rangeSelectable: false,
		deselectOnOutsideClick: false,
		classRangeSelectedFirst: "selectStart",
		classRangeSelectedSecond: "selectEnd",
		classRangeSelectedDate: "selected"
	},
	
	dayHeaderTypes: {
		"letter": ["S", "M", "T", "W", "T", "F", "S"],
		"short": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
		"long": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
	},
	
	eventSelectors: {
		body: {
			dateMousedown: "mousedown:relay(li)",
			dateMouseup: "mouseup:relay(li)",
			dateClick: "click:relay(li)",
			dateMouseover: "mouseover:relay(li)",
			dateMouseout: "mouseout:relay(li)"
		}
	},
	
	overlays: [],
	
	selected: null,
	
	initialize: function() {
		var params = Array.link(arguments, {options: Object.type, replaceContainer: Boolean.type, container: $defined, startDate: $defined, endDate: $defined});
		
		this.setOptions(params.options || {});
		
		this.options.continuous = this.options.continuous === true ? 2 : $pick(this.options.continuous.toInt(), false);
		
		this.setStartDate($pick(params.startDate, this.options.startDate, this.options.start, new Date().set("date", 1)), false);
		this.setEndDate($pick(params.endDate, this.options.endDate, this.options.end, this.startDate.clone().increment("month", this.options.continuous || 1).decrement("day")), false);
		
		this.build();
		
		if(params.container && (params.container = document.id(params.container) || document.getElement(params.container)) != null) {
			this.container[params.replaceContainer ? "replaces" : "inject"](params.container);
		}
		
		if(this.options.rangeSelectable) {
			this.selected = new Hash({});
		}
		
		this.render();
		this.attach();
	},
	
	build: function() {
		var headerWeek, numHeaderWeeks = 1;
		
		this.container = new Element("div", {"class": this.options.classContainer});
		this.title = new Element("h4", {"class": this.options.classTitle}).inject(this.container);
		this.element = new Element("ul", {"class": this.options.classCalendar}).inject(this.container);
		this.header = new Element("li", {"class": this.options.classHeader}).inject(this.element);
		this.body = new Element("ul").inject(new Element("li", {"class": this.options.classBody}).inject(this.element));
		
		if(this.options.continuous) {
			this.element.addClass(this.options.classContinuous.replace("#", this.options.continuous));
			numHeaderWeeks = this.options.continuous;
		}
		
		// Build Header Week(s)
		this.dayHeaders = $pick(this.dayHeaderTypes[this.options.dayHeaders], this.options.dayHeaders);
		
		for(var i = 0; i < numHeaderWeeks; i++) {
			headerWeek = new Element("ul", {"class": this.options.classHeaderWeek}).inject(this.header);
			
			this.dayHeaders.each(function(day, i) {
				new Element("li", {"class": this.options.classDayHeader}).inject(headerWeek).grab(new Element("span", {"class": this.options.classDayHeaderContent, "html": day}));
			}, this);
		}
		
		["inject", "replaces", "dispose", "getElement", "getElements", "hide", "show", "dissolve", "reveal", "mask", "unmask"].each(function(method) {
			this[method] = this.container[method].bind(this.container);
		}, this);
		
		return this;
	},
	
	render: function(clearOverlays) {
		// m is the month of the current date being processed in the loop
		// _m is the month of the previous date that was processed
		var today = new Date().clearTime(), date, y, m, _m, d, cell, monthClass = this.options.classEvenMonth;
		this.fireEvent("beforeRender");
		
		this.body.empty();
		this.dateCells = [];
		this.monthHeaders = [];
		this.selected = $H({first: null, second: null, range: null});
		this.endDate.clearTime();
		this.title.set("html", this.options.titleFormat.substitute({start: this.startDate.format(this.options.titleDateFormat), end: this.endDate.format(this.options.titleDateFormat)}));
		
		date = this.startDate.clearTime().clone();
		
		while(date <= this.endDate) {
			_m = m;
			m = date.get("mo");
			y = date.get("year");
			d = date.get("date");
			
			if(m != _m) {
				if( ! $defined(this.dateCells[y])) {
					this.dateCells[y] = [];
				}
				this.dateCells[y][m] = [];
				monthClass = monthClass == this.options.classOddMonth ? this.options.classEvenMonth : this.options.classOddMonth;
				
				if(cell) {
					cell.addClass(this.options.classMonthLastDay);
					cell = new Element("li");
				}
				else {
					cell = new Element("li").addClass(this.options.classFirstDayOffset.replace("#", date.get("day")));
				}
				
				if (this.options.monthHeaders) {
					if ( ! $defined(this.monthHeaders[y])) {
						this.monthHeaders[y] = [];
					}
					
					this.monthHeaders[y][m] = new Element("li", {"class": this.options.classMonthHeader + " " + monthClass, "html": date.format(this.options.monthHeaderDateFormat), "title": date.format(this.options.monthHeaderDateFormat)}).store("firstDate", cell).inject(this.body);
				}
				
				if(d == 1) {
					cell.addClass(this.options.classMonthFirstDay);
				}
			}
			else {
				cell = new Element("li");
			}
			
			cell.grab(
				new Element("acronym", {"title": date.format(this.options.acronymDateFormat)}).grab(
					new Element("span", {"class": this.options.classDayContent, "html": date.format(this.options.cellDateFormat)})
				)
			);
			
			if(date == today) {
				cell.addClass(this.options.classToday);
			}
			
			this.dateCells[y][m][d] = cell.store("date", date.clone()).addClass(this.options.classDate).addClass(monthClass).inject(this.body);
			date.increment("day");
		}
		
		if(date.get("date") == date.get("lastdayofmonth")) {
			cell.addClass(this.options.classMonthLastDay);
		}
		
		if($pick(clearOverlays, this.options.clearOverlaysOnRender)) {
			this.clearOverlays();
		}
		else {
			this.overlays.each(this.renderOverlay);
		}
		
		this.positionMonthHeaders();
		
		this.fireEvent("render");
		return this;
	},
	
	positionMonthHeaders: function() {
		if ($type(this.monthHeaders) == "array") {
			this.monthHeaders.each(function(months, year) {
				months.each(function(header, month) {
					var pos = header.position({
						relativeTo: header.retrieve("firstDate"),
						returnPos: true,
						position: "centerTop",
						edge: "centerTop"
					});
					
					if (pos) header.setStyle("top", pos.top);
				}, this);
			}, this);
		}
	},
	
	attach: function() {
		$each(this.eventSelectors, function(nativeEvents, parentElement) {
			$H(nativeEvents).each(function(eventSelector, nativeEvent) {
				this[parentElement].addEvent(eventSelector, this[nativeEvent]);
			}, this);
		}, this);
		
		if(this.options.rangeSelectable && this.options.deselectOnOutsideClick) {
			document.addEvent("click", this.deselectRange);
		}
	},
	
	detach: function() {
		$each(this.eventSelectors, function(events, el) {
			$each(events, function(selector, eventName) {
				this[el].removeEvent(selector, this[eventName]);
			}, this);
		}, this);
		
		if(this.options.rangeSelectable) {
			document.removeEvent("click", this.deselectRange);
		}
	},
	
	dateMousedown: function(e, cell) {
		this.fireEvent("dateMousedown", [e, cell, cell.retrieve("date")]);
	},
	
	dateMouseup: function(e, cell) {
		this.fireEvent("dateMouseup", [e, cell, cell.retrieve("date")]);
	},
	
	dateClick: function(e, cell) {
		this.fireEvent("dateClick", [e, cell, cell.retrieve("date")]);
		
		if(this.options.rangeSelectable) {
			this.selectRange(cell, cell.retrieve("date"));
		}
	},
	
	selectRange: function(cell, date) {
		var first = this.selected.get("first"), second = this.selected.get("second"), range = this.selected.get("range");
		
		if(range || ! first) {
			if(range) {
				this.fireEvent("deselectRange");
				range.cells.removeClasses(this.options.classRangeSelectedFirst, this.options.classRangeSelectedSecond, this.options.classRangeSelectedDate);
			}
			
			first = {"cell": cell.addClasses(this.options.classRangeSelectedFirst, this.options.classRangeSelectedDate), "date": date};
			second = range = null;
			
			this.fireEvent("selectRangeFirst", [cell, date]);
		}
		else if(first) {
			second = {"cell": cell.addClass(this.options.classRangeSelectedSecond, this.options.classRangeSelectedDate), "date": date};
			
			if(first.date <= second.date) {
				range = {start: first.date, end: second.date};
			}
			else {
				range = {start: second.date, end: first.date};
				first.cell.removeClass(this.options.classRangeSelectedFirst).addClass(this.options.classRangeSelectedSecond);
				second.cell.removeClass(this.options.classRangeSelectedSecond).addClass(this.options.classRangeSelectedFirst);
			}
			
			range.dates = this.getDateRange(range.start, range.end);
			range.cells = new Elements(this.getCellsInRange(range.dates).clean()).addClass(this.options.classRangeSelectedDate);
			
			this.fireEvent("selectRangeSecond", [cell, date]);
			this.fireEvent("selectRange", [range.start, range.end, range.cells, range.dates]);
		}
		
		this.selected.set("first", first).set("second", second).set("range", range);
	},
	
	deselectRange: function(e) {
		var deselect = true, first, second, range;
		
		if($type(e) == "event") {
			document.id(e.target).getParents().each(function(parent) {
				if(parent == this.body) {
					deselect = false;
				}
			}, this);
		}
		
		if(deselect) {
			range = this.selected.get("range");
			first = this.selected.get("first");
			
			if(range) {
				range.cells.removeClasses(this.options.classRangeSelectedFirst, this.options.classRangeSelectedSecond, this.options.classRangeSelectedDate);
			}
			else if(first) {
				first.cell.removeClasses(this.options.classRangeSelectedFirst, this.options.classRangeSelectedDate);
			}
			
			this.selected = $H({first: null, second: null, range: null});
			this.fireEvent("deselectRange");
		}
	},
	
	dateMouseover: function(e, cell) {
		this.fireEvent("dateMouseover", [e, cell.addClass(this.options.classDateHover), cell.retrieve("date")]);
	},
	
	dateMouseout: function(e, cell) {
		this.fireEvent("dateMouseout", [e, cell.removeClass(this.options.classDateHover), cell.retrieve("date")]);
	},
	
	renderOverlay: function(start, end, cellClass, startClass, endClass) {
		var date, cell, cells = [];
		
		if(start > this.endDate || end < this.startDate) {
			return this;
		}
		
		if(start < this.startDate) {
			start = this.startDate;
		}
		else {
			this.dateCells[start.get("year")][start.get("mo")][start.get("date")].addClass(startClass).addClass(this.options.classOverlayStart);
		}
		if(end > this.endDate) {
			end = this.endDate;
		}
		else {
			this.dateCells[end.get("year")][end.get("mo")][end.get("date")].addClass(endClass).addClass(this.options.classOverlayEnd);
		}
		
		date = start.clone();
		while(date <= end) {
			cells.push(this.dateCells[date.get("year")][date.get("mo")][date.get("date")].addClass(cellClass));
			date.increment("day");
		}
		
		return cells;
	},
	
	unrenderOverlay: function() {
		
	},
	
	setStartDate: function(date, render) {
		date = Date.from(date);
		
		if(date) {
			this.startDate = date;
			
			if($pick(render, true)) {
				this.render();
			}
		}
		
		return this;
	},
	
	setEndDate: function(date, render) {
		date = Date.from(date);
		
		if($type(date) == "date") {
			this.endDate = date;
			
			if($pick(render, true)) {
				this.render();
			}
		}
		
		return this;
	},
	
	setDateRange: function(startDate, endDate, render) {
		return this.setStartDate(startDate, false).setEndDate(endDate, render);
	},
	
	getDateRange: function(start, end) {
		var temp, range = [];
		
		start = $pick(Date.from(start), this.startDate);
		end = $pick(Date.from(end), this.endDate);
		temp = start.clone();
		
		if(end < start) {
			start = end.clone();
			end = temp.clone();
			temp = start.clone();
		}
		
		while(temp <= end) {
			range.push(temp.clone());
			temp.increment("day");
		}
		
		return range;
	},
	
	getCellsInRange: function(start, end) {
		var cells = [], range = $type(start) == "array" ? start : this.getDateRange(start, end);
		
		range.each(function(date) {
			var y = date.get("year"), m = date.get("mo"), d = date.get("date");
			
			if($defined(this.dateCells[y]) && $defined(this.dateCells[y][m]) && $defined(this.dateCells[y][m][d])) {
				cells.push(this.dateCells[y][m][d]);
			}
			else {
				cells.push(null);
			}
		}, this);
		
		return cells;
	},
	
	addOverlays: function() {
		$A(arguments).flatten().each(this.addOverlay);
		
		return this;
	},
	
	// This function doesn't do much right now but defer to renderOverlay, however things could be done here in future revisions
	// such as checking against other overlays via hooked validation or something like that.
	addOverlay: function(params, store) {
		var cells;
		
		params = $merge({start: null, end: null, "class": "", "startClass": "", "endClass": "", "data": null}, params);
		
		params.start = Date.from(params.start);
		params.end = Date.from(params.end);
		
		if( ! params.start || ! params.end) {
			return this;
		}
		
		if($pick(store, true)) {
			this.overlays.push(params);
		}
		
		cells = this.renderOverlay(params.start, params.end, params["class"], params.startClass, params.endClass);
		
		if ($type(cells) == "array") {
			this.fireEvent("addOverlay", [cells, params.data]);
		}
		
		return this;
	},
	
	removeOverlays: function() {
		
	},
	
	removeOverlay: function() {
		
		return this;
	},
	
	getOverlays: function() {
		
	},
	
	clearOverlays: function() {
		
		
		return this;
	},
	
	toElement: function() {
		return this.element;
	}
});


/*
---

script: Tabs.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.Tabs = new Class({
	Binds: ["show", "hideNav", "showNav"],
	
	initialize: function(container, active, tabSelector, contentSelector) {
		container = document.id(container) || document.getElement(container);
		this.active = $chk(active) ? active : 0;
		this.nav = container.getElements($pick(tabSelector, '.tabs li'));
		this.content = container.getElements($pick(contentSelector, '.tabContent'));
		
		this.nav.each(function(navItem, i) {
			navItem.addEvent('click', function() {
				this.show(i);
			}.bind(this));
			
			if(this.active == i) {
				this.content[i].morph({
					opacity: 1,
					display: 'block'
				});
				this.nav[i].addClass('active');
			}
			else {
				this.content[i].morph({
					opacity: 0,
					display: 'none'
				});
				this.nav[i].removeClass('active');
			}
		}.bind(this));
	},
	
	show: function(i) {
		if(i == null) {
			i = this.active;
		}
		
		this.content[i].morph({
			display: 'block',
			opacity: 1
		});
		
		if(i != this.active) {
			this.content[this.active].morph({
				display: 'none',
				opacity: 0
			});
			
			this.nav[this.active].removeClass('active');
			this.nav[i].addClass('active');
			
			this.active = i;
		}
		
		return this;
	},
	
	hideNav: function() {
		this.nav[0].getParent().hide();
	},
	
	showNav: function() {
		this.nav[0].getParent().show();
	}
});


/*
---

script: Fader.js

description: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

requires:
- /SCRIPTNAME

provides: [CLASSNAME]

...
*/

RS.Fader = new Class({
	Implements: [Options, Class.Occlude],
	options: {
		startIndex: 0,
		interval: 3.5,
		fadeDuration: 1,
		pauseOnHover: false
	},
	property: "RS.Fader",
	
	initialize: function(banners, options) {
		this.setOptions(options)
		
		this.banners = document.getElements(banners);
		this.element = this.banners[0];
		
		if(this.occlude()) {
			return this.occluded;
		}
		
		this.interval = this.options.interval * 1000;
		this.fadeDuration = this.options.fadeDuration * 1000;
		this.activeIndex = this.options.startIndex
		
		this.banners.each(function(banner, i) {
			if(i == this.activeIndex) {
				banner.setStyle('zIndex', 51);
				banner.fade('show');
			}
			else {
				banner.setStyle('zIndex', 50);
				banner.fade('hide');
			}
			
			if(this.options.pauseOnHover) {
				banner.addEvents({
					'mouseover': this.pause.bind(this),
					'mouseout': this.play.bind(this)
				});
			}
			
			banner.set('tween', {duration: this.fadeDuration}).store("RS.Fader", this);
		}, this);
		
		this.play();
	},
	
	swap: function() {
		var next = this.activeIndex == this.banners.length - 1 ? 0 : this.activeIndex + 1;
		
		this.banners.each(function(banner, i) {
			banner.setStyle('zIndex', i == next ? 51 : 50);
		});
		
		this.banners[this.activeIndex].fade('out');
		this.banners[next].fade('in');
		
		this.activeIndex = next;
	},
	
	pause: function() {
		$clear(this.intervalID);
	},
	
	play: function() {
		this.intervalID = this.swap.periodical(this.interval + this.fadeDuration, this);
	}	
});
