
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;
	}
	async_rpc(url, updateCheckoutModifiers, null);
}

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

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];
	
		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 ) {
					showID('collect-shipping_' + parcel_index);
					did("shipping_total_" + parcel_index).innerHTML = 'TBD';
				} else {
					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 sub_total';
				
				var td = document.createElement('td');
				td.className = '';
				td.colSpan = '3';
				td.appendChild(document.createTextNode(obj.parcels[parcel_index].modifiers[i].name));
				tr.appendChild(td);
				
				var td = document.createElement('td');
				td.className = '' + (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;
		
		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 sub_total';
		
		var td = document.createElement('td');
		td.className = '';
		td.colSpan = '3';
		td.appendChild(document.createTextNode(obj.modifiers[i].name));
		tr.appendChild(td);
		
		var td = document.createElement('td');
		td.className = '' + (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;
		showID('promo_code_error');
	} else {
		did('promo_code_error').innerHTML = 'fds';
		hideID('promo_code_error');
	}
	
	did("grand_total").innerHTML = "$" + obj.grand_total;
}


/**
 * Array and object functions
 */

/**
 * Array methods indexOf, forEach, map, filter
 * 		Added for browsers without native support
 */
if(!Array.indexOf){
	Array.prototype.indexOf = function(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;
	}
}
if(!Array.forEach){
	Array.prototype.forEach = function(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); }
		}
	};
}
if(!Array.map){
	Array.prototype.map = function(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;
	};
}
if(!Array.filter){
	Array.prototype.filter = function(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;
	};
}

if ( !Array.every ) {
	Array.prototype.every = function(fn /*, bind*/){
		return this.filter.apply(this,slice(arguments)).length === this.length;
	};
}
if ( !Array.some ) {
	Array.prototype.some = function(fn /*, bind*/){
		return this.filter.apply(this,slice(arguments)).length >0;
	};
}

/**
 * slice - 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 = slice(arguments); alert(args instanceof Array); }
 */
function slice(obj, start, end){
	var ret = obj;
	if(window.ActiveXObject){
		if(typeof obj.length=='undefined'){ obj.length = getLength(obj); }
		ret = Array.prototype.map.call(obj,function(item){return item;});
	}
	var args = [(start || 0)];
	if(end && !isNaN(Number(end))) {
		args.push(Number(end));
	}
	return Array.prototype.slice.apply(ret,args);
}

function isIE(){
	if(window.ActiveXObject){
		return (window.JSON) ? 8 : (window.XMLHttpRequest) ? 7 : 6;
	}
	return false;
}

function hasClass(elm,className) {
	if(!elm || className.trim() == ''){ return false; }
	return (String(elm.className).split(' ').indexOf(className) != -1);
}

function addClass(elm,className){
	var classes = elm.className.split(" ");
	if(classes.indexOf(className)==-1){
		classes.push(className);
	}
	elm.className=classes.join(" ");
}

function removeClass(elm,className){
	var classes = elm.className.split(" ");
	for (var i = classes.length; i--; i){
		if (classes[i] === className) {
			classes.splice(i, 1);
		}
	}
	elm.className=classes.join(" ");
}

function setOpacity(elm,amount){
	if(amount == 0){
		elm.style.visibility = 'hidden';
	}else{
		elm.style.visibility = '';
	}
	if(isIE()!==false){
		elm.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity = '+Math.round(amount*100)+')';
		elm.style.zoom = '1';
	}else{
		elm.style.opacity = amount;
	}
}

function getOpacity(elm){
	var opacity = Number(elm.style.opacity)
	if(!isNaN(opacity)){
		return opacity;
	}
	if(elm.style.filter!=''){
		var matches = elm.style.filter.match(/opacity\s?=\s?(\d+)/i);
		return matches.length > 1 ? Number(matches[1])/100 : false;
	}
	return false;
}

