var Activa = {};

//DOM Getters
/**
 * did - Shorcut for getElementById
 * @param	id		The element id you are looking for
 * @return			Element with matching id
 */
function did(id) {
	return document.getElementById(id);
}
/**
 * dbn - Shorcut for getElementsByName
 * @param	name	The element name you are looking for
 * @param	parent	optional;The parent item to search within
 * @return			Collection of elements
 */
function dbn(name, parent) {
	return (parent || document).getElementsByName(name);
}
/**
 * dbt - Shorcut for getElementsByTagName
 * @param	tag		The element tagName you are looking for
 * @param	parent	optional;The parent item to search within
 * @return 			Collection of elements
 */
function dbt(tag, parent) {
	return (parent || document).getElementsByTagName(tag);
}
/**
 * dbc - Shortcut for getElementsByClassName
 * @param	className	The className you are looking for
 * @param	parent		optional;The parent item to search within
 * @return				Array of elements
 */
function dbc(className, parent) {
	className = 'dev_'+className;
	//use default provided by browser if it exists otherwise use our implementation
	if ( document.getElementsByClassName ) {
		return (parent || document).getElementsByClassName(className);
	}
	var nodes = Activa.toArray(dbt('*', parent)), elms = [];
	nodes.forEach(function(node){
		if ( Activa.DOM.hasClass(node, className) ) {
			elms.push(node);
		}
	});
	return elms;
}

Activa.Exception = function activaException(message, file, line, trace, url) {
	this.message = message;
	this.file = file;
	this.line = line;
	this.trace = trace;
	this.url = url;
};

Activa.urlencode = function urlencode(string) {
	return encodeURIComponent(string).replace("%20", "+");
};

Activa.NewWindow = function NewWindow(mypage, myname, w, h, scroll) {
	var winl = (screen.width - w) / 2;
	var wint = (screen.height - h) / 2;
	winprops = 'height='+h+',width='+w+',top='+wint+',left='+winl+',scrollbars='+scroll+',resizable=yes'
	win = window.open(mypage, myname, winprops)
	if ( parseInt(navigator.appVersion) >= 4 ) { 
		win.window.focus(); 
	}
	return win;
};

