﻿
   /**
 * TEST : OOP JAVASCRIPT LIBRARY
 * @author Dan Dean
 * @version 0.0.4
 * Changes:
 * 		Added XML namespace and 3rd party xml2json method
 * 		Added String.addQueryParams()
 * TO DO: (lots!)
 * 		FX
 * 		Fix Cookies
 * Note: This library is subject to MASSIVE changes
 */
var uspopinit = {
	init: function() {
		if (typeof window.us == "undefined") {
			us = new Object();
		}
		if (typeof window.us.pops == "undefined") {
			us.popdt = new Object();
		}
		
		/**
		 * AUGMENT
		 * Used to augment Native JS Object capabilities.
		 * FROM: Object.extend in Prototype base.js
		 * NOTE: Added typeof check to make sure there isn't already a native version
		 * @param destination: The Object to augment
		 * @param source: The Object Literal to augment destination with.
		 */
		Object.augment = function(destination, source) {
			for (property in source) {
				if (typeof destination[property] == "undefined") {
					// [UNCOMMEND TO SEE WHAT IS BEING AUGMENTED IN YOUR TARGET BROWSER]
					// alert(source[property]);
					destination[property] = source[property];
				}
			}
			return destination;
		}
	}
}
uspopinit.init();


/**
 * Mimicks the Firebug console object for non-firebug browsers.
 * TODO Add debugging flag and only output debug messages when set to true
 */
dbug = {
	customEventLogging: false,
	build: function(type, args) {
		var o = '';
		for (var i=0; i<args.length; i++) {
			o += type + ' ' + args[i] + '\n';
		}
		if (us.popdt.client.browser == 'applewebkit') {	// Safari
			console.log(o);
		} else if (typeof opera == 'object') {			// Opera
			opera.postError(o)
		} else {
			alert(o);
		}
	},
	log:	function() { dbug.build("[LOG]", arguments); },
	debug:	function() { dbug.build("[DEBUG]", arguments); },
	info:	function() { dbug.build("[INFO]", arguments); },
	error:	function() { dbug.build("[ERROR]", arguments); },
	warn:	function() { dbug.build("[WARNING]", arguments); }
}
if (typeof console != "undefined") { // both safari and firebug have [console] objects
	if (typeof console.debug != "undefined") { // only firebug has the console.debug object
		dbug.log = console.log;
		dbug.debug = console.debug;
		dbug.info = console.info;
		dbug.error = console.error;
		dbug.warn = console.warn;
	}
}

/**
 * CONFIGURATION SETTINGS
 * Must be set in order to utilize dynamic package loading
 * When POP.Core is available you can uncomment the below variables and change
 * this to an aspx file. The import statement is at the very top of this page
 * and SHOULD REMAIN COMMENTED OUT.
 */
us.popdt.config = {
	ROOT: "",
	ROOT_SECURE: "",
	ROOT_VIRTUAL: "",
	PKG_DIR: "/_inc/popdt/src"
}



/**
 * In/Out namespace. Place code to deal with things like XHR, iframe, etc here.
 */
us.popdt.io = {
	/**
	 * Creates a x-browser XHR object. This is used by [client] and [packages] Objects.
	 * @return w3c:XMLHttpRequest | IE6-:XMLHttp | false
	 */
	XHR: function() {
		var req = false;
		if (window.XMLHttpRequest) {
			try {
				req = new XMLHttpRequest();
			} catch (e) {
				req = false;
			}
		} else if (window.ActiveXObject) { // IE
			try { // IE 6
				req = new ActiveXObject("Msxml2.XMLHTTP");
			} catch (e) { // IE older
				try {
					req = new ActiveXObject("Microsoft.XMLHTTP");
				} catch (e) {
					req = false;
				}
			}
		}
		return req;
	}
}



/**
 * Handles environment detection
 */
