// <%@ Import namespace="POP.Core" %>
// <% // The above IMPORT statement should remain commented out - it will work just fine. %>

// ======================================================================= //
// INITIALIZE LIBRARY NAMESPACE
if (typeof window.PopJavaScriptFramework == "undefined") {
	PopJavaScriptFramework = new Object();
}
// INITIALIZE VERSION NAMESPACE
if (typeof window.PopJavaScriptFramework.v1B1 == 'undefined') {
	PopJavaScriptFramework.v1B1 = new Object();
}
// SET THE VERSION ALIASED BY $POP
PopJavaScriptFramework.setVersion = function(sVersion) {
	if (typeof PopJavaScriptFramework[sVersion] == 'undefined') {
		throw new Error(
			'Config Error: The version requested (' + sVersion +
			') has not yet been instantiated or does not exist.'
		);
	} else {
		this.version = sVersion;
		$pop =	PopJavaScriptFramework[sVersion];
		// make sure dom is present
		if (typeof $pop.dom != 'undefined') { $dom = $pop.dom; }
		// make sure event is present
		if (typeof $pop.event != 'undefined') { $event = $pop.event; }
	}
}
// FIND OUT WHICH VERSION IS CURRENTLY USED BY $POP
PopJavaScriptFramework.getVersion = function() {
	return this.version;
}

/**
 * Requires that a package be included in order to run the subsequent scripts
 * @param string sObjectName The object/package to require
 * @param string sFromScript The name of the object/package/script that requires another object/package
 * @param object oScope The scope to check in - defaults to our Library. Useful for checking for global objects
 */
PopJavaScriptFramework.require = function(sObjectName,sFromScript, oScope) {
	var scope = (typeof oScope == 'undefined') ? PopJavaScriptFramework[PopJavaScriptFramework.getVersion()] : oScope ;
	if (typeof scope[sObjectName] == 'undefined') {
		throw new Error(
			'Inclusion Error: ' +
			'You have not included [' + sObjectName + '] in your script references, ' +
			'which is required before [' + sFromScript + ']');
	}
}

/**
 * Extends the functionality of one object with the functionality of another
 * @param object oDestination The object to extend
 * @param object oSource The object to extend off of
 */
Object.extend = function(oDestination, oSource) {
	for (var property in oSource) {
		oDestination[property] = oSource[property];
	}
	return oDestination;
}

/**
 * Extends functionality but does not override if already present in oDestination
 * @param object oDestination The Object to augment
 * @param object oSource The Object Literal to augment oDestination with.
 */
Object.augment = function(oDestination, oSource) {
	for (var property in oSource) {
		if (typeof oDestination[property] == "undefined") {
			oDestination[property] = oSource[property];
		}
	}
	return oDestination;
}


/**
 * Holds reuseable bits of code for use by Library Classes and Functions
 * @author	Dan Dean
 */
PopJavaScriptFramework.v1B1.snippet = {
	// Used by String.extractScripts, String.stripScripts
	ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)'
}

/**
 * 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(PopJavaScriptFramework.v1B1.snippet.ScriptFragment, 'img'), '');
	},
	
	extractScripts: function() {
		var matchAll = new RegExp(PopJavaScriptFramework.v1B1.snippet.ScriptFragment, 'img');
		var matchOne = new RegExp(PopJavaScriptFramework.v1B1.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 parameter in a string
	 * Example: 'index.aspx?one=1'.addQueryParams('two',2) will return:
	 * 'index.aspx?one=1?two=2'
	 */
	addQueryParameter: function(key,value) {
		try { // get string before query params
			var front = this.match(/^.*[\?]/)[0];
		} catch (e) {
			var front = '';
		}
		var pairs = this.toQueryParams();
		pairs[key] = value;
		var i = 0;
		for (var j in pairs) {
			if (typeof pairs[j] != 'undefined') {
				front += (i>0) ? '&' : '';
				front += (j + '=' + pairs[j]); 
				i++;
			}
		}
		return front;
	},
	
	removeQueryParameter: function(key) {
		try { // get string before query params
			var front = this.match(/^.*[\?]/)[0];
		} catch (e) {
			var front = '';
		}
		var pairs = this.toQueryParams();
		for (var i in pairs) {
			if (i != key) {
				front = front.addQueryParameter(i,pairs[i]);
			}
		}
		return front;
	},
	
	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);
		}
	}
});