//Cookies
Activa.createCookie = function createCookie(name, value, days) {
	if ( days ) {
		var date = new Date();
		date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
};

Activa.getCookie = function getCookie(name) {
	var start = document.cookie.indexOf(name + "=");
	var len = start + name.length + 1;
	if ( start < 0 ) {
		return null;
	}
	if ( start == 1 ) { 
		return null; 
	}
	var end = document.cookie.indexOf(';', len);
	if ( end == -1 ) { 
		end = document.cookie.length; 
	}
	return unescape( document.cookie.substring(len, end) );
};

Activa.padString = function padString(str, len) {
	if ( typeof(str) != 'string' ) {
		str = String(str);
	}
	while ( str.length < len ) {
		str = '0'+str;
	}
	return str;
};

Activa.gaTrack = function gaTrack(str) {
	try {
		if ( typeof(pageTracker) != 'undefined' ) {
			pageTracker._trackPageview(str);
		} else if ( typeof(urchinTracker) != 'undefined' ) {
			urchinTracker(str);
		}
	} catch ( err ) {}
}

/**
 * Activa.fixEvent - Returns an event object with common properties/methods normalized for easier cross browser usage.
 * @param e		object	optional;Event object to normalize
 * @return		object	Event object after normalization
 */
Activa.fixEvent = function fixEvent(e) {
	var evnt = e || window.event;
	if ( !evnt ) { 
		return null; 
	}
	if ( !evnt.target ) { 
		evnt.target = evnt.srcElement; 
	}
	evnt.preventDefault = (evnt.preventDefault)? evnt.preventDefault : function() { this.returnValue = false; };
	evnt.stopPropagation = (evnt.stopPropagation)? evnt.stopPropagation : function() { this.cancelBubble = true; };
	var scroll = Activa.Dimensions.getScrollXY();
	if ( e.pageX === undefined ) { 
		e.pageX = e.clientX + scroll.x; 
	}
	if ( e.pageY === undefined ) { 
		e.pageY = e.clientY + scroll.y; 
	}
	if ( typeof e.layerX != 'number' ) { 
		e.layerX = e.offsetX; 
	}
	if ( typeof e.layerY != 'number' ) { 
		e.layerY = e.offsetY; 
	}
	if ( !e.relatedTarget ) {
		switch ( e.type ) {
			case 'mouseover':
			case 'mouseenter':
				e.relatedTarget = e.fromElement;
				break;
			case 'mouseleave':
			case 'mouseout':
				e.relatedTarget = e.toElement;
				break;
		}
	};
	return evnt;
};

Activa.createDelegate = function createDelegate(oObject, sMethodName, data) {
	return function () {
		if ( data ) {
			var args = [];
			var i = 0;
			while ( arguments[i] ) {
				args[i] = arguments[i];
				i++;
			}
			args[i] = data;
			return oObject[sMethodName].apply(oObject, args);
		}
		return oObject[sMethodName].apply(oObject, arguments);
	};
};

Activa.registerEvent = function registerEvent(elem, event, callback, capture) {
	capture = Boolean(capture);
	elem = Activa.DOM.check(elem);
	
	if ( event == 'allchange' ) {
		Activa.registerEvent(elem, 'change', callback, capture);
		Activa.registerEvent(elem, 'click', callback, capture);
		Activa.registerEvent(elem, 'keyup', callback, capture);
		return;
	}
	
	function wrapCallback(e) { 
		callback(Activa.fixEvent(e));
	};
	
	if ( elem.addEventListener ) {
		elem.addEventListener(event, wrapCallback, capture);
	} else {
		elem.attachEvent('on'+event, wrapCallback); 
	}
	
	return new Activa.registeredEvent(elem, event, wrapCallback, capture);
};

Activa.unregisterEvent = function unregisterEvent(elem, event, callback, capture) {
	capture = Boolean(capture);
	elem = Activa.DOM.check(elem);
	
	if ( event == 'allchange' ) {
		Activa.unregisterEvent(elem, 'change', callback, capture);
		Activa.unregisterEvent(elem, 'click', callback, capture);
		Activa.unregisterEvent(elem, 'keyup', callback, capture);
		return;
	}
	
	if ( elem.removeEventListener ) {
		elem.removeEventListener(event, callback, capture);
	} else {
		elem.detachEvent('on'+event, callback); 
	}
}

Activa.registeredEvent = function registeredEvent(elem, event, callback, capture) {
	this.elem = elem;
	this.event = event;
	this.callback = callback;
	this.capture = capture;
	
	this.unregister = function() {
		Activa.unregisterEvent(this.elem, this.event, this.callback, this.capture);
	}
};

/**
 * Activa.toArray - Shortcut for Array.prototype.slice.call(obj, idx)
 * @param	obj		mixed		Object to call array.slice on
 * @param	start	integer		Index at which to begin slicing
 * @param	end		integer		Index at which to end slicing
 * @return			array		New array containing values from the idx to the end of the obj
 *		Note: Useful for transforming arguments object and collections into regular arrays
 *		ex.	function(){ var args = Activa.toArray(arguments); alert(args instanceof Array); }
 */

Activa.toArray = function toArray(obj, start, end) {
	var ret = obj;
	if ( window.ActiveXObject ) {
		if ( typeof obj.length == 'undefined' ) { 
			obj.length = Activa.getLength(obj); 
		}
		ret = Array.prototype.map.call(obj, function mapCall(item) { return item; });
	}
	var args = [(start || 0)];
	if ( end && !isNaN(Number(end)) ) {
		args.push(Number(end));
	}
	return Array.prototype.slice.apply(ret, args);
};


/**
 * getLength - Finds the total number of all non-function properties owned by the object (see hasOwnProperty)
 * @param obj    object    The object whose properties to count.
 * @return       int       The length of the object or 0 if obj was invalid
 */
Activa.getLength = function getLength(obj) {
    if ( !obj ) {
    	return 0; 
    }
    var i=0;
    for ( var key in obj ) {
        if ( obj.hasOwnProperty(key) && typeof obj[key] !='function' ) { 
        	i++; 
        }
    }
    return i;
};


//Prototypes
String.prototype.ltrim = function() {
	return this.replace(/^\s*/, '');
};

String.prototype.rtrim = function() {
	return this.replace(/\s*$/, '');
};

String.prototype.trim = function() {
	return this.rtrim().ltrim();
};

/**
 * Array and object functions
 */
/**
 * Array methods indexOf, forEach, map, filter
 * 		Added for browsers without native support
 */
Array.prototype.indexOf = (function() {
	var fn;
	if ( typeof Array.prototype.indexOf == 'function' ) {
		fn = Array.prototype.indexOf; 
	} else {
		fn = function indexOf(obj, start){
			var len = this.length;
			start = Number(start) || 0;
			start = (start < 0) ? Math.ceil(start) : Math.floor(start);
			if ( start < 0 ) { 
				start+= len; 
			}
			for ( ; start < len; start++ ) {
				if ( start in this && this[start] === obj ) { 
					return start; 
				}
			}
			return -1;
		}
	}
	var indexOf = null;
	return fn;
})();
Array.prototype.forEach = (function() {
	var fn;
	if ( typeof Array.prototype.forEach == 'function' ) {
		fn = Array.prototype.forEach;
	} else {
		fn = function forEach(fn /*, bind*/){
			var len = this.length;
			if ( typeof(fn) != 'function' ) { 
				throw new TypeError(); 
			}
			var bind = arguments[1];
			for ( var i = 0; i < len; i++ ) {
				if ( i in this ) { 
					fn.call(bind, this[i], i, this); 
				}
			}
		};
	}
	var forEach = null;
	return fn;
})();
Array.prototype.map = (function() {
	var fn;
	if ( typeof Array.prototype.map == 'function' ) {
		fn = Array.prototype.map;
	} else {
		fn = function map(fn /*, bind*/){
			var len = this.length;
			if ( typeof(fn) != 'function' ) { 
				throw new TypeError(); 
			}
			var ret = [], bind = arguments[1];
			for ( var i = 0; i < len; i++ ) {
				if ( i in this ) { 
					ret[i] = fn.call(bind, this[i], i, this); 
				}
			}
			return ret;
		};
	}
	var map = null;
	return fn;
})();
Array.prototype.filter = (function() {
	var fn;
	if ( typeof Array.prototype.filter == 'function' ) {
		fn = Array.prototype.filter;
	} else {
		fn = function filter(fn /*, bind*/){
			var len = this.length;
			if ( typeof(fn) != 'function' ) { 
				throw new TypeError(); 
			}
			var ret = [], bind = arguments[1], val=null;
			for ( var i = 0; i < len; i++ ) {
				if ( i in this ) {
					val = this[i];
					if ( fn.call(bind,val,i,this) ) { 
						ret.push(val); 
					}
				}
			}
			return ret;
		};
	}
	var filter = null;
	return fn;
})();
Array.prototype.every = (function() {
	var fn;
	if ( typeof Array.prototype.every == 'function' ) {
		fn = Array.prototype.every;
	} else {
		fn = function every(fn /*, bind*/) {
			return this.filter.apply(this, Activa.toArray(arguments)).length === this.length;
		};
	}
	var every = null;
	return fn;
})();
Array.prototype.some = (function() {
	var fn;
	if ( typeof Array.prototype.some == 'function' ) {
		fn = Array.prototype.some;
	} else {
		fn = function some(fn /*, bind*/) {
			return this.filter.apply(this, Activa.toArray(arguments)).length > 0;
		};
	}
	var some = null;
	return fn;
})();

Array.prototype.inArray = function(targ) {
	if ( this.indexOf(targ) != -1 ) {
		return true;
	} else {
		return false;
	}
};

Array.prototype.remove = function(pos) {
	if ( pos != -1 ) {
		this.splice(pos, 1);
	}	
};

Date.prototype.format = function(format) {
	var res = '';
	for ( var i = 0; i < format.length; i++ ) {
		switch ( format.charAt(i) ) {
			case 'm':
				res += Activa.padString(this.getMonth() + 1, 2);
				break;
				
			case 'd':
				res += Activa.padString(this.getDate(), 2);
				break;
				
			case 'Y':
				res += this.getFullYear();
				break;
				
			case 'H':
				res += Activa.padString(this.getHours(), 2);
				break;
				
			case 'i':
				res += Activa.padString(this.getMinutes(), 2);
				break;
				
				
			default:
				res += format.charAt(i);
				break;
				
		}
	}
	return res;
};

Function.prototype.bind = function() {
	var fn = this;
	var args = Activa.toArray(arguments);
	var reference = args.shift();
	return function binded() {
		var arglist = args.concat(Activa.toArray(arguments));
		return fn.apply(reference, arglist);
	};
};


/**
 * Create a new Class.
 * 
 * Note:
 * 	Two things are available inside methods of the instances of the class:
 * 		parent	- Gives access to the instance of the parent object. ( use: this.parent )
 * 		root	- Call the parent's identically named method. ( use: this.root(arg1, arg2, ...) ) 
 * 
 * Usage:
 *	var className = new Class({
 *		[Extends: constructor,]
 *		[property: value..,]
 *		[method: function [funcname]() { .... }, ...]
 *	});
 * 
 * @param object params Properties and methods for the class as an object literal
 * @return Class		The constructor for the new class
 */
Activa.Class = function Class(params) {
	params = (params instanceof Function) ? {init: params} : params;
	
	var obj = function construct() {
		Activa.Class._clean(this);
		var simple = arguments.callee.simple;
		if ( simple ) {
			delete arguments.callee.simple;
		}
		return (this.init && !simple) ? this.init.apply(this, arguments) : this;
	}
	
	for ( var k in this ) {
		obj[k] = this[k];
	}
	obj._implement(params);
	obj.constructor = Class;
	obj.prototype.constructor = obj;
	return obj;
}

/**
 * Add static properties/methods to the class. They are accessible via the class name.
 * 
 * Usage:
 * 	myClass.statics({
 * 		[property: value..,]
 * 		[method: function [funcname]() { .... }, ...]
 *	});
 * 
 * @param object args	Static properties and methods for the class as an object literal 
 * @return Class		The class constructor that was extended
 */
Activa.Class.prototype.statics = function statics(args) {
	for ( var k in args ) {
		this[k] = args[k];
	}
	return this;
}

/**
 * Add properties/methods to instances of the class. Instances that already exist will gain
 * these as well.
 * 
 * Usage:
 * --Add a specific property/method from an object
 * 	myClass._implement(
 *	key,
 *	{
 * 		[property: value..,]
 * 		[method: function [funcname]() { .... }, ...]
 * 	});
 * 
 * or:
 * --Add all the properties/methods from an object
 * 	myClass._implement({
 * 		[property: value..,]
 * 		[method: function [funcname]() { .... }, ...]
 * 	});
 * 
 * @param string/object key		Key name or object depending on what to add (see usage above)
 * @param object value			Object to to get the property/method from when using key (see usage above)
 * @return Class				The class constructor that implemented the new properties/methods
 */
Activa.Class.prototype._implement = function _implement(key, value) {
	if ( typeof key == 'object' && !(key instanceof Array) ) {
		if ( 'Extends' in key ) {
			var parent = key.Extends;
			parent.simple = true;
			this.prototype = new parent();
			delete key.Extends;
		}
		for ( var k in key ) {
			this._implement(k, key[k]);
		}
		return this;
	}
	if ( typeof value == 'function' ) {
		var parentMethod = (key in this.prototype) ? this.prototype[key] : null;
		this.prototype[key] = function method() {
			var _root = null, _oldroot = null;
			if ( this.root ) {
				_oldroot = this.root;
				delete this.root;
			}
			this.root = _root = (_root) ? _root :
				function root() {
					if ( parentMethod == null ) {
						throw new ReferenceError("The method '"+key+"' does not exist in the parent class.");
					}
					return parentMethod.apply(this, arguments);
				}.bind(this);
			var ret = value.apply(this, arguments);
			delete this.root;
			if ( _oldroot ) {
				this.root = _oldroot;
				delete _oldroot;
			}
			return ret;
		}
		return this;
	} else if ( typeof value == 'object' && key in this.prototype ) {
		var obj = this.prototype[key];
		this.prototype[key] = value;
		for ( var k in obj ) {
			this.prototype[key][k] = obj[k];
		}
		return this;
	}
	this.prototype[key] = value;
	return this;
}
/**
 * Clean a new instance of a class so they do not share the prototype properties.
 * 
 * @param object object		The class instance to clean
 * @return object			The clean instance of the class
 */
Activa.Class._clean = function(object){
	function destruct(obj){
		var ret, type = (obj instanceof Array ? 'array' : typeof obj);
		switch ( type ) {
			case 'object':
				ret = {};
				for ( var k in obj ) {
					ret[k] = destruct(obj[k]);
				}
				break;
			case 'array':
				ret = [];
				for ( var i = 0, z = obj.length; i < z; i++ ) {
					ret[i] = destruct(obj[i]);
				}
				break;
			default: return obj;
		}
		return ret;
	}
	
	for ( var key in object ) {
		delete object[key];
		
		var type = (object[key] instanceof Array ? 'array' : typeof object[key]);
		switch ( type ) {
			case 'object':
				if ( object[key] === null ) {
					object[key] = null;
					continue;
				}
				var n = function(){};
				n.prototype = object[key];
				var o = new n();
				object[key] = Activa.Class._clean(o);
				break;
			case 'array':
				object[key] = destruct(object[key]);
				break;
		}
	}
	return object;
};

Activa.DOM = {
	/**
	 * next - Returns the next non-whitespace sibling element
	 * @param	el	object	Element node from which to start
	 * @return		object	Next non-whitespace child element
	 */
	next: function next(el) {
		if ( !el || !el.nextSibling ) { 
			return null; 
		}
		el = el.nextSibling;
		return (el.nodeType == 1) ? el : this.next(el);
	},
	/**
	 * previous - Returns the previous non-whitespace sibling element
	 * @param	el	object	Element node from which to start
	 * @return		object	Previous non-whitespace child element
	 */
	previous: function previous(el) {
		if ( !el || !el.previousSibling ) { 
			return null; 
		}
		el = el.previousSibling;
		return (el.nodeType==1) ? el : this.previous(el);
	},
	/**
	 * first - Returns the first non-whitespace child element
	 * @param	el	object	Parent element node
	 * @return		object	First non-whitespace child element
	 */
	first: function first(el) {
		if ( !el || !el.firstChild ) { 
			return null; 
		}
		el = el.firstChild;
		return (el.nodeType==1) ? el : this.next(el);
	},
	/**
	 * last - Returns the last non-whitespace child element
	 * @param	el	object	Parent element node
	 * @return		object	Last non-whitespace child element
	 */
	last: function last(el) {
		if ( !el || !el.lastChild ) { 
			return null; 
		}
		el = el.lastChild;
		return (el.nodeType==1) ? el : this.previous(el);
	},
	/**
	 * owner - Returns the parent of the element
	 * @param	el	object	Element from which to retrieve the parent
	 * @return		object	Parent node of the element
	 */
	owner: function owner(el){
		if ( !el || !el.parentNode ) { 
			return null; 
		}
		el = el.parentNode;
		return (el.nodeType == 1) ? el : this.owner(el);
	},
	/**
	 * contains - Returns whether the given node is contained with the element
	 * @param el object Element in which to check
	 * @param node object Element to check for
	 * @return boolean True if node is a child of el, false if not
	 */
	contains: function contains(el, node){
	  return el.contains ?
	    el != node && el.contains(node) :
	    !!(el.compareDocumentPosition(node) & 16);
	},
	/**
	 * hasClass - Returns true if the element has the given class applied
	 * @param	obj			Element on which to check for the class
	 * @param	className	Class to look for on the element
	 * @return				True (if element has the class) / False (if it doesn't)
	 */
	hasClass: function hasClass(obj, className) {
		if ( !obj || className.trim() == '' ) { 
			return false; 
		}
		return (String(obj.className).split(' ').indexOf(className) != -1);
	},
	/**
	 * addClass - Adds a class to an element
	 * @param	obj			Element on which to add the class
	 * @param	className	Class to add to the element
	 * @return				True (if successfully added class) / False (if invalid object or empty classname given)
	 */
	addClass: function addClass(obj, className) {
		if ( !obj || className.trim() == '' || this.hasClass(obj, className) ) { 
			return false; 
		}
		obj.className = String(obj.className).split(' ').concat([className]).join(' ').trim();
		return true;
	},
	/**
	 * removeClass - Removes a class from an element
	 * @param	obj			Element from which to remove the class
	 * @param	className	Class to remove from the element
	 * @return				True (if successfully removed class) / False (if invalid object or empty classname given)
	 */
	removeClass: function removeClass(obj, className) {
		if ( !obj || className.trim() == '' ) { 
			return false; 
		}
		obj.className = String(obj.className).split(' ').filter(function(cls) {
			return (cls != className);
		}).join(' ');
		return true;
	},
	/**
	 * check - Ensures an element object is returned.
	 * @param string el		ID or element object
	 * @return object		Element object
	 */
	check: function check(el) {
		return (typeof el == 'object') ? el : did(el);
	},
	/**
	 * showID - Display a hidden element
	 * @param string id		ID of the element to show
	 */
	showID: function showID(id){
		var el = this.check(id);
		if ( el ) {
			el.style.display = '';
		}
	},
	/**
	 * hideID - Hide an element
	 * @param string id		ID of the element to hide
	 */
	hideID: function hideID(id){
		var el = this.check(id);
		if ( el ) {
			el.style.display = 'none';
		}
	},
	/**
	 * toggleID - Toggle an element to be shown or hidden
	 * @param string id		ID of the element to toggle
	 */
	toggleID: function toggleID(id){
		var el = this.check(id);
		if ( el ) {
			var fn = el.style.display == 'none' ? 'showID' : 'hideID';
			this[fn](el);
		}
	},
	/**
	 * ready - Register callbacks to execute when the DOM is ready
	 * 
	 * Example using the optional object and args parameters:
	 *		-keyword 'this' will be the person object
	 * 		-arg1 and arg2 become 'name' and 'job'
	 * 		-function will log:
	 * 				name: bob job: plummer
	 * 		
	 * 		var person = {'name':'bob','job':'plummer'};
	 * 		domLoader.register(function(arg1, arg2){
	 * 			console.log(arg1+': '+this[arg1]+' arg2: '+this[arg2]);
	 * 		}, person, ['name','job']);
	 * 
	 *  @param fn
	 *  @param obj
	 *  @param args
	 *  @return
	 */
	ready: function ready(fn, obj, args) {
		var rdy = arguments.callee;
		rdy.isReady = rdy.isReady === undefined ? false : rdy.isReady;
		rdy.isBound = rdy.isBound === undefined ? false : rdy.isBound;
		rdy.queue = rdy.queue === undefined ? [] : rdy.queue;
		rdy.binds = rdy.binds === undefined ? [] : rdy.binds;
		rdy.args = rdy.args === undefined ? [] : rdy.args;
		
		rdy.register = function register(fn, obj, args) {
			rdy.checkReady();
			rdy.queue = (rdy.queue instanceof Array) ? rdy.queue : [];
			if ( !fn || typeof fn != 'function' ) {
				return;
			}
			
			var offset = rdy.queue.push(fn) - 1;
			
			obj = obj || null;
			rdy.binds = (rdy.binds instanceof Array) ? rdy.binds : [];
			rdy.binds[offset] = obj;
			
			args = args ? ((args instanceof Array) ? args :  Activa.toArray(args)) : [];
			rdy.args = (rdy.args instanceof Array) ? rdy.args : [];
			rdy.args[offset] = args;
			if ( rdy.isReady ) {
				rdy.exec();
			}
			return rdy;
		}
		
		rdy.checkReady = function checkReady() {
			if ( rdy.isBound ) {
				return;
			}
			rdy.isBound = true;
			//var self = this;
			var events = {'load': window};
			// Mozilla, Opera and Safari
			if ( document.addEventListener ) {
				events['DOMContentLoaded'] = document;
			// IE
			} else if ( document.attachEvent ) {
				events['onreadystatechange'] = document;
				// If IE and not an iframe -- continually check to see if the document is ready
				if ( document.documentElement.doScroll && window == window.top ) {
					(function ieDoScrollTest(){
						if ( rdy.isReady ) {
							return;
						}
						try {
							// If IE is used, use the trick by Diego Perini -- http://javascript.nwbox.com/IEContentLoaded/
							document.documentElement.doScroll("left");
							// and execute any waiting functions
							rdy.isReady = true;
							rdy.exec();
						} catch ( error ) {
							setTimeout(arguments.callee, 0);
							return;
						}
					})();
				}
			}
			var idDoScrollTest = null;
			//Faster Safari detection
			if ( typeof navigator.taintEnabled === 'undefined' ) {
				var timer = window.setInterval(function safariReadyTest() {
					if ( /loaded|complete/.test(document.readyState) ) {
						window.clearInterval(timer);
						if ( rdy.isReady ) {
							return;
						}
						rdy.isReady = true;
						rdy.exec();
					}
				}, 10);
			}
			var safariReadyTest = null;
			for ( var name in events ) {
				if ( !events.hasOwnProperty(name) ) {
					continue;
				}
				(function scopeFix(name, obj) {
					Activa.registerEvent(obj, name, function pageLoadEventWrapper() {
						Activa.unregisterEvent(obj, name, arguments.callee);
						if ( !rdy.isReady ) {
							rdy.isReady = true;
							rdy.exec();
						}
					});
					var pageLoadEventWrapper = null;
				})(name, events[name]);
			}
			var varprotector = null;
		}
		
		rdy.exec = function exec() {
			if ( !rdy.isReady || !(rdy.queue instanceof Array) ) {
				return;
			}
			var fn, obj, args;
			while ( fn = rdy.queue.shift() ) {
				obj = rdy.binds.shift() || window;
				args = rdy.args.shift();
				fn.apply(obj, args);
			}
			
			rdy.queue = [];
			rdy.binds = [];
			rdy.args = [];
		}
		rdy.register(fn, obj, args);
	}
};

/**
 * Activa.rpc
 * 
 * Parameters:
 * path			The destination of the remote file.
 * options		An object that holds optional arguments.
 * 
 * options:
 * input		mixed		Can either be a string or an object to send as POST data
 * errorHandler		function	Set the error handling function.  Defaults to console.error if exists.
 * async		bool		Boolean to set to enable/disable async.  Defaults to false unless a callback is specified.
 * method		string		Sets whether to use POST or GET.  Defaults to POST
 * onLoad		function	Function is called once when a XHR request is loading.
 * onComplete		function	Function is called when XHR is successful.  Has one parameter for responseText.
 * 
 * Usage:
 * new Activa.rpc('testPath', {input: {a:'Testing!'}, onComplete: function(res){ alert(res); }});
 * 
 */
Activa.rpc = new Activa.Class({
	options: {
		input: null,
		errorHandler: null,
		async: false,
		method: 'POST',
		onComplete: function(){},
		onLoad: function(){}
	},
	setOptions: function setOptions(options) {
		options = options || {};
		
		if ( typeof options.onComplete == 'function' && typeof options.async == 'undefined' ) {
			options.async = true;
		}
		
		for ( var k in this.options ) {
			this.options[k] = (k in options) ? options[k] : this.options[k];
		}
	},
	loaded:false,
	init: function init(path, options) {
		this.request = Activa.rpc.xhr();
		this.path = path;
							
		this.setOptions(options);

		this.input = Activa.rpc.objectToString(this.options.input);
		
		this.request.onreadystatechange = Activa.createDelegate(this, 'onReadyStateChange');

		this.request.open(this.options.method.toUpperCase(), 'rpc/'+this.path, this.options.async);
		this.request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		Activa.rpc.instances.push(this);
		this.request.send(this.input);
	},
	onReadyStateChange: function onReadyStateChange() {
		if ( 4 == this.request.readyState && 200 == this.request.status ) {
			Activa.rpc.instances.remove(Activa.rpc.instances.indexOf(this));
			eval("var res="+this.request.responseText);
			if ( res instanceof Activa.Exception ) {
				if ( typeof(this.options.errorHandler) == 'undefined' ) {
					if ( console && console.error ) {
						console.error(res);
					}
					return;
				} else {
					this.options.errorHandler(res);
					return;
				}
			}
			this.options.onComplete(res);
		} else if ( 4 == this.request.readyState && this.request.status != 0 ) {
			Activa.rpc.instances.remove(Activa.rpc.instances.indexOf(this));
			if ( typeof(this.errHandler) != "undefined" ) {
				this.options.errorHandler(new Activa.Exception("Server returned error code "+this.request.status));
			}
		} else if ( this.request.readyState != 4 && !this.loaded ) {
			this.options.onLoad();
			this.loaded = true;
		}
	},
	abort: function abort() {
		this.request.abort();
		Activa.rpc.instances.remove(Activa.rpc.instances.indexOf(this));
	}
});

Activa.rpc.statics({
	instances:[],
	callWhenReady: function callWhenReady(func) {
		if ( Activa.rpc.instances.length > 0 ) {
			window.setTimeout(function() { Activa.rpc.callWhenReady(func); }, 250);
			return;
		}
		func();
	},
	xhr: function xhr(){
		if ( window.XMLHttpRequest ) {
			return new XMLHttpRequest();
		} else if ( window.ActiveXObject ) {
			try {
				return new ActiveXObject( "Msxml2.XMLHTTP" );
			} catch ( error ) {
				try {
					return new ActiveXObject( "Microsoft.XMLHTTP" );
				} catch ( error2 ) {
					return alert("Fatal Error: No XMLHttp Interface Available");
				}
			}
		} else {
			return alert("Fatal Error: No XMLHttp Interface Available");
		}
	},
	objectToString: function objectToString(input) {
		if ( !input instanceof Object ) {
			return input;
		}
		
		var str = '';
		for ( var prop in input ) {
			if ( input[prop] instanceof Array ) {
				for ( var x = 0; x < input[prop].length; x++ ) {
					str += prop+'[]='+encodeURIComponent(input[prop][x])+'&';
				}
			} else if ( input[prop] instanceof Object ) {
				for ( var prop2 in input[prop] ) {
					if ( input[prop][prop2] instanceof Array ) {					
						for ( var x = 0; x < input[prop][prop2].length; x++ ) {
							str += prop+'['+prop2+'][]='+encodeURIComponent(input[prop][prop2][x])+'&';
						}
					} else if ( !(input[prop][prop2] instanceof Function) ) {
						str += prop+'['+prop2+']='+encodeURIComponent(input[prop][prop2])+'&';
					}
				}
			} else if ( !(input[prop] instanceof Function) ) {
				str += prop+'='+encodeURIComponent(input[prop])+'&';
			}
		}
		return str;
	},
	activaX: function activaX(element, url, input) {
		this.element = Activa.DOM.check(element);
		this.url = url;
		
		var request = Activa.rpc.xhr();
		this.request.onreadystatechange = Activa.createDelegate(this, 'callback');
		this.request.open("GET", this.url, true);
		this.request.send(null);
		
		this.callback = function() {
			this.element.innerHTML = this.request.responseText;
			this.element.innerHTML = this.element.innerHTML; //ie fix
		};		
	}
});

//Dimensions

Activa.Dimensions = {
	/**
	 * getDocSize - Get the size of the current viewable document area
	 * @param	boolean	scrollbar	Whether the width of the scrollbars should be included. ( default: false )
	 * @return	object		The width, height of the current viewable document area in a keyed object ( obj.width obj.height )
	 */
	getDocSize: function getDocSize(scrollbar) {
		scrollbar = scrollbar || false;
		var w = 0, h = 0;
		if ( typeof(window.innerWidth) == 'number' ) {
			//Non-IE
			w = window.innerWidth;
			h = window.innerHeight;
		} else {
			if ( document.compatMode == 'CSS1Compat' ) {
				w = document.documentElement.clientWidth;
				h = document.documentElement.clientHeight;
			} else {
				w = document.body.clientWidth;
				h = document.body.clientHeight;
			}
		}
		if ( !scrollbar ) {
			var sbar = this.getScrollbarWidth();
			w -= sbar.right;
			w = w < 0 ? 0 : w;
			h -= sbar.bottom;
			h = h < 0 ? 0 : h;
		}
		return {'width': w, 'height': h};
	},

	/**
	 * getMaxDocSize - Get the total document size
	 * @param	boolean	scrollbar	Whether the width of the scrollbars should be included. ( default: false )
	 * @return	object				The width, height of the total document size in a keyed object ( obj.width obj.height )
	 */
	getMaxDocSize: function getMaxDocSize(scrollbar) {
		scrollbar = scrollbar || false;
		var w = 0, h = 0, x = 0, y = 0;
		var docsize = this.getDocSize(true), sbar = this.getScrollbarWidth();
		if ( typeof window.scrollMaxY == 'number' ) {
			x = window.scrollMaxX;
			y = window.scrollMaxY;
		} else {
			var scroll = this.getScrollXY();
			x = scroll.x;
			y = scroll.y;
		}
		w = docsize.width + x - (scrollbar ? 0 : sbar.right);
		w = w < 0 ? 0 : w;
		h = docsize.height + y - (scrollbar ? 0 : sbar.bottom);
		h = h < 0 ? 0 : h;
		return {'width':w,'height':h};
	},

	/**
	 * getScrollbarWidth - Get the width of the scrollbars on the right/bottom
	 * @return	object		The width of the scrollbars on the right/bottom of the window if present ( obj.right, obj.bottom )
	 */
	getScrollbarWidth: function getScrollbarWidth() {
		var size = {'right': 0, 'bottom': 0};
		if ( !document || !document.documentElement ) {
			return size;
		}
		var docEl = document.documentElement;
		size['right'] = ((typeof window.innerWidth == 'number') ? window.innerWidth : docEl.offsetWidth) - docEl.clientWidth;
		size['bottom'] = ((typeof window.innerHeight == 'number') ? window.innerHeight : docEl.offsetHeight) - docEl.clientHeight;
		return size;
	},

	/**
	 * getScrollXY - Gets the distance the page has been scrolled vertically and horizontally
	 * @return	object		The x,y distance in a keyed object ( obj.x obj.y )
	 */
	getScrollXY: function getScrollXY() {
		var sX = 0, sY = 0;
		if ( typeof(window.pageYOffset)=='number' ){
			//Non-IE
			sY = window.pageYOffset;
			sX = window.pageXOffset;
		} else if ( document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop) ) {
			//IE 6+ in 'standards mode'
			sY = document.documentElement.scrollTop;
			sX = document.documentElement.scrollLeft;
		} else if ( document.body && (document.body.scrollLeft || document.body.scrollTop) ){
			//IE 6 in 'strict mode' & some other browsers
			sY = document.body.scrollTop;
			sX = document.body.scrollLeft;
		}
		return {'x':sX,'y':sY};
	}
};