us.popdt.client = {
	browser: "",
	version: "",
	platform: "",
	capable: {
		xhr: false
	},
	/**
	 * Parse the environment
	 */
	parse: function() {
		this.detectXHR();
		this.detectBrowser.init(this);
		this.detectPlatform();
	},
	detectBrowser: {
		webkit: function(userAgent, obj) { // safari and derivitives
			// var obj = obj;
			if (userAgent.indexOf("applewebkit") != -1) {
				var sIndex = userAgent.indexOf("applewebkit");
				var bVersion = userAgent.substring(sIndex).split(" ")[0].split("/")[1].split(".");
				this.put(obj, "applewebkit", bVersion);
				return true;
			} else {
				return false;
			}
		},
		opera: function(userAgent, obj) { // detect before IE
			// var obj = obj;
			if (userAgent.indexOf("opera") != -1) {
				var sIndex = userAgent.indexOf("opera");
				var bVersion = userAgent.substring(sIndex + 6).split(" ")[0].split(".");
				this.put(obj, "opera", bVersion);
				return true;
			} else {
				return false;
			}
		},
		gecko: function(userAgent, obj) { // mozilla and derivitives
			// var obj = obj;
			if (userAgent.indexOf("gecko") != -1 && userAgent.indexOf("rv:") != -1) {
				var rvIndex = userAgent.indexOf("rv:");
				var bVersion = userAgent.substring(rvIndex + 3).split(")")[0].split(".");
				this.put(obj, "gecko", bVersion);
				return true;
			} else {
				return false;
			}
		},
		msie: function(userAgent, obj) {
			// var obj = obj;
			if (userAgent.indexOf("msie") != -1) {
				var sIndex = userAgent.indexOf("msie");
				var bVersion = userAgent.substring(sIndex + 5).split(";")[0].split(".");
				this.put(obj, "msie", bVersion);
				return true;
			} else {
				return false;
			}
		},
		/**
		 * Begin the browser detection process
		 */
		init: function(obj) {
			// var obj = obj;
			var userAgent = navigator.userAgent.toLowerCase();
			for (prop in this) {
				if (prop != "init" && prop != "put") { // don't call init and put methods
					var success = this[prop](userAgent, obj);
					if (success) { break; } // stop processing when browser is detected
				}
			}
		},
		/**
		 * Assign the version to the object for use
		 */
		put: function(obj, browser, vArray) {
			obj.browser = browser;
			obj.version = {
				major: parseInt(vArray[0]),
				minor: parseInt(vArray[1]),
				rev: (isNaN(parseInt(vArray[2]))) ? 0 : parseInt(vArray[2])
			}
		}
	},
	detectPlatform: function() {
		var p = navigator.platform.toLowerCase();
		if (p.indexOf("win") != -1) {
			p = "windows";
		} else if (p.indexOf("mac") != -1) {
			p = "mac";
		} else if (p.indexOf("linux") != -1) {
			p = "linux";
		}
		this.platform = p;
	},
	/**
	 * Detect XMLHTTP
	 */
	detectXHR: function() {
		var c_xhr = us.popdt.io.XHR();
		this.capable.xhr = (c_xhr === false) ? false : true ;
		delete c_xhr;
	}
	
}
us.popdt.client.parse();


/**
 * Holds data about the us.popdt Library
 * @author	Dan Dean
 */
us.popdt.stats = {
	org: "Pop Design + Technology",
	title: "JS Library",
	version: {
		major: 0,
		minor: 4,
		note: "Killer.",
		toFloat: function() {
			return this.major + "." + this.minor;
		},
		toString: function() {
			return us.popdt.stats.org + " " + us.popdt.stats.title + "\n" + "Version: " + this.major + "." + this.minor + "\nNote: " + this.note;
		}
	}
}


/**
 * Holds reuseable bits of code for use by Library Classes and Functions
 * @author	Dan Dean
 */
us.popdt.snippet = {
	// Used by String.extractScripts, String.stripScripts
	ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)'
}

/**
 * Augment Native JavaScript Objects, where necessary
 * @author	Dan Dean
 */