/**
* DOM Event Functions & Objects
*/
/* domLoader - detects when the DOM is ready in the browser (typically before onload would fire).
* Also allows registration of functions to execute once 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']); 
*/
var domLoader = {
	isReady: false,
	isBound: false,
	queue: [],
	binds: [],
	args: [],
	/**
	 * register - Register a function to be executed when the dom is ready
	 * @param	fn		function	Function to execute
	 * @param	obj		object		optional;Object that will be the scope for fn(keyword 'this' inside function, default is window)
	 * @param	args	array		optional;Array of parameters to be passed to the function upon execution
	 */
	register: function register(fn, obj, args) {
		this.checkReady();
		this.queue = (this.queue instanceof Array) ? this.queue : [];
		if ( !fn || typeof fn != 'function' ) {
			return;
		}
		
		var offset = this.queue.push(fn) - 1;
		
		obj = obj || null;
		this.binds = (this.binds instanceof Array) ? this.binds : [];
		this.binds[offset] = obj;
		
		args = args ? ((args instanceof Array) ? args :  slice(args)) : [];
		this.args = (this.args instanceof Array) ? this.args : [];
		this.args[offset] = args;
		if ( this.isReady ) {
			this.ready();
		}
		return this;
	},
	/**
	 * checkReady - Starts checking the for the dom to be ready (internal use)
	 */
	checkReady: function checkReady() {
		if ( this.isBound ) {
			return;
		}
		this.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 ( self.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
						self.isReady = true;
						self.ready();
					} 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 ( self.isReady ) {
						return;
					}
					self.isReady = true;
					self.ready();
				}
			}, 10);
		}
		var safariReadyTest = null;
		for ( var name in events ) {
			if ( !events.hasOwnProperty(name) ) {
				continue;
			}
			(function scopeFix(name, obj) {
				registerEvent(obj, name, function pageLoadEventWrapper() {
					unregisterEvent(obj, name, arguments.callee);
					if ( !self.isReady ) {
						self.isReady = true;
						self.ready();
					}
				});
				var pageLoadEventWrapper = null;
			})(name, events[name]);
		}
		var varprotector = null;
	},
	/**
	 * ready - Executes all registered functions with any bound scope & args (internal use)
	 */
	ready: function ready(){
		if ( !this.isReady || !(this.queue instanceof Array) ) {
			return;
		}
		var fn, obj, args;
		while(fn = this.queue.shift()) {
			obj = this.binds.shift() || window;
			args = this.args.shift();
			fn.apply(obj, args);
		}
		
		this.queue = [];
		this.binds = [];
		this.args = [];
	}
};
/**
 * 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;
}

/**
 * DOM Utilities
 */
/**
 * did - Shorcut for getElementById
 * @param	id		The element id you are looking for
 * @param	parent	optional;The parent item to search within
 * @return			Element with matching id
 */
function did(id, parent) {
	return (parent || 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) {
	//use default provided by browser if it exists otherwise use our implementation
	if(document.getElementsByClassName) {
		return (parent || document).getElementsByClassName(className);
	}
	var nodes = slice(dbt('*', parent)), elms = [];
	nodes.forEach(function(node){
		if(hasClass(node, className)) {
			elms.push(node);
		}
	});
	return elms;
}
/**
 * next - Returns the next non-whitespace sibling element
 * @param	el	object	Element node from which to start
 * @return		object	Next non-whitespace child element
 */
function next(el) {
	if(!el || !el.nextSibling) { return null; }
	el=el.nextSibling;
	return (el.nodeType==1) ? el : next(el);
}
/**
 * prev - Returns the previous non-whitespace sibling element
 * @param	el	object	Element node from which to start
 * @return		object	Previous non-whitespace child element
 */
function prev(el) {
	if(!el || !el.previousSibling) { return null; }
	el=el.previousSibling;
	return (el.nodeType==1) ? el : prev(el);
}
/**
 * first - Returns the first non-whitespace child element
 * @param	el	object	Parent element node
 * @return		object	First non-whitespace child element
 */
function first(el) {
	if(!el || !el.firstChild) { return null; }
	el=el.firstChild;
	return (el.nodeType==1) ? el : next(el);
}
/**
 * last - Returns the last non-whitespace child element
 * @param	el	object	Parent element node
 * @return		object	Last non-whitespace child element
 */
function last(el) {
	if(!el || !el.lastChild) { return null; }
	el=el.lastChild;
	return (el.nodeType==1) ? el : prev(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
 */
function owner(el){
	if(!el || !el.parentNode){ return null; }
	el = el.parentNode;
	return (el.nodeType == 1) ? el : 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
 */
function contains(el, node){
  return el.contains ?
    el != node && el.contains(node) :
    !!(el.compareDocumentPosition(node) & 16);
}
/**
 * createElement - Shortcut for document.createElement
 * @param	tag		Tag of the element to create
 * @return			Element or null if no tag specified
 */
function createElement(tag) {
	if (!tag) { return null; }
	return document.createElement(tag);
}
/**
 * createTextNode - Shortcut for document.createTextNode
 * @param	text	Text to place inside the new node
 * @return			New text node
 */
function createTextNode(text) {
	return document.createTextNode(String(text));
}
/**
 * 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)
 */
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)
 */
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;
}
/**
 * getDocSize - Get the size of the current viewable document area
 * @return	object		The width, height of the current viewable document area in a keyed object ( obj.width obj.height )
 */
function getDocSize() {
	var w = 0, h = 0;
	if(typeof(window.innerWidth) == 'number'){
		//Non-IE
		w = window.innerWidth;
		h = window.innerHeight;
		var sbar = getScrollbarWidth();
		if(window.scrollMaxY > 0){ w -= sbar['right']; }
		if(window.scrollMaxX > 0){ h -= sbar['bottom']; }
	} else {
		if(document.compatMode == 'CSS1Compat') {
			w = document.documentElement.clientWidth;
			h = document.documentElement.clientHeight;
		} else {
			w = document.body.clientWidth;
			h = document.body.clientHeight;
		}
	}
	return {'width':w,'height':h};
}

/**
 * getMaxDocSize - Get the total document size
 * @return	object		The width, height of the total document size in a keyed object ( obj.width obj.height )
 */
function getMaxDocSize() {
	var w = 0, h = 0;
	if(window.innerHeight && typeof window.scrollMaxY == 'number') {
		w = window.innerWidth + window.scrollMaxX;
		h = window.innerHeight + window.scrollMaxY;
		var sbar = getScrollbarWidth();
		if(window.scrollMaxY > 0){ w -= sbar['right']; }
		if(window.scrollMaxX > 0){ h -= sbar['bottom']; }
	} else {
		var scroll = getScrollXY();
		if(document.compatMode == 'CSS1Compat') {
			w = document.documentElement.clientWidth+scroll.x;
			h = document.documentElement.clientHeight+scroll.y;
		} else {
			w = document.body.clientWidth+scroll.x;
			h = document.body.clientHeight+scroll.y;
		}
	}
	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 )
 */
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 )
 */
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};
}