PopJavaScriptFramework.v1B1.objectTemplate = {
	versionable: function() {
		this.major = null;
		this.minor = null;
		this.rev = null;
	
		this.toString = function() {
			return this.major + '.' + this.minor + '.' + this.rev;
		}
		this.set = function(sVersion) {
			var v = sVersion.split('.');
			this.major =	parseInt(v[0]) || 0;
			this.minor =	parseInt(v[1]) || 0;
			this.rev =		parseInt(v[2]) || 0;
		}
		this.test = function(sVersion) { // Ex: 1.8, '1.8.1', 500, [5,2,6]
			var v = sVersion.toString().split('.');
			for (var i=0; i<3; i++) {
				v[i] = parseInt(v[i]) || 0;
			}
			switch (true) {
				case (v[0] < this.major):
					return true;
				case (v[0] <= this.major && v[1] < this.minor):
					return true;
				case (v[0] <= this.major && v[1] <= this.minor && v[2] <= this.rev):
					return true;
				default:
					return false;
			}
		}
	}
}

// ======================================================================= //
/**
 * 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.
 */
PopJavaScriptFramework.v1B1.config = {
	root: root, //"<%=Config.Root %>",
	rootSecure: rootSecure, //"<%=Config.RootSecure %>",
	rootVirtual: rootVirtual, //"<%=Config.RootVirtual %>",
	pkgDir: pkg_dir, //"<%=Config.RootVirtual %>/_include/popdt/src"
	cookieDomain: '.popmultimedia.com'
}
/**
 * Google Analytics Configuration information.
 */
PopJavaScriptFramework.v1B1.config.analytics = {
	enabled: true,
	clickLogging: true,	// Print clicklog instead of tracking?
	domains: [				// You must put all possible live domains in the domains array
		location.hostname,
		PopJavaScriptFramework.v1B1.config.root.getHostname(),
		'dandean.popmultimedia.com',
		'popmultimedia.com',
		'dandean'
	],
	params: {			// GA Variables
		_uacct: "UA-331898-1",
		_udn: "popmultimedia.com",
		_userv: 2
	}
}
// ======================================================================= //

// SET THE DEFUALT VERSION
PopJavaScriptFramework.setVersion('v1B1');


/**
 * DOM
 * More advanced functionality will reside in an external dom.js file
 * @alias $dom
 */