us.popdt.jsaugment = function() {
	var ns = us.popdt; // referenced in multiple places in this function, enables easy updating of NameSpace

	/**
	 * Augment the browser String capabilities, where necessary
	 * Taken directly from prototype.js
	 */
	Object.augment(String.prototype, {
		
		/**
		 * Remove whitespace at the front and back of a String
		 * FROM: http://kasimchen.com/2005/03/27/javascript-trim/#comment-789
		 */
		trim: function() {
			return this.replace(/^\s*|\s*$/g,'');
		},
		
		stripTags: function() {
			return this.replace(/<\/?[^>]+>/gi, '');
		},
	
		stripScripts: function() {
			return this.replace(new RegExp(ns.snippet.ScriptFragment, 'img'), '');
		},
		
		extractScripts: function() {
			var matchAll = new RegExp(ns.snippet.ScriptFragment, 'img');
			var matchOne = new RegExp(ns.snippet.ScriptFragment, 'im');
			return (this.match(matchAll) || []).map(function(scriptTag) {
				return (scriptTag.match(matchOne) || ['', ''])[1];
			});
		},
		
		evalScripts: function() {
			return this.extractScripts().map(eval);
		},
	
		escapeHTML: function() {
			var div = document.createElement('div');
			var text = document.createTextNode(this);
			div.appendChild(text);
			return div.innerHTML;
		},
	
		unescapeHTML: function() {
			var div = document.createElement('div');
			div.innerHTML = this.stripTags();
			return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
		},
		
		/**
		 * Prototype.js version of this was dependent on other Prototype.js Objects.
		 * Rewritten for Library independence.
		 * @author Dan Dean
		 * @return Object containing name/value pairs
		 * @return Value of supplied "key"
		 * @usage alert("firstname=dan&lastname=dean".toQueryParams()["lastname"]);
		 */
		toQueryParams: function() {
			var query_array =	this.substr(this.indexOf("?") + 1).split("&");
			var queryObj =		{};
			var query_count =	query_array.length;
			for (var i=0; i < query_count; i++) {
				query_pair = query_array[i].split("=");
				queryObj[query_pair[0]] = query_pair[1];
			}
			return queryObj;
		},
		
		/**
		 * Add/Update query parameters in a string.
		 * Example: 'index.aspx?hi=ho'.addQueryParams({hi:'there',id:'500'}) will return:
		 * 'index.aspx?hi=there?id=500'
		 * @param Object new_p JSON object to update the string with
		 * @author Dan Dean
		 */
		addQueryParams: function(new_p) {
			var url =		this.split('?'); // find non-query params
			var out =		(typeof url[0] != 'undefined' && url[0].trim() != '') ? url[0].trim() + '?' : '' ; // add query symbol
			var params =	(typeof url[1] != 'undefined') ? url[1].toQueryParams() : {} ; // find existing params
			for (p in new_p) { // iterate new params, updating old values
				params[p] = new_p[p];
			}
			var i = 0;
			for (p in params) { // iterate updated params, appending to 'out' string
				if (typeof params[p] != 'undefined') { // keep undefined params from adding
					out += (i>0) ? '&' : '' ;
					out += (p + '=' + params[p]);
					i++;
				}
			}
			return out; // update self
		},
		
		toArray: function() {
			return this.split('');
		},
		
		camelize: function() { // converts css style props to js style props: 'border-color' = 'borderColor'
			var oStringList = this.split('-');
			if (oStringList.length == 1) return oStringList[0];
				
			var camelizedString = this.indexOf('-') == 0
				? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) 
				: oStringList[0];
				
			for (var i = 1, len = oStringList.length; i < len; i++) {
				var s = oStringList[i];
				camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
			}
			
			return camelizedString;
		},
	
		inspect: function() {
			return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
		},
		
		/**
		 * C# style String.format();
		 * @param String strings Arguments to format the String with.
		 * Example: "Rock and {0} {1}!".format("Roll", "Forever") will output "Rock and Roll Forever!".
		 */
		format: function(strings) {
				var val = this;
				for (var i=0; i < arguments.length; i++) {
					// Since we're building this on the fly, we have to double escape. I think that's the case, anyways.
					var regex = new RegExp("\\{" + i + "\\}", "g");
					val = val.replace(regex, arguments[i]);
				}
				return val;
		},
		contains: function(re, str) {
			if (str.search(re) != -1) {
				return true;
			} else {
				return false;
			}
		},
		getHostname: function() {
			return this.toString().replace(/^\w+\:\/\//,'').split('/')[0];
		}
	});
	
	/**
	 * Augment the browser Function capabilities, where necessary
	 */
	Object.augment(Function.prototype, {
		// both call() and apply() are need by IE5 for some of the Array methods to work.
		// FROM: http://www.browserland.org/scripts/dragdrop/
		apply: function(scope, args) {
			if (!args) args = [];
			var index = 0, result;
			do { -- index } while (typeof scope[index] != "undefined");
			scope[index] = this;
		
			switch (args.length) {
				case 0:
					result = scope[index]();
					break;
				case 1:
					result = scope[index](args[0]);
					break;
				case 2:
					result = scope[index](args[0], args[1]);
					break;
				case 3:
					result = scope[index](args[0], args[1], args[2]);
					break;
				case 4:
					result = scope[index](args[0], args[1], args[2], args[3]);
					break;
				default:
					result = scope[index](args[0], args[1], args[2], args[3], args[4]);
					break;
			}
		
			delete scope[index];
			return result;
		},

		// FROM: http://www.browserland.org/scripts/dragdrop/
		call: function(scope) {
			var args = new Array(Math.max(arguments.length-1, 0));
			for (var i = 1; i < arguments.length; i++)
				args[i-1] = arguments[i];
			return this.apply(scope, args);
		}
	});
	
	/**
	 * Augment the browser Array capabilities, where necessary
	 * For a full explanation of JS Array capabilities, see the Array documentation:
	 * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference
	 */
	Object.augment(Array.prototype, {
		// FROM DECONCEPT
		// Adds one or more elements to the end of an array and returns the new length of the array.
		push: function(item) { // IE5
			this[this.length] = item;
			return this.length;
		},

		/**
		 * Adds and/or removes elements from an array.
		 * @param Number index	Index at which to start changing the array.
		 * @param Number count	An integer indicating the number of old array elements to remove.
		 * 				 		If count is 0, no elements are removed. In this case, you should specify at least one new element.
		 * @return Array An array containing the removed elements.
		 * FROM http://www.webreference.com/dhtml/column33/13.html
		 */
		splice: function(index,count){
			if(arguments.length == 0) return index;
			if(typeof index != "number") index = 0;
			if(index < 0) index = Math.max(0,this.length + index);
			if(index > this.length) {
				if(arguments.length > 2) index = this.length;
				else return [];
			}
			if(arguments.length < 2) count = this.length-index;
			count = (typeof count == "number") ? Math.max(0,count) : 0;
			removeArray = this.slice(index,index+count);
			endArray = this.slice(index+count);
			this.length = index;
			for(var i=2;i < arguments.length;i++){
				this[this.length] = arguments[i];
			}
			for(var i=0;i < endArray.length;i++){
				this[this.length] = endArray[i];
			}
			return removeArray;
		},

		/**
		 * Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.
		 * @param Object obj Needle
		 * @param Number si Index to start search. Optional.
		 * FROM http://erik.eae.net
		 */
		indexOf: function (obj, si) {
			if (si == null) {
				si = 0;
			} else if (si < 0) {
				si = Math.max(0, this.length + si);
			}
			for (var i=si; i < this.length; i++) {
				if (this[i] === obj) { return i; }
			}
			return -1;
		},

		/**
		 * Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
		 * @param Object obj Needle
		 * @param Number si Index to start search. Optional.
		 * FROM http://erik.eae.net
		 */
		lastIndexOf: function (obj, si) {
			if (si == null) {
				si = this.length - 1;
			} else if (si < 0) {
				si = Math.max(0, this.length + si);
			}
			for (var i = si; i >= 0; i--) {
				if (this[i] === obj) { return i; }
			}
			return -1;
		},

		/**
		 * Calls a function for each element in the array.
		 * @param Function f Callback function to apply to each element in the array.
		 * @param Object obj Optional object to apply function to. Defaults to 'this'.
		 * FROM http://erik.eae.net
		 */
		forEach: function (f, obj) {
			obj = (obj != null) ? obj : this; // IE5 [dd]

			var l = this.length;
			for (var i = 0; i < l; i++) {
				f.call(obj, this[i], i, this);
			}
		},

		/**
		 * Creates a new array with all of the elements of this array for which the provided filtering function returns true.
		 * @param Function f Callback function to apply to each element in the array.
		 * @param Object obj Optional object to apply function to. Defaults to 'this'.
		 * FROM http://erik.eae.net
		 */
		filter: function (f, obj) {
			obj = (obj != null) ? obj : this; // IE5 [dd]

			var l = this.length;
			var res = [];
			for (var i = 0; i < l; i++) {
				if (f.call(obj, this[i], i, this)) {
					res.push(this[i]);
				}
			}
			return res;
		},
		// Creates a new array with the results of calling a provided function on every element in this array.
		map: function (f, obj) {
			obj = (obj != null) ? obj : this; // IE5 [dd]

			var l = this.length;
			var res = [];
			for (var i = 0; i < l; i++) {
				res.push(f.call(obj, this[i], i, this));
			}
			return res;
		},
		// Determines the existance of the needle in the stack
		contains: function (obj) {
			return this.indexOf(obj) != -1;
		},
		// Duplicates the subject array to a new array: var blah = myArray.copy()
		copy: function (obj) {
			return this.concat();
		},
		// Duplicates the passes array into the new array: var blah = new Array(); blah.copyFrom(arguments);
		// Useful for copying non Array types into arrays
		copyFrom: function(arr) {
			for (var i=0; i<arr.length; i++) {
				this.push(arr[i]);
			}
		},
		// Add something to stack at the specified index.
		insertAt: function (obj, i) {
			this.splice(i, 0, obj);
			// return this; // should this be here?
		},
		// Add something at the first index of obj2. Bumps everything from obj2 to the end of the stack down one index.
		// If obj2 is not found, obj is added to the end of the array
		insertBefore: function (obj, obj2) {
			var i = this.indexOf(obj2);
			if (i == -1) {
				this.push(obj);
			} else {
				this.splice(i, 0, obj);
			}
		},
		// Removes needle at specified index.
		removeAt: function (i) {
			this.splice(i, 1);
		},
		// Removes the first occurence of the supplied obj.
		remove: function (obj) {
			var i = this.indexOf(obj);
			if (i != -1) {
				this.splice(i, 1);
			}
		},
		// does the opposite as String.split();
		unSplit: function(str) {
			dbug.error('Replace reference to unSplit with join');
			return this.join(arguments[0]);
		}
	});
}
us.popdt.jsaugment();