/**
 * 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, slice(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, slice(arguments)).length > 0;
		};
	}
	var some = null;
	return fn;
})();


/**
 * slice - 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 = slice(arguments); alert(args instanceof Array); }
 */
function slice(obj, start, end){
	var ret = obj;
	if(window.ActiveXObject){
		if(typeof obj.length=='undefined'){ obj.length = getLength(obj); }
		ret = Array.prototype.map.call(obj,function(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
 */
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;
}

/**
 * DOM Event Functions & Objects
 */

/* domLoader - detects when the DOM is ready in the browser (typically before onload would fire).
 * Also allows registration of functions to execute once 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']); 
 */
var domLoader = {
	isReady: false,
	isBound: false,
	queue: [],
	binds: [],
	args: [],
	/**
	 * register - Register a function to be executed when the dom is ready
	 * @param	fn		function	Function to execute
	 * @param	obj		object		optional;Object that will be the scope for fn(keyword 'this' inside function, default is window)
	 * @param	args	array		optional;Array of parameters to be passed to the function upon execution
	 */
	register: function register(fn, obj, args) {
		this.checkReady();
		this.queue = (this.queue instanceof Array) ? this.queue : [];
		if(!fn || typeof fn != 'function') {
			return;
		}
		
		var offset = this.queue.push(fn) - 1;
		
		obj = obj || null;
		this.binds = (this.binds instanceof Array) ? this.binds : [];
		this.binds[offset] = obj;
		
		args = args ? ((args instanceof Array) ? args :  slice(args)) : [];
		this.args = (this.args instanceof Array) ? this.args : [];
		this.args[offset] = args;
		if(this.isReady) {
			this.ready();
		}
		return this;
	},
	/**
	 * checkReady - Starts checking the for the dom to be ready (internal use)
	 */
	checkReady: function checkReady() {
		if(this.isBound) {
			return;
		}
		this.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(self.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
						self.isReady = true;
						self.ready();
					} 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(self.isReady) {
						return;
					}
					self.isReady = true;
					self.ready();
				}
			}, 10);
		}
		var safariReadyTest = null;
		for(var name in events) {
			if(!events.hasOwnProperty(name)) {
				continue;
			}
			(function scopeFix(name, obj) {
				registerEvent(obj, name, function pageLoadEventWrapper() {
					unregisterEvent(obj, name, arguments.callee);
					if(!self.isReady) {
						self.isReady = true;
						self.ready();
					}
				});
				var pageLoadEventWrapper = null;
			})(name, events[name]);
		}
		var varprotector = null;
	},
	/**
	 * ready - Executes all registered functions with any bound scope & args (internal use)
	 */
	ready: function ready(){
		if(!this.isReady || !(this.queue instanceof Array)) {
			return;
		}
		var fn, obj, args;
		while(fn = this.queue.shift()) {
			obj = this.binds.shift() || window;
			args = this.args.shift();
			fn.apply(obj, args);
		}
		
		this.queue = [];
		this.binds = [];
		this.args = [];
	}
};

/**
 * 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;
}