/*

unbad object library Copyright © 2006-2008 - Sean Claflin

unbad object library is licensed under the Creative Commons Attribution 3.0
United States License - http://creativecommons.org/licenses/by/3.0/us/

*/

/**
 * @fileoverview This file contains the base unbad object (ub)
 * @author <a href="mailto:seanclaflin@gmail.com">Sean Claflin</a>
 * @version 0.5
*/

/**
 * Global unbad namespace
 * @namespace
 */
(ub = {
	/** @private */
	init: function() {
		/** attach to the window load event */
		this.e.attach(window, 'load', function(e) {
			//fire our internal load event
			this.onload(e);
		}, false, this);
		/** attach to the window unload event */
		this.e.attach(window, 'unload', function(e) {
			//fire our internal unload
			this.onunload(e);
			//clean up our mess
			this.destruct();
		}, false, this);
		/**
		 * Convenience mapping
		 * @see ub.u.$
		 * @function
		 */
		this.$ = this.u.$;
		/**
		 * Convenience mapping
		 * @see ub.u.$c
		 * @function
		 */
		this.$c = this.u.$c;
	},
	/** @private */
	destruct: function() {
		//run through the event stack backwards, detaching each event
		this.a.each(this.e.stack, function(event) {
			event.detach();
		}, this, true);
	},
	/**
	 * Helper function to make sure required extensions are loaded
	 * @param {String} req The name of the required plugin (i.e. - fx, mouse, json, widget, etc)
	 * @returns {Boolean}
	 */
	require: function(req) {
		if(typeof(req) == 'string') req = [req];
		ub.a.each(req, function(name) {
			if(!this[name])
				throw new Error(name +' has not been loaded.');
		}, this);
		return true;
	},
	/**
	 * Array helper functions
	 * @namespace
	 */
	a: {
		/**
		 * Array iterator function that passes each array element and index to a function
		 * @param {Array} array The array to iterate through
		 * @param {Function} func The function called with each iteration, it is passed the current element in the iteration and the current index of that element
		 * @param {Object} [scope] The scope the function should be called in, defaults to the scope of the array
		 * @param {Boolean} [reverse] Flag to set if the iterator should run backwards through the array
		 * @example
		 * ub.a.each(
		 *   ['foo', 'bar'],
		 *   function(val, i) { alert(i +' = '+ val); }
		 * );
		 */
		each: function(array, func, scope, reverse) {
			if(!scope) scope = array;
			if(reverse) for(var i=array.length-1;o=array[i];i--) func.call(scope, o, i);
			else for(var i=0;o=array[i];i++) func.call(scope, o, i);
		},
		/**
		 * Returns the index of where element occurs in array, -1 on fail
		 * @returns {Integer}
		 * @param {Array} array The array to search in (haystack)
		 * @param {Object} el The object to search for (needle)
		 * @example
		 * ub.a.indexOf(['foo', 'bar'], 'bar'); //returns 1
		 * ub.a.indexOf(['foo', 'bar'], 'yebbafleb'); //returns -1
		 */
		indexOf: function(array, el) {
			var res = -1;
			for(var i=0;i<array.length;i++)
				if (array[i] == el) { res = i; break; }
			return res;
		},
		/**
		 * Returns whether or not element exists in array
		 * @returns {Boolean}
		 * @param {Array} array The array to seach in (haystack)
		 * @param {Object} el The element to search for (needle)
		 * @example
		 * ub.a.contains(['foo', 'bar'], 'foo'); //returns true
		 * ub.a.contains(['foo', 'bar'], 'yebbafleb']); //returns false
		 */
		contains: function(array, el) {
			return (ub.a.indexOf(array, el) >= 0);
		},
		/**
		 * Safely appends a new element to the end of an array and returns the element parameter
		 * @returns {Element}
		 * @param {Array} array The array to append an element to
		 * @param {Object} el The element to be appended
		 * @param {Boolean} [noDupe] Flag to keep duplicate elements from being added
		 * @example
		 * var testArray = ['foo', 'bar'];
		 * ub.a.append(testArray, 'yebbafleb'); //returns 'yebbafleb'
		 * //testArray is now ['foo', 'bar', 'yebbafleb']
		 */
		append: function(array, el, nodupe) {
			if (!(nodupe && ub.a.contains(array, el))) {
				return array[array.length] = el;
			}
			return el;
		},
		/**
		 * Safely clear an array of elements
		 * @param {Array} array The array to clear
		 * @example
		 * var testArray = ['foo', 'bar'];
		 * ub.a.clear(testArray); //testArray is now []
		 */
		clear: function(array) {
			array.length = 0;
		},
		/**
		 * Insert an element at specified index in array
		 * @returns {Element}
		 * @param {Array} array The array to insert the element into
		 * @param {Integer} index The index the element should be inserted at
		 * @param {Object} el The element to be inserted
		 * @example
		 * var testArray = ['foo', 'bar'];
		 * ub.a.insertAt(testArray, 0, 'yebbafleb'); //returns 'yebbafleb'
		 * //testArray is now ['yebbafleb', 'foo', 'bar']
		 */
		insertAt: function(array, index, el) {
			array.splice(index, 0, el);
			return el;
		},
		/**
		 * Remove an element from array at index
		 * @returns {Array}
		 * @param {Array} array The array to have the element removed from
		 * @param {Integer} index The index to be removed
		 * @example
		 * var testArray = ['foo', 'bar'];
		 * ub.a.removeAt(testArray, 0); //returns ['foo']
		 * //testArray is now ['bar']
		 */
		removeAt: function(array, index) {
			return array.splice(index, 1);
		},
		/**
		 * Remove an element from array
		 * @returns {Array}
		 * @param {Array} array The array to have the element removed from
		 * @param {Object} el The element to be removed
		 * @example
		 * var testArray = ['foo', 'bar'];
		 * ub.a.remove(testArray, 'bar'); //returns ['bar']
		 * //testArray is now ['foo']
		 */
		remove: function(array, el) {
			var i = ub.a.indexOf(array, el);
			if (i >= 0){
				ub.a.removeAt(array, i);
			}
			return el;
		}
	},
	/**
	 * String helper functions
	 * @namespace
	 */
	s: {
		/**
		 * Capitalize the first letter of a string
		 * @returns {String}
		 * @param {String} str The string to have it's first letter capitalized
		 * @example
		 * ub.s.ucFirst('foo'); //returns 'Foo'
		 */
		ucFirst: function(str) {
			return str.charAt(0).toUpperCase() + str.substring(1);
		},
		/**
		 * Convert dash style to camelCase style
		 * @returns {String}
		 * @param {String} str The string to convert to camelCase
		 * @example
		 * ub.s.camelCase('a-test-string'); //returns 'aTestString'
		 */
		camelCase: function(str) {
			var list = str.split('-');
			if (list.length == 1) return list[0];
			var camel = (str.indexOf('-') == 0) ? ub.s.ucFirst(list[0]) : list[0];
			for (var i = 1; i < list.length; i++) {
				camel += ub.s.ucFirst(list[i]);
			}
			return camel;
		},
		/**
		 * Remove leading and trailing whitespace from a string
		 * @returns {String}
		 * @param {String} str The string to have leading and trailing whitespace to be removed
		 * @example
		 * ub.s.trim('  foo   '); //returns 'foo'
		 */
		trim: function(str) {
			return str.replace(/(^\s+|\s+$)/g,'');
		},
		/**
		 * Truncate a string to desired length with an endcap. If string is at
		 * or below desired length, the unchanged string is returned
		 * @returns {String}
		 * @param {String} str The string to be truncated
		 * @param {Integer} [len] The desired length of the string, default is 30
		 * @param {String} [endCap] The string placed at the end of our truncated string
		 * @example
		 * ub.s.truncate('This is 30 characters long!!!!', 20, '...'); //returns 'This is 30 charac...'
		 */
		truncate: function(str, len, endCap) {
			len = (parseInt(len)) ? parseInt(len) : 30;
			if(str.length > len)
				return str.substring(0, len - endCap.length) + endCap;
			else return str;
		},
		/**
		 * Replaces < and > with their html entities
		 * @returns {String}
		 * @param {String} str The string to be modified
		 */
		disableTags: function(str) {
			return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
		},
		/**
		 * Replaces line breaks with html <br /> tags
		 * @returns {String}
		 * @param {String} str The string to be modified
		 * @example
		 * ub.s.nl2br('foo\nbar'); //returns 'foo<br />bar'
		 */
		nl2br: function(str) {
			return str.replace(/\r\n|\n|\r/g, '<br />');
		},
		/**
		 * Replaces html <br /> tags with line breaks
		 * @returns {String}
		 * @param {String} str The string to be modified
		 * @example
		 * ub.s.br2nl('foo<br />bar'); //returns 'foo\nbar'
		 */
		br2nl: function(str) {
			return str.replace(/<br \/>|<br\/>|<br>/g, '\n');
		},
		/**
		 * Replaces illegal XML characters with thier entities
		 * @returns {String}
		 * @param {String} str The string to be modified
		 * @example
		 * ub.s.prepXML('&\'"<>'); //returns '&amp;&apos;&quot;&lt;&gt;'
		 */
		prepXML: function(str) {
			return str.replace(/&/g, '&#38;').
				replace(/</g, '&#60;').
				replace(/>/g, '&#62;').
				replace(/'/g, '&#39;').
				replace(/"/g, '&#34;');
		}
	},
	/**
	 * Cookie helper functions
	 * @namespace
	 */ 
	c: {
		/**
		 * Set a cookie in the client browser
		 * @param {String} name The name of the cookie to set
		 * @param {String} value The value of the cookie to set
		 * @param {Integer} [days] The number of days the cookie should persist.
		 * If not provided, the cookie will expire as soon as the client browser
		 * is closed.
		 * @example
		 * ub.c.set('foo', 'bar', 30);
		 * //Sets a cookie named 'foo' with the value 'bar' that expires in 30 days
		 */
		set: function (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=/";
		},
		/**
		 * Get a cookie by name from the client browser
		 * @returns {String}
		 * @param {String} name The name of the cookie to get
		 * @example
		 * ub.c.set('foo', 'bar');
		 * ub.c.get('foo'); //returns 'bar'
		 */
		get: function (name) {
			var nameEQ = name + "=";
			var ca = document.cookie.split(';');
			for(var i=0;i < ca.length;i++) {
				var c = ca[i];
				while (c.charAt(0)==' ') c = c.substring(1,c.length);
				if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
			}
			return '';
		},
		/**
		 * Erase a cookie by name from the client browser
		 * @param {String} name The name of the cookie to erase
		 * @example
		 * ub.c.erase('foo');
		 */
		erase: function (name) {
			this.set(name,'',-1);
		}
	},
	/**
	 * Utility helper functions 
	 * @namespace
	 */
	u: {
		/**
		 * Copy an array to another by reference and not by value. This is
		 * useful for converting DOM NodeLists into actual arrays
		 * @returns {Array} A new array copied by reference
		 * @param {Array} array The array to be copied by reference
		 */
		$c: function (array){
			var _array = [];
			for(var i=0;i<array.length;i++)
				ub.a.append(_array, array[i]);
			return _array;
		},
		/**
		 * Shorthand version of document.getElementById, with the ability to pass a string or array
		 * @returns {Array} An array of DOM nodes if an array was passed in
		 * @returns {DOMNode} A single DOM node if a string was passed in
		 */
		$: function () {
			function get$(el){
				if (typeof el == 'string') el = document.getElementById(el);
				return el;
			}
			if (arguments.length == 1)
				return get$(arguments[0]);
			var elements = [];
			ub.a.each($c(arguments), function(el){
				ub.a.append(elements, get$(el));
			});
			return elements;
		},
		/**
		 * Copy properties of src onto dest by reference
		 * @returns {Object} Reference to the original dest object
		 * @param {Object} dest The object to be extended
		 * @param {Object} src The object to extend from
		 * @example
		 * var foo = { yebba: ['fleb'] };
		 * var bar = {};
		 * ub.u.extend(bar, foo); //bar is now: { yebba: ['fleb'] };
		 */
		extend: function(dest, src) {
			if(dest && src)
				for(p in src) dest[p] = src[p];
			return dest;
		},
		/**
		 * Clone another object
		 * @returns {Object}
		 * @param {Object} o The object to be cloned
		 */
		clone: function(o){
			if(o == null || typeof(o) != 'object')
				return o;
			var temp = new o.constructor();
			for(var key in o)
				temp[key] = ub.u.clone(o[key]);
			return temp;
		},
		/**
		 * Generate a random number between a defined floor and ceiling
		 * @returns {Integer} The random number generated
		 * @param {Integer} floor The lowest possible value for the random number
		 * @param {Integer} ceil The highest possible value for the random number
		 */
		randNum: function(floor, ceil) {
			return Math.floor((ceil-floor+1)*Math.random()) + floor;
		},
		/**
		 * Generate a random string of a specified length
		 * @returns {String} The random string generated
		 * @param {Integer} [len = 32] The length of the string generated
		 */
		randStr: function(len) {
			len = (parseInt(len)) ? parseInt(len) : 32;
			var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';
			var str = '';
			for(var i=0; i<len; i++) {
				var rand = Math.floor(Math.random() * chars.length);
				str += chars.substring(rand,rand+1);
			}
			return str;
		},
		/**
		 * Fetch the current viewport dimensions of the client browser
		 * @returns {Array} Integer array of the width and height of the viewport
		 */
		viewportDims: function() {
			return [self.innerWidth || (document.documentElement.clientWidth || document.body.clientWidth),
				self.innerHeight || (document.documentElement.clientHeight || document.body.clientHeight)];
		},
		/**
		 * Convert specially prepared object into a DOM object
		 * @returns {DOM Node}
		 */
		buildDOM: function(structure, pNode) {
			//make sure the first element is a string
			if(typeof(structure[0]).toLowerCase() != 'string')
				throw new Error('First element must be a string');
			//try to make the DOM Node
			try { var node = document.createElement(structure[0]); }
			catch(e) { throw new Error('Unable to create DOM Node of type: '+ structure[0]); }
			//run through remaining elements
			for(var i=1;i<structure.length;i++) {
				//is this an Array Object?
				if(structure[i] instanceof Array) {
					ub.u.buildDOM(structure[i], node);
				}
				//is this an Object?
				else if(structure[i] instanceof Object)
					for(var p in structure[i]) {
						//special handling for style attribute
						if(p == 'style') {
							try {
							//Everyone else...
							node.setAttribute(p, structure[i][p]);
							//IE :'(
							node.style['cssText'] =  structure[i][p];
							} catch(e) { alert(e); }
						}
						else if(
							(typeof(structure[i][p])).toLowerCase() == 'string'
							//special check for className
							&& p != 'className'
						)
							node.setAttribute(p, structure[i][p]);
						else
							node[p] = structure[i][p];
					}
				//is it a string?
				else if((typeof(structure[i])).toLowerCase() == 'string')
					node.appendChild(document.createTextNode(structure[i]));
			}
			if(pNode) pNode.appendChild(node);
			else pNode = node;
			return pNode;
		},
		//Experimental function - still buggy DO NOT USE
		parseXHTML: function(xhtml, pNode) {
			var parse = function() {
				var xmlDoc = null;
				//parse the passed xhtml string
				if(typeof DOMParser != 'undefined')
					xmlDoc = (new DOMParser()).parseFromString(xhtml, "application/xml");
				else if(typeof ActiveXObject != 'undefined') {
					xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
					xmlDoc.async = "false";
					xmlDoc.loadXML(xhtml);
				}
				return xmlDoc;
			}
			var convert = function(xml, pNode, recursed) {
				if(recursed) {
					//nodeType 1 = Element
					if(xml.nodeType == 1) {
						cNode = document.createElement(xml.nodeName);
						for(var i=0;i<xml.attributes.length;i++) {
							var name = xml.attributes[i].name;
							var val = xml.attributes[i].value;
							if(name == 'class') cNode.className = val;
							else cNode.setAttribute(name, val);
						}
						pNode.appendChild(cNode);
					}
					//nodeType 3 = Text
					if(xml.nodeType == 3) {
						pNode.appendChild(document.createTextNode(xml.nodeValue));
					}
					if(typeof cNode == 'undefined') return;
					pNode = cNode;
				}
				for(var i=0;i<xml.childNodes.length;i++) {
					convert(xml.childNodes[i], pNode, true);
				}
			}
			//make a default container if none was given
			if(!pNode) pNode = document.createElement('div');
			convert(parse('<wrapper>'+ xhtml +'</wrapper>').documentElement, pNode);
			return pNode;
		}
	},
	/**
	 * DOM Node helper functions
	 * @namespace
	 */
	n: {
		/**
		 * Fetch the dimensions of a DOM Node. This includes border, padding,
		   and client dimensions but NOT margin
		 * @returns {Array} Array containing the width and height of node i.e. - [100,200]
		 * @param {DOM Node} node The DOM Node to get dimensions from
		 */
		getDims: function(node) {
			return [node.offsetWidth, node.offsetHeight];
		},
		/**
		 * Set the dimensions of a DOM Node. This includes client area dimenions
		   only and NOT padding, border, or margin
		 * @param {DOM Node} node The DOM Node to set dimsions of
		 * @param {Array} dims An array containing the width and height of node i.e. - [100,200]
		 */
		setDims: function(node, dims) {
			node.style['width'] = dims[0] +'px';
			node.style['height'] = dims[1] +'px';
		},
		/**
		 * Calculate scrolled offset of node (not including body level scrollbars)
		 * @returns {Array} An array containing the XY scrolled offset of node
		 * @param {DOM Node} node The DOM Node to calculate the scrolled offset of
		 */
		scrollOffset: function(node) {
			var top = 0, left = 0;
			//do not calculate body level scrollbars
			while (node && node != document.body) {
				top += node.scrollTop  || 0;
				left += node.scrollLeft || 0;
				node = node.parentNode;
			}  
			return [left, top];
		},
		/**
		 * Calculate node cumulative offset from document.body
		 * @returns {Array} An array containing the XY cumulative offset of node
		 * @param {DOM Node} node The DOM Node to calculate the cumulative offset from document.body
		 */
		cumulativeOffset: function(node) {
			var top = 0, left= 0;
			while (node) {
				top += node.offsetTop  || 0;
				left += node.offsetLeft || 0;
				node = node.offsetParent;
			} 
			return [left, top];
		},
		/**
		 * Calculate the realOffset from ancestor element (document.body) less scroll offset delta
		 * @returns {Array} An array containing the XY real offset of node
		 * @param {DOM Node} node The DOM Node to calculate the real offset from document.body
		 */
		realOffset: function(node) {
			var delta = ub.n.scrollOffset(node);
			var pos = ub.n.cumulativeOffset(node);
			return [pos[0] - delta[0], pos[1] - delta[1]];
		},
		/**
		 * Get the XY coordinates of node using style.left and style.top
		 * @returns {Array} An array containing the XY coordinates of node as set by the style attribute
		 * @param {DOM Node} node The DOM Node to calculate the coordinates from
		 */
		getCoords: function(node) {
			return [parseInt(node.style.left) || 0, parseInt(node.style.top) || 0];
		},
		/**
		 * Set the XY coordinates of node using style.left and style.top
		 * @param {DOM Node} node The DOM Node to apply coords to
		 * @param {Array} coords An array containing the let and top coords of the node
		 */
		setCoords: function(node, coords) {
			node.style.left = (coords[0]) ? coords[0] + 'px' : 0;
			node.style.top = (coords[1]) ? coords[1] + 'px' : 0;
		},
		/**
		 * Set the opacity of a node
		 * @param {DOM Node} node The DOM Node to apply opacity to
		 * @param {Float} opacity The opacity value to apply to the DOM Node
		 */
		setOpacity: function(node, opacity) {
			if (opacity == 0 && node.style.visibility != "hidden") node.style.visibility = "hidden";
			else if (node.style.visibility != "visible") node.style.visibility = "visible";
			if (window.ActiveXObject) node.style.filter = "alpha(opacity=" + opacity*100 + ")";
			node.style.opacity = opacity;
		},
		/**
		 * Determine whether a set of coordinates exists within the dimensions of DOM Node
		 * @returns {Boolean}
		 * @param {DOM Node} node The DOM Node to check for coords within
		 * @param {Array} coords The coordinates used to see if are contained within DOM Node
		 */
		within: function(node, coords) {
			var realOffset = ub.n.realOffset(node);
			var dims = ub.n.getDims(node);
			return (
				coords[0] >= realOffset[0] &&
				coords[0] < (realOffset[0] + dims[0]) &&
				coords[1] >= realOffset[1] &&
				coords[1] < (realOffset[1] + dims[1])
			);
		},
		/**
		 * Check for className assigned to DOM Node
		 * @returns {Boolean}
		 * @param {DOM Node} node The DOM Node to check for className
		 * @param {String} className The class name to check for in DOM Node
		 */
		hasClassName: function(node, className) {
			var hasClass = false;
			ub.a.each(node.className.split(' '), function(cn){
				if (cn == className) hasClass = true;
			});
			return hasClass;
		},
		/**
		 * Remove a className assigned to DOM Node
		 * @param {DOM Node} node The DOM Node to remove the className from
		 * @param {String} className The className to remove from the DOM Node
		 */
		removeClassName: function(node, className) {
			var newClassName = '';
			ub.a.each(node.className.split(' '), function(cn, i){
				if (cn != className){
					if (i > 0) newClassName += ' ';
					newClassName += cn;
				}
			});
			node.className = newClassName;
		},
		/**
		 * Add a className to a DOM Node
		 * @param {DOM Node} node The DOM Node to add a className to
		 * @param {String} className The className to add to the DOM Node
		 */
		addClassName: function(node, className) {
			if(!ub.n.hasClassName(node, className))
				node.className += ((node.className) ? ' ' : '') + className;
		},
		/**
		 * Get an array of DOM Nodes containing className
		 * @returns {Array} An array of DOM Nodes containing className
		 * @param {DOM Node} node The parent DOM Node to search for matching childNodes
		 * @param {String} className The className to search for in matching childNodes
		 */
		getElementsByClassName: function(node, className) {
			if(node.getElementsByClassName) {
				return ub.u.$c(node.getElementsByClassName(className));
			}
			var children = ub.u.$c(node.getElementsByTagName('*'));
			var elements = [];
			ub.a.each(children, function(child){
				if (ub.n.hasClassName(child, className))
					ub.a.append(elements, child);
			});
			return elements;
		},
		/**
		 * Get the ancestor element of a DOM Node
		 * @returns {DOM Node} The ancester DOM Node of node
		 * @param {DOM Node} node The node to find the ancestor of
		 */
		ancestor: function(node) {
			if(node.parentNode)
				return ub.n.ancestor(node.parentNode);
			else return node;
		},
		/**
		 * Check to see if child node is contained by parent node
		 * @returns {Boolean}
		 * @param {DOM Node} pNode The parent node
		 * @param {DOM Node} cNode The child node
		 */
		contains: function(pNode, cNode) {
			if(!pNode || !cNode) return false;
			if(cNode.parentNode == pNode)
				return true;
			else return ub.n.contains(pNode, cNode.parentNode);
		},
		/**
		 * Remove all child nodes from DOM Node
		 * @param {DOM Node} node The DOM Node to remove all child nodes from
		 * @param {Boolean} [recurse = false] Flag to remove all child nodes recursively.
		 */
		removeChildren: function(node, recurse) {
			for(var i=node.childNodes.length - 1;i>=0;i--) {
				if(recurse) ub.n.removeChildren(node.childNodes[i], recurse);
				node.removeChild(node.childNodes[i]);
			}
		},
		/**
		 * Replaces child nodes of pNode with cNode
		 * @param {DOM Node} pNode The DOM Node to have its child nodes replaced
		 * @param {DOM Node} cNode The DOM Node to append the pNode
		 */
		replaceChildren: function(pNode, cNode) {
			ub.n.removeChildren(pNode);
			pNode.appendChild(cNode);
			return pNode;
		}
	},
	/**
	 * Event helper functions
	 * @namespace
	 */
	e: {
		/** Array containing all registered events */
		stack: [],
		/** Counter containing the number of events handled */
		handled: 0,
		event: function() {
			/**
			 * @constructor
			 * @param {String} type The event type. i.e. - click, mouseover, unload
			 * @param {Object} target The target object triggering the event.  Could be a DOM Node or an Object
			 * @param {Integer} [keyCode] The keyCode for the event
			 * @param {Array} coords An array containing the XY coords of the mouse
			 * @name ub.e.event
			 */
			var e =	function(type, target, keyCode, coords) {
				this.type = type;
				this.target = target,
				this.keyCode = keyCode;
				this.coords = (coords) ? coords : [0,0];
			};
			/**
			 * Change the type attribute, and return the event object
			 * @function
			 * @param {String} type
			 * @returns {ub.e.event}
			 * @name ub.e.event.chType
			 */
			e.prototype.chType = function(type) {
				this.type = type;
				return this;
			}
			return e;
		}(),
		block: function() {
			/**
			 * Not to be instanced by user but can be modified once instanced.
			   Instanced and returned by {@link ub.e.attach}
			 * @constructor
			 * @param {Object} node The DOM Node or Object to attach to
			 * @param {String} type The event to attach to. i.e. mouseover,
			   click, unload
			 * @param {Function} callback The function to be called when the
			   attached event occurs
			 * @param {Boolean} [noBubbling = false] Boolean to cancel event
			   bubbling
			 * @param {Object} [scope = node] The scope the callback function
			   should be called in
			 * @param {Boolean} [forceLegacy = false] If set to true,
			   addEventListener and attachEvent are not used even if available
			 * @name ub.e.block
			 */
			var b = function(node, type, callback, noBubbling, scope, forceLegacy) {
				this.node = node;
				this.type = type;
				this.callback = callback;
				this.noBubbling = noBubbling || false;
				this.scope = scope || node;
				this.forceLegacy = forceLegacy || false;
			}
			/**
			 * Remove this event block from the {@link ub.e.stack} array as well
			 * as the node event originally attached from {@link ub.e.attach}
			 * @function
			 * @name ub.e.block.detach
			 */
			b.prototype.detach = function() {
				ub.e.detach(this);
				this.detach = function() {};
			}
			return b;
		}(),
		/**
		 * Attach an event handler to a DOM node or Object. When attached to a
		   DOM Node, native addEventListener or attachEvent are used if
		   available. Otherwise the failsafe of Object.onevent method is used.
		 * @returns {ub.e.block} An object block with references to node, type, callback, noBubbling, scope, and forceLegacy params.  Additionally, it contains
		 * @param {Object} node The DOM Node or Object to attach to
		 * @param {String} type The event to attach to. i.e. mouseover, click, unload
		 * @param {Function} callback The function to be called when the attached event occurs
		 * @param {Boolean} [noBubbling = false] Boolean to cancel event bubbling
		 * @param {Object} [scope = node] The scope the callback function should be called in
		 * @param {Boolean} [forceLegacy = false] If set to true, addEventListener and attachEvent are not used even if available
		 */
		attach: function(node, type, callback, noBubbling, scope, forceLegacy) {
			//alert(node +' - '+ type);
			var block = ub.a.append(
				ub.e.stack,
				new ub.e.block(node, type, callback, noBubbling, scope, forceLegacy)
			);
			//adding a non-prototyped function here to use the block as a
			//closure. We do this because it's passed as a function reference
			//via addEventListener and attachEvent and called later outside of
			//it's normal scope
			block.listener = function(e) {
				return ub.e.handler(e, block);
			};
			//W3C
			if(!block.forceLegacy && block.node.addEventListener)
				block.node.addEventListener(block.type, block.listener, false);
			//IE
			else if(!block.forceLegacy && node.attachEvent)
				block.node.attachEvent('on'+block.type, block.listener);
			//Failsafe and non-dom objects
			else {
				//check for __lsnrs object
				if(!block.node.__lsnrs)
					block.node.__lsnrs = {};
				//check for __lsnrs[block.type] array
				if(!block.node.__lsnrs[block.type])
					block.node.__lsnrs[block.type] = [];
				//append our block to the proper listener
				ub.a.append(block.node.__lsnrs[block.type], block);
				block.node['on'+block.type] = function(e) {
					var args = [];
					//All but IE provide an automagic event argument
					if(!e) {
						//stuff in a fake event if we absolutely have to
						ub.a.append(args, (
							(window.event) ? window.event : new ub.e.event('unknown'))
						);
					}
					for(var i=0;i<arguments.length;i++)
						ub.a.append(args, arguments[i]);
					//set a default return result of true
					var result = true;
					ub.a.each(block.node.__lsnrs[block.type], function(lsnr, i) {
						//add our block as the second argument
						if(i==0) args.splice(1, 0, lsnr);
						else args.splice(1, 1, lsnr);
						//call the handler, test for false return
						if(ub.e.handler.apply(this, args) === false)
							//set result to false. Note that this is flagged
							//even if only one handler for the event returned a
							//false value.
							result = false;
					});
					return result;
				};
			}
			this.onattach(new ub.e.event('attach'), block);
			return block;
		},
		/**
		 * Detach an event handler from a DOM node or Object. When attached to a
		   DOM Node, native removeEventListener or detachEvent are used if
		   available. Otherwise the failsafe of Object.onevent method is used.
		 * @param {ub.e.block} block The block object created when ub.e.attach
		   was originally called
		 */
		detach: function(block) {
			//remove the block from the stack
			ub.a.remove(this.stack, block);
			//W3C
			if(!block.forceLegacy && block.node.removeEventListener)
				block.node.removeEventListener(block.type, block.listener, false);
			//IE
			else if(!block.forceLegacy && block.node.detachEvent)
				block.node.detachEvent('on'+block.type, block.listener);
			//Failsafe and non-dom objects
			else if(block.node['on'+block.type]) {
				//got listeners?
				var lsnrs = (block.node.__lsnrs && block.node.__lsnrs[block.type]) ? block.node.__lsnrs[block.type] : false;
				if(lsnrs) {
					//remove this block
					ub.a.remove(block.node.__lsnrs[block.type], block);
					//no more listeners for this event type?
					if(!block.node.__lsnrs[block.type].length) {
						//delete our event type array
						delete block.node.__lsnrs[block.type];
						//delete our event handler
						block.node['on'+block.type] = function() {};
						//check to see if there are other attached event types
						var lsnrCount = 0;
						//check for other event types
						for(var p in block.node.__lsnrs) {
							if(typeof(block.node.__lsnrs[p]) == 'object') {
								lsnrCount++;
							}
						}
						//no more event type lsnrs?
						if(lsnrCount == 0) {
							//delete our __lsnrs extendo
							block.node.__lsnrs = null;
						}
					}
					
				}
			}
			this.ondetach(new ub.e.event('detach'), block);
			//wipe out the block
			for(var p in block)
				delete block[p];
		},
		/** @private */
		handler: function(e, block) {
			//All but IE provide an automagic e argument
			if(!e && window.event) e = window.event;
			//failsafe, make up a new event 
			else if(!e) e = {
				type: 'Unknown',
				target: block.node,
				keyCode: null,
				coords: [0,0]
			};
			//create a new unbad event object
			var event = new ub.e.event(
				e.type,
				e.target || e.srcElement,
				e.keyCode || e.which
			);
			//add relatedTarget property when the event is mouseover or mouseout
			if(event.type == 'mouseover')
				event.relatedTarget = e.relatedTarget || e.fromElement;
			else if(event.type == 'mouseout')
				event.relatedTarget = e.relatedTarget || e.toElement || {};
			//cross-browser coords discovery
			//TYVM PPK http://www.quirksmode.org/js/events_properties.html
			if(e.coords) event.coords = e.coords;
			else if (e.pageX || e.pageY) event.coords = [e.pageX, e.pageY];
			else if (e.clientX || e.clientY) {
				event.coords = [
					e.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft),
					e.clientY + (document.body.scrollTop || document.documentElement.scrollTop)
				];
			}
			//check to see if we should stop bubbling up
			if(block.noBubbling) {
				//W3C
				if (e.stopPropagation) e.stopPropagation();
				//IE
				e.cancelBubble = true;
			}
			//capture the native arguments collection, stuff into an array
			var args = [];
			for(var i=0;i<arguments.length;i++)
				ub.a.append(args, arguments[i]);
			//remove the e and block arguments, inject our event object
			args.splice(0, 2, event);
			//increment our number of events handled
			ub.e.handled++;
			//do our callback in the desired scope and pass our event object
			return (block.callback.apply(block.scope, args) !== false);
		},
		onattach: function(e, block) {},
		ondetach: function(e, block) {}
	},
	/**
	 * Stubbed function to override as an onload event handler. This function is
	   called when the main unbad library has loaded
	 * @param {ub.e.event} e
	 */
	onload: function(e) {},
	/**
	 * Stubbed function to override as an onunload event handler. This function
	   is called when the main unbad librady is unloading.
	 * @param {ub.e.event} e
	 */
	onunload: function(e) {}
}).init();