us.popdt.packages = {
	// list of registered packages
	registered: {},
	/**
	 * Determines if the supplied package is registerd or not.
	 * @param String The package to check for
	 * @return Boolean Whether the package is registered or not
	 */
	getRegistered: function(pkg) {
		if (typeof this.registered[pkg] == "undefined") {
			return false;
		} else {
			return true;
		}
	},
	/**
	 * Registers a package in the package registry.
	 * @param {String} The JS package to register
	 */
	register: function(pkg) {
		this.registered[pkg] = true;
	},
	/**
	 * REQUIRE
	 * Includes a js package
	 */
	require: function(pkg) {
		var lib = us.popdt;
		
		if (!this.getRegistered(pkg)) {
			var file = pkg.replace(/[.]/,"/").replace(/[*]/,"__package__") + ".js";
			if (document.body) { // if the page has loaded, add scripts via DOM
				this.DOMAttach(file);
			} else { // otherwise, include immediately on page.
				this.xhrAttach(file);
			}
		}
	},
	/**
	 * Adds JavaScript directly to the page using document.write.
	 * The downside of this is that the JS doesn't show up in the Firebug debugger.
	 * This must be done DURING page load, not after. Also, this is done synchronously,
	 * so the page stops loading while each js file loads, just like dojo.
	 * @param String js The JavaScript file to load
	 */
	xhrAttach: function(js) {
		var lib = us.popdt;
		// Opera and IE will cache the request. Adding a unique query parameter prevents this.
		var js_path = lib.config.PKG_DIR + "/" + js + "?cacheBuster=" + (new Date()).getTime();

		this.req = false;
		this.req = us.popdt.io.XHR();
		
		// Sychronously grab js files.
		if (this.req) {
			this.req.open("GET", js_path, false); // Asynchronous flag set to false
			this.req.send("");
			
			// Instead of assigning a function to onreadystatechange (which FF doesn't recognize for sychronous XHR)
			// we're waiting here for the readyState to equal 4 (ready). Since is potentially dangerous.
			if (this.req.readyState == 4) {
				if (this.req.status == 200) {
					document.write("<scr" + "ipt type=\"text/javascript\">" + this.req.responseText +"</scr" + "ipt>");
				} else {
					alert("There was a problem retrieving the XML data:\n" + this.req.statusText);
				}
			}
		}
	},
	/**
	 * Attach a script element to the DOM. Only occurs after page load.
	 * @param String js The JavaScript file to load
	 */
	DOMAttach: function(js) {
		var lib = us.popdt;
		var js_path = lib.config.PKG_DIR + "/" + js;

		this.head = (typeof this.head == "undefined") ? document.getElementsByTagName("head")[0] : this.head;

		var s = document.createElement("script");
		s.setAttribute("type","text/javascript");
		s.setAttribute("src",js_path);
		this.head.appendChild(s);
	}
}