PopJavaScriptFramework.v1B1.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/Array} content	Optional text to set as the textNode OR array of DOMNodes to add to the returned element
	 * @return {DOMNode} DOMNode
	 * NOTE: dom.create is *heavily* influence by the prototype library
	 */
	create: function(nodeName /* obj attributes, str text */) {
		var node = document.createElement(nodeName);
		for (var i=1; i<arguments.length; i++) {
			if (typeof arguments[i] == 'string' && (arguments[i].indexOf('<') > -1 && (arguments[i].indexOf('</') > -1 || arguments[i].indexOf('/>') > -1))) {
				// HTML STRING
				node.innerHTML += arguments[i];
			} else if (typeof arguments[i] == 'string') {
				// STRING
				node.appendChild(this._text(arguments[i]));
			} else if (typeof arguments[i] == 'object' && typeof arguments[i].nodeName != 'undefined') {
				// NODE
				node.appendChild(arguments[i]);
			} else if (typeof arguments[i] == 'object' && typeof arguments[i].length != 'undefined') {
				// ARRAY OF NODES OR NODELIST
				for (var j=0; j<arguments[i].length; j++) {
					node.appendChild(arguments[i][j].cloneNode(true));
				}
			} else if (typeof arguments[i] == 'object') {
				// ATTRIBUTES
				this._attributes(node, arguments[i]);
			}
		}
		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'}
	 * @return void
	 * WARNING: An attibute can NOT be named 'class', but must be 'className'
	 */
	_attributes: function(node, attributes) {
		for (var 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
	 * @param {String} text
	 */
	_text: function(text) {
		return document.createTextNode(text);
	},
	
	/**
	 * Removes nodes from the DOM
	 * @param {String/Object} Elements 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') ? this.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++) {
			var el;
			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 = PopJavaScriptFramework.v1B1.dom;


/**
 * Add and Remove events from objects
 */
PopJavaScriptFramework.v1B1.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
	 * @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
	 */
	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)
	 * @param {DOMEvent} DOMEvent
	 */
	stop: function(DOMEvent) {
		var e = DOMEvent;
		if (e) { // event object
			if (e.preventDefault) {	// W3C
				e.preventDefault();
				e.stopPropagation();
			} else {				// IE
				e.returnValue = false;
				e.cancelBubble = true;
			}
		} else {
		}
		return false;
	},
	/**
	 * Returns the DOMNode associated with an event
	 * @param {DOMEvent} DOMEvent
	 */
	getTarget: function(DOMEvent) {
		var e = DOMEvent, node;
		if (typeof e.target != 'undefined') { // w3c and safari
			node = (e.target.nodeType == 3) ? e.target.parentNode : e.target;
		} else if (typeof e.srcElement != 'undefined') { // IE
			node = e.srcElement;
		}
		return node;
	},
	/**
	 * 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 (typeof dbug != 'undefined' && dbug.customEventLogging === true) {
					dbug.log(id, sEventName, method);
				}
			}
			if (typeof dbug != 'undefined' && 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} oListenScope Optional, scope to call the method in. Default is Window
		 * @param {String} sListenMethod The method to call in the given scope
		 * @param {Array} aListenArguments
		 */
		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 = PopJavaScriptFramework.v1B1.event;


/**
 * In/Out namespace. Place code to deal with things like XHR, iframe, etc here.
 */
PopJavaScriptFramework.v1B1.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;
	}
}


PopJavaScriptFramework.require('io','client');

/**
 * Handles environment detection
 */
PopJavaScriptFramework.v1B1.client = {
	platform: '',
	browser: '',
	version: {
		init: function() {
			this.superclass = PopJavaScriptFramework.v1B1.objectTemplate.versionable;
			this.superclass();
		}
	},
	plugin: {
		embedMethod: (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) ? 'embed' : 'object',
		swf: {
			detect: function() {
				this.superclass = PopJavaScriptFramework.v1B1.objectTemplate.versionable;
				this.superclass();
				
				if (PopJavaScriptFramework.v1B1.client.plugin.embedMethod == 'embed') {
					var plugin = navigator.plugins["Shockwave Flash"];
					var version = 0;
					if (plugin && plugin.description) {
						version = plugin.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".");
						this.set(version);
					}
				} else {
					var axo = null;
					try {
						axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
					} catch (e) {
						try {
							axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
						} catch (e) {
							try {
								axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
							} catch (e) {}
						}
					}
					if (axo != null) {
						this.set(axo.GetVariable("$version").split(" ")[1].split(",").join('.'));
					}
				}
			}
		}
	},
	capable: {
		xhr: false
	},
	detectBrowser: function() {
		var browser = '';
		var version = '';
		var userAgent = navigator.userAgent.toLowerCase();
		switch(true) {
			case (userAgent.indexOf("applewebkit") != -1):
				browser = 'webkit';
				var i = userAgent.indexOf("applewebkit");
				var v = userAgent.substring(i).split(" ")[0].split("/")[1];
				break;
			case (userAgent.indexOf("opera") != -1):
				browser = 'opera';
				var i = userAgent.indexOf("opera");
				var v = userAgent.substring(i + 6).split(" ")[0];
				break;
			case (userAgent.indexOf("gecko") != -1 && userAgent.indexOf("rv:") != -1):
				browser = 'gecko';
				var i = userAgent.indexOf("rv:");
				var v = userAgent.substring(i + 3).split(")")[0];
				break;
			case (userAgent.indexOf("msie") != -1):
				browser = 'msie';
				var i = userAgent.indexOf("msie");
				var v = userAgent.substring(i + 5).split(";")[0];
				break;
		}
		this.browser = browser;
		this.version.init();
		this.version.set(v);
	},
	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;
	},
	detectXHR: function() {
		var xhr = PopJavaScriptFramework.v1B1.io.XHR();
		this.capable.xhr = (xhr === false) ? false : true ;
		delete xhr;
	}
	
}
PopJavaScriptFramework.v1B1.client.detectXHR();
PopJavaScriptFramework.v1B1.client.detectBrowser();
PopJavaScriptFramework.v1B1.client.detectPlatform();
PopJavaScriptFramework.v1B1.client.plugin.swf.detect();