/**
 * Create a validaton engine
 * 
 * @param string form_id			ID of the form
 * @param string alert_type		Type of alert to show the user (current values: alert(default), inline)
 * @param string message_box	ID of the element in which to insert the message (only used for inline alerts)
 * @param bool autoload			Whether to automatically register the handler for the submit event (true(default), false(not recommended))
 */
var Validate = new Activa.Class({
	forms: {},
	message: '',
	invalid: '',
	currentForm: null,
	autoload: true,
	init: function initValidate(alert_type, message_box, autoload) {
		this.message_box = message_box || '';
		// inline_each doesn't require a message_box
		if ( ( alert_type && this.message_box ) || alert_type == 'inline_each' ) {
			this.alert_type = alert_type;
		} else {
			this.alert_type = 'alert';
		}
		this.autoload = autoload === undefined ? true : Boolean(autoload);
	},
	addForm: function addForm(form) {
		var form_id = '';
		if ( typeof form == 'object' ) {
			form_id = form.id;
			this.forms[form.id] = form;
		} else {
			form_id = form;
			this.forms[form] = new Form(form);
		}
		this.currentForm = form_id;
	},
	validate: function validate(form_id) {
		form_id = form_id || '';
		if ( form_id ) {
			if ( this.alert_type == 'inline_each' ) {
				return Inline_Validate.validate(this.forms[form_id].rules, new Inline_Validate.Focus());
			} else {
				return this.forms[form_id].validate();
			}
		}
		var errors = false;
		for ( var id in this.forms ) {
			if ( this.alert_type == 'inline_each' ) {
				if ( !Inline_Validate.validate(this.forms[id].rules) ) {
					errors = true;
				}
			} else {
				if ( !this.forms[id].validate() ) {
					return false;
				}
			}
		}
		if ( this.alert_type == 'inline_each' ) {
			return !errors;
		}
		return true;
	},
	end: function end() {
		if ( !this.autoload ) {
			return;
		}
		// Add Dynamic Rule Checking, when each field is blur'd
		if ( this.alert_type == 'inline_each' ) {
			
			for ( var form_id in this.forms ) {
				var form = this.forms[form_id];
				var success_rules = [];
				for ( i = 0; i < form.rules.length; i++ ) {
					var rule = form.rules[i];
					if ( rule instanceof ConditionalSuccess ) {
						success_rules.push(rule);
					}
				}
				(function registerSuccess(cur_form) {
					Activa.registerEvent(did(cur_form.id), 'keyup', function(e) {
						Inline_Validate.chkSuccess(success_rules);
					});
				}.bind(this))(form);
				for ( var i = 0, z = form.rules.length; i < z; i++ ) {
					(function registerValidate(cur_rule) {
						var field_id = cur_rule.field.id;
						var instance = Inline_Msg.getInstance(did(field_id));
						instance.addRule(cur_rule);
					}.bind(this))(form.rules[i]);
				}
			}
		}
		for ( var id in this.forms ) {
			(function registerSubmit(form_id) {
				Activa.registerEvent(did(form_id), 'submit', function(e) {
					e.stopPropagation();
					if ( !this.validate(form_id) ) {
						e.preventDefault();
						this.message = this.forms[form_id].message;
						this.invalid = this.forms[form_id].invalid;
						return this.notify(this.forms[form_id], this.invalid);
					}
				}.bind(this));
			}.bind(this))(id);
		}
	},
	notify: function notify(form, field) {
		var form = did(form.id);
		if ( this.alert_type == 'inline_each' ) {
			// Do nothing here
			return;
		}
		if ( this.alert_type == 'inline' ) {
			var msgbox = did(this.message_box);
			if ( msgbox ) {
				msgbox.innerHTML = this.message;
				return false;
			}
		}
		alert(this.message);
		if ( field ) {
			field.focus();
		}
		return false;
	}
});

var RuleContainer = new Activa.Class({
	containerStack: [],
	currentContainer: null,
	addRule: function addRule(rule) {
		var args = Activa.toArray(arguments);
		rule = false;
		args.forEach(function(item) {
			if ( rule === false && (item instanceof Rule || item instanceof RuleContainer) ) {
				rule = item;
			}
		});
		if ( rule === false ) {
			return;
		}
		if ( this.currentContainer instanceof RuleContainer ) {
			this.currentContainer.addRule.apply(this.currentContainer, args);
			return false;
		}
		if ( rule instanceof RuleContainer ) {
			this.containerStack.push(rule);
			this.currentContainer = this.containerStack[this.containerStack.length-1];
		}
		return rule;
	},
	endContainer: function endContainer() {
		
		// if the currentContainer is a RuleContainer and it has a currentContainer end that container
		if ( this.currentContainer instanceof RuleContainer && this.currentContainer.currentContainer instanceof RuleContainer ) {
			this.currentContainer.endContainer();
			return;
		}
		
		this.containerStack.pop();
		if ( this.containerStack.length > 0 ) {
			this.currentContainer = this.containerStack[this.containerStack.length-1];
		} else {
			this.currentContainer = null;
		}
	}
});

/**
 * Define a form to attach validation rules to
 * 
 * @param string form_id		ID of the form
 */
var Form = new Activa.Class({
	Extends: RuleContainer,
	id: '',
	rules: [],
	message: '',
	invalid: '',
	init: function initForm(form_id) {
		this.id = form_id;
		var form = did(this.id);
		if ( !form ) {
			throw new ReferenceError('form_id "' + this.id + '" is not a valid id of a form.');
		}
	},
	addRule: function addRule(rule) {
		rule = this.root.apply(this, arguments);
		if ( !rule ) {
			return;
		}
		rule.setForm(this);
		this.rules.push(rule);
	},
	validate: function validateForm(){
		var rule;
		for ( var i = 0, z = this.rules.length; i < z; i++ ) {
			rule = this.rules[i];
			if ( !rule.validate() ) {
				this.message = rule.message;
				this.invalid = rule.field;
				return false;
			}
		}
		return true;
	}
});

/**
 * Define basic validation rule.
 * 
 * @param string field		ID of field
 * @param string message	Error message to display when validation fails
 * @param string match		Match value (available for rules to use to match a value)
 * @param bool required		Whether the rule is required for the form to successfully validate (true, false(default))
 */
var Rule = new Activa.Class({
	form: null,
	field: '',
	field_id: '',
	message: '',
	match: '',
	required: false,
	init: function initRule(field, message, match, required) {
		var ftmp = null;
		if ( typeof field == 'object' ) {
			if ( field.id ) {
				ftmp = did(field.id);
				this.field_id = field.id;
			} else if ( field.name ) {
				ftmp = dbn(field.name);
				ftmp = ftmp.length ? ftmp[0] : null;
				this.field_id = field.name;
			}
		} else {
			ftmp = did(field);
			this.field_id = field;
		}
		this.field = ftmp;	
		this.message = message || '';
		this.match = match || '';
		this.required = !!(required);
	},
	setForm: function setForm(form) {
		if ( !(form instanceof Form) ) {
			throw new TypeError('form must be a valid Form object');
		}
		this.form = form;
	},
	validate: function validateRule() {
		if ( this.field.type != 'select-multiple' ) {
			if ( !this.required ) {
				if ( this.field.value.trim() == '' ) {
					return true;
				}
			}
			return false;
		}
	}
});

/**
 * Create a container for conditional rules
 * 
 * @param string field
 */
var Conditional = new Activa.Class({
	Extends: RuleContainer,
	form: '',
	rules: {},
	elses: [],
	message: '',
	condField: '',
	field: '',
	field_id: '',
	init: function initConditional(field) {
		var ftmp = null;
		if ( typeof field == 'object' ) {
			if ( field.id ) {
				ftmp = did(field.id);
				this.field_id = field.id;
			} else if ( field.name ) {
				ftmp = dbn(field.name);
				ftmp = ftmp.length ? ftmp[0] : null;
				this.field_id = field.name;
			}
		} else {
			ftmp = did(field);
			this.field_id = field;
			this.field = ftmp;
		}
		if ( !ftmp ) {
			throw new ReferenceError('field "' + this.field_id + '" must be a valid element id or name.');
		}
		this.condField = ftmp;
	},
	addRule: function addConditionalRule(matching, rule) {
		var args = Activa.toArray(arguments);
		rule = this.root.apply(null, args);
		if ( !rule ) {
			return;
		}
		if ( this.form ) {
			rule.setForm(this.form);
		}
		if ( matching === null ) {
			this.elses.push(rule);
		} else {
			if ( !this.rules[matching] ) {
				this.rules[matching] = [];
			}
			this.rules[matching].push(rule);
		}
	},
	setForm: function setConditionalForm(form) {
		if ( !(form instanceof Form) ) {
			throw new TypeException('form must be a valid Form object');
		}
		this.form = form;
		for ( var match in this.rules ) {
			this.rules[match].forEach(function(rule) {
				rule.setForm(form);
			});
		}
		this.elses.forEach(function(rule) {
			rule.setForm(form);
		});
	},
	validate: function validateConditional() {
		var validate_else = true;
		for ( var match in this.rules ) {
			var toggled = (this.condField.type == 'checkbox' || this.condField.type == 'radio');
			if ( this.condField && ((toggled && this.condField.checked) || (!toggled && this.condField.value == match)) ) {
				validate_else = false;
				var rule = null;
				for ( var i = 0, z = this.rules[match].length; i < z; i++ ) {
					rule = this.rules[match][i];
					if ( !rule.validate() ) {
						this.message = rule.message;
						this.field = rule.field;
						return false;
					}
				}
			}
		}
		if ( validate_else ) {
			var rule = null;
			for ( var i = 0, z = this.elses.length; i < z; i++ ) {
				rule = this.elses[i];
				if ( !rule.validate() ) {
					this.message = rule.message;
					this.field = rule.field;
					return false;
				}
			}
		}
		return true;
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		var errors = false;
		var validate_else = true;
		from_success = from_success === undefined ? false :Boolean(from_success);
		for ( var match in this.rules ) {
			var toggled = (this.condField.type == 'checkbox' || this.condField.type == 'radio');
			if ( this.condField && ((toggled && this.condField.checked) || (!toggled && this.condField.value == match)) ) {
				validate_else = false;
				var rule = null;
				for ( var i = 0, z = this.rules[match].length; i < z; i++ ) {
					rule = this.rules[match][i];
					if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
						errors = true;
					}
				}
			}
		}
		if ( validate_else ) {
			var rule = null;
			for ( var i = 0, z = this.elses.length; i < z; i++ ) {
				rule = this.elses[i];
				if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
					errors = true;
				}
			}
		}
		return !errors;
	}
});

/**
 * Create a container for conditional rules
 * 
 * @param Rule field
 */
var ConditionalRule = new Activa.Class({
	Extends: Conditional,
	condRule: null,
	init: function initConditionalRule(rule) {
		this.condRule = rule;
		this.field = rule.field;
	},
	addRule: function addConditionalRuleRule(rule) {
		var args = Activa.toArray(arguments);
		// If there is more than 1 arg, we want to pass to a child RuleContainer
		if ( args.length > 1 || this.currentContainer instanceof RuleContainer ) {
			this.root.apply(null, args);
		} else {
			this.root("all", rule);
		}
	},
	validate: function validateConditionalRule() {
		if ( this.condRule.validate() ) {
			for ( var i = 0; i < this.rules["all"].length; i++ ) {
				var rule = this.rules["all"][i];
				if ( !rule.validate() ) {
					this.message = rule.message;
					this.field = rule.field;
					return false;
				}
			}
		}
		return true;
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		from_success = from_success === undefined ? false :Boolean(from_success);
		var errors = false;
		if ( this.condRule.validate() ) {
			for ( var i = 0; i < this.rules["all"].length; i++ ) {
				var rule = this.rules["all"][i];
				if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
					errors = true;
				}
			}
		}
		return !errors;
	}
});

var ConditionalSuccess = new Activa.Class({
	Extends: Conditional,
	message: '',
	required: true,
	init: function initConditionalSuccess(field, message, required) {
		this.field = did(field);
		this.message = message;
		this.required = required === undefined? true : Boolean(required);
	},
	addRule: function addConditionalSuccessRule(type, rule) {
		var type = type === undefined? 'hide_errors' : type;
		var args = Activa.toArray(arguments);
		// If there is more than 1 arg, we want to pass to a child RuleContainer
		if ( args.length > 2 || this.currentContainer instanceof RuleContainer ) {
			this.root.apply(null, args);
		} else {
			this.root(type, rule);
		}
	},
	validate: function validate () {
		return this.validateRecursive(null);
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		from_success = from_success === undefined ? false :Boolean(from_success);
		var errors = false;
		if ( this.rules && this.rules['hide_errors'] ) {
			for ( i = 0; i < this.rules['hide_errors'].length; i++ ) {
				var rule = this.rules['hide_errors'][i];
				if ( !rule.validate() && from_success ) {
					errors = true;
				}
			}
		}
		if ( this.rules && this.rules['show_errors'] ) {
			for ( x = 0; x < this.rules['show_errors'].length; x++ ) {
				var rule2 = this.rules['show_errors'][x];
				if ( !Inline_Validate.validateRule(rule2, focus, true) ) {
					errors = true;
				}
				this.chkFalse(rule2);
			}
		}
		if ( this.required ) {
			return !errors;
		}
		return true;
	},
			// hide all child rules errors
	chkFalse: function check_false_validates(rule) {
		// currently only supports ConditionalRule
		if ( rule instanceof ConditionalRule ) {
			if ( !rule.condRule.validate() ) {
				for ( var i = 0; i < rule.rules["all"].length; i++ ) {
					var cur_rule = rule.rules["all"][i];
					if ( cur_rule instanceof RuleContainer ) {
						this.chkFalse(cur_rule);
						continue;
					}
					var inst = Inline_Msg.getInstance(did(cur_rule.field.id));
					inst.error(cur_rule.message, false);
				}	
			}
		}
	}
});

/**
 *
 * Validation Rules
 * 
 */



var Rule_Alphanumeric = new Activa.Class({
	Extends: Rule,
	validate: function validateAlphanumeric() {
		if ( this.root() ) {
			return true;
		}
		return /^[a-zA-Z0-9]+$/.test(this.field.value);
	}
});

var Rule_Between = new Activa.Class({
	Extends: Rule,
	min: 0,
	max: 0,
	init: function initBetween(field, message, match, required) {
		this.root(field, message, match, required);
		var parts = match.split('-');
		this.min = parseFloat(parts[0] || 0);
		this.max = parseFloat(parts[1] || 0);
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var flen = parseFloat(this.field.value.length);
		return (flen >= this.min && flen <= this.max);
	}
});

var Rule_Captcha = new Activa.Class({
	Extends: Rule,
	validate: function validateCaptcha() {
		if ( this.root() ) {
			return true;
		}
		return this.field.value.trim() != '';
	}
});

var Rule_Checkbox = new Activa.Class({
	Extends: Rule,
	validate: function validateCheckbox() {
		if ( this.root() ) {
			return true;
		}
		return !!(this.field.checked);
	}
});

var Rule_Checkboxes = new Activa.Class({
	Extends: Rule,
	validate: function validateCheckboxes() {
		var boxes = Activa.toArray(dbn(this.field_id+'[]', this.form || null));
		var count = 0;
		boxes.forEach(function isChecked(box) {
			if ( box.checked ) {
				count++;
			}
		});
		return (count == this.match);
	}
});

var Rule_CreditCardNumber = new Activa.Class({
	Extends: Rule,
	validate: function validateCreditCardNumber() {
		if ( this.root()  ) {
			return true;
		}
		var len = (this.field.value.replace(/[^\d]/g, '')).length;
		if ( !(/^[\d -]+$/.test(this.field.value)) && (len < 13 || len > 16) ) {
			return false;
		}
		return true;
	}
});

var Rule_Email = new Activa.Class({
	Extends: Rule,
	regex: null,
	init: function initEmail(field, message, match, required) {
		this.root(field, message, match, required);
		this.regex = new RegExp(match);
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		return (this.regex.test(this.field.value));
	}
});

var Rule_Equals_Field = new Activa.Class({
	Extends: Rule,
	allowempty: false,
	init: function initEqualsField(field, message, match, required, allowempty) {
		this.root(field, message, match, required);
		this.allowempty = allowempty === undefined? false : Boolean(allowempty);
	},
	validate: function validateEqualsField() {
		if ( this.root() ) {
			return true;
		}
		var field = did(this.match);
		if ( !this.allowempty ) {
			if ( this.field.value == '' || ( field && field.value == '' ) ) {
				return false;
			}
		}
		return (field && this.field.value == field.value); 
	}
});

var Rule_Equals = new Activa.Class({
	Extends: Rule,
	validate: function validateEquals() {
		if ( this.root() ) {
			return true;
		}
		return (this.field.value == this.match);
	}
});

var Rule_Greater = new Activa.Class({
	Extends: Rule,
	min: 0,
	init: function initGreater(field, message, match, required) {
		this.root(field, message, match, required);
		this.min = parseFloat(match) || 0;
	},
	validate: function validateGreater() {
		if ( this.root() ) {
			return true;
		}
		var val = parseFloat(this.field.value);
		return (!isNaN(val) && val > this.min);
	}
});

var Rule_Length = new Activa.Class({
	Extends: Rule,
	len: 0,
	init: function initLength(field, message, match, required) {
		this.root(field, message, match, required);
		this.len = parseInt(this.match) || 0;
	},
	validate: function validateLength() {
		if ( this.root() ) {
			return true;
		}
		var len = parseInt(this.field.value.length);
		return (!isNaN(len) && len == this.len);
	}
});

var Rule_Less = new Activa.Class({
	Extends: Rule,
	max: 0,
	init: function initLess(field, message, match, required) {
		this.root(field, message, match, required);
		this.max = parseFloat(match) || 0;
	},
	validate: function validateLess() {
		if ( this.root() ) {
			return true;
		}
		var val = parseFloat(this.field.value);
		return (!isNaN(val) && val < max);
	}
});

var Rule_Longer = new Activa.Class({
	Extends: Rule,
	min: 0,
	init: function initLonger(field, message, match, required) {
		this.root(field, message, match, required);
		this.min = parseInt(match) || 0;
	},
	validate: function validateLonger() {
		if ( this.root() ) {
			return true;
		}
		return (parseInt(this.field.value.length) > this.min);
	}
});

var Rule_Match = new Activa.Class({
	Extends: Rule,
	regex: null,
	init: function initMatch(field, message, match, required) {
		this.root(field, message, match, required);
		this.regex = new RegExp(match);
	},
	validate: function validateMatch() {
		if ( this.root() ) {
			return true;
		}
		return (this.regex.test(this.field.value));
	}
});

var Rule_Notequals = new Activa.Class({
	Extends: Rule,
	validate: function validateNotEquals() {
		if ( this.root() ) {
			return true;
		}
		return (this.field.value != this.match);
	}
});

var Rule_Numeric = new Activa.Class({
	Extends: Rule,
	validate: function validateNumeric() {
		if ( this.root() ) {
			return true;
		}
		return /^[0-9]+$/.test(this.field.value);
	}
});

var Rule_Phone_Int = new Activa.Class({
	Extends: Rule,
	validate: function validatePhoneInt() {
		if ( this.root() ) {
			return true;
		}
		var val = this.field.value = this.field.value.trim().replace(/[\s-\(\)]/g, '');
		return /^\+?[\d+]{10,16}$/.test(val);
	}
});

var Rule_Phone = new Activa.Class({
	Extends: Rule,
	validate: function validatePhone() {
		if ( this.root() ) {
			return true;
		}
		var val = this.field.value = this.field.value.trim().replace(/[\s-\(\)]/g, '');
		return /^\d{10}$/.test(val);
	}
});

var Rule_RadioList = new Activa.Class({
	Extends: Rule,
	validate: function validateRadioList() {
		var radios = Activa.toArray(dbn(this.field_id));
		var value = false; 
		radios.forEach(function(radio) {
			if ( radio.checked ) {
				if ( this.match && this.match != '' && this.match != '0' ) {
					value = radio.value == this.match;
				} else {
					value = true;
				}
			}
		}.bind(this));
		return value;
	}
});

var Rule_Range = new Activa.Class({
	Extends: Rule_Between,
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var flen = parseFloat(this.field.value);
		return (flen > this.min && flen < this.max);
	}
});

var Rule_Shorter = new Activa.Class({
	Extends: Rule,
	max: 0,
	init: function initShorter(field, message, match, required) {
		this.root(field, message, match, required);
		this.max = parseInt(match) || 0;
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var val = parseInt(this.field.value.length);
		return (!isNaN(val) && val < this.max);
	}
});

var Rule_Text = new Activa.Class({
	Extends: Rule,
	validate: function validateText() {
		if ( this.root() ) {
			return true;
		}
		return !this.field.value.trim() == '';
	}
});



var Inline_Validate = new Activa.Class({
});

Inline_Validate.statics({
	validate: function validate(rules, focus) {
		var focus = focus;
		var errors = false;
		for ( var i = 0; i < rules.length; i++ ) {
			var rule = rules[i];
			if ( !this.validateRule(rule, focus) ) {
				errors = true;
			}
		}
		return !errors;
	},
	validateRule: function validateRule(rule, focus, from_success) {
		var errors = false;
		// From Success means its updating from a success conditional. Which is updated every keyup on the 
		// conditionals fields, and child fields.
		from_success = from_success === undefined ? false :Boolean(from_success);
		if ( rule instanceof RuleContainer ) {
			if ( !rule.validateRecursive(focus, from_success) ) {
				errors = true;
			}
		} else {
			if ( !rule.validate() ) {
				Inline_Msg.getInstance(did(rule.field.id)).error(rule.message, true, focus? true : false);
				if ( focus ) {
					focus.focusField(rule.field.id);
				}
				errors = true
			} else {
				Inline_Msg.getInstance(did(rule.field.id)).error(rule.message, false, focus? true : false);
			}
		}
		return !errors;
	},
	chkSuccess: function check_success(rules) {
		for ( i = 0; i < rules.length; i++ ) {
			var rule = rules[i];
			var show = rule.validateRecursive(null, true);
			Inline_Msg.getInstance(rule.field).success(rule.message, show);
		}
	}
});

Inline_Validate.Focus = new Activa.Class({
	focused: false,
	field: null,
	focusField: function focus_field(field_id) {
		if ( this.focused ) {
			return false;
		}
		this.field = did(field_id);
		this.field.focus();
		this.focused = true;
	}
});

var Inline_Msg = new Activa.Class({
	field: null,
	holder: null,
	errors: {},
	showing: false,
	rules: [],
	has_focus: false,
	init: function construct(field) {
		this.field = field;
		this.holder = Activa.DOM.next(this.field);
		// Create the holder Element if needed
		if ( !this.holder || this.holder.tagName.toUpperCase() != Inline_Msg.tag || !Activa.DOM.hasClass(this.holder, Inline_Msg.css_class) ) {
			this.holder = document.createElement(Inline_Msg.tag);
			Activa.DOM.addClass(this.holder, Inline_Msg.css_class);
			this.field.parentNode.insertBefore(this.holder, this.field.nextSibling);
			Activa.registerEvent(this.field, 'blur', function(e) {
				this.validate(true);
			}.bind(this));
			Activa.registerEvent(this.field, 'focus', function(e) {
				this.has_focus = true;
			}.bind(this));
		}
	},
	addRule: function add_rule(rule) {
		this.rules.push(rule);
	},
	validate: function validate(force) {
		force = force === undefined? false : Boolean(force);
		if ( force ) {
			this.has_focus = true;
		}
		Inline_Validate.validate(this.rules);
		if ( force ) {
			this.has_focus = false;
		}
	},
	error: function error(message, show, from_submit) {
		from_submit = from_submit === undefined ? false : Boolean(from_submit);
		if ( !from_submit && !this.has_focus ) {
			return;
		}
		return this.msg(message, Inline_Msg.css_error, show);
	},
	success: function success(message, show) {
		return this.msg(message, Inline_Msg.css_success, show);
	},
	msg: function msg(message, type, show) {
		var show = show === undefined ? true : Boolean(show);
		var index = message+type;
		// IF Error doesn't exist on this field
		if ( !Inline_Msg.exists(index, this.errors) && show ) {
			var msg = document.createElement('strong');
			msg.appendChild(document.createTextNode(message));
			Activa.DOM.addClass(msg, type);
			this.holder.appendChild(msg);
			this.errors[index] = msg;
		}
		if ( !this.showing ) {
			this.show();
		}
		if ( !show ) {
			this.hideMsg(message, type);
			this.chkHide();
		}
	},
	show: function show() {
		this.holder.style.display = '';
		this.showing = true;
	},
	hide: function hide() {
		this.holder.style.display = 'none';
		this.showing = false;
	},
	hideMsg: function hideMsg(message, type) {
		var index = message+type;
		delete this.errors[index];
		var length = this.holder.childNodes.length;
		for ( i = 0; i < length; i++ ) {
			var node = this.holder.childNodes[i];
			if ( Activa.DOM.hasClass(node, type) && node.innerHTML == message ) {
				this.holder.removeChild(node);
			}
		}
	},
	chkHide: function check_hide() {
		var hide = true;
		if ( this.holder.childNodes.length > 0 ) {
			for ( i = 0; i < this.holder.childNodes.length; i++ ) {
				var node = this.holder.childNodes[i];
				if ( node.tagName && node.tagName.toUpperCase() == 'STRONG' ) {
					hide = false;
				}
			}
		}
		if ( hide ) {
			this.hide();
		}
	}
});

Inline_Msg.statics({
	css_error: 'error',
	css_success: 'success',
	css_class: 'dev_inline_errors',
	tag: 'P',
	instances: {},
	getInstance: function getInstance(field) {
		var instance = null;
		if ( instance = this.exists(field.id, this.instances) ) {
		} else {
			instance = this.instances[field.id] = new Inline_Msg(field);
		}
		return instance;
	},
	exists: function field_exists(field_id, obj) {
		for ( var key in obj ) { 
			if ( key == field_id ) {
				return obj[key];
			}
		}
		return false;
	}
});


function updateShipping(parcel_index, url) {
	if ( did("service_" + parcel_index) ) {
		url += "/" + did("service_" + parcel_index).value;
	}
	if ( did("carrier_" + parcel_index) ) {
		url += "/" + did("carrier_" + parcel_index).value;
	}
	new Activa.rpc(url, { onComplete: updateCheckoutModifiers });
}

function doUpdateShipping(obj) {	
	if ( obj.shipping_method == 'order' ) {
		did("shipping_total").innerHTML = "$" + obj.shipping;
		did("grand_total").innerHTML = "$" + obj.grand_total;
		Activa.DOM.showID("grand_total_row");
	}
}

function rgbToHex(r,g,b){
	var rgb = slice(arguments,0,3), hex = '0123456789ABCDEF';
	for(var i=0;i < 3;i++){
		rgb[i] = isNaN(parseInt(rgb[i],10)) ? 0 : parseInt(rgb[i],10);
		rgb[i] = Math.round(Math.max(Math.min(255,rgb[i]),0));
		rgb[i] = hex.charAt( (rgb[i]-rgb[i]%16)/16 ) + hex.charAt( rgb[i]%16 );
	}
	return rgb.join('');
}

function getPosition(elm) {
	var pos = { x: 0, y: 0 };
	if ( elm.offsetParent ) {
		do {
			pos.x += elm.offsetLeft;
			pos.y += elm.offsetTop;
		} while ( elm = elm.offsetParent );
	}
	return pos;
};

function getScroll(elm) {
	var pos = { x: 0, y: 0 };
	if ( elm && elm.tagName != 'BODY' ) {
		pos.y += elm.scrollTop;
		pos.x += elm.scrollLeft;
		elm = elm.parentNode;
	}
	return pos;
}

/**
 * getStyle - Retrieve the value of the current style property
 * @param	mixed		obj		Element id or object
 * @param	string		prop	Name of style property to retrieve
 * @return	mixed				Value of the style property. *Note: Unit values will be returned as integers. (ie. 'px' will be stripped off)*
 */
function getStyle(obj,prop){
	obj = (typeof obj == 'object') ? obj : did(obj);
	if(!obj || !prop){ return null; }
	var regRGB = /rgb\((\d+),\s(\d+),\s(\d+)\)/i, color;
	var compVal = obj.currentStyle ? obj.currentStyle[prop] : window.getComputedStyle(obj,null).getPropertyValue(prop);
	if((color = regRGB.exec(compVal)) != null){ color.shift(); compVal = '#'+rgbToHex.apply(null,color); }
	return isNaN(parseFloat(compVal)) ? compVal : parseFloat(compVal);
}

/**
 * getStyles - Retrieve the current value of several styles
 * @param	mixed	obj		Element id or object
 * @param	string	prop	Name of style property to retrieve (pass another parameter for each style property)
 * @return	object			Object whose keys are the style property and values are the style property's value
 */
function getStyles(obj, prop) {
	var args = slice(arguments,1);
	var styles = {};
	args.forEach(function(arg){
		this[arg] = getStyle(obj,arg);
	},styles);
	return styles;
}

function updateCheckoutModifiers(obj) {
	var before_shipping = false;
	
	for ( var parcel_index = 0; parcel_index < obj.parcels.length; parcel_index++ ) {
	
		var tbody = did("checkout_table_" + parcel_index).tBodies[0];
	
		Activa.DOM.hideID("shipping_row_" + parcel_index);
		
		var sub_total_row_found = false;
		for ( var i = 0; i < tbody.childNodes.length; i++ ) {
			if ( tbody.childNodes[i].nodeName == 'TR' ) {
				if ( tbody.childNodes[i].id == 'grand_total_row_' + parcel_index ) {
					break;
				} else if ( tbody.childNodes[i].id == 'sub_total_row_' + parcel_index ) {
					sub_total_row_found = true;
				} else if ( sub_total_row_found ) {
					if ( tbody.childNodes[i].id != 'shipping_row_' + parcel_index ) {
						tbody.childNodes[i].parentNode.removeChild(tbody.childNodes[i]);
						i--;
					}
				}		
			}	
		}
		
		var insert_before_target = did("shipping_row_" + parcel_index) || did("grand_total_row_" + parcel_index);
		
		for ( var i=0; i < obj.parcels[parcel_index].modifiers.length; i++ ) {
			
			if ( obj.parcels[parcel_index].modifiers[i].service ) {
			
				if ( did("carrier_" + parcel_index) ) {
				
					while ( did("carrier_" + parcel_index).options.length > 0 ) {
						did("carrier_" + parcel_index).remove(0);
					}
					
					for ( var k=0; k < obj.parcels[parcel_index].modifiers[i].carriers.length; k++ ) {
						var optn = document.createElement("option");
						optn.text = obj.parcels[parcel_index].modifiers[i].carriers[k].text;
						optn.value = obj.parcels[parcel_index].modifiers[i].carriers[k].value;
						did("carrier_" + parcel_index).options.add(optn);
					}
					
					did("carrier_" + parcel_index).value = obj.modifiers[i].carrier;
				}
				
				if ( did("service_" + parcel_index) ) {
				
					while ( did("service_" + parcel_index).options.length > 0 ) {
						did("service_" + parcel_index).remove(0);
					}
					
					for ( var k = 0; k < obj.parcels[parcel_index].modifiers[i].services.length; k++ ) {
						var optn = document.createElement("option");
						optn.text = obj.parcels[parcel_index].modifiers[i].services[k].text;
						optn.value = obj.parcels[parcel_index].modifiers[i].services[k].value;
						did("service_" + parcel_index).options.add(optn);
					}
					
					did("service_" + parcel_index).value = obj.parcels[parcel_index].modifiers[i].service;
				}
				
				if ( obj.parcels[parcel_index].modifiers[i].tbd ) {
					Activa.DOM.showID('collect-shipping_' + parcel_index);
					did("shipping_total_" + parcel_index).innerHTML = 'TBD';
				} else {
					Activa.DOM.hideID('collect-shipping_' + parcel_index);
					did("shipping_total_" + parcel_index).innerHTML = obj.parcels[parcel_index].modifiers[i].amount;
				}				
			
				insert_before_target = did("grand_total_row_" + parcel_index);	
			} else {
				var tr = document.createElement('tr');
				tr.className = 'discount';
				
				var td = document.createElement('td');
				td.className = 'title';
				td.colSpan = '2';
				td.appendChild(document.createTextNode(obj.parcels[parcel_index].modifiers[i].name));
				tr.appendChild(td);
				
				var td = document.createElement('td');
				td.className = 'price ' + (obj.parcels[parcel_index].modifiers[i].positive ? 'green' : 'red');
				td.appendChild(document.createTextNode(obj.parcels[parcel_index].modifiers[i].amount));
				tr.appendChild(td);
				
				tbody.insertBefore(tr, insert_before_target);
			}
		}
		
		did("grand_total_" + parcel_index).innerHTML = "$" + obj.parcels[parcel_index].total;
		
		Activa.DOM.showID("shipping_row_" + parcel_index);
	}
	
	var trs = did("grand_total_row").parentNode.childNodes;
	for ( var i = 0; i < trs.length; i++ ) {
		if ( trs[i].nodeName == "TR" && trs[i].className == 'discount' ) {
			did("grand_total_row").parentNode.removeChild(trs[i]);
			i--;
		}
	}
	
	for ( var i = 0; i < obj.modifiers.length; i++ ) {	
		var tr = document.createElement('tr');
		tr.className = 'discount';
		
		var td = document.createElement('td');
		td.className = 'title';
		td.colSpan = '2';
		td.appendChild(document.createTextNode(obj.modifiers[i].name));
		tr.appendChild(td);
		
		var td = document.createElement('td');
		td.className = 'price ' + (obj.modifiers[i].positive ? 'green' : 'red');
		td.appendChild(document.createTextNode(obj.modifiers[i].amount));
		tr.appendChild(td);
		
		did("grand_total_row").parentNode.insertBefore(tr, did("grand_total_row"));
	}
		
	if ( obj.promo_error ) {
		did('promo_code_error').innerHTML = obj.promo_error;
		Activa.DOM.showID('promo_code_error');
	} else {
		did('promo_code_error').innerHTML = 'fds';
		Activa.DOM.hideID('promo_code_error');
	}
	
	did("grand_total").innerHTML = "$" + obj.grand_total;
}

function addBookmark(url, title) {
	if ( window.sidebar ) { // Firefox
		window.sidebar.addPanel(title, url, "");
	} else if ( window.external ) { // IE
		window.external.AddFavorite(url, title); 
	}
}

/**
 * addClass - Adds a class to an element
 * @param	obj			Element on which to add the class
 * @param	className	Class to add to the element
 * @return				True (if successfully added class) / False (if invalid object or empty classname given)
 */
function addClass(obj, className) {
	if(!obj || className.trim() == '' || hasClass(obj, className)){ return false; }
	obj.className = String(obj.className).split(' ').concat([className]).join(' ').trim();
	return true;
}
/**
 * removeClass - Removes a class from an element
 * @param	obj			Element from which to remove the class
 * @param	className	Class to remove from the element
 * @return				True (if successfully removed class) / False (if invalid object or empty classname given)
 */
function removeClass(obj, className) {
	if(!obj || className.trim() == ''){ return false; }
	obj.className = String(obj.className).split(' ').filter(function(cls){
		return (cls != className);
	}).join(' ');
	return true;
}

/**
 * rgbToHex - Converts an rgb color value into a hexidecimal value (without the leading '#')
 * @param	integer		r		Value of the color red in range 0-255;
 * @param	integer		g		Value of the color green in range 0-255;
 * @param	integer		b		Value of the color blue in range 0-255;
 * @return	string				Hexidecimal representation of the the color
 */
function rgbToHex(r,g,b){
	var rgb = slice(arguments,0,3), hex = '0123456789ABCDEF';
	for(var i=0;i < 3;i++){
		rgb[i] = isNaN(parseInt(rgb[i],10)) ? 0 : parseInt(rgb[i],10);
		rgb[i] = Math.round(Math.max(Math.min(255,rgb[i]),0));
		rgb[i] = hex.charAt( (rgb[i]-rgb[i]%16)/16 ) + hex.charAt( rgb[i]%16 );
	}
	return rgb.join('');
}

/**
 * getStyle - Retrieve the value of the current style property
 * @param	mixed		obj		Element id or object
 * @param	string		prop	Name of style property to retrieve
 * @return	mixed				Value of the style property. *Note: Unit values will be returned as integers. (ie. 'px' will be stripped off)*
 */
function getStyle(obj,prop){
	obj = (typeof obj == 'object') ? obj : did(obj);
	if(!obj || !prop){ return null; }
	var regRGB = /rgb\((\d+),\s(\d+),\s(\d+)\)/i, color;
	var compVal = obj.currentStyle ? obj.currentStyle[prop] : window.getComputedStyle(obj,null).getPropertyValue(prop);
	if((color = regRGB.exec(compVal)) != null){ color.shift(); compVal = '#'+rgbToHex.apply(null,color); }
	return isNaN(parseFloat(compVal)) ? compVal : parseFloat(compVal);
}

/**
 * getStyles - Retrieve the current value of several styles
 * @param	mixed	obj		Element id or object
 * @param	string	prop	Name of style property to retrieve (pass another parameter for each style property)
 * @return	object			Object whose keys are the style property and values are the style property's value
 */
function getStyles(obj, prop) {
	var args = slice(arguments,1);
	var styles = {};
	args.forEach(function(arg){
		this[arg] = getStyle(obj,arg);
	},styles);
	return styles;
}

/**
 * triggerEvent - Manually fires an event on an element
 * @param	el			object		Element on which to fire the event
 * @param	type		string		Event type to fire
 * @param	bubbles		boolean		Whether the event bubbles through the DOM (no effect on ie)
 * @param	cancelable	boolean		Whether the event can be cancelled with preventDefault (no effect on ie)
 */
function triggerEvent(el, type, bubbles, cancelable) {
	try{
		el = typeof el == 'object' ? el : did(el);
		bubbles = bubbles || true;
		cancelable = cancelable || true;
		if(document.createEvent){
			var groups = {
				'UIEvents':['focusin','focusout','activate','deactivate'],
				'MouseEvents': ['click','dblclick','mousedown','mouseup','mouseover','mouseout','mousemove'],
				'HTMLEvents': ['load','unload','abort','error','select','change','submit','reset','focus','blur','resize','scroll']
			};
			var groupName = null;
			for(var group in groups) {
				if(groups[group].indexOf(type) != -1) {
					groupName = group;
					break;
				}
			}
			if(groupName) {
				var event = document.createEvent(groupName);
				event.initEvent(type, bubbles, cancelable);
				//safari 3 doesn't have window.dispatchEvent()
				(el == window && !el.dispatchEvent ? document : el).dispatchEvent(event);
				return true;
			}
		}else if(document.createEventObject){
			var event = document.createEventObject();
			// IE6,IE7 thinks window==document and doesn't have window.fireEvent()
			// IE6,IE7 cannot properly call document.fireEvent()
			(el == document ? document.documentElement : el).fireEvent("on"+type, event);
			return true;
		}
	}catch(e){}
	return false;
}

function enterField(field, value) {
	if ( field.value == value ) {
		field.value = '';
	}
	field.className = 'focus';
}

function leaveField(field, value) {
	if ( field.value == '' ) {
		field.value = value;
		field.className = 'blur';
	}
	
}

var BillingConditional = new Activa.Class({
	Extends: Conditional,
	init: function initBillCond(field) {
		try {
			this.root(field);
		} catch ( e ) {
		}
	},
	validate: function validateBillCond() {
		var validate_else = true;
		for ( var match in this.rules ) {
			var toggled = (this.condField.type == 'checkbox' || this.condField.type == 'radio');
			if ( this.condField && ((toggled && this.condField.checked) || (!toggled && this.condField.value == match)) ) {
				validate_else = false;
				var rule = null;
				for ( var i=0, z=this.rules[match].length; i < z; i++ ) {
					rule = this.rules[match][i];
					if ( !rule.validate() ) {
						this.message = rule.message;
						this.field = rule.field;
						return false;
					}
				}
			}
		}
		if ( validate_else ) {
			var rule = null;
			for ( var i=0, z=this.elses.length; i < z; i++ ) {
				rule = this.elses[i];
				if ( !rule.validate() ) {
					this.message = rule.message;
					this.field = rule.field;
					return false;
				}
			}
		}
		return true;
	}
});

var Rule_Date = new Activa.Class({
	Extends: Rule,
	defaultDelims: '-/',
	init: function initDate(field, message, match, required, delims) {
		this.root(field, message, match, required);
		delims = typeof delims == 'string' && delims ? delims : this.defaultDelims;
		this.regUS = new RegExp('^([1-9]|0[1-9]|1[0-2])['+delims+']([1-9]|0[1-9]|[1-2][0-9]|3[0-1])['+delims+'](\\d{4})$');
		this.regEU = new RegExp('^(\\d{4})['+delims+']([1-9]|0[1-9]|1[0-2])['+delims+']([1-9]|0[1-9]|[1-2][0-9]|3[0-1])$');
	},
	validate: function validateDate() {
		if ( this.root() ) {
			return true;
		}
		if ( !this.regUS.test(this.field.value) ) {
			if ( !this.regEU.test(this.field.value) ) {
				return false;
			}
		}
		return true;
	}
});

var Rule_Time = new Activa.Class({
	Extends: Rule,
	validate: function validate() {
		if ( this.root() ) {
			return true;
		}
		
		var regex = /(\d{1,2})(?::(?=\d{1,2}))?(\d{1,2})?(?::(?=\d{1,2}))?(\d{1,2})?\s?([AP]M)?$/i;
		var parts = regex.exec(this.field.value);
		if ( parts == null ) {
			return false;
		}
		parts.shift();
		var suffix = parts.pop();
		suffix = (suffix) ? suffix.toUpperCase() : (parts[0] >= 12 ? 'PM' : 'AM');
		parts[0] = padString(suffix == 'PM' ? (parts[0] < 12 ? Number(parts[0])+12 : parts[0]) : (parts[0] < 12 ? parts[0] : '00'), 2);
		for ( var i = 1; i < 3; i++ ) {
			parts[i] = padString(parts[i] != null ? parts[i] : '00');
		}
		var t = new Date();
		var tstr = t.getFullYear()+'/'+t.getMonth()+'/'+t.getDay()+' '+parts.join(':');
		return !isNaN(Date.parse(tstr));
	}
});

var currCalendar;

/**
 * pad - Left-pads a number to a specified number of places. (Will maintain a negative)
 * @param	mixed		val		Number or String to pad
 * @param	integer		len		Number of places to pad the string to.(Default is 2. ie. 9 becomes 09)
 * @return	string				val padded to len places. (if val was negative then string is prepended with '-')
 */
function pad(val, len){
	var v = Number(val), vlen = Math.abs(v).toString().length, len = len || 2;
	if ( isNaN(v) || v==0 ) { return new Array(len+1).join('0'); }
	return (vlen) < len ? (val < 0?'-':'')+(new Array(len-vlen+1).join('0')+Math.abs(v)) : v.toString();
}

/**
 * Date.format - Emulates PHP's date() function by accepting a string to format the date and time.
 * 					Unrecognized characters will be returned inline. To print the literal version of
 * 					a formatting character, the character can be escaped with double backslashes (ie. \\d)
 * Note:
 *	The following formatting characters are currently unsupported and will be replaced with an empty string:
 * 		W - ISO-8601 week number of year, weeks starting on Monday
 * 		o - ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead.
 * 		e - Timezone identifier
 */
Date.prototype.format = function(fs){
	fs = ((typeof fs=='string' || fs instanceof String)?fs:null) || null;
	if ( !fs ) { return ''; }
	fs = fs.split("");
	var ret='';
	var cmn = {date:this.getDate(), day:this.getDay(), month:this.getMonth()+1, year:this.getFullYear(),
		hour:this.getHours(), minute:this.getMinutes(), second:this.getSeconds(), time:this.getTime(),
		ordinals:{1:'st', 2:'nd', 3:'rd', 'else':'th'}, offset:pad(this.getTimezoneOffset()/60*-100, 4),
		daynames:['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
		monthnames:['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
	};
	cmn['dayone'] = new Date(cmn['year'], 0, 1); //january 1st of the year
	cmn['suffix'] = (cmn['hour'] < 12 ? 'am' : 'pm');
	var lastDigit = cmn['date'].toString().substr(-1);
	var skip = false;
	for ( var i=0;i < fs.length;i++ ) {
		if ( skip ) { ret+=fs[i]; skip=false; continue; }
		switch(fs[i]){
			case 'd': ret+=pad(cmn['date']); break;
			case 'D': ret+=cmn['daynames'][cmn['day']].substr(0, 3); break;
			case 'j': ret+=cmn['date']; break;
			case 'l': ret+=cmn['daynames'][cmn['day']]; break;
			case 'N': ret+=cmn['day']+1; break;
			case 'S': ret+=(cmn['ordinals'][((lastDigit > 3 || lastDigit==0) ? 'else':lastDigit)]); break;
			case 'w': ret+=cmn['day']; break;												//86400000 (864e5) = milliseconds per day
			case 'z': ret+=Math.floor((cmn['time'] - cmn['dayone'].getTime()) / 864e5); break;
			case 'W': ret+=''; break; //todo
			case 'F': ret+=cmn['monthnames'][cmn['month']-1]; break;
			case 'm': ret+=pad(cmn['month']); break;
			case 'M': ret+=cmn['monthnames'][cmn['month']].substr(0, 3); break;
			case 'n': ret+=cmn['month']; break;
			case 't': ret+=new Date(cmn['year'], cmn['month'], 0).getDate(); break;
			case 'L': ret+=(new Date(cmn['year'], 2, 0).getDate()==29 ? 1 : 0); break;
			case 'o': ret+=''; break; //todo
			case 'Y': ret+=cmn['year']; break;
			case 'y': ret+=cmn['year'].toString().substr(-2); break;
			case 'a': ret+=cmn['suffix']; break;
			case 'A': ret+=cmn['suffix'].toUpperCase(); break;
			case 'g': ret+=(cmn['hour']==0 ? 12 : (cmn['hour'] > 12 ? cmn['hour']-12: cmn['hour'])); break;
			case 'G': ret+=cmn['hour']; break;
			case 'h': ret+=pad(cmn['hour']==0 ? 12 : (cmn['hour'] > 12 ? cmn['hour']-12: cmn['hour'])); break;
			case 'H': ret+=pad(cmn['hour']); break;
			case 'i': ret+=pad(cmn['minute']); break;
			case 's': ret+=pad(cmn['second']); break;
			case 'u': ret+=this.getMilliseconds()*1000; break;
			case 'e': ret+=''; break; //todo
			case 'I': ret+=((this.getTimezoneOffset() - cmn['dayone'].getTimezoneOffset()) ? 1 : 0); break;
			case 'O': ret+=cmn['offset']; break;
			case 'P': ret+=cmn['offset'].replace(/^([+-]\d{2})(\d{2})/i, '$1:$2'); break;
			case 'T': ret+=this.toTimeString().replace(/.*\((\w+)\)$/, '$1'); break;
			case 'Z': ret+=(this.getTimezoneOffset()*60); break;
			case 'c': ret+=this.format('Y-m-d')+'T'+this.format('H:i:s'+(this.getTimezoneOffset() < 60 ? '\\Z':'P')); break;
			case 'r': ret+=this.format('D, d M Y H:i:s O'); break;
			case 'U': ret+=cmn['time']/1000; break;
			case '\\': skip=true; break;
			default:
				ret+=fs[i]; break;
		}
	}
	return ret;
};

/**
 * CalendarManager ( Static Class )
 * Properties and methods declared statically
 */
function CalendarManager(){}
/* Array of Calendar objects */
CalendarManager.calendars = [];
/* Reference table so a calendar can be referred to by target element id */
CalendarManager.calRef = {};
/**
 * Returns a member variable (or a calander instance if value is a valid target element id)
 * @param mvar mixed	Name of member variable or (string)ID of calendar target element or (int)Index of calendar object in array. 
 * @return 				The Calendar object instance (or null if not found)
 */
CalendarManager.get = function(mvar){
	if ( typeof mvar =='string' || mvar instanceof String ) {
		if ( mvar in this.calRef ) { 
			return this.calendars[this.calRef[mvar]];
		}else if ( mvar in this ) { return this[mvar]; }
	}else if ( typeof mvar=='number' || mvar instanceof Number ) {
		if ( this.calendars.indexOf(mvar) != -1 ) { return this.calendars[mvar]; }
	}
	return null;
};
/**
 * Sets an existing Calendar object into the calendars member array.
 * @param target string	ID of the element that will hold the datetime string
 * @param cal Calendar		Calendar object to set into the array.
 * @return					The static CalenderManager object
 */
CalendarManager.set = function(target, cal){
	if ( (target in this.calRef)||(!cal)||(cal.constructor != Calendar) ) { return null; }
	this.calRef[target] = this.calendars.length;
	this.calendars.push(cal);
	return this;
}
/**
 * Returns either an existing calendar for the target if one exists, or a new calendar for the target.
 * @param target string	ID of element that will hold datetime string
 * @return					Either the pre-existing Calendar object or the new Calendar object.
 */
CalendarManager.add = function(target){
	var cal = this.get(target);
	if ( cal ) { return cal; }
	this.calRef[target]=this.calendars.length;
	this.calendars.push(new Calendar(target));
	return this.calendars[this.calendars.length-1];
};
/**
 * Removes a Calendar object from the array and returns it.
 * @param target string	ID of the element for the calendar to remove
 * @return					The Calendar object removed from the manager.
 */
CalendarManager.remove = function(target){
	if ( !target || !(target in this.calRef) ) { return null; }
	return this.calendars.splice(this.calRef[target], 1);
};

/**
 * Calendar ( Class )
 * @param target	mixed	Element object or string ID of the element that will hold the datetime string.
 * @param button	mixed	Element object or string ID of the element that will popup the calendar.
 * @param options	object	Key/value pairs in an object specifying options
 *
 * Member Variables (publicly accessible, but use 'set' method)
 * target			element		Element that will receive date/time
 * button			element		Element that will activate the popup calendar
 * timeRequired		boolean		True - popup will show Time selects, False - popup will not show Time selects
 * yearScroll		boolean		Allow the years to be scrolled through in the popup
 * autoUpdate		boolean		When using the 'set' method and setting 'date', 'time', or 'datetime' the target element's value will be changed automatically
 * popOffset		object		Set the distance x/y to offset the popup placement from the cursor. (ex. { x: 50, y: 0 })
 * poptype			string		Specify the method used to display the calendar (popup, popin(default))
 * calPath			string		Path to the popup calendar file.
 * targetFormat		string		Format string to target element to receive on update. Format characters match PHP's date() function. See: Date.prototype.format
 * onUpdate			function	Function to be called when date is picked in popup. Overrides default behaviour of assigning value into target element
 * datetime			Date		Object that holds the chosen date/time
 * popWin			object		(Read-only please)Holds reference to the popup window
 */
function Calendar(target, button, options){
	 /* if we have a string treat it as an id and get the element */
	this.set('target', target);
	this.set('button', button);
	
	//Settings
	this.timeRequired = false;
	this.yearScroll = true;
	this.autoUpdate = false;
	this.popOffset = {
			x: 0,
			y: 0
	}
	this.popType = 'popin';
	this.calPath = '';
	this.calFrame = '';
	this.targetFormat = 'm/d/Y';
	this.onUpdate = null;
	
	for ( var key in options ) {
		this.set(key, options[key]);
	}
	
	if ( this.timeRequired && !options.targetFormat ) {
		this.targetFormat += ' H:i:s';
	}
	
	//Data members
	this.datetime = new Date();
	this.popWin = null;
	this.initialized = false;
	this.isIE = window.ActiveXObject ? (window.XMLHttpRequest ? 7 : 6) : false;
	
	var self = this;
	
	Activa.DOM.ready(function(){
		if ( this.popType == 'popin' && !this.calFrame ) {
			var docbody = dbt('body')[0];
			this.calFrame = document.createElement('iframe');
			this.calFrame.frameBorder = 0;
			this.calFrame.style.cssText = 'border:0; position:absolute; z-index:1000; display:none; padding:0; background-color:transparent; width:194px; height:175px;';
			docbody.insertBefore(this.calFrame, Activa.DOM.first(docbody));
		}
		
		if ( this.calFrame ) {
			var obj = self.isIE ? document.body : document;
			Activa.registerEvent(obj, 'click', function repositionFrame(e) {
				self.popout();
			});
		}
	}, this);
	
	function popCal(e){
		e.preventDefault();
		e.stopPropagation();
		var fn = self[self.popType];
		var scroll = Activa.Dimensions.getScrollXY();
		var x = e.pageX + (self.popOffset.x || 0), y = e.pageY + (self.popOffset.y || 0);
		fn.call(self, x, y);
	}
	
	if ( !button || !target ) { return null; }
	Activa.registerEvent(button, 'click', popCal);
	if ( CalendarManager && CalendarManager.get(target) == null ) {
		CalendarManager.set(this.target.id, this);
	}
}
/**
 * Prototype (applies to all instances - not available statically)
 */
Calendar.prototype = {
	/**
	 * set - sets member variables
	 * @param string mvar	The name of the member variable (or 'date', or 'time')
	 * @param mixed value	The value to assign to the member variable
	 * @return				The instance of the Calendar object (or null on error)
	 */
	set: function set(mvar, value){
		switch(mvar){
			case 'timeRequired': case 'yearScroll': case 'autoUpdate': /* cast to boolean */
				this[mvar] = (value ? true : false);break;
			case 'datetime':
				if ( typeof value =='string'|| value instanceof String ) { value = this.parse(value); } /* if we have a string, parse it first */
				/* test for getTime because for some reason instanceof and constructor don't work right when coming from a popup window */
				if ( !value.getTime ) { this.showError('member', 'set', mvar, value);return null; }
				var tmp = new Date(value.getTime()); /* make a new object so we aren't using a reference */
				this[mvar] = tmp;
				this.initialized = true;
				if ( this.autoUpdate ) { this.updateTarget(); }
				break;
			case 'target': /* if we have a string treat it as an id and get the element */
			case 'button':
			case 'calFrame':
				var tmp = (value != null) ? ((typeof value=='object') ? value : did(value)) : null;
				if ( tmp==null ) { Calendar.showError(mvar, value); return null; }
				this[mvar] = tmp;
				break;
			case 'date':
				if ( typeof value =='string'|| value instanceof String ) { value = this.parseDate(value); } /* if we have a string, parse it first */
				/* copy the values (so we don't overwrite any time settings) */
				this.datetime.setFullYear(value.getFullYear(), value.getMonth(), value.getDate());
				if ( this.autoUpdate && this.initialized ) { this.updateTarget(); }
				break;
			case 'time':
				if ( typeof value =='string'|| value instanceof String ) { value = this.parseTime(value); } /* if we have a string, parse it first */
				/* copy the values (so we don't overwrite any date settings) */
				this.datetime.setHours(value.getHours(), value.getMinutes(), value.getSeconds(), value.getMilliseconds());
				if ( this.autoUpdate && this.initialized ) { this.updateTarget(); }
				break;
			case 'displayErrors':
				Calendar.displayErrors = (value ? true : false);break;
			case 'onUpdate':
				if ( typeof(value) == 'function' ) { this[mvar]=value; } break;
			case 'calPath':
			case 'targetFormat':
				this[mvar] = value; break;
			case 'popType':
				value = String(value).toLowerCase();
				if ( value != 'popup' && value != 'popin' ) {
					Calendar.showError(mvar, value);
					return null;
				}
				this.popType = value;
			default:
				if ( !(mvar in this) ) { this.showError('member', 'set', mvar, value);return null; }
				break;
		}
		return this;
	},
	/**
	 * get - gets the value of a member variable
	 * @param string mvar	The name of the member variable who's value to retrieve (or 'date', or 'time', or 'dateandtime')
	 * @return				The value of the member variable (or null on error)
	 */
	get: function get(mvar){
		if ( this.initialized ) {
			switch(mvar){ /* Get full date/time in this format: mm-dd-yyyy dd:dd (AM or PM) */
				case 'dateandtime': return this.datetime.format('m-d-Y h:i A');break;
				case 'date': /* return date in this format: mm-dd-yyyy */
					return this.datetime.format('m-d-Y');
					break;
				case 'time': /* return time in this format: dd:dd (AM or PM) */
					return this.datetime.format('h:i A');
					break;
			}
		}
		if ( !(mvar in this) ) { return null; } /* for everything else make sure it's a real member var */
		return this[mvar];
	},
	/**
	 * popup - Opens the pop-up with the mini calendar in it.
	 * @param integer x		optional;Distance from left side of screen to position popup
	 * @param integer y		optional;Distnace from top side of screen to position popup
	 * @return	The instance of the Calendar object
	 */
	popup: function popup(x, y){
		if ( !this.initialized ) {
			this.datetime = this.parse();
			this.initialized=true;
		}
		var argstr='', args = {
				datetime: this.get('datetime').getTime(),
				id: this.target.id,
				yscroll: this.get('yearScroll'),
				timereq: this.get('timeRequired')
		};
		for ( var key in args ) {
			argstr+= key+'='+encodeURIComponent(args[key])+'&';
		}
		argstr = argstr.replace(/&$/, ''); /* remove trailing & */
		x = x || 200; y = y || 200;
		this.popWin = window.open(this.calPath+'?'+argstr, 'Calendar', 'width=210,height='+(this.timeRequired ? 228 : 200)+',status=no,resizable=no,top='+y+',left='+x+',dependent=yes,alwaysRaised=yes');
		this.popWin.opener = window;
		this.popWin.focus();
		return this;
	},
	/**
	 * popin - Opens the mini calendar in a pop-in
	 * @param integer x		Left position of calendar
	 * @param integer y		Top position of calendar
	 * @return	The instance of the Calendar object
	 */
	popin: function popin(x, y){
		if ( !this.initialized ) {
			this.datetime = this.parse();
			this.initialized=true;
		}
		var argstr='', args = {
				datetime: this.get('datetime').getTime(),
				id: this.target.id,
				yscroll: this.get('yearScroll'),
				timereq: this.get('timeRequired')
		};
		for ( var key in args ) {
			argstr+= key+'='+encodeURIComponent(args[key])+'&';
		}
		argstr = argstr.replace(/&$/, ''); /* remove trailing & */
		this.calFrame.src = this.calPath+'?'+argstr;
		this.calFrame.style.left = x+'px';
		this.calFrame.style.top = y+'px';
		this.calFrame.style.display = '';
		return this;
	},
	/**
	 * popout - Closes the mini calendar pop-in
	 * @return	The instance of the Calendar object
	 */
	popout: function popout(){
		this.calFrame.style.display = 'none';
		return this;
	},
	/**
	 * updateTarget - Updates the targeted element with the current date (and if required time)
	 * @return	The instance of the Calendar object (or null if not initialized)
	 */
	updateTarget: function updateTarget(){
		if ( !this.initialized ) { return null; }
		if ( typeof(this.onUpdate) == 'function' ) {
			this.onUpdate.call(this, this.target, this.datetime);
		}else{
			this.target.value = this.get('datetime').format(this.targetFormat);
		}
		return this;
	},
	/* Fix the constructor property for all instances */
	constructor:Calendar,
	/* A means to call the static methods from a Calendar object */
	parse: function parse(datetimestr){ return Calendar.parse.call(this, datetimestr || this.target.value); },
	parseDate: function parseDate(datestr){ return Calendar.parseDate.call(this, datestr || this.target.value); },
	parseTime: function parseTime(timestr){ return Calendar.parseTime.call(this, timestr || this.target.value); },
	formatTime: function formatTime(timestr){ return Calendar.formatTime.call(this, timestr || this.target.value); },
	showError: function showError(type, source, data){ return Calendar.showError.apply(this, arguments); }
};
/**
 * parse - Attempts to get a valid time an date from a string
 * @param string datetimestr	The string to parse
 * @return 						Date object with corresponding date and time values
 */
Calendar.parse = function(datetimestr){
	var value = datetimestr || '';
	if ( /^\s*$/i.test(value) ) { return new Date(); } /* test for empty string */ 
	else if ( /^\d+$/.test(value) ) { return new Date(value); } /* if it's all digits treat it as milliseconds since unix epoch */

	var d=null, t=null, tmp = Calendar.displayErrors; /* store old setting */
	Calendar.displayErrors=false; /* disable error messages so we can accept one or the other */
	var d = Calendar.parseDate(value); /* parse the date portion and receive Date object */
	var t = Calendar.parseTime(value); /* parse the time portion and receive Date object */
	if ( d==null && t==null ) { return null; } /* if we got errors on both then return */
	if ( t && d ) {
		/* Copy time values into our 'd' Date object */
		d.setHours(t.getHours(), t.getMinutes(), t.getSeconds(), t.getMilliseconds());
	}
	Calendar.displayErrors=tmp; /* restore error message setting */
	return d || t; /* return date if we have it (will have adjusted time if time was valid), otherwise return time */
};
/**
 * parseDate - Attempts to get a valid date from a string
 * @param string datestr	The string to parse
 * @return					A Date object with the corresponding date (or null on error)
 */
Calendar.parseDate = function(datestr){
	if ( !datestr ) { Calendar.showError('param', 'parseDate', datestr);return null; }
	var parts = null;
	parts = /^([1-9]|0[1-9]|1[0-2])[\/-]([1-9]|0[1-9]|[1-2][0-9]|3[0-1])[\/-](\d{4})/.exec(datestr);
	if ( !parts ) {
		parts = /^(\d{4})[\/-]([1-9]|0[1-9]|1[0-2])[\/-]([1-9]|0[1-9]|[1-2][0-9]|3[0-1])/.exec(datestr);
		parts.shift();
	}else if ( parts ) {
		parts.shift(); /* shift the full match off since we don't need it */
		parts.unshift(parts.pop()); /* stick the year value onto the beginning */
	}
	if ( parts ) {
		return new Date(Date.parse(parts.join('/')));
	}else{
		Calendar.showError('date_format', 'parseDate', datestr); return null;
	}
};
/**
 * parseTime - Attempts to get a valid time from a string
 * @param string timestr	The string to parse
 * @return					A Date object with the corresponding time (or null on error)
 */
Calendar.parseTime = function(timestr){
	if ( !timestr ) { Calendar.showError('param', 'parseTime', timestr);return null; }
	var t = new Date(), parts=null;
	/* match range of 00:00:00 - 23:59:59 (Input format allows single digit hours, optional minutes and seconds, and use of AM/PM)  */
	if ( (parts = /^([0-9]|0[0-9]|[1-2][0-4])(:[0-5][0-9])?(:[0-5][0-9])?\s?([AP]M)?$/i.exec(timestr)) != null ) {
		parts.shift(); /* shift the full match off since we don't need it */
		parts = Calendar.formatTime(timestr).split(':'); /* See formatTime static method */
		t.setHours.apply(t, parts);
		return t;
	}else{
		Calendar.showError('time_format', 'parseTime', timestr); return null;
	}
};
/**
 * formatTime - Transforms a time string with optional minutes/seconds and AM/PM into a 24hour time in the format (hh:mm:ss)
 * @param string timestr	The string to parse and transform
 * @return					A time string in standard 24 hour format with minutes and seconds (hh:mm:ss)
 */
Calendar.formatTime = function(timestr){
	var timeCheck = /(\d{1,2})(:\d{1,2})?(:\d{1,2})?\s?([AP]M)?$/i, parts=null, suffx='';
	if ( (parts= timeCheck.exec(timestr)) != null ) {		/* correct the hour based on AM/PM. If PM 1-11 become 13-23 and 12 stays 12. If AM 1-11 stay the same, 12 becomes 00. */
		parts.shift();
		suffix = parts.pop();
		suffix = (suffix!=null)? suffix.toUpperCase() : (parts[0] >= 12 ? 'PM':'AM'); /* Assume Noon or Morning if not specified */
		parts[0] = pad(suffix=='PM' ? (parts[0] < 12 ? Number(parts[0])+12 : parts[0]) : (parts[0] < 12 ? parts[0] : '00'));
		for ( var i=1;i < 3;i++ ) { parts[i] = pad((parts[i]!=null) ? parts[i].replace(/[:\s]/, '') : '00'); }
		return parts.join(':');
	}
	return '00:00:00';
};

/* Setting causing alerts to be shown or hidden */
Calendar.displayErrors = true;
/* Holds data for the last error trigged */
Calendar.lastError = {type:'', source:null, data:null, message:''};
/**
 * showError - Displays an alert with a message based on the error type
 * @param string type	The type of the error (corresponding to a value in err object)
 * @param string source	The source of the error (ie. function name where error was triggered)
 * @param mixed data	The data that caused the error
 * @return				null
 */
Calendar.showError = function(type, source, data){
	type = type || 'param';
	source = source || 'showError';
	data = (data === undefined) ? 'undefined': (data===null ? 'null' : data);
	var err = {
		error:'Invalid error type {data} sent to {source}',
		member:'You may not set the {data} member variable to {1}',
		target:'Invalid target element: {data}.',
		button:'Invalid button element: {data}.',
		param:"Missing or invalid paramter\nSent: {data} to {source}.",
		date_format:"Invalid date format: {data} supplied to {source}.\nAccepted formats are mm-dd-yyyy and mm/dd/yyyy.",
		date_part:"Invalid date: {data} supplied to {source}.\n{1} cannot be found or is invalid.",
		time_format:"Invalid time format: {data} supplied to {source}.",
		time_part:"Invalid time: {data} supplied to {source}.\n{1} cannot be found or is invalid."
	};
	if ( !(type in err) ) { Calendar.showError('error', 'showError', type); return null; }
	var msg = err[type].replace(/\{data\}/ig, data).replace(/\{source\}/ig, source);
	for ( var i=2;i < arguments.length;i++ ) {
		msg = msg.replace('{'+(i-2)+'}', arguments[i]);
	}
	Calendar.lastError = {type:type, source:source, data:data, message:msg};
	if ( Calendar.displayErrors ) { alert(msg); }
	return null;
};


function updateSubTotal() {
	var total = 0;
	for ( var i = 0; i < deliverySetups.length; i++ ) {
		total += deliverySetups[i].getTotal();
	}	
	did("total").innerHTML = "$" + String(total).split(".")[0] + "." + padString(Math.round(parseInt(String(total).split(".")[1] ? String(total).split(".")[1] : 0)), 2);
}

function DeliverySetup(key, qty, unit_price) {
	
	this.key = key;
	this.qty = qty;
	this.unit_price = unit_price;
	
	this.split_table;
	this.split_index = 0;
	
	this.splits = new Array();
		
	this.init = function() {
		did("qty_" + this.key).innerHTML = "";
		var table = document.createElement("table");
		did("qty_" + this.key).appendChild(table);
		this.split_table = document.createElement("tbody");
		table.appendChild(this.split_table);
		
		this.add(this.qty, 0);
		
		this.updateTotal();
	}
	
	this.add = function(qty, address_id) {
		if ( !qty ) {
			qty = 1;
		}		
		var split = new DeliverySetupSplit(this, this.key, this.split_table, qty, address_id);		
		this.splits[this.splits.length] = split;		
		split.updateQty();	
		this.updateSplitRemoveLinks();
		this.updateTotal();
	}
	
	this.removeSplit = function(split) {
		for ( var i = 0; i < this.splits.length; i++ ) {
			if ( this.splits[i] == split ) {
				this.splits.remove(i);
				break;
			}
		}
		this.updateSplitRemoveLinks();
	}
	
	this.updateSplitRemoveLinks = function() {		
		for ( var i = 0; i < this.splits.length; i++ ) {
			this.splits[i].removeLink.style.display = (this.splits.length == 1 ? 'none' : '');
		}
	}
	
	this.updateQty = function() {
		this.qty = 0;
		for ( var i = 0; i < this.splits.length; i++ ) {
			this.qty += parseInt(this.splits[i].qtyInput.value);
		}
		this.updateTotal();
	}	
	
	this.getTotal = function() {
		return this.unit_price * this.qty;
	}
	
	this.updateTotal = function() {
		var price = this.getTotal();
		did("total_" + this.key).innerHTML = "$" + String(price).split(".")[0] + "." + padString(Math.round(parseInt(String(price).split(".")[1] ? String(price).split(".")[1] : 0)), 2);
		updateSubTotal();
	}
	
	this.init();
	//registerEvent(window, 'load', createDelegate(this, "init"));	
}

function DeliverySetupSplit(parent, key, table, qty, address_id) {
	this.parent = parent;
	this.key = key;
	this.table = table;
	this.qty = qty;	
	this.address_id = address_id;
	this.qtyTD;
	this.qtyInput;	
	this.addresses;	
	
	this.up = function(event) {
		event = fixEvent(event);
		this.qty++;
		this.updateQty();
		event.preventDefault();
		event.stopPropagation();
	}
	
	this.down = function(event) {
		event = fixEvent(event);
		if ( this.qty > 1 ) {
			this.qty--;
		}
		this.updateQty();
		event.preventDefault();
		event.stopPropagation();
	}
	
	this.updateQty = function() {
		this.qtyTD.innerHTML = "";
		this.qtyTD.appendChild(document.createTextNode(this.qty));
		this.qtyInput.value = this.qty;
		this.parent.updateQty();
	}
	
	this.updateAddress = function() {
		this.qtyInput.name = "qty[" + this.addresses.options[this.addresses.selectedIndex].value + "][" + this.key + "][]";
	}
	
	this.remove = function(event) {
		event = fixEvent(event);
		var target = this.qtyInput;
		while ( target.nodeName != "TR" ) {
			target = target.parentNode;
		}
		target.parentNode.removeChild(target);
		
		this.parent.removeSplit(this);
		
		this.parent.updateQty();
		
		event.preventDefault();
		event.stopPropagation();
	}
	
	this.splitup = function(event) {
		event = fixEvent(event);
		this.parent.add();
		this.updateQty();
		event.preventDefault();
		event.stopPropagation();
	}
		
	var tr = document.createElement("tr");
	this.table.appendChild(tr);
	
	this.qtyTD = document.createElement("td");
	tr.appendChild(this.qtyTD);
	this.qtyTD.appendChild(document.createTextNode(this.qty));
	
	var td = document.createElement("td");
	tr.appendChild(td);
	
	this.qtyInput = document.createElement("input");
	this.qtyInput.type = "hidden";
	td.appendChild(this.qtyInput);
	this.qtyInput.value = this.qty;

	var a = document.createElement("a");
	td.appendChild(a);
	a.href = "";
	a.appendChild(document.createTextNode("up"));
	Activa.registerEvent(a, 'click', createDelegate(this, "up"));
	a.className = "up";
	
	var a = document.createElement("a");
	td.appendChild(a);
	a.href = "";
	a.appendChild(document.createTextNode("down"));
	a.className = "down";
	Activa.registerEvent(a, 'click', createDelegate(this, "down"));
	
	var td = document.createElement("td");
	tr.appendChild(td);
	
	this.addresses = document.createElement("select");
	td.appendChild(this.addresses);
	
	for ( var i = 0; i < did("addresses").options.length; i++ ) {
		var option = document.createElement("option");
		option.text = did("addresses").options[i].text;
		option.value = did("addresses").options[i].value;
		this.addresses.options[i] = option;
		if ( this.address_id == did("addresses").options[i].value ) {
			option.selected = true;
		}
	}
	
	if ( this.addresses.selectedIndex == -1 ) {
		this.addresses.selectedIndex = 0;
	}
	
	Activa.registerEvent(this.addresses, 'allchange', createDelegate(this, "updateAddress"));
	this.updateAddress();
	
	var td = document.createElement("td");
	tr.appendChild(td);
	
	var a = document.createElement("a");
	td.appendChild(a);
	a.href = "";
	a.appendChild(document.createTextNode("Split-Up Delivery"));
	Activa.registerEvent(a, 'click', createDelegate(this, "splitup"));
	
	var td = document.createElement("td");
	tr.appendChild(td);
	
	this.removeLink = document.createElement("a");
	td.appendChild(this.removeLink);
	this.removeLink.appendChild(document.createTextNode("(remove)"));
	this.removeLink.className = "remove";
	if ( this.parent.splits.length == 1 ) {
		this.removeLink.style.display = "none";
	}
	Activa.registerEvent(this.removeLink, 'click', createDelegate(this, "remove"));
		
	this.updateAddress();
}


/**
 * LightBox - Builds and manages a set of elements that makeup a lightbox
 * options:
 * box					mixed		Element id or object that holds the content
 * overlay				mixed		Element id or object that acts as the overlay
 * closeButton			mixed		Element id or object that acts as a close button
 * boxClass				string		CSS class that can be added to the box
 * overlayClass			string		CSS class that can be added to the overlay
 * closeClass			string		CSS class that can be added to the close button (default: lbClose)
 * imgClass				string		CSS class used to identify images to preload
 * contentClass			string		CSS class used for content wrapper (default: lbContentWrapper)
 * frameClass			string		CSS class used for iframe wrapper (default: lbContentFrame)
 * autoRegisterEvents	boolean		Determines if click events for displaying images should be automatically created
 * makeCloseButton		boolean		Determines if a close button is created if one is not specified
 * hideOverlay			boolean		Prevents overlay from displaying when the lightbox is shown
 * onshow				function	Function to execute when the lightbox is shown
 * onhide				function	Function to execute when the lightbox is hidden
 * pinTop				boolean		Determines if the lightbox should pin it's top position in place (default: false)
 * pinLeft				boolean		Determines if the lightbox should pin it's left position in place (default: false)
 * autopin				boolean		Determines if the lightbox will automatically pin itself when it is larger than the viewable area. (default: true)
 *
 * @param	options		object		optional;Object specifying options
 * @return				LightBox	New LightBox instance
 */
var LightBox = new Activa.Class({
	_isShowing: false,
	_initialized: false,
	_isIE: false,
	_images: {},
	box: null,
	overlay: null,
	closeButton: null,
	options: {
		box: null,
		overlay: null,
		closeButton: null,
		boxClass: '',
		overlayClass: '',
		closeClass: 'lbClose',
		imgClass: '',
		contentClass: 'lbContentWrapper',
		frameClass: 'lbContentFrame',
		makeCloseButton: true,
		hideOverlay: false,
		autoRegisterEvents: true,
		pinTop: false,
		pinLeft: false,
		autopin: true
	},
	events: {
		onshow: null,
		onhide: null
	},
	init: function initLightBox(options) {
		options = options || {};
		this.setOptions(options);
		Activa.DOM.ready(function initialize() {
			this._isIE = window.ActiveXObject ? (window.XMLHttpRequest ? 7 : 6) : false;
			
			function isString(str) {
				return str && (typeof str == 'string' || (typeof str == 'object' && typeof str.valueOf() == 'string'));
			}
			
			var opts = this.options;
			var docbody = dbt('body')[0];
			
			//Get or create the main box
			if ( opts.box && isString(opts.box) ) {
				this.box = did(opts.box);
			} else if ( opts.box && typeof opts.box == 'object' ) {
				this.box = opts.box;
			}
			if ( !this.box ) {
				this.box = document.createElement('div');
				if ( opts.box ) {
					opts.box.id = opts.box;
				}
				docbody.insertBefore(this.box, Activa.DOM.first(docbody));
			}
			Activa.DOM.addClass(this.box, opts.boxClass);
			this.box.style.display = 'none';
			
			//Get or create the close button
			if ( opts.closeButton && isString(opts.closeButton) ) {
				this.closeButton = did(opts.closeButton);
			} else {
				this.closeButton = opts.closeButton;
			}
			if ( !this.closeButton && this.options.makeCloseButton ) {
				this.closeButton = document.createElement('a');
				if ( opts.closeButton ) {
					this.closeButton.id = opts.closeButton;
				}
				this.closeButton.innerHTML = 'Close X';
				this.box.appendChild(this.closeButton);
			}
			if ( this.closeButton ) {
				Activa.DOM.addClass(this.closeButton, this.options.closeClass);
			}
			
			//Get or create the overlay
			if ( opts.overlay && isString(opts.overlay) ) {
				this.overlay = did(opts.overlay);
			} else if ( opts.overlay && typeof opts.overlay == 'object' ) {
				this.overlay = opts.overlay;
			}
			if ( !this.overlay ) {
				this.overlay = document.createElement('div');
				if ( opts.overlay ) {
					this.overlay.id = opts.overlay;
				}
				docbody.insertBefore(this.overlay, this.box);
			}
			Activa.DOM.addClass(this.overlay, this.options.overlayClass);
			this.overlay.style.display = 'none';
			
			//Create the iframe shim to hide controls if we're using IE
			if ( this._isIE ) {
				this._shim = document.createElement('iframe');
				var overz = this.getStyle(this.overlay, 'z-index') - 1;
				this._shim.style.cssText = 'display: none;position: absolute;top: 0px;left: 0px;filter:progid:DXImageTransform.Microsoft.alpha(opacity=0);z-index:'+overz;
				this._shim.frameBorder = 0;
				this._shim.scrolling = 'no';
				this._shim.src = dbt('base')[0].href + 'blankpage';
				docbody.insertBefore(this._shim, this.overlay);
			}
			
			//Register necessary events
			Activa.registerEvent(window, 'resize', this._createCaller(this, this._resize));
			Activa.registerEvent(window, 'scroll', this._createCaller(this, this._resize));
			Activa.registerEvent(this.overlay, 'click', this._createCaller(this, this.hide));
			if ( this.closeButton ) {
				Activa.registerEvent(this.closeButton, 'click', this._createCaller(this, this.hide));
			}
			
			//Register lightbox custom events
			for ( var eName in this.events ) {
				if ( this.events.hasOwnProperty(eName) && typeof options[eName] == 'function' ) {
					this.events[eName] = options[eName];
					this.listen(eName, this.events[eName]);
				}
			}
			
			//Preload images.
			this.preloadImages();
			this.initialized = true;
		}, this);
	},
	setOptions: function setOptions(options) {
		options = options || {};
		for ( var k in this.options ) {
			if ( k in options ) {
				if ( /Class/i.test(k) ) {
					options[k] = String(options[k]).trim();
				}
				this.options[k] = options[k];
			}
		}
	},
	showHTML: function showHTML(html) {
		if ( !this.initialized ) {
			return false;
		}
		this._createWrapper(html);
		this.show();
		return this;
	},
	showDOM: function showDOM(node, clone) {
		if ( !this.initialized ) {
			return false;
		}
		this._createWrapper();
		clone = clone == null ? true : clone;
		if ( node && node.nodeType == 1 && !Activa.DOM.contains(this.content, node) ) {
			if ( clone ) {
				var nodeClone = node.cloneNode(true);
				this.content.appendChild(nodeClone);
			} else {
				this.content.appendChild(node);
			}
		}
		this.show();
		this._resize();
		return this;
	},
	showIMG: function showIMG(src, width, height, alt) {
		if ( !this.initialized ) {
			return false;
		}
		this._createWrapper('<div>Loading Image...</div>');
		
		this.preloadImage(src, function displayImage(e) {
			var img = e.target;
			var attributes = {'width':(width || img.width), 'height':(height || img.height), 'alt':(alt || '')};
			for ( var atr in attributes ) {
				if ( attributes.hasOwnProperty(atr) ) {
					img.setAttribute(atr, attributes[atr]);
				}
			}
			this.content.innerHTML = '';
			this.showDOM(img, false);
		});
		return this;
	},
	showURL: function showURL(url) {
		if ( !this.initialized ){
			return false;
		}
		if ( this.content ){
			this.content.style.display = 'none';
		}
		if ( !this.frame ) {
			this.options.frameClass = this.options.frameClass || 'lbContentFrame';
			this.frame = document.createElement('iframe');
			this.frame.className = this.options.frameClass;
			this.frame.frameBorder = 0;
			this.box.appendChild(this.frame);
		}
		
		this.frame.src = url;
		this.frame.style.display = '';
		this.show();
		return this;
	},
	clear: function clear() {
		this.content.innerHTML = '';
		return this;
	},
	show: function show() {
		this._resize();
		this.box.style.display = '';
		if ( !this.options.hideOverlay ) {
			this.overlay.style.display = '';
		}
		if ( this._shim ) {
			this._shim.style.display = '';
		}
		if(!this._isShowing) {
			this._triggerEvent(this.box, 'focus', false, false);
		}
		this._isShowing = true;
		return this;
	},
	hide: function hide() {
		this.box.style.display = 'none';
		this.overlay.style.display = 'none';
		if ( this._shim ) {
			this._shim.style.display = 'none';
		}
		if ( this._isShowing ) {
			this._triggerEvent(this.box, 'blur', false, false);
		}
		this._isShowing = false;
		return this;
	},
	toggle: function toggle() {
		var fn = this._isShowing ? 'hide':'show';
		this[fn]();
	},
	pin: function pin() {
		var val = Activa.toArray(arguments).shift();
		switch(val) {
			case 'top':
				this.options.pinTop = true;
				break;
			case 'left':
				this.options.pinLeft = true;
				break;
			default:
				this.options.pinTop = true;
				this.options.pinLeft = true;
		}
	},
	unpin: function unpin() {
		var val = Activa.toArray(arguments).shift();
		switch(val) {
			case 'top':
				this.options.pinTop = false;
				break;
			case 'left':
				this.options.pinLeft = false;
				break;
			default:
				this.options.pinTop = false;
				this.options.pinLeft = false;
		}
	},
	preloadImages: function preloadImages(className) {
		className = (className ? String(className) : this.options.imgClass).trim();
		if(className == '') {
			return false;
		}
		var nodes = Activa.toArray(dbc(className));
		nodes.forEach(this.preloadImage, this);
	},
	preloadImage: function preloadImage(src, fn) {
		var node;
		var isobj = (typeof ((this._isIE) ? src : src.valueOf()) == 'object');
		if ( isobj && src.nodeType == 1 ) {
			node = src;
			var tag = node.tagName.toLowerCase();
			if ( tag == 'a' && node.href != '' ) {
				src = node.href;
			} else if ( tag == 'img' && node.src != '' ) {
				src = node.src;
			}
		}
		src = (src ? String(src) : '').trim();
		var fname = src.replace(/^(?:https?:\/\/|(\/))?(.*)$/i, '$1$2');
		if(!src || !fname) {
			return null;
		}
		
		if ( !this._images[fname] ) {
			this._images[fname] = new Image();
			if ( fn && typeof fn == 'function' ) {
				var self = this;
				Activa.registerEvent(this._images[fname], 'load', function imgLoad(e){
					if ( !e.target ) {
						e = {target: this._images[fname]};
					}
					Activa.unregisterEvent(e.target, 'load', arguments.callee);
					fn.call(this, e);
				}.bind(this));
			}
			this._images[fname].src = src;
		} else if ( typeof fn == 'function' ) {
			fn.call(this, {target: this._images[fname]});
		}
		if ( this.options.autoRegisterEvents && node ) {
			Activa.registerEvent(node, 'click', this._createCaller(this, this.showIMG, true, fname, '', '', ''));
		}
		return (fname in this._images) ? this._images[fname] : null;
	},
	_createWrapper: function _createWrapper(html) {
		if ( !this.initialized ){
			return false;
		}
		if ( this.frame ){ this.frame.style.display = 'none'; }
		if ( !this.content ) {
			this.options.contentClass = this.options.contentClass || 'lbContentWrapper';
			this.content = document.createElement('div');
			this.content.className = this.options.contentClass;
			this.box.appendChild(this.content);
		}
		html = html || '';
		if ( html != '' ) {
			this.content.innerHTML = String(html);
		}
		this.content.style.display = '';
	},
	_resize: function _resize() {
		var max = Activa.Dimensions.getMaxDocSize();
		if ( !this.options.hideOverlay ) {
			['overlay','shim'].forEach(function(item){
				if(this[item]) {
					this[item].style.width = max['width']+'px';
					this[item].style.height = max['height']+'px';
				}
			}, this);
		}
		
		var docsize = Activa.Dimensions.getDocSize(), scroll = Activa.Dimensions.getScrollXY();
		if ( !this._isShowing ) {
			this.box.style.visibility = 'hidden';
			this.box.style.display = '';
		}
		//var boxWidth = this._isIE ? this.box.offsetWidth : getStyle(this.box,'width')
		//var boxHeight = this._isIE ? this.box.offsetHeight : getStyle(this.box,'height');
		var boxWidth = this.box.offsetWidth;
		var boxHeight = this.box.offsetHeight;
		if ( !this._isShowing ) {
			this.box.style.display = 'none';
			this.box.style.visibility = 'visible';
		}
		boxWidth = isNaN(boxWidth) ? 0 : boxWidth;
		boxHeight = isNaN(boxHeight) ? 0 : boxHeight;
		var topleft = {
			'x':Math.round((docsize['width']-boxWidth)/2)+scroll['x'],
			'y':Math.round((docsize['height']-boxHeight)/2)+scroll['y']
		};
		if ( boxWidth > docsize['width'] ) {
			topleft.x = 0;
		}
		var pintop = this.options.autopin ? (boxHeight > docsize['height']) : false;
		var pinleft = this.options.autopin ? (boxWidth > docsize['width']) : false;
		this._setPosition(topleft.x, topleft.y, pinleft, pintop);
		return this;		 
	},
	_setPosition: function _setPosition(x, y, pinx, piny) {
		var pin = false;
		if ( this.options.autopin && !(pinx && piny) ) {
			pin = (pinx ? 'top' : (piny ? 'left' : false));
			this.unpin(pin);
		}
		x = (x || 0) < 0 ? 0 : x;
		y = (y || 0) < 0 ? 0 : y; 
		if ( !this.options.pinLeft && x != 'auto' ) {
			this.box.style.left = parseInt(x)+'px';
		}
		if ( !this.options.pinTop && y != 'auto' ) {
			this.box.style.top = parseInt(y)+'px';
		}
		if ( this.options.autopin && (pinx || piny) ) {
			pin = (pinx && piny) ? true : (pinx ? 'left' : (piny ? 'top': false));
			this.pin(pin);
		}
		if ( this.options.hideOverlay && this._shim ) {
			this._shim.style.position = 'absolute';
			['width','height','left','top'].forEach(function(prop) {
				var value = this.box.style[prop];
				if ( !value ) {
					value = this.getStyle(this.box, prop);
				}
				this._shim.style[prop] = value;
			}, this);
		}
	},
	getStyle: function getStyle(obj, prop) {
		obj = (typeof obj == 'object') ? obj : did(obj);
		if ( !obj || !prop ) {
			return null;
		}
		var compVal = obj.currentStyle ? obj.currentStyle[prop] : window.getComputedStyle(obj,null).getPropertyValue(prop);
		return isNaN(parseFloat(compVal)) ? compVal : parseFloat(compVal);
	},
	listen: function listen(event, fn) {
		if ( typeof fn != 'function' ) {
			return false;
		}
		var eventWrapper = (function eventWrapper(func, stopDefault, stopBubble) {
			return (function wrapping(e) {
				if(stopDefault) {
					e.preventDefault();
				}
				if(stopBubble) {
					e.stopPropagation();
				}
				func.call(this, e);
			}).bind(this);
		}).bind(this);
		switch(event) {
			case 'onshow':
				Activa.registerEvent(this.box, 'focus', eventWrapper(fn, true, true));
				break;
			case 'onhide':
				Activa.registerEvent(this.box, 'blur', eventWrapper(fn, true, true));
				break;
			default:
				break;
		}
	},
	_createCaller: function _createCaller(obj, fn, stopDefault){
		var args = Activa.toArray(arguments, 3);
		return function callMethod(e) {
			if ( stopDefault ) {
				e.preventDefault();
			}
			fn.apply(obj, args.concat([e]));
		}
	},
	_triggerEvent: function _triggerEvent(el, type, bubbles, cancelable) {
		try {
			el = Activa.DOM.check(el);
			bubbles = bubbles || true;
			cancelable = cancelable || true;
			if ( document.createEvent  ) {
				var groups = {
					UIEvents: ['focusin', 'focusout', 'activate', 'deactivate'],
					MouseEvents: ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove'],
					HTMLEvents: ['load', 'unload', 'abort', 'error', 'select', 'change', 'submit', 'reset', 'focus', 'blur', 'resize', 'scroll']
				};
				var groupName = null;
				for ( var group in groups ) {
					if ( groups[group].indexOf(type) != -1 ) {
						groupName = group;
						break;
					}
				}
				if ( groupName ) {
					var event = document.createEvent(groupName);
					event.initEvent(type, bubbles, cancelable);
					//safari 3 doesn't have window.dispatchEvent()
					(el == window && !el.dispatchEvent ? document : el).dispatchEvent(event);
					return true;
				}
			} else if ( document.createEventObject ) {
				var event = document.createEventObject();
				// IE6,IE7 thinks window==document and doesn't have window.fireEvent()
				// IE6,IE7 cannot properly call document.fireEvent()
				(el == document ? document.documentElement : el).fireEvent("on"+type, event);
				return true;
			}
		} catch ( e ) {
		}
		return false;
	}
});

function array_merge () {
    // Merges elements from passed arrays into one array  
    // 
    // version: 1004.2314
    // discuss at: http://phpjs.org/functions/array_merge
    // +   original by: Brett Zamir (http://brett-zamir.me)
    // +   bugfixed by: Nate
    // +   input by: josh
    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
    // *     example 1: arr1 = {"color": "red", 0: 2, 1: 4}
    // *     example 1: arr2 = {0: "a", 1: "b", "color": "green", "shape": "trapezoid", 2: 4}
    // *     example 1: array_merge(arr1, arr2)
    // *     returns 1: {"color": "green", 0: 2, 1: 4, 2: "a", 3: "b", "shape": "trapezoid", 4: 4}
    // *     example 2: arr1 = []
    // *     example 2: arr2 = {1: "data"}
    // *     example 2: array_merge(arr1, arr2)
    // *     returns 2: {0: "data"}
    
    var args = Array.prototype.slice.call(arguments),
                            retObj = {}, k, j = 0, i = 0, retArr = true;
    
    for (i=0; i < args.length; i++) {
        if (!(args[i] instanceof Array)) {
            retArr=false;
            break;
        }
    }
    
    if (retArr) {
        retArr = [];
        for (i=0; i < args.length; i++) {
            retArr = retArr.concat(args[i]);
        }
        return retArr;
    }
    var ct = 0;
    
    for (i=0, ct=0; i < args.length; i++) {
        if (args[i] instanceof Array) {
            for (j=0; j < args[i].length; j++) {
                retObj[ct++] = args[i][j];
            }
        } else {
            for (k in args[i]) {
                if (args[i].hasOwnProperty(k)) {
                    if (parseInt(k, 10)+'' === k) {
                        retObj[ct++] = args[i][k];
                    } else {
                        retObj[k] = args[i][k];
                    }
                }
            }
        }
    }
    return retObj;
}