/**
 * WINDOW
 * Encapsulates all functionality related to windows: Print, Popup, Non-Modal, etc
 * @author Dan Dean
 */
us.popdt.window = {
	// PRINT: Encapsulates all functionality relating to PRINTING THE PAGE
	print: {

		/**
		 * Send the current page to the printer
		 * @author	Dan Dean
		 */
		send: function() {
			if (typeof window.print != "undefined"){
				window.print();
				return true;
			} else {
				alert("Please use your web browsers Print button.");
				return false;
			}
		},

		/**
		 * Attaches PRINT functionality to any link on the page of "print_btn", or the supplied class name
		 * @author TBD
		 */
		attach: function() {
			alert('attach');
			return true;
		}
	}
}



/**
 * NAVIGATION
 * Things like redirection, select navigation, etc
 */
us.popdt.navigation = {
	redirect: function(where) {
		document.location = where;
	}
}


/**
 * Add and Remove events from objects
 */
us.popdt.event = {
	/**
	 * Adds an event to an object
	 * @param Object obj The object to attach an event to
	 * @param String type [load | blur | focus | etc]
	 * @param Function fn The function to call when the even fires
	 */
	add: function( obj, type, fn ) {
		if (obj.addEventListener) {
			obj.addEventListener( type, fn, false );
		} else if (obj.attachEvent) {
			obj["e"+type+fn] = fn;
			obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
			obj.attachEvent( "on"+type, obj[type+fn] );
		}		
	},
	/**
	 * The exact same as event.add, but removing instead of adding
	 */
	remove: function( obj, type, fn ) {
		if (obj.removeEventListener) {
			obj.removeEventListener( type, fn, false );
		} else if (obj.detachEvent) {
			obj.detachEvent( "on"+type, obj[type+fn] );
			obj[type+fn] = null;
			obj["e"+type+fn] = null;
		}
	},
	/**
	 * Stop an event from firing
	 * This does no work in Safari prior to 2.0.? (find webkit version)
	 */
	stop: function(e) {
		if (e) { // event object
			if (e.preventDefault) {	// W3C
				e.preventDefault();
				e.stopPropagation();
			} else {				// IE
				e.returnValue = false;
				e.cancelBubble = true;
			}
		} else {
			dbug.log("onSubmit: try to cancel submit");
		}
		return false;
	},
	/**
	 * Allows for the implementation of custom events on a per Object level
	 */
	CustomEvents: function() {
		/**
		 * Container object for custom events.
		 */
		this.events = {};
		/**
		 * Create a custom event
		 * @param String sEventName The name of your custom event
		 */
		this.create = function(sEventName) {
			this.events[sEventName] = [];
		}
		/**
		 * Fire a custom event
		 * @param String sEventName The name of the event to fire
		 */
		this.fire = function(sEventName) {
			if (typeof this.events[sEventName] == 'undefined') { return; }

			for (var i=0; i<this.events[sEventName].length; i++) {
				if (typeof this.events[sEventName][i] == 'undefined') { continue; }

				var id =		this.events[sEventName][i][0];
				var scope =		this.events[sEventName][i][1];
				var method =	this.events[sEventName][i][2];
				var arg =		this.events[sEventName][i][3] || [];
				scope[method].apply(scope, arg);

				if (dbug.customEventLogging === true) {
					dbug.log(id, sEventName, method);
				}
			}
			if (dbug.customEventLogging === true && this.events[sEventName].length == 0) {
				dbug.log(sEventName);
			}
		}
		/**
		 * Add a method to call when the event is fired
		 * @param String sEventName The name of the event to listen for
		 * @param Object oScope Optional, scope to call the method in. Default is Window
		 * @param String sMethod The method to call in the given scope
		 */
		this.addListener = function(sEventName, oListenScope, sListenMethod, aListenArguments) {
			var oListenScope=oListenScope, sListenMethod=sListenMethod, aListenArguments=aListenArguments;
			var sListenID = sEventName + '_' + Math.random().toString().split('.')[1];
			if (typeof sListenMethod == 'undefined') {
				sListenMethod = oListenScope;
				oListenScope = window;
			}
			if (typeof this.events[sEventName] != 'undefined') {
				this.events[sEventName].push([sListenID, oListenScope,sListenMethod,aListenArguments]);
				return sListenID;
			} else {
				return false;
			}
		}
		this.removeListener = function(sListenID) {
			var event_name = sListenID.split('_')[0];
			var ev = this.events[event_name];
			for (var i=0; i<ev.length; i++) {
				if (ev[i][0] == sListenID) {
					delete this.events[event_name][i];
					break;
				}
			}
		}
	}
}
$event = us.popdt.event;