PopJavaScriptFramework.require('dom','plugin');

PopJavaScriptFramework.v1B1.plugin = {
	/**
	 * @constructor Base
	 * @param {String} sId The ID to assign to the instance of the class
	 */
	Base: function(sId) {
		this.embedMethod = PopJavaScriptFramework.v1B1.client.plugin.embedMethod;
		
		// OBJECT VARIABLES
		this.params = {};
		this.attributes = {};
		
		// GETTERS/SETTERS
		this.setAttribute =	function(name, value)	{ this.attributes[name] = value;}
		this.getAttribute =	function(name)			{ return this.attributes[name];	}
		this.getAttributePair =	function(name) {
			var attributeName = (name == 'name') ? 'id' : name ; // force name and id attributes to match
			if (typeof this.getAttribute(attributeName) != 'undefined') {
				return '{0}="{1}"'.format(name, this.getAttribute(attributeName));
			} else {
				return '';
			}
		}
		this.addParam =		function(name, value)	{ this.params[name] = value;	}
		this.getParam =		function(name)			{ return this.params[name];		}
		this.getParamTag =	function(name, value)	{
			return '<param name="{0}" value="{1}" />'.format(name, this.getParam(name));
		}
		
		this.toHTML = function() {
			var pluginNode = "";
			if (this.embedMethod == 'embed') { // netscape plugin architecture
				pluginNode = '<embed {0} {1} {2} {3} {4} {5} {6} />'.format(
								this.getAttributePair('type'),this.getAttributePair('src'),
								this.getAttributePair('width'),this.getAttributePair('height'),
								this.getAttributePair('id'),this.getAttributePair('name'),
								'{0}' /*placeholder for params*/);
				var paramString = '';
				for(var key in this.params){
					paramString += '{0}="{1}"'.format(key,this.params[key]);
				}
				pluginNode = pluginNode.format(paramString);
			} else { // PC IE
				pluginNode = '<object {0} {1} {2} {3}> {4} </object>'.format(
								this.getAttributePair('id'),this.getAttributePair('classid'),
								this.getAttributePair('width'),this.getAttributePair('height'),
								'{0}' /*placeholder for params*/);
				var paramNodes = '<param name="movie" value="'+ this.getAttribute('src') +'" />';
				for(var key in this.params) {
					paramNodes += this.getParamTag(key,this.params[key]);
				}
				pluginNode = pluginNode.format(paramNodes);
			}
			// dbug.log(pluginNode);
			return pluginNode;
		}
		this.write = function(sId) {
			// escape output so it shows up on page as a string instead of actual html
			$dom.getById(sId).innerHTML = this.toHTML().escapeHTML();
		}
		
		// OBJECT INITIALIZATION
		this.setAttribute('id',sId);
		this.setAttribute('src','fake/src/path');
		this.setAttribute('type','fake/mimetype');
		this.setAttribute('classid','clsid:FAKE-CLASS-ID');
	},
	
	/**
	 * @constructor SWF
	 * @extends PopJavaScriptFramework.v1B1.plugin.Base
	 * @param {String}	sSwf		Path to the SWF file
	 * @param {String}	sId			ID for the instance of the Class
	 * @param {Integer}	iWidth		Width of your swf
	 * @param {Integer}	iHeight		Height of your swf
	 * @param {Mixed}	mVersion	Flashplayer version to require; String, Number or Array
	 * @param {String}	sColor		Background color to set for your swf
	 * @param {String}	sQuality	Quality of playback
	 */
	SWF: function(sSwf, sId, iWidth, iHeight, mVersion, sColor, sQuality) {
		// INHERIT FROM BASE
		this.superclass = PopJavaScriptFramework.v1B1.plugin.Base;
		this.superclass(sId);
		
		// OBJECT VARIABLES
		this.variables = {};
		this.wrapper;
		
		// FAILURE
		this.detect = true;
		this.failureRedirectUrl = false;
		this.bypassLink = false;
		
		this.bypassDetection = function() {
			this.detect = false;
			this.write();
		}
		
		this.setBypassLink = function(swfObject) {
			this.bypassLink = $dom.create('a',{href:'#',onclick:swfObject + '.bypassDetection(); return false;'},'Bypass Detection');
		}
		
		this.displayBypassLink = function(sId) {
			var l = $dom.create('p',this.bypassLink);
			$dom.getById(sId).appendChild(l);
		}
		
		// GETTERS/SETTERS
		this.addVariable =		function(name, value)	{ this.variables[name] = value;	}
		this.getVariable =		function(name)			{ return this.variables[name];	}
		this.getVariables =		function()				{ return this.variables;		}
		this.getVariablePairs =	function() {
			var variablePairs = new Array();
			var key;
			var variables = this.getVariables();
			for(key in variables){
				variablePairs.push(key +"="+ variables[key]);
			}
			return variablePairs;
		}
		
		this.appendVariables = function(html) {
			var vars = this.getVariablePairs();
			if (vars.length < 1) { return html; }
			if (this.embedMethod == 'embed') {
				html = html.replace("/>", 'flashvars="{0}" />'.format(vars.join('&')));
			} else {
				html = html.replace('</object>','<param name="flashvars" value="{0}" /></object>'.format(vars.join('&')));
			}
			return html;
		}
		
		/**
		 * Writes the SWF to the page
		 * @param {String} sId The id of the element that you want to write the SWF into
		 */
		this.write = function(sId) {
			this.wrapper = (typeof sId != 'undefined') ? sId : this.wrapper ;
			if(!PopJavaScriptFramework.v1B1.client.plugin.swf.test(mVersion) 
			   && this.failureRedirectUrl != false 
			   && this.detect) {
				// redirect if set
				location.href = this.failureRedirectUrl;
				return;
			} else if(!PopJavaScriptFramework.v1B1.client.plugin.swf.test(mVersion) && this.detect) {
				// display bypass link if set, then fail silently
				if (this.bypassLink !== false) {
					this.displayBypassLink(this.wrapper);
				}
				return;
			} else {
				// write the swf
				var html = this.appendVariables(this.toHTML());
				$dom.getById(this.wrapper).innerHTML = html;
				return;
			}
		}
		
		// OBJECT INITITIALIZATION
		this.setAttribute('src',sSwf);
		this.setAttribute('width',iWidth);
		this.setAttribute('height',iHeight);
		this.setAttribute('version',mVersion);
		this.setAttribute('type','application/x-shockwave-flash');
		this.setAttribute('classid','clsid:D27CDB6E-AE6D-11cf-96B8-444553540000');
		
		this.addParam('bgcolor', (sColor || '#FFFFFF'));
		this.addParam('quality', (sQuality || 'high'));
	},
	/**
	 * Placeholder object for the QT Plugin Object
	 * @constructor QT
	 * @param {String} sType The type of quicktime media, audio or video
	 * @param {String} sId The id of the media file
	 * @param {Integer} iWidth
	 * @param {Integer} iHeight
	 * @param {Mixed} mVersion The QT version to require. To check against revision, pass a string, such as "1.8.1"
	 * @param {Bool} bAutoStart
	 * @param {String} sPlaceHolder Path to the placeholder image
	 */
	QT: function(sType, sId, iWidth, iHeight, mVersion, bAutoStart, sPlaceHolder) {
		dbug.error('Plugin.QT is not yet implemented.')
	},
	Real: function(sType, sId, iWidth, iHeight, mVersion) {
		dbug.error('Plugin.Real is not yet implemented.')
	}
}