/**
 * DOM
 * More advanced functionality will reside in an external dom.js file
 */
us.popdt.dom = {
	/**
	 * Create a new DOM node
	 * @param String nodeName The kind of node to create (div, br, p, etc);
	 * @param Object attributes Optional object map of attributes and values. Example: {id: 'myEl', class: 'myClass'}
	 * @param String/Number text Optional text to set as the textNode within the returned element
	 * @return DOM Node
	 * NOTE: dom.create is *heavily* influence by the prototype library
	 */
	create: function(nodeName /* obj attributes, str text */) {
		var node = document.createElement(nodeName);
		if (arguments.length == 3) {
			this._attributes(node, arguments[1]);
			node.appendChild(
				(typeof arguments[2] == 'string') ? this._text(arguments[2]) : node.appendChild(arguments[2])
			);
		} else if (arguments.length == 2) {
			if (typeof arguments[1] == 'string') {
				node.appendChild(this._text(arguments[1]))
			} else if (typeof arguments[1].nodeName != 'undefined') {
				node.appendChild(arguments[1]);
			} else if (typeof arguments[1] == 'object') {
				this._attributes(node, arguments[1]);
			}
		}
		return node;
	},
	/**
	 * Cycles through the supplied attributes and applies them to the supplied element
	 * @param DOMNode node The node to apply attributes to
	 * @param Object attributes A JSON Object of attributes: {className:'myclass', id:'myID'}
	 * WARNING: An attibute can NOT be named 'class', but must be 'className'
	 */
	_attributes: function(node, attributes) {
		for (attr in attributes) {
			switch (true) {
				case (attr=='className'):
					node.className = attributes[attr];
					break;
				case (attr=='htmlFor'): // must pass htmlFor, as 'for' is a keyword
					node.htmlFor = attributes[attr];
					break;
				default:
					node.setAttribute(attr, attributes[attr]);
			}
		}
	},
	/**
	 * Creates and returns a text node with the supplied value
	 */
	_text: function(text) {
		return document.createTextNode(text);
	},
	
	/**
	 * Removes nodes from the DOM
	 * @param String/Object The node you want to remove passed via Node OR ID string
	 * @return Object/Array The Element or an Array of Elements removed
	 * FIXME This method currently won't accept an Array of elements or element ID's
	 */
	remove: function(Elements) {
		var removed = new Array();
		for (var i=0; i<arguments.length; i++) {
			var el = (typeof arguments[i] == 'string') ? $dom.getById(arguments[i]) : arguments[i] ;
			removed.push(el.parentNode.removeChild(el));
		}
		return (removed.length > 1) ? removed : removed[0];
	},
	
	/**
	 * Gets the requested element(s).
	 * If you pass in a single string, returns FALSE on failure or the DOMNode
	 * on success. If you pass in many strings an Array of all found elements 
	 * is returned, which means an Empty array on failure
	 * @param String ElementIDs A comma-seperated list of element ID's
	 * @return Boolean/Array/DOMNode
	 */
	getById: function(ElementIDs) {
		var elements = new Array();
		var result;
		for (var i=0; i<arguments.length; i++) {
			if (el = document.getElementById(arguments[i])) {
				elements.push(el);
			}
		}
		switch (true) {
			case (arguments.length == 1 && elements.length == 1):
				result = elements[0];
				break;
			case (arguments.length > 1):
				result = elements;
				break;
			default:
				result = false;
				break;
		}
		return result;
	},
	
	/**
	 * Returns an Array of all found elements by supplied tag name
	 * @param String ElementTagNames A comma seperated list of NodeNames
	 * @return Array An Array of all found elements. An empty Array on failure
	 */
	getByTag: function(ElementTagNames) {
		var elements = new Array();
		for (var i=0; i<arguments.length; i++) {
			els = document.getElementsByTagName(arguments[i]);
			for (var j=0; j<els.length; j++) {
				elements.push(els[j]);
			}
		}
		return elements;
	},
	
	/**
	 * Get all nodes with the given className
	 * @param String classNames A list of node.className(s) to check for
	 * @return An array of elements or an empty array on failure
	 */
	getByClass: function(classNames) {
		var o = new Array();
		var all = (typeof document.getElementsByTagName != 'undefined') ? document.getElementsByTagName('*') : document.all ;
		for (var i=0; i<all.length; i++) {
			(this.hasClass(all[i],arguments)) ? o.push(all[i]) : true ;
		}
		return o;
	},

	/**
	 * Add a class to an element
	 * @param Object node The element to work on
	 * @param String cls The class to add to the element
	 */
	addClass: function(node, cls) {
		var c = node.className.split(' ');
		(!c.contains(cls)) ? c.push(cls) : true ;
		node.className = c.join(' ');
	},

	/**
	 * Remove a class from an element
	 * @param Object node The element to work on
	 * @param String cls The class to remove from the element
	 */
	removeClass: function(node, cls) {
		var c = node.className.split(' ');
		(c.contains(cls)) ? c.remove(cls) : true ;
		node.className = c.join(' ');
	},
	
	/**
	 * Swap one class with another. If the first class doesn't exist the new 
	 * class is added.
	 * @param Object node The element to work on
	 * @param String sOldClass The class to swap out
	 * @param String sNewClass The class to swap in
	 */
	swapClass: function(node, sOldClass, sNewClass) {
		if (this.hasClass(node,sOldClass)) {
			this.removeClass(node, sOldClass);
		}
		this.addClass(node, sNewClass);
	},

	/**
	 * Check an element for the given className(s)
	 * @param {Object} node The node to check for a className
	 * @param {String} classNames A list of classNames to check for
	 * @return Boolean
	 */
	hasClass: function(node, classNames) {
		var args = arguments;
		var start = 1;
		if (typeof arguments[1] == 'object') {
			args = arguments[1];
			start = 0;
		}
		var success = false;
		for (var i=start; i<args.length; i++) {
			if (node.className.split(' ').contains(args[i])) {
				success = true;
				break;
			}
		}
		return success;
		// return (node.className.split(' ').contains(cls));
	},
	
	/**
	 * Gets a property from an elements computed style in a x-browser fashion
	 * @param DOMNode oNode The element to work on
	 * @param String sProperty The CSS property to find
	 */
	getComputedStyle: function(oNode, sProperty) {
		var computedStyle = null;
		if (typeof oNode.currentStyle != 'undefined') {
			computedStyle = oNode.currentStyle;
		} else {
			computedStyle = document.defaultView.getComputedStyle(oNode,null);
		}
		return computedStyle[sProperty];
	},

	/**
	 * $dom.get() has been deprecated. Use getById and getByTag instead.
	 */
	get: function() {
		dbug.error('$dom.get() has been deprecated. Use getById and getByTag instead.');
	}
}
$dom = us.popdt.dom; // shortcut to the DOM methods