/*
 * Glow JavaScript Library
 * Copyright (c) 2008 British Broadcasting Corporation
 */
if (window.glow) { throw new Error("glow Core module already included"); }
/*
Namespace: glow
*/
var glow = (function() {
	/*
	PrivateVar: moduleRegister
		Holds info on which modules are registered {name:true}
	*/
	var moduleRegister = {glow: true},
		/*
		PrivateVar: regexEscape
			For escaping strings to go in regex
		*/
		regexEscape = /([$^\\\/()|?+*\[\]{}.-])/g,
		/*
		PrivateVar: ua
			A lowercase representation of the user's useragent string
		*/
		ua = navigator.userAgent.toLowerCase();

	return {
		/*
		Property: VERSION
			Version number of the module.
		*/
		VERSION: "0.4.0",
		
		/*
		Property: isReady
			Set to true when the DOM is ready. If the library is included after 
			the DOM is loaded, you should manually set this to true
		*/
		isReady: false,
		
		/*
		Property: isSupported
			Set to true in supported user agents.
		*/
		/* This is set at the bottom of the page to contain conditional comment scope */
		
		/*
		Property: env
			Contains information about the environment.

		Properties:
			rhino - true if the user agent is running inside the Rhino javascript engine.
			gecko - Gecko release number (eg 20070725) or null
			ie - IE version number (eg 6.0) or null
			opera - Opera version (eg 8.02) or null
			webkit - Webkit version number before 2nd decimal point (eg 419.3)
			khtml - konqy (khtml/N.N.N - take major and minor and leave point)
			standardsMode - True if the browser reports itself to be in 'standards mode'
		*/
		env: function(){
			var nanArray = [0, NaN],
				opera = Number((/opera[\s\/]([\d\.]+)/.exec(ua) || nanArray)[1]),
				ie = opera ? NaN : Number((/msie ([\d\.]+)/.exec(ua) || nanArray)[1]);
			return {
				rhino  : !!window.load,
				gecko  : !window.load && Number((/gecko\/(\d+)/.exec(ua) || nanArray)[1]),
				ie     : ie,
				opera  : opera,
				webkit : Number((/applewebkit\/(\d+(?:\.\d+)?)/.exec(ua) || nanArray)[1]),
				khtml  : Number((/khtml\/(\d+(?:\.\d+)?)/.exec(ua) || nanArray)[1]),
				standardsMode : document.compatMode != "BackCompat" && (!ie || ie >= 6)
			}
		}(),

		/*
		Method: module
			Registers a new module with the library, checking version numbers & dependencies.
		
		Arguments:
			sName - (String) Namespaced name of the module, eg "glow.dom", "glow.ui.lightbox"
			sVer - (String) Version of the module, must match Core module version, eg "0.4.0"
			oParams - (Object) Additional parameters as object literal, see below
		
		oParams props:
			require - (Array) array of modules required, eg ["glow.dom", "glow.ui.lightbox"]
			implementation - (Function) function which returns module object / function
			
		Returns:
			<glow>
		
		Example:
			(code)
			glow.module("glow.ui", "1.5", {
				require: ["glow.dom", "glow.lang"],
				implementation: function() {
					var privateVar = "foo";
					function privateMethod() {
						return "bar";
					}
					
					return {
						publicVar: "hello",
						publicMethod: function() {
							return "bar";
						}
					};
				}
			})
			(end)
		*/
		module: function(sName, sVer, oParams) {
			var i, namePartsLen, nameParts,
				objRef = window; //holds the parent object for the new module
			//check version number match core version
			if (sVer != this.VERSION) {
				throw new Error("Cannot register " + sName + ": Version mismatch");
			}
			
			//check dependencies loaded
			if (oParams.require) {
				if (typeof oParams.require == 'string') { oParams.require = [ oParams.require ]; }
				for (i = 0; oParams.require[i]; i++) {
					//check exists
					if (!moduleRegister[oParams.require[i]]) {
						//module is missing
						var missingModule = oParams.require[i];
						//check again ondomready to detect if modules are being included in wrong order
						this.ready(function() {
							if (moduleRegister[missingModule]) {
								throw new Error("Module " + missingModule + " is included after modules that depend on it, include it sooner.");
							}
						});
						throw new Error("Module " + missingModule + " required in " + sName);
					}
				}
			}
			
			//create module
			nameParts = sName.split("."); //split module name into parts
				
			//make empty objects for missing parts, except the last part which will be the module
			for (i = 0, namePartsLen = nameParts.length; i < namePartsLen - 1; i++) {
				if (!objRef[nameParts[i]]) {
					objRef[nameParts[i]] = {};
				}
				objRef = objRef[nameParts[i]];
			}
			//create module
			objRef[nameParts[i]] = oParams.implementation ? oParams.implementation() : {};
			moduleRegister[sName] = true;
			return this;
		},
		
		/*
		Method: ready
			Calls a function when/if the DOM is ready & the browser is supported.
		
		Arguments:
			callback - (Function) Function to run
			
		Returns:
			<glow>
		
		Example:
			(code)
			glow.ready(function() {
				alert("DOM Ready!");
			});
			(end)
		*/
		ready: function(callback) {
			if (glow.isSupported) { this.onDomReady(callback); }
			return this;
		},
		
		/*
		Method: onDomReady
			Calls a function when/if the DOM is ready. Same as <ready> but 
			ignores <isSupported>.
		
		Arguments:
			callback - (Function) Function to run
			
		Returns:
			<glow>
		
		Example:
			(code)
			glow.onDomReady(function() {
				alert("DOM Ready!");
			});
			(end)
		*/
		onDomReady: function(f) {
			//just run function if already ready
			if (this.isReady) {
				f();
			} else {
				var oldLoadFunc = this._lf;
				this._lf = function () {
					oldLoadFunc();
					f();
				};
			}
		},
		
		/*
		PrivateMethod: _lf
			run when dom is ready, becomes a queue of all ready functions.
			This is maintained within the library as Safari sometimes
			runs the ready functions in a different order
		*/
		_lf: function() { },
		
		/*
		Namespace: glow.lang
		*/
		lang: {
			/*
			Method: trim
				Removes leading and trailing whitespace from a string.
			
			Arguments:
				sStr - (String) A string to trim
				
			Returns:
				String without leading and trailing whitespace.
			
			Example:
				(code)
				glow.lang.trim("  Hello World  "); // "Hello World"
				(end)
			*/
			trim: function(sStr) {
				//this optimisation from http://blog.stevenlevithan.com/archives/faster-trim-javascript
				return sStr.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1');
			},
			
			/*
			Method: toArray
				Converts an array-like object to a real array.
			
			Arguments:
				aArrayLike - An array-like object
				
			Returns:
				Array
			
			Example:
				(code)
				var a = glow.lang.toArray(document.getElementsByTagName('div'));
				(end)
			*/
			toArray: function(aArrayLike) {
				if (aArrayLike.constructor == Array) {
					return aArrayLike;
				}
				//use array.slice if not IE? Could be faster
				var r = [], i=0, len = aArrayLike.length;
				for (; i < len; i++) {
					r[i] = aArrayLike[i];
				}
				return r;
			},
			
			/*
			Method: apply
				Copies properties from one object to another
			
			Arguments:
				destination - this is the main object
				source - properties of this object will be copied onto a clone of destination
				
			Returns:
				Object
			
			Example:
				(code)
				var obj = glow.lang.apply({foo: "hello", bar: "world"}, {bar: "everyone"});
				//results in {foo: "hello", bar: "everyone"}
				(end)
			*/
			
			apply: function(destination, source) {
				for (var i in source) {
					destination[i] = source[i];
				}
				return destination;
			},
			
			/*
			Method: map
				Runs a function for each element of an array and returns an array of the results.

			Arguments:
				arr - (array) the array.
				callback - (function) the function to run on each element. This
				thisp - (optional object) the invocant for the callback function (the array is used
						if not specified.

			Returns:
				A new array containing one element for each value returned from the callback.

			Example:
				(code)
				var days = glow.lang.map([1, 3, 5], function (pos) { return 'mtwtfss'.charAt(pos - 1) });
				(end)
			*/
			map: function (arr, callback) {
				if (Array.prototype.map) { return Array.prototype.map.call(arr, callback, arguments[2]); }
				if (typeof callback != "function") { throw new TypeError(); }
				var len = arr.length,
					res = new Array(len),
					thisp = arguments[1] || arr,
					i = 0;
				for (; i < len; i++) {
					if (i in arr) {
						res[i] = callback.call(thisp, arr[i], i, arr);
					}
				}
				return res;				
			},

			/*
			Method: replace
				Makes a replacement in a string. Has the same interface as the builtin
				String.prototype.replace method, but takes the input string as the first
				parameter. In general the native string method should be used unless you
				need to pass a function as the second parameter, as this method will work
				accross our supported browsers.

			Arguments:
				str - (string) the input string.
				pattern - (string|RegExp) the string or regular expression to match.
				replacement - (string|function) the string to make replacements with, or a
							  function to generate the replacements.

			Returns:
				A new string with the replacement(s) made.

			Example:
				(code)
				var myDays = '1 3 6';
				var dayNames = glow.lang.replace(myDays, /(\d)/, function (day) {
					return " MTWTFSS".charAt(day - 1);
				});
				// dayNames now contains "M W S"
				(end)
			*/
			replace: (function () {
				var replaceBroken = "g".replace(/g/, function () { return 'l'; }) != 'l',
					def = String.prototype.replace;
				return function (inputString, re, replaceWith) {
					var pos, match, last, buf;
					if (! replaceBroken || typeof(replaceWith) != 'function') {
						return def.call(inputString, re, replaceWith);
					}
					if (! (re instanceof RegExp)) {
						pos = inputString.indexOf(re);
						return pos == -1 ?
							inputString :
							def.call(inputString, re, replaceWith.call(null, re, pos, inputString));
					}
					buf = [];
					last = re.lastIndex = 0;
					while ((match = re.exec(inputString)) != null) {
						pos = match.index;
						buf[buf.length] = inputString.slice(last, pos);
						buf[buf.length] = replaceWith.apply(null, match);
						if (re.global) {
							last = re.lastIndex;
						} else {
							last = pos + match[0].length;
							break;
						}			
					}
					buf[buf.length] = inputString.slice(last);
					return buf.join("");
				};
			})(),

			/*
			Method: interpolate
				Replaces placeholders in a string with data from an object
			
			Arguments:
				template - The string containing {placeholders}
				data - Object where the key is the placeholder name
				
			Returns:
				String
			
			Example:
				(code)
				var data = {name: "Domino", colours: "black & white"};
				var template = "My cat's name is {name}. His colours are {colours}";
				var result = glow.lang.interpolate(template, data);
				//result == "My cat's name is Domino. His colours are black & white"
				(end)
			*/
			interpolate: function(template, data) {
				var r = template, i;
				for (i in data) {
					r = r.replace(new RegExp("\\{" + i.replace(regexEscape, "\\$1") + "\\}", "g"), data[i]);
				}
				return r;
			},
			/*
			Method: hasOwnProperty
				http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object:hasOwnProperty
				Safari 1.3 doesn't support Object.hasOwnProperty, use this method instead.
				
			Arguments:
				obj - The object to check
				property - String property name
			
			Returns:
				Returns false if a property doesn't exist in an object, or it was inherited from
				the object's prototype. Otherwise, returns true
				
			*/
			hasOwnProperty: {}.hasOwnProperty ? //not supported in Safari 1.3
				function(obj, prop) {
					return obj.hasOwnProperty(prop);
				} :
				function(obj, prop) {
					var propVal = obj[prop], //value of the property
						objProto = obj.__proto__, //prototype of obj
						protoVal = objProto ? objProto[prop] : {}; //prototype val
					if (propVal !== protoVal) {
						return true;
					}
					//try changing prototype and see if obj reacts
					var restoreProtoVal = glow.lang.hasOwnProperty(objProto, prop),
						tempObjProtoVal = objProto[prop] = {},
						hasOwn = (obj[prop] !== tempObjProtoVal);
					
					delete objProto[prop];
					if (restoreProtoVal) {
						objProto[name] = tempObjProtoVal;
					}
					return hasOwn;
				},
			
			/*
			Method: extend
				Copies the prototype of one object to another. Also, the 'subclass' can also access
				the 'base class' via subclass.base
				
			Arguments:
				sub - Class to copy the prototype to
				base - Class to copy the prototype from
			
			Example:
				(code)
				function MyClass(arg) {
					this.prop = arg;
				}
				MyClass.prototype = {
					showProp: function() { alert(this.prop); }
				};
				function MyOtherClass(arg) {
					//call the base class's constructor
					arguments.callee.base.apply(this, arguments);
				}
				glow.lang.extend(MyOtherClass, MyClass);
				glow.lang.apply(MyOtherClass.prototype, {
					setProp: function(newProp) { this.prop = newProp; }
				});
				
				var test = new MyOtherClass("hello");
				test.showProp(); // alerts "hello"
				test.setProp("world");
				test.showProp(); // alerts "world"
				(end)
				
			*/
			extend: function(sub, base) {
				var f = function () {}, p;
				f.prototype = base.prototype;
				p = new f();
				sub.prototype = p;
				p.constructor = sub;
				sub.base = base;
			}
		}
	};
})();

//run queued ready functions when DOM is ready
(function(){
	var d = document, env = glow.env;
	if (env.ie) {
		// polling for no errors
		(function () {
			try {
				// throws errors until after ondocumentready
				d.documentElement.doScroll('left');
			} catch (e) {
				setTimeout(arguments.callee, 50);
				return;
			}
			// no errors, fire
			glow._lf();
		})();
	} else if (
		typeof d.readyState != 'undefined'
		&& ! (env.webkit < 312)
	) {
		var f = function(){ /loaded|complete/.test(d.readyState) ? glow._lf() : setTimeout(f, 10); };
		f();
	} else {
		var callback = function () {
			if (arguments.callee.fired) { return; }
			arguments.callee.fired = true;
			glow._lf();
		};
		d.addEventListener("DOMContentLoaded", callback, false);
		var oldOnload = window.onload;
		window.onload = function () {
			if (oldOnload) { oldOnload(); }
			callback();
		};
	}
})()

glow.onDomReady(function() { glow.isReady = true; });
/* documented above */
glow.isSupported = /*@cc_on @if (@_jscript_version > 5.1)@*/!/*@end @*/!1;
;/*@cc_on @*//*@if (@_jscript_version > 5.1)@*/;/*
Namespace: glow.dom
*/
glow.module("glow.dom", "0.4.0", {
	require: [],
	implementation: function() {
		//private
		
		var env = glow.env,
			lang = glow.lang,
		/*
		PrivateVar: cssRegex
			For matching CSS selectors
		*/
			cssRegex = {
				tagName: /^(\w+|\*)/,
				combinator: /^\s*([>]?)\s*/,
				//safari 1.3 is a bit dim when it comes to unicode stuff, only dot matches them (not even \S), so some negative lookalheads are needed
				classNameOrId: (env.webkit < 417) ? new RegExp("^([\\.#])((?:(?![\\.#\\[:\\s\\\\]).|\\\\.)+)") : /^([\.#])((?:[^\.#\[:\\\s]+|\\.)+)/
			},
		
		/*
		PrivateVar: cssCache
			Cache of arrays representing an execution path for css selectors
		*/
			cssCache = {},
		
		/*
		PrivateVar: dom0PropertyMapping
			Mapping of HTML attribute names to DOM0 property names.
		*/
			dom0PropertyMapping = {
				checked    : "checked",
				"class"    : "className",
				"disabled" : "disabled",
				"for"      : "htmlFor",
				maxlength  : "maxLength"
			},

		/*
		PrivateVar: dom0BooleanAttribute
			The HTML attributes names that should be converted from true/false
			to ATTRIBUTENAME/undefined (i.e. boolean attributes like checked="checked").
		*/
			dom0BooleanAttribute = {
				checked  : true,
				disabled : true
			},

		/*
		PrivateVar: dom0AttributeMappings
			Functions that map dom0 values to sane ones.
		*/
			dom0AttributeMappings = {
				maxlength : function (val) { return val.toString() == "2147483647" ? undefined : val; }
			},
		/*
		PrivateVar: ucheck
			Used by unique(), increased by 1 on each use
		*/
			ucheck = 1,
		
		/*
		PrivateVar: htmlColorNames
			Mapping of colour names to hex values
		*/
			htmlColorNames = {
				black: 0,
				silver: 0xc0c0c0,
				gray: 0x808080,
				white: 0xffffff,
				maroon: 0x800000,
				red: 0xff0000,
				purple: 0x800080,
				fuchsia: 0xff00ff,
				green: 0x8000,
				lime: 0xff00,
				olive: 0x808000,
				yellow: 0xffff00,
				navy: 128,
				blue: 255,
				teal: 0x8080,
				aqua: 0xffff,
				orange: 0xffa500
			},
		/*
		PrivateVar: usesYAxis
			regex for detecting which css properties need to be calculated relative to the y axis
		*/
			usesYAxis = /height|top/,
			colorRegex = /^rgb\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)/i,
			cssPropRegex = /^(?:(width|height)|(top|bottom|left|right)|(border-(top|bottom|left|right)-width))$/,
			//append gets set to a function below
			append,
			//unique gets set to a function below
			unique,
			trbl = ["Top", "Right", "Bottom", "Left"],
			trblLen = 4,
			paddingStr = "padding",
			marginStr = "margin",
			borderStr = "border",
			widthStr = "Width",
			//getByTagName gets get to a function below
			getByTagName,
			win = window,
			doc = document,
			docBody,
			docElm;
			
		glow.ready(function() {
			docBody = doc.body;
			docElm = doc.documentElement;
		})

		/*
		PrivateMethod: removeClassRegex
			Get a regex that can be used to remove a class from a space separated list of classes.

		Arguments:
			name - (string) the name of the class.

		Returns:
			The regex.
		*/
		function removeClassRegex (name) {
			return new RegExp(["\\b", name, "\\b"].join(""));
		}
		

		/*
		PrivateMethod: stringToNodes
			Creates a w3 NodeList from a string
		*/
		function stringToNodes(str) {
			// TODO: need to change container for certain elements. Make a lookup table for
			// {element: container}, for exceptions to div container
			var div = document.createElement("div"),
				r = [], rLen = 0;
			div.innerHTML = str;
			while (div.childNodes[0]) {
				r[rLen++] = div.removeChild(div.childNodes[0]);
			}
			return r;
		}
		
		/*
		PrivateMethod: nodelistToArray
			Converts a w3 NodeList to an array
		*/
		function nodelistToArray(nodelist) {
			var r = [], i = 0;
			for (; nodelist[i]; i++) {
				r[i] = nodelist[i];
			}
			return r;
		}

		/*
		PrivateMethod: setAttribute
			Sets the attribute in the nodelist using the supplied function.

		Arguments:
			value - (String|Function) the value/value generator.
			attributeSetter - (Function) a function that can be called to actually set the attribute.

		Returns:
			The <NodeList> object.
		*/
		// is marginal having this separated out as it is only used twice and call is almost as big
		// leaving it separate for now for once and only once, as well as in case attr does some more mutating stuff
        // could be merged back with attr later
		function setAttribute (value, attributeSetter) {
			for (var that = this, i = 0, length = that.length; i < length; i++) {
				attributeSetter.call(
					that[i],
					value.call ?
						value.call(that[i], i) :
						value
				);
			}
			return that;
		}
		
		
		/*
		PrivateMethod: append
			append the nodes in "b" to the array / list "a"
		*/		
		//return different function for IE & Opera to deal with their stupid bloody expandos. Pah.
		if (document.all) {
			append = function(a, b) {
				var i = 0,
					ai = a.length,
					length = b.length;
				if (typeof b.length == "number") {
					for (; i < length; i++) {
						a[ai++] = b[i];
					}
				} else {
					for (; b[i]; i++) {
						a[ai++] = b[i];
					}
				}
			};
		} else {
			append = function(a, b) {
				var i = 0, ai = a.length;
				for (; b[i]; i++) {
					a[ai++] = b[i];
				}
			};
		}
		
		/*
		PrivateMethod: isXml
			Is this node an XML Document node or within an XML Document node

		Arguments:
			node

		Returns:
			Bool
		*/
		function isXml(node) {
			//test for nodes within xml element
			return  (node.ownerDocument && !node.body)||
					//test for xml document elements
					(node.documentElement && !node.body);
		}
		
		/*
		PrivateMethod: unique
			Get an array of nodes without duplicate nodes from an array of nodes.

		Arguments:
			aNodes - (Array|<NodeList>)

		Returns:
			An array of nodes without duplicates.
		*/
		//worth checking if it's an XML document?
		if (env.ie) {
			unique = function(aNodes) {
				if (aNodes.length == 1) { return aNodes; }
				
				//remove duplicates
				var r = [],
					ri = 0,
					i = 0;
				
				for (; aNodes[i]; i++) {
					if (aNodes[i].getAttribute("_ucheck") != ucheck && aNodes[i].nodeType == 1) {
						r[ri++] = aNodes[i];
					}
					aNodes[i].setAttribute("_ucheck", ucheck);
				}
				for (i=0; aNodes[i]; i++) {
					aNodes[i].removeAttribute("_ucheck");
				}
				ucheck++;
				return r;
			}
		} else {
			unique = function(aNodes) {
				if (aNodes.length == 1) { return aNodes; }
				
				//remove duplicates
				var r = [],
					ri = 0,
					i = 0;
					
				for (; aNodes[i]; i++) {
					if (aNodes[i]._ucheck != ucheck && aNodes[i].nodeType == 1) {
						r[ri++] = aNodes[i];
					}
					aNodes[i]._ucheck = ucheck;
				}
				ucheck++;
				return r;
			}
		}

		/*
		PrivateMethod: getElementsByTag
			Get elements by a specified tag name from a set of context objects. If multiple
			context objects are passed, then the resulting array may contain duplicates. See
			<unique> to remove duplicate nodes.

		Arguments:
			tag - (string) Tag name. "*" for all.
			contexts - (array) DOM Documents and/or DOM Elements to search in.

		Returns:
			An array(like) collection of elements with the specified tag name.
		*/
		if (document.all) { //go the long way around for IE (and Opera)
			getByTagName = function(tag, context) {
				var r = [], i = 0;
				for (; context[i]; i++) {
					//need to check .all incase data is XML
					//TODO: Drop IE5.5
					if (tag == "*" && context[i].all && !isXml(context[i])) { // IE 5.5 doesn't support getElementsByTagName("*")
						append(r, context[i].all);
					} else {
						append(r, context[i].getElementsByTagName(tag));
					}
				}
				return r;
			};
		} else {
			getByTagName = function(tag, context) {
				var r = [], i = 0, len = context.length;
				for (; i < len; i++) {
					append(r, context[i].getElementsByTagName(tag));
				}
				return r;
			};
		}
		
		/*
		PrivateMethod: getElmSize
			Gets the size of an element as {width: x, height:y}
			
		Returns:
			Size not including padding or border
		*/
		function getElmSize(elm) {
			var r,
				oldVals = {},
				i = 0,
				docElmOrBody = env.standardsMode ? docElm : docBody,
				elmStyle = elm.style;
				
			if (elm.window) {
				r = (env.webkit < 522.11 && {width: elm.innerWidth, height: elm.innerHeight}) ||
					(env.webkit && 			{width: docBody.clientWidth, height: elm.innerHeight}) ||
					(env.opera && 			{width: docBody.clientWidth, height: docBody.clientHeight}) ||
					/*else*/				{width: docElmOrBody.clientWidth, height: docElmOrBody.clientHeight};
					
			} else if (elm.getElementById) {
				r = {
						width: Math.max(
							docBody.scrollWidth,
							docBody.offsetWidth,
							docElm.offsetWidth
						),
						height: Math.max(
							docBody.scrollHeight,
							docBody.offsetHeight,
							docElm.offsetHeight
						)
					};
			} else {
				//set border and padding to 0, backing up old vals
				for (; i < trblLen; i++) {
					oldVals[paddingStr + trbl[i]] = elmStyle[paddingStr + trbl[i]];
					oldVals[borderStr + trbl[i] + widthStr] = elmStyle[borderStr + trbl[i] + widthStr];
					elmStyle[paddingStr + trbl[i]] = "0";
					elmStyle[borderStr + trbl[i] + widthStr] = "0";
				}

				//capture values
				r = {width: elm.offsetWidth, height: elm.offsetHeight};
				
				//reset old vals
				for (i = 0; i < trblLen; i++) {
					elmStyle[paddingStr + trbl[i]] = oldVals[paddingStr + trbl[i]];
					elmStyle[borderStr + trbl[i] + widthStr] = oldVals[borderStr + trbl[i] + widthStr];
				}
			}
			return r;
		}
		/*
		PrivateMethod: getElmPos
			Gets the position of an element relative to its offsetParent
		
		Arguments:
			elm - element
			pos - string t|l|b|r to get top/left/bottom/right position
		
		Returns:
			Integer value
		*/
		function getElmPos(elm, pos) {
			// TODO - get Jake to sanity check this
			if (elm.nodeName == 'BODY' || elm.nodeName == 'HTML') return 0;
			var map = {t:"Top",l:"Left"},
				oldVals = {},
				elmBody = getBodyElm(elm),
				compTo = env.ie && env.standardsMode ? elmBody.parentNode : elmBody,
				r,
				elmOffsetParent = elm.offsetParent,
				i = 0,
				offsetParentPos,
				rbPositionContext = (elmOffsetParent == compTo ? win : elmOffsetParent);
			
			for (; i < trblLen; i++) {
				oldVals[marginStr + trbl[i]] = elm.style[marginStr + trbl[i]];
				if (elmOffsetParent) {
					oldVals[borderStr + trbl[i] + widthStr] = elmOffsetParent.style[borderStr + trbl[i] + widthStr];
					elm.style[marginStr + trbl[i]] = elmOffsetParent.style[borderStr + trbl[i] + widthStr] = "0";
				}
			}
			if (pos == "t" || pos == "l") {
				r = elm["offset" + map[pos]];
				if (env.ie) {
					offsetParentPos = elmOffsetParent.currentStyle["position"];
					if (elmOffsetParent.offsetParent && offsetParentPos != "relative" && offsetParentPos != "absolute") {
						r += getElmPos(elmOffsetParent, pos);
					}
				}
				
			} else if (pos == "r") {
				r = getElmSize(rbPositionContext).width - elm.offsetLeft - elm.offsetWidth;
			} else if (pos == "b") {
				r = getElmSize(rbPositionContext).height - elm.offsetTop - elm.offsetHeight;
			}
			for (i = 0; i < trblLen; i++) {
				elm.style[marginStr + trbl[i]] = oldVals[marginStr + trbl[i]];
				if (elmOffsetParent) {
					elmOffsetParent.style[borderStr + trbl[i] + widthStr] = oldVals[borderStr + trbl[i] + widthStr];
				}
			}
			return r;
		}
		
		/*
		PrivateMethod: getBodyElm
			Gets the body elm for a given element. Gets around IE5.5 nonsense. do getBodyElm(elm).parentNode to get documentElement
		*/
		function getBodyElm(elm) {
			if (env.ie < 6) {
				return elm.document.body;
			} else {
				return elm.ownerDocument.body;
			}
		}
		
		/*
		PrivateMethod: setElmsSize
			Set element's size
		
		Arguments:
			elms - (<NodeList>) Elements 
			val - (Mixed) Set element height / width. In px unless stated
			type - (String) "height" or "width"
		
		Returns:
			Nowt.
		*/
		function setElmsSize(elms, val, type) {
			if (typeof val == "number" || /\d$/.test(val)) {
				val += "px";
			}
			for (var i = 0, len = elms.length; i < len; i++) {
				elms[i].style[type] = val;
			}
		}
		
		/*
		PrivateMethod: toStyleProp
			Converts a css property name into its javascript name, such as "background-color" to "backgroundColor".
		
		Arguments:
			prop - (String) CSS Property name
		
		Returns:
			String, javascript style property name
		*/
		function toStyleProp(prop) {
			if (prop == "float") {
				return env.ie ? "styleFloat" : "cssFloat";
			}
			return lang.replace(prop, /-(\w)/g, function(match, p1) {
				return p1.toUpperCase();
			});
		}
		
		/*
		PrivateMethod: tempBlock
			Gives an element display:block (but keeps it hidden) and runs a function, then sets the element back how it was
		
		Arguments:
			elm - element
			func - function to run
		
		Returns:
			Return value of the function
		*/
		function tempBlock(elm, func) {
			var r,
				elmStyle = elm.style,
				oldDisp = elmStyle.display,
				oldVis = elmStyle.visibility,
				oldPos = elmStyle.position;
			
			elmStyle.visibility = "hidden";
			elmStyle.position = "absolute";
			elmStyle.display = "block";
			if (!isVisible(elm)) {
				elmStyle.position = oldPos;
				r = tempBlock(elm.parentNode, func);
				elmStyle.display = oldDisp;
				elmStyle.visibility = oldVis;
			} else {
				r = func();
				elmStyle.display = oldDisp;
				elmStyle.position = oldPos;
				elmStyle.visibility = oldVis;
			}
			return r;
		}
		
		/*
		PrivateMethod: isVisible
			Is the element visible?
		*/
		function isVisible(elm) {
			//this is a bit of a guess, if there's a better way to do this I'm interested!
			return elm.offsetWidth ||
				elm.offsetHeight;
		}
		
		/*
		PrivateMethod: getCssValue
			Get a computed css property
		
		Arguments:
			elm - element
			prop - css property or array of properties to add together
		
		Returns:
			String, value
		*/
		function getCssValue(elm, prop) {
			var r, //return value
				total = 0,
				i = 0,
				propLen = prop.length,
				compStyle = doc.defaultView && (doc.defaultView.getComputedStyle(elm, null) || doc.defaultView.getComputedStyle),
				elmCurrentStyle = elm.currentStyle,
				oldDisplay,
				match,
				propTest = prop.push || cssPropRegex.exec(prop) || [];
			
			
			if (prop.push) { //multiple properties, add them up
				for (; i < propLen; i++) {
					total += parseInt(getCssValue(elm, prop[i]), 10) || 0;
				}
				return total + "px";
			}
			//had fun with opera including padding in width, this seems safer
			if (propTest[1]) { //is 'width' or 'height'
				if (!isVisible(elm)) { //element may be display: none
					return tempBlock(elm, function() {
						return getElmSize(elm)[prop] + "px";
					});
				} else {
					return getElmSize(elm)[prop] + "px";
				}
			} else if (propTest[2] && getCssValue(elm, "position") != "relative") { //is 'top' 'left' 'bottom' 'right'
				if (!isVisible(elm)) { //element may be display: none
					return tempBlock(elm, function() {
						return getElmPos(elm, prop.charAt(0)) + "px";
					});
				} else {
					return getElmPos(elm, prop.charAt(0)) + "px";
				}
            } else if (propTest[3] //is border-*-width
				&& glow.env.ie
                && getCssValue(elm, "border-" + propTest[4] + "-style") == "none"
            ) {
				return "0";
			} else if (compStyle) { //W3 Method
				//this returns computed values
				if (typeof compStyle == "function") {
					//safari returns null for compStyle
					
					oldDisplay = elm.style.display;
					r = tempBlock(elm, function() {
						if (prop == "display") { //get true value for display, since we've just fudged it
							elm.style.display = oldDisplay;
							if (!doc.defaultView.getComputedStyle(elm, null)) {
								return "none";
							}
							elm.style.display = "block";
						}
						return getCssValue(elm, prop);
					});
				} else {
					// assume equal horizontal margins in safari 3
					// TODO put max version on this when http://bugs.webkit.org/show_bug.cgi?id=13343 is fixed
					if (glow.env.webkit >= 522 && prop == 'margin-right') prop = 'margin-left';
					r = compStyle.getPropertyValue(prop);
				}
			} else if (elmCurrentStyle) { //IE method
				if (prop == "opacity") {
					match = /alpha\(opacity=([^\)]+)\)/.exec(elmCurrentStyle.filter);
					return match ? String(parseInt(match[1], 10) / 100) : "1";
				}
				//this returns cascaded values so needs fixing
				r = String(elmCurrentStyle[toStyleProp(prop)]);
				if (/^-?\d+[a-z%]+$/i.test(r) && prop != "font-size") {
					r = getPixelValue(elm, r, usesYAxis.test(prop)) + "px";
				}
			}
			//some results need post processing
			if (prop.indexOf("color") != -1) { //deal with colour values
				r = normaliseCssColor(r).toString();
			} else if (r.indexOf("url") == 0) { //some browsers put quotes around the url, get rid
				r = r.replace(/\"/g,"");
			}
			return r;
		}
		
		/*
		PrivateMethod: getPixelValue
			Converts a relative value into an absolute pixel value. Only works in IE with dimention value (not stuff like relative font-size).
			Based on some Dean Edward's code
		
		Arguments:
			element - element used to calculate relative values
			value - (string) relative value
			useYAxis - (string) calulate relative values to the y axis rather than x
		
		Returns:
			Number
		*/
		function getPixelValue(element, value, useYAxis) {
			if (/^-?\d+(px)?$/i.test(value)) { return parseInt(value); }
			var axisSetValue = useYAxis ? "top" : "left",
				axisGetValue = useYAxis ? "Top" : "Left",
				elmStyle = element.style,
				origWidth = elmStyle.left,
				origPos = elmStyle.overflow,
				origMargin = elmStyle.margin;
			elmStyle.position = "absolute";
			elmStyle.margin = "0";
			elmStyle[axisSetValue] = value || 0;
			value = element["offset" + axisGetValue];
			elmStyle.position = origPos;
			elmStyle[axisSetValue] = origWidth;
			elmStyle.margin = origMargin;
			return value;
		}
		
		/*
		PrivateMethod: normaliseCssColor
			Converts a CSS colour into "rgb(255, 255, 255)" or "transparent" format
		*/
		
		function normaliseCssColor(val) {
			if (/^(transparent|rgba\(0, ?0, ?0, ?0\))$/.test(val)) { return 'transparent'; }
			var match, //tmp regex match holder
				r, g, b, //final colour vals
				hex, //tmp hex holder
				mathRound = Math.round,
				parseIntFunc = parseInt,
				parseFloatFunc = parseFloat;
				
			if (match = colorRegex.exec(val)) { //rgb() format, cater for percentages
				r = match[2] ? mathRound(((parseFloatFunc(match[1]) / 100) * 255)) : parseIntFunc(match[1]);
				g = match[4] ? mathRound(((parseFloatFunc(match[3]) / 100) * 255)) : parseIntFunc(match[3]);
				b = match[6] ? mathRound(((parseFloatFunc(match[5]) / 100) * 255)) : parseIntFunc(match[5]);
			} else {
				if (typeof val == "number") {
					hex = val;
				} else if (val.charAt(0) == "#") {
					if (val.length == "4") { //deal with #fff shortcut
						val = "#" + val.charAt(1) + val.charAt(1) + val.charAt(2) + val.charAt(2) + val.charAt(3) + val.charAt(3);
					}
					hex = parseIntFunc(val.slice(1), 16);
				} else {
					hex = htmlColorNames[val];
				}
				
				r = (hex) >> 16;
				g = (hex & 0x00ff00) >> 8;
				b = (hex & 0x0000ff);
			}
			
			val = new String("rgb(" + r + ", " + g + ", " + b + ")");
			val.r = r;
			val.g = g;
			val.b = b;
			return val;
		}
		
		/*
		PrivateMethod: getTextNodeConcat
			Take an element and returns a string of its text nodes combined. This is useful when a browser (Safari 2) goes mad and doesn't return anything for innerText or textContent
		*/
		function getTextNodeConcat(elm) {
			var r = "",
				nodes = elm.childNodes,
				i = 0,
				len = nodes.length;
			for (; i < len; i++) {
				if (nodes[i].nodeType == 3) { //text
					//this function is used in safari only, string concatination is faster
					r += nodes[i].nodeValue;
				} else if (nodes[i].nodeType == 1) { //element
					r += getTextNodeConcat(nodes[i]);
				}
			}
			return r;
		}
		
		/*
		PrivateMethod: getNextOrPrev
			This gets the next / previous sibling element of each node in a nodeset
			and returns the new nodeset.
		*/
		function getNextOrPrev(nodelist, dir /* "next" or "previous" */) {
			var ret = [],
				ri = 0,
				nextTmp,
				i = 0,
				length = nodelist.length;
			
			for (; i < length; i++) {
				nextTmp = nodelist[i];
				while (nextTmp = nextTmp[dir + "Sibling"]) {
					if (nextTmp.nodeType == 1 && nextTmp.nodeName != "!") {
						ret[ri++] = nextTmp;
						break;
					}
				}
			}
			return r.get(ret);
		}
		
		//public
		var r = {}; //object to be returned
		/*
		Method: get
			Returns a <NodeList> from CSS selectors and/or DOM nodes.
		
		CSS Selector Support:
			- Universal selector (* - all elements in the document tree).
			- Type selector (div - tag name).
			- Class selector (.myClass - class name).
			- ID selector (#myDiv - ID).
			- Child selector (\> - Child selector).
			- Grouping (selector, selector, ...).
			
		Arguments:
			*nodeSpec* ([, ...])
			
			CSS selectors, DOM Elements or arrays of DOM
							   Elements (which can be <NodeLists>).

		Returns:
			<NodeList>
		
		Example:
			(code)
			// selects links in a nav
			var aNodes = glow.dom.get("#nav a");

			// NodeList containing the parameter nodes
			var aNodes2 = glow.dom.get(someNode, anotherNode);

			//NodeList containing all links
			var aNodes2 = glow.dom.get(document.getElementsByTagName("a")); 
			(end)
		*/
		
		r.get = function() {
			var r = new glow.dom.NodeList(),
				i = 0,
				args = arguments,
				argsLen = args.length;
			
			for (; i < argsLen; i++) {
				if (typeof args[i] == "string") {
					r.push(new glow.dom.NodeList().push(doc).get(args[i]));
				} else {
					r.push(args[i]);
				}
			}
			return r;
		};
		/*
		Method: create
			Returns a <NodeList> from an HTML fragment. All top-level nodes must be
			elements (i.e. text content in the html must be wrapped in HTML tags).
			
			Returned elements are HTML elements and cannot be inserted into XML NodeLists.
		
		Arguments:
			*sHtml* (string)
			
			An HTML fragment.
			
		Returns:
			<NodeList>
		
		Example:
			(code)
			// NodeList of two elements
			var aNodes = glow.dom.create("<div>Hello</div><div> World</div>");
			(end)
		*/
		r.create = function(sHtml) {
			var toCheck = stringToNodes(sHtml),
				ret = [],
				i = 0,
				rLen = 0;
			for (; toCheck[i]; i++) {
				if (toCheck[i].nodeType == 1 && toCheck[i].nodeName != "!") {
					ret[rLen++] = toCheck[i];
				} else if (toCheck[i].nodeType == 3 && lang.trim(toCheck[i].nodeValue) !== "") {
					throw new Error("glow.dom.create - Text must be wrapped in an element");
				}
			}
			return new r.NodeList().push(ret);
		};
		
		/*
		Method: parseCssColor
			Returns an object representing a css colour string
		
		Arguments:
			*cssColor* (string) 
			
			A CSS colour. Valid examples are "red", "#f00", "#ff0000", "rgb(255,0,0)", "rgb(100%, 0%, 0%)"
			
		Returns:
			An object with the following properties
			r - Red integer value (0 - 255)
			g - Green integer value (0 - 255)
			b - Blue integer value (0 - 255)
		*/
		r.parseCssColor = function(cssColor) {
			var normal = normaliseCssColor(cssColor);
			return {r: normal.r, g: normal.g, b: normal.b};
		}
		
		/*
		Class: glow.dom.NodeList
			An array-like collection of DOM Elements.
		
		Constructor:
			It is recommended you create a <NodeList> using <glow.dom.get>, or 
			<glow.dom.create>, but you can also use the constructor to create an 
			empty <NodeList>.

			(code)
			// empty NodeList
			var nodes = new glow.dom.NodeList();
			(end)
		*/
		r.NodeList = function() {
			/*
			Property: length
				Number of nodes in a NodeList

			Example:
				(code)
				//get the number of paragraphs on the page
				glow.dom.get("p").length;
				(end)
			*/
			this.length = 0; //number of elements in NodeList
		};
		
		/*
			Group: Methods
		*/
		r.NodeList.prototype = {
			
			/*
			Method: item
				Returns an indexed item from the <NodeList>.

			Arguments:
				index - (Int) a numeric index for the item to return.

			Example:
				(code)
				// get the forth node
				myNodeList.item(3)
				(end)
			*/
			item: function (nIndex) {
				return this[nIndex];
			},
			
			/*
			Method: push
				Adds nodes to the <NodeList>.
			
			Arguments:
				This method can take a variety of arguments, see below.
				
				(NodeList/array of elements)
				
				Appends to the contents of the <NodeList>.
				
				(Node, Node, ...)
				
				Appends each node to the <NodeList>.
				
				(HTML string)
				
				Converts the fragment into nodes and adds them to the <NodeList>.
				
			Returns:
				<NodeList>
			
			Example:
				(code)
				var myNodeList = glow.dom.create("<div>Hello world</div>");
				myNodeList.push("<div>Foo</div>", glow.dom.get("#myList li"));
				(end)
			*/
			push: function() {
				var args = arguments,
					argsLen = args.length,
					i = 0,
					n,
					nNodeListLength,
					that = this,
					arrayPush = Array.prototype.push;
				
				for (; i < argsLen; i++) {
					if (args[i].constructor == Array) { //is array
						arrayPush.apply(that, args[i]);
					} else if (args[i].item && args[i][0]) { //is nodelist
						for (n = 0, nNodeListLength = args[i].length; n < nNodeListLength; n++) {
							arrayPush.call(that, args[i][n]);
						}
					} else if (args[i].nodeType == 1 || args[i].nodeType == 9 || args[i].document) { //is Node
						arrayPush.call(that, args[i]);
					}
				}
				return that;
			},
			
			/*
			Method: each
				Calls a function for each node in the node list. When the callback
				is run, the node is the invocant (i.e. this) and the index is the
				first parameter.

			Arguments:
				*callback* (Function)
				
				The function to run for each node.

			Example:
				(code)
				var myNodeList = glow.dom.create("<div>Hello world</div>");
				myNodeList.each(function (i) { this == myNodeList[i] });
				(end)

			*/
			each: function (callback) {
				for (var i = 0, that = this, length = that.length; i < length; i++) {
					callback.call(that[i], i, that);
				}
				return that;
			},
			
			/*
			Method: eq
				Checks if the <NodeList> is the same as the passed in node/array 
				of nodes/<NodeList> (and in the same order).
				

			Arguments:
				*nodelist*
				
				The element, array or <NodeList> to compare the 
				<NodeList> with.

			Example:
				(code)
				var myNodeList = glow.dom.get('#blah');
				if (myNodeList.eq(document.getElementById('blah')) ...
				(end)
			*/
			eq: function (nodelist) {
				var that = this, i = 0, length = that.length;
			
				if (! nodelist.push) { nodelist = [nodelist]; }
				if (nodelist.length != that.length) { return false; }
				for (; i < length; i++) {
					if (that[i] != nodelist[i]) { return false; }
				}
				return that;
			},

			/*
			Method: isWithin
				Checks if all the elements in the <NodeList> are decendents of the passed in
				element. 
				
				If passed a <NodeList>, then the first node will be used.

			Arguments:
				*node* (element/nodeList)
				
				The element or <NodeList> that the <NodeList> is being tested against.

			Example:
				(code)
				var myNodeList = glow.dom.get("input");
				if (! myNodeList.isWithin(glow.dom.get("form")) ...
				(end)
			*/
			isWithin: function (node) {
				if (node.push) { node = node[0]; }
				var that = this,
					i = 0,
					length = that.length,
					toTest; //node to test in manual method
					
				if (node.contains && env.webkit >= 521) { //proprietary method, safari 2 has a wonky implementation of this, avoid, avoid, avoid
					//loop through
					for (; i < length; i++) {
						// myNode.contains(myNode) returns true in most browsers
						if (!(node.contains(that[i]) && that[i] != node)) { return false; }
					}
				} else if (that[0].compareDocumentPosition) { //w3 method
					//loop through
					for (; i < length; i++) {
						//compare against bitmask
						if (!(that[i].compareDocumentPosition(node) & 8)) { return false; }
					}
				} else { //manual method
					for (; i < length; i++) {
						toTest = that[i];
						while (toTest = toTest.parentNode) {
							if (toTest == node) { break; }
						}
						if (!toTest) { return false; }
					}
				}
				return true;
			},

			/*
			Method: attr
				Gets or sets attributes. 
				
				When getting an attribute, it is retrieved from the first
				node in the <NodeList>. Setting attributes applies the change to each element in
				the <NodeList>. 
				
				To set an attribute, pass in the value as a second parameter. To
				set multiple attributes in one call, pass in an object as a single parameter. For
				browsers that don't support manipulating attributes using the DOM, this method
				will try to do the right thing (i.e. don't expect the semantics of this method
				to consistent across browsers as this is not possible with currently supported
				browsers).

			Arguments:
				_Either_ :

				*name* (String)
				
				The name of the attribute.
				
				value (String)
				
				Optional value to set the attribute to.

				_Or_ :

				values (Object)
				
				Object containing one entry for each attribute to set.

			Returns:
				When setting attributes, it returns the <NodeList>. Otherwise returns the
				attribute value.

			Example:
				(code)
				var myNodeList = glow.dom.get("myImgClass");

				// get an attribute
				myNodeList.attr("class") == "myImgClass"

				// set an attribute
				myNodeList.attr("class", "anotherImgClass");

				// set multiple attributes
				myNodeList.attr({ src : "a.png", alt : "a PNG" });
				(end)
			*/
			attr: function (name /* , val */) {
				var that = this,
					args = arguments,
					argsLen = args.length,
					i,
					value;
			
				if (that.length === 0) {
					return argsLen > 1 ? that : undefined;
				}
				if (typeof name == 'object') {
					for (i in name) {
						if (lang.hasOwnProperty(name, i)) {
							that.attr(i, name[i]);
						}
					}
					return that;
				}
				if (env.ie && dom0PropertyMapping[name]) {
					if (argsLen > 1) {
						setAttribute.call(
							that,
							args[1],
							// in the callback this is the dom node
							function (val) { this[dom0PropertyMapping[name]] = val; }
						);
						return that;
					}
					value = that[0][dom0PropertyMapping[name]];
					if (dom0BooleanAttribute[name]) {
						return value ? name : undefined;
					}
					else if (dom0AttributeMappings[name]) {
						return dom0AttributeMappings[name](value);
					}
					return value;
				}
				if (argsLen > 1) {
					setAttribute.call(
						that,
						args[1],
						// in the callback this is the dom node
						function (val) { this.setAttribute(name, val); }
					);
					return that;
				}
				//2nd parameter makes IE behave, but errors for XML nodes (and isn't needed for xml nodes)
				return isXml(that[0]) ? that[0].getAttribute(name) : that[0].getAttribute(name, 2);
			},

			/*
			Method: removeAttr
				Removes an attrbute from each node in the <NodeList>.

			Arguments:
				*name* (String)
				
				The name of the attribute to remove.

			Returns:
				The <NodeList>.

			Example:
				(code)
				glow.dom.get("a").removeAttr("target");
				(end)
			*/
			removeAttr: function (name) {
				var mapping = env.ie && dom0PropertyMapping[name],
					that = this,
					i = 0,
					length = that.length;
				
				for (; i < length; i++) {
					if (mapping) {
						that[i][mapping] = "";
					} else {
						that[i].removeAttribute(name);
					}
				}
				return that;
			},

			/*
			Method: hasAttr
				Checks if the attribute is present in the first node.

			Arguments:
				*name* (String)
				
				The name of the attribute to check for.

			Returns:
				Boolean.

			Example:
				(code)
				if (! glow.dom.get("#myImg").hasAttr("alt")) ...
				(end)
			*/
			hasAttr: function (name) {
				var firstNode = this[0],
					attributes = firstNode.attributes;
					
				if (isXml(firstNode) && env.ie) { //getAttributeNode not supported for XML
					var attributes = firstNode.attributes,
						i = 0,
						len = attributes.length;
					
					//named node map doesn't seem to work properly, so need to go by index
					for (; i < len; i++) {
						if (attributes[i].nodeName == name) {
							return attributes[i].specified;
						}
					}
					return false;
				} else if (this[0].getAttributeNode) {
					var attr = this[0].getAttributeNode(name);
					return attr ? attr.specified : false;
				}
				
				return typeof attributes[attr] != "undefined";
			},

			/*
			Method: hasClass
				Checks if any element in the <NodeList> has a class.
				
				(this function is irrelevant on XML NodeLists)

			Arguments:
				*name* (String)
				
				The name of the class.

			Returns:
				Boolean.

			Example:
				(code)
				if (glow.dom.get("a").hasClass("termsLink")) ...
				(end)
			*/
			hasClass: function (name) {
				for (var i = 0, length = this.length; i < length; i++) {
					if ((" " + this[i].className + " ").indexOf(" " + name + " ") != -1) {
						return true;
					}
				}
				return false;
			},

			/*
			Method: addClass
				Adds a class to each node in the <NodeList>.
				
				(this function is irrelevant on XML NodeLists)
				
			Arguments:
				 *name* (String)
				 
				 The class name.

			Returns:
				The <NodeList>.

			Example:
				(code)
				glow.dom.get("#toolbar #login a").addClass("highlight");
				(end)
			*/
			addClass: function (name) {
				for (var i = 0, length = this.length; i < length; i++) {
					if ((" " + this[i].className + " ").indexOf(" " + name + " ") == -1) {
						this[i].className += " " + name;
					}
				}
				return this;
			},

			/*
			Method: removeClass
				Removes a class from each node in the <NodeList>.
				
				(this function is irrelevant on XML NodeLists)

			Arguments:
				*name* (String)
				
				The class name.

			Returns:
				The <NodeList>.

			Example:
				(code)
				glow.dom.get("#footer #login a").removeClass("highlight");
				(end)
			*/
			removeClass: function (name) {
				var re = removeClassRegex(name),
					that = this,
					i = 0,
					length = that.length;
				
				for (; i < length; i++) {
					that[i].className = that[i].className.replace(re, "");
				}
				return that;
			},

			/*
			Method: toggleClass
				Toggles a class in each node in the <NodeList>.
				
				(this function is irrelevant on XML NodeLists)

			Arguments:
				*name* (String)
				
				The class name.

			Returns:
				The <NodeList>.

			Example:
				(code)
				glow.dom.get(".onOffSwitch").toggleClass("on");
				(end)
			*/
			toggleClass: function (name) {
				for (var that = this, i = 0, length = that.length; i < length; i++) {
					if ((" " + that[i].className + " ").indexOf(" " + name + " ") != -1) {
						that[i].className = that[i].className.replace(removeClassRegex(name), "");
					} else {
						that[i].className += " " + name;
					}
				}
				return that;
			},

			/*
			Method: val
				Gets/sets form value(s) for the first item in the node list.
				
				(this function is irrelevant on XML NodeLists)

			Get value from form element:

				The returned value depends on the type of element.

				radio button or checkbox - if checked, then the contents of the value attribute,
										   otherwise an empty string.
				select - the contents of value attribute of the selected option.
				select (multiple) - an array of selected option values.
				other form element - the value of the input.

			Get value from form:

				If the first element in the <NodeList> is a form, then an object is returned containing
				the form data. Each item property of the object is a value as above, apart from when
				multiple elements of the same name exist, in which case the it will contain an array
				of values.

			Set value for form element:

				If a value is passed and the first element of the <NodeList> is a form element, then
				the form element is given that value. For select elements, this means that the first
				option that matches the value will be selected. For selects that allow multiple
				selection, the options which have a value that exists in the array of values/match the
				value will be selected and others will be deselected.
				
				Currently checkboxes and radio buttons are not checked or unchecked, just their value
				is changed. This does mean that this does not do exactly the reverse of getting the
				value from the element (see above) and as such may be subject to change (TODO - decide
				on this before production ready release, should checkboxes and radios just get
				and return boolean values).

			Set value for form:

				If the first element in the <NodeList> is a form and the value is an object, then
				each element of the form has its value set to the corresponding property of the
				object, using the method described above.

			Arguments:
				*newValue* (string/int)
				
				The value to set the form element/elements to.

			Returns:
				When a value is passed it returns the <NodeList>, otherwise returns the value
				as described above.

			Example:
				(code)
				// get a value
				var username = glow.dom.get("input.username").val();
				
				// get values from a form
				var userDetails = glow.dom.get("form").val();

				// set a value
				glow.dom.get("input.username").val("example username");

				// set values in a form
				glow.dom.get("form").val({
					username : "another",
					name     : "A N Other"
				});
				(end)
			*/
			val: function () {

				/*
				PrivateFunction: elementValue
					Get a value for a form element.
				*/
				function elementValue (el) {
					var elType = el.type,
						elChecked = el.checked,
						elValue = el.value,
						vals = [],
						i = 0;
					
					if (elType == "radio") {
						return elChecked ? elValue : undefined;
					} else if (elType == "checkbox") {
						return elChecked ? elValue : undefined;

					} else if (elType == "select-one") {
						return el.selectedIndex > -1 ?
							el.options[el.selectedIndex].value : "";

					} else if (elType == "select-multiple") {
						for (var length = el.options.length; i < length; i++) {
							if (el.options[i].selected) {
								vals[vals.length] = el.options[i].value;
							}
						}
						return vals;
					} else {
						return elValue;
					}
				}
	
				/*
				PrivateMethod: formValues
					Get an object containing form data.
				*/
				function formValues (form) {
					var vals = {},
						radios = {},
						formElements = form.elements,
						i = 0,
						length = formElements.length,
						name,
						formElement,
						j = 0,
						radio;
					
					for (; i < length; i++) {
						formElement = formElements[i];
						name = formElement.name;
						
						if (formElement.type == "checkbox" && ! formElement.checked) {
							if (! name in vals) {
								vals[name] = undefined;
							}
						} else if (formElement.type == "radio") {
							if (radios[name]) {
								radios[name][radios[name].length] = formElement;
							} else {
								radios[name] = [formElement];
							}
						} else {
							var value = elementValue(formElement);
							if (name in vals) {
								if (vals[name].push) {
									vals[name][vals[name].length] = value;
								} else {
									vals[name] = [vals[name], value];
								}
							} else {
								vals[name] = value;
							}
						}
					}
					for (i in radios) {
						for (length = radios[i].length; j < length; j++) {
							radio = radios[i][j];
							name = radio.name;
							if (radio.checked) {
								vals[radio.name] = radio.value;
								break;
							}
						}
						if (! name in vals) { vals[name] = undefined; }
					}
					return vals;
				}

				/*
				PrivateFunction: setFormValues
					Set values of a form to those in passed in object.
				*/
				function setFormValues (form, vals) {
					var prop, currentField,
						fields = {},
						storeType, i = 0, n, len, foundOne, currentFieldType;
						
					for (prop in vals) {
						currentField = form[prop];
						if (currentField && currentField[0]) { // is array of fields
							//normalise values to array of vals
							vals[prop] = vals[prop] && vals[prop].push ? vals[prop] : [vals[prop]];
							//order the fields by types that matter
							fields.radios = [];
							fields.checkboxesSelects = [];
							fields.multiSelects = [];
							fields.other = [];
							
							for (; currentField[i]; i++) {
								currentFieldType = currentField[i].type;
								if (currentFieldType == "radio") {
									storeType = "radios";
								} else if (currentFieldType == "select-one" || currentFieldType == "checkbox") {
									storeType = "checkboxesSelects";
								} else if (currentFieldType == "select-multiple") {
									storeType = "multiSelects";
								} else {
									storeType = "other";
								}
								//add it to the correct array
								fields[storeType][fields[storeType].length] = currentField[i];
							}
							
							for (i = 0; fields.multiSelects[i]; i++) {
								vals[prop] = setValue(fields.multiSelects[i], vals[prop]);
							}
							for (i = 0; fields.checkboxesSelects[i]; i++) {
								setValue(fields.checkboxesSelects[i], "");
								for (n = 0, len = vals[prop].length; n < len; n++) {
									if (setValue(fields.checkboxesSelects[i], vals[prop][n])) {
										vals[prop].slice(n, 1);
										break;
									}
								}
							}
							for (i = 0; fields.radios[i]; i++) {
								fields.radios[i].checked = false;
								foundOne = false;
								for (n = 0, len = vals[prop].length; n < len; n++) {
									if (setValue(fields.radios[i], vals[prop][n])) {
										vals[prop].slice(n, 1);
										foundOne = true;
										break;
									}
									if (foundOne) { break; }
								}
							}
							for (i = 0; fields.other[i] && vals[prop][i] !== undefined; i++) {
								setValue(fields.other[i], vals[prop][i]);
							}
						} else if (currentField && currentField.nodeName) { // is single field, easy
							setValue(currentField, vals[prop]);
						}
					}
				}
	
				/*
				PrivateFunction: setValue
					Set the value of a form element.
				
				Returns:
					values that weren't able to set if array of vals passed (for multi select). Otherwise true if val set, false if not
				*/
				function setValue (el, val) {
					var i = 0,
						length,
						n = 0,
						nlen,
						elOption,
						optionVal;
					
					if (el.type == "select-one") {
						for (length = el.options.length; i < length; i++) {
							if (el.options[i].value == val) {
								el.selectedIndex = i;
								return true;
							}
						}
						return false;
					} else if (el.type == "select-multiple") {
						var isArray = !!val.push;
						for (i = 0, length = el.options.length; i < length; i++) {
							elOption = el.options[i];
							optionVal = elOption.value;
							if (isArray) {
								elOption.selected = false;
								for (nlen = val.length; n < nlen; n++) {
									if (optionVal == val[n]) {
										elOption.selected = true;
										val.splice(n, 1);
										break;
									}
								}
							} else {
								return elOption.selected = val == optionVal;
							}
						}
						return false;
					} else if (el.type == "radio" || el.type == "checkbox") {
						el.checked = val == el.value;
						return val == el.value;
					} else {
						el.value = val;
						return true;
					}
				}

				// toplevel implementation
				return function (/* [value] */) {
					var args = arguments,
						val = args[0],
						that = this,
						i = 0,
						length = that.length;
					
					if (args.length === 0) {
						return that[0].nodeName == 'FORM' ?
							formValues(that[0]) :
							elementValue(that[0]);
					}
					if (that[0].nodeName == 'FORM') {
						if (! typeof val == 'object') {
							throw 'value for FORM must be object';
						}
						setFormValues(that[0], val);
					} else {
						for (; i < length; i++) {
							setValue(that[i], val);
						}
					}
					return that;
				};
			}(),

			/*
			Method: slice
				Extracts items from a <NodeList> and returns as in new <NodeList>.

			Arguments:
				*start* (Int) 
				
				zero based index at which to begin extraction. A negative
				index starts from the end of the <NodeList>.
				
				*end* (Int) 
				
				optional zero based index at which to begin extraction. A
				negative index starts from the end of the <NodeList>.

			Returns:
				A new <NodeList>.

			Example:
				(code)
				var myNodeList = glow.dom.create("<div></div><div></div>");
				myNodeList = myNodeList.slice(1, 2); // just second div
				(end)
			*/
			slice: function (/* start, end */) {
				return new r.NodeList().push(Array.prototype.slice.apply(this, arguments));
			},

			/*
			Method: sort
				Returns a new <NodeList> containing the same nodes in order. 
				
				Sort order defaults to document order if no sort function is passed in.

			Arguments:
				*func* (Function) 
				
				Optional. You function will be passed 2 nodes (a, b). The function
					   should return a number less than 0 to sort a lower than b and greater than 0
					   to sort a higher than b.

			Returns:
				A new <NodeList> containing the sorted nodes.

			Example:
				(code)
				 // heading elements in document order
				var headings = glow.dom.get("h1, h2, h3, h4, h5, h6").sort();
				
				//get links in alphabetical (well, lexicographical) order
				var links = glow.dom.get("a").sort(function(a, b) {
					return ((a.textContent || a.innerText) < (b.textContent || b.innerText)) ? -1 : 1;
				})
				(end)
			*/			
			sort: function(func) {
				var that = this, i=0, aNodes;
				
				if (!that.length) { return that; }
				if (!func) {
					if (typeof that[0].sourceIndex == "number") {
						// sourceIndex is IE proprietary (but Opera supports)
						func = function(a, b) {
							return a.sourceIndex - b.sourceIndex;
						};
					} else if (that[0].compareDocumentPosition) {
						// DOM3 method
						func = function(a, b) {
							return 3 - (a.compareDocumentPosition(b) & 6);
						};
					} else {
						// js emulation of sourceIndex
						aNodes = getByTagName("*", [doc]);
						for (; aNodes[i]; i++) {
							aNodes[i]._sourceIndex = i;
						}
						func = function(a, b) {
							return a._sourceIndex - b._sourceIndex;
						};
					}
				}
				
				return r.get([].sort.call(that, func));
			},
			
			/*
			Method: filter
				Calls a function for each node in the node list and returns the nodes for
				which it returns true. The callback is executed with the node as the
				invocant and is passed the index as the first parameter.

			Arguments:
				*callback* (Function)
				
				The function to run for each node.

			Returns:
				A new <NodeList> containing the filtered nodes.

			Example:
				(code)
				//return images with a width greater than 320
				glow.dom.get("img").filter(function (i) {
					return this.width > 320;
				});
				(end)
			*/
			filter: function(callback) {
				var ret = [], //result
					ri = 0,
					i = 0,
					length = this.length;
				for (; i < length; i++) {
					if (callback.apply(this[i], [i])) {
						ret[ri++] = this[i];
					}
				}
				return r.get(ret);
			},
			
			/*
			Method: children
				Gets the child nodes of each element in the <NodeList>.

			Returns:
				A new <NodeList> containing the child nodes.

			Example:
				(code)
				 // get all list items
				glow.dom.get("ul, ol").children();
				(end)
			*/
			children: function() {
				var ret = [],
					ri = 0,
					i = 0,
					n = 0,
					length = this.length,
					childTmp;

				for (; i < length; i++) {
					childTmp = this[i].childNodes;
					for (; childTmp[n]; n++) {
						if (childTmp[n].nodeType == 1 && childTmp[n].nodeName != "!") {
							ret[ri++] = childTmp[n];
						}
					}
				}
				return r.get(ret);
			},
			
			/*
			Method: parent
				Gets the parent node of each node in the <NodeList> with duplicates removed.

			Returns:
				A new <NodeList> containing the parent elements.

			Example:
				(code)
				// elements which contain links
				glow.dom.get("a").parent();
				(end)
			*/
			parent: function() {
				var ret = [],
					ri = 0,
					i = 0,
					length = this.length;
				
				for (; i < length; i++) {
					ret[ri++] = this[i].parentNode;
				}
				return r.get(unique(ret));
			},
			
			/*
			Method: next
				Gets the next sibling element for each node in the <NodeList>.

			Returns:
				A new <NodeList> containing the sibling elements.

			Example:
				(code)
				// gets the element following #myLink (if there is one)
				glow.dom.get("#myLink").next();
				(end)
			*/
			next: function() {
				return getNextOrPrev(this, "next");
			},
			
			/*
			Method: prev
				Gets the previous sibling element for each node in the <NodeList>.

			Returns:
				A new <NodeList> containing the sibling elements.

			Example:
				(code)
				 // gets the elements before #myLink (if there is one)
				glow.dom.get("#myLink").previous();
				(end)
			*/
			prev: function() {
				return getNextOrPrev(this, "previous");
			},
			
			/*
			Method: is
				Checks if the nodes in the <NodeList> all match a CSS selector.

			Arguments:
				*selector* (CSS Selector)
				
				The element to check against

			Returns:
				Boolean.

			Example:
				(code)
				var bigHeadings = glow.dom.get("h1, h2, h3");

				// true
				if (bigHeadings.is("h1, h2, h3, h4, h5, h6")) ...

				// false
				if (bigHeadings.is("a")) ...
				(end)
			*/
			is: function (selector) {
				// TODO - this implementation needs to be optimized
				var nodes = glow.dom.get(selector),
					i = 0,
					iLen = this.length,
					j,
					jLen;
				
				node:
				for (; i < iLen; i++) {
					for (j = 0, jLen = nodes.length; j < jLen; j++) {
						if (this[i] == nodes[j]) {
							continue node;
						}
					}
					return false;
				}
				return true;
			},
			
			/*
			Method: text
				Gets the inner text of the first node, or set the inner text of all matched nodes.

			Arguments:
				*text* (string)
				
				If passed then the inner text is set.

			Returns:
				If the text argument is passed then the <NodeList> is returned, otherwise
				the text is returned.

			Example:
				(code)
				var div = glow.dom.create("<div></div>").text("Hello World!");
				var greeting = div.text();
				(end)
			*/
			text: function (/* text */) {
				var args = arguments,
					i = 0,
					that = this,
					length = that.length;
				
				if (args.length > 0) {
					for (; i < length; i++) {
						that[i].innerHTML = "";
						that[i].appendChild(doc.createTextNode(args[0]));
					}
					return that;
				}
				//innerText (empty) and textContent (undefined) don't work in safari 2 for hidden elements
				return that[0].innerText || that[0].textContent == undefined ? getTextNodeConcat(that[0]) : that[0].textContent;
			},

			/*
			Method: empty
				Removes the contents of all the nodes.
			
			Returns:
				The <NodeList>.

			Example:
				(code)
				// remove the contents of all textareas
				glow.dom.get("textarea").empty();
				(end)
			*/
			empty: function () {
				for (var i = 0, length = this.length, child; i < length; i++) {
					while (child = this[i].firstChild) {
						this[i].removeChild(child);
					}
				}
				return this;
			},

			/*
			Method: remove
				Removes each node from its parent node.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// take all the links out of a document
				glow.dom.get("a").remove();
				(end)
			*/
			remove: function () {
				for (var that = this, i = 0, length = that.length, parentNode; i < length; i++) {
					if (parentNode = that[i].parentNode) {
						parentNode.removeChild(that[i]);
					}
				}
				return that;	
			},

			/*
			Method: clone
				Gets a <NodeList> containing a clone of each node.
				
			Returns:
				The new <NodeList>.

			Example:
				(code)
				// get a copy of all heading elements
				glow.dom.get("h1, h2, h3, h4, h5, h6").clone();
				(end)
			*/
			clone: function () {
				// TODO - clone events
				var ret = [],
					i = 0,
					length = this.length;
					
				for (; i < length; i++) {
					ret[i] = this[i].cloneNode(true);
				}
				return r.get(ret);
			},

			/*
			Method: html
				Get the inner HTML of the first node, or set the inner HTML of all matched nodes.
				
				(this function is irrelevant on XML NodeLists)
				
			Arguments:
				*html* (string)
				
				If passed then the inner html is set.

			Returns:
				If the html argument is passed, then the <NodeList> is returned, otherwise
				the inner HTML of the first node is returned.

			Example:
				(code)
				// get the html in #footer
				var footerContents = glow.dom.get("#footer").html();

				// set a new footer
				glow.dom.get("#footer").html("<strong>Hello World!</strong>");
				(end)
			*/
			html: function (/* html */) {
				var args = arguments,
					that = this,
					i = 0,
					length = that.length;
			
				if (args.length > 0) {
					for (; i < length; i++) {
						that[i].innerHTML = args[0];
					}
					return that;
				}
				return that[0].innerHTML;
			},
			
			/*
			Method: width
				Gets the width of the first element in pixels. Or sets the width of all elements in the <NodeList> with the given integer. This does not include the padding or border of the element in browsers supporting the correct box model.
				
				(this function is irrelevant on XML NodeLists)

			Arguments:
				*width* (Number)
				
				Optional. New width in pixels.

			Returns:
				Width of first element in pixels, or <NodeList> when setting widths.

			Example:
				(code)
				glow.dom.get("#myDiv").width(); //get the width of #myDiv
				glow.dom.get("#myDiv li").width(200); //sets the width of list items in #myDiv to 200 pixels
				(end)
			*/
			width: function(width) {
				if (width == undefined) {
					return getElmSize(this[0]).width;
				}
				setElmsSize(this, width, "width");
				return this;
			},
			
			/*
			Method: height
				Gets the height of the first element in pixels. Or sets the height of all elements in the <NodeList> with the given integer. This does not include the padding or border of the element in browsers supporting the correct box model.
				
				(this function is irrelevant on XML NodeLists)
				
			Arguments:
				*width* (Number)
				
				Optional. New height in pixels.

			Returns:
				Height of first element in pixels, or <NodeList> when setting heights.

			Example:
				(code)
				glow.dom.get("#myDiv").height(); //get the height of #myDiv
				glow.dom.get("#myDiv li").height(200); //sets the height of list items in #myDiv to 200 pixels
				(end)
			*/
			height: function(height) {
				if (height == undefined) {
					return getElmSize(this[0]).height;
				}
				setElmsSize(this, height, "height");
				return this;
			},
			
			/*
			Method: css
				Get a CSS property for the first element in the <NodeList> or set the CSS property on all list items.
				Multiple properties can be passed, resulting in the total of those values being returned, or the passed value being set on all properties.
				
				Setting "opacity" will work in IE by using filter:alpha(opacity=val);
				
				Return values are strings. For instance, "height" will return "25px" for an element 25 pixels high. You may want to use parseInt to convert these values.
				
				Here are the compatible properties you can get, if you use one which isn't on this list the return value may differ between browsers.
				
				width - Returns pixel width, eg "25px". Can be used even if width has not been set with CSS.
				height - Returns pixel height, eg "25px". Can be used even if height has not been set with CSS.
				top, right, bottom, left - Pixel position relative to positioned parent, or original location if position:relative, eg "10px". These can be used even if position:static.
				padding-top - Pixel size of top padding, eg "5px"
				padding-right - Pixel size of right padding, eg "5px"
				padding-bottom - Pixel size of bottom padding, eg "5px"
				padding-left - Pixel size of left padding, eg "5px"
				margin-top - Pixel size of top margin, eg "5px"
				margin-right - Pixel size of right margin, eg "5px"
				margin-bottom - Pixel size of bottom margin, eg "5px"
				margin-left - Pixel size of left margin, eg "5px"
				border-top-width - Pixel size of top border, eg "5px"
				border-right-width - Pixel size of right border, eg "5px"
				border-bottom-width - Pixel size of bottom border, eg "5px"
				border-left-width - Pixel size of left border, eg "5px"
				border-*-style - eg "dotted"
				border-*-color - returns colour in format "rgb(255, 255, 255)", return value also has properties r, g & b to get individual values as integers
				color - returns colour in format "rgb(255, 255, 255)", return value also has properties r, g & b to get individual values as integers
				list-style-position - eg "outside"
				list-style-type - eg "square"
				list-style-image - Returns full image path, eg "url(http://www.bbc.co.uk/lifestyle/images/bullets/1.gif)" or "none"
				background-image - Returns full image path, eg "url(http://www.bbc.co.uk/lifestyle/images/bgs/1.gif)" or "none"
				background-attachment - eg "scroll"
				background-repeat - eg "repeat-x"
				direction - eg "ltr"
				font-style - eg "italic"
				font-variant - eg "small-caps"
				line-height - Pixel height, eg "30px". Note, Opera may return value with 2 decimal places, eg "30.00px"
				letter-spacing - Pixel spacing, eg "10px"
				text-align - eg "right"
				text-decoration - eg "underline"
				text-indent - Pixel indent, eg "10px"
				white-space - eg "nowrap"
				word-spacing - Pixel spacing, eg "5px"
				float - eg "left"
				clear - eg "right"
				opacity - Value between 0 & 1. In IE, this value comes from the filter property (/100)
				position - eg "relative"
				z-index - eg "32"
				display - eg "block"
				
				The following return values that may be usable but have differences in browsers
				
				font-family - Some browsers return the font used ("Verdana"), others return the full list found in the declaration ("madeup, verdana, sans-serif")
				font-size - Returns size as pixels except in IE, which will return the value in the same units it was set in ("0.9em").
				font-weight - Returns named values in some browsers ("bold"), returns computed weight in others ("700")
				text-transform - IE5.5 returns "uppercase" instead of "capitalize"
				
				(this function is irrelevant on XML NodeLists)

			Arguments:
				*property* (String / Array)
				
				CSS property / properties, such as "border-width" or "float"
				
				*value* (String)
				
				Optional. Value to apply

			Returns:
				The CSS value of the first element or <NodeList> when setting values

			Example:
				(code)
				glow.dom.get("#myDiv").css("display"); //"block"
				glow.dom.get("#myDiv").css("padding-left", "10px"); //set left padding to 10px
				glow.dom.get("#myDiv").css(["padding-left", "padding-right"]); // "30px", total of left & right padding
				glow.dom.get("#myDiv").css(["padding-left", "padding-bottom"], "10px"); //set left & bottom padding to 10px
				(end)
			*/
			css: function(prop, val) {
				var that = this,
					thisStyle,
					i = 0,
					len = that.length;
				
				if (val != undefined) { //setting stuff
					prop = toStyleProp(prop);
					for (; i < len; i++) {
						thisStyle = that[i].style;
						if (prop == "opacity" && env.ie) {
							if (val === "") {
								thisStyle.filter = "";
							} else {
								thisStyle.filter = "alpha(opacity=" + Math.round(Number(val, 10) * 100) + ")";
							}
						} else {
							thisStyle[prop] = val;
						}
					}
					return that;
				} else { //getting stuff
					if (!that.length) { return; }
					return getCssValue(that[0], prop);
				}
			},
			
			/*
			Method: offset
				Get the cumlative offset of the first node from the top left of the document.
				
				(this function is irrelevant on XML NodeLists)
				
			Arguments:
				*ignoreScrolling* (boolean)
				
				Default false. By default, elements inside elements that have scroll bars will have the scroll offset deducted from the total offset. You can disable this processing by setting this argument to true. The scroll of the document is always ignored.

			Returns:
				An object containing x and y in pixels.

			Example:
				(code)
				// get the x position of myDiv
				glow.dom.get('#myDiv').offset().x
				(end)
			*/
			offset: function (ignoreScrolling) {
				var node = this[0],
					x = 0,
					y = 0,
					first = true;
				
				if (! node) { return undefined; }
				do {
					x += node.offsetLeft;
					y += node.offsetTop;
					if (!ignoreScrolling && !first && node != docElm && node != docBody) {
						x -= node.scrollLeft;
						y -= node.scrollTop;
					}
					first = false;
				} while (node = node.offsetParent);
				return {x:x, y:y};
			},
			

			/*
			Method: append
				Append the given nodes to each element in the <NodeList>. If there is
				more than one Element in the <NodeList>, then the given elements
				are prepended to the first Element and clones are prepended to the other
				Elements.
				
				(string parameters cannot be used when dealing with XML NodeLists)
				
			Arguments:
				*nodeSpec* (String/Node/NodeList)
				
				node/nodes to append to each element.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// ends every paragraph with '...'
				glow.dom.get('p').append(
					'<span>...</span>'
				);
				(end)
			*/
			append: function (nodeSpec) {
				var that = this,
					j = 0,
					i = 1,
					length = that.length,
					nodes;
			
				if (length == 0) { return that; }
				nodes = typeof nodeSpec == "string" ? nodelistToArray(stringToNodes(nodeSpec)) :
						nodeSpec.nodeType ? [nodeSpec] : nodelistToArray(nodeSpec);
				
				for (; nodes[j]; j++) {
					that[0].appendChild(nodes[j]);
				}
				for (; i < length; i++) {
					for (j = 0; nodes[j]; j++) {
						that[i].appendChild(nodes[j].cloneNode(true));
					}
				}
				return that;
			},

			/*
			Method: prepend
				Prepend the given nodes to each element in the <NodeList>. If there is
				more than one Element in the <NodeList>, then the given elements
				are prepended to the first Element and clones are prepended to the other
				Elements.
				
				(string parameters cannot be used when dealing with XML NodeLists)
				
			Arguments:
				*nodeSpec* (String/Node/NodeList)
				
				node/nodes  to append to each element.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// prepends every paragraph with 'Paragraph: '
				glow.dom.get('p').prepend(
					'<span>Paragraph: </span>'
				);
				(end)
			*/
			prepend: function (nodeSpec) {
				var that = this,
					j = 0,
					i = 1,
					length = that.length,
					nodes,
					first;
			
				if (length == 0) { return that; }
				
				nodes = typeof nodeSpec == "string" ? nodelistToArray(stringToNodes(nodeSpec)) :
						nodeSpec.nodeType ? [nodeSpec] : nodelistToArray(nodeSpec);
				
				first = that[0].firstChild;
				
				for (; nodes[j]; j++) {
					that[0].insertBefore(nodes[j], first);
				}
				
				for (; i < length; i++) {
					first = that[i].firstChild;
					for (j = 0; nodes[j]; j++) {
						that[i].insertBefore(nodes[j].cloneNode(true), first);
					}
				}
				return that;
			},

			/*
			Method: appendTo
				Append the <NodeList> to the given nodes. If there is more than one node
				(i.e. if the argument is an array of nodes or <NodeList>) then it will
				append the <NodeList> to the first node and a clone to each subsequent node.
				
			Arguments:
				*nodes* (Element/array of Elements/NodeList/Selector)
				
				The node/nodes  to append the <NodeList> to.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// appends '...' to every paragraph
				glow.dom.create('<span>...</span>').appendTo('p');
				(end)

			*/
			appendTo: function (nodes) {
				if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
				nodes.append(this);
				return this;
			},

			/*
			Method: prependTo
				Prepend the <NodeList> to the given nodes. If there is more than one node
				(i.e. if the argument is an array of nodes or <NodeList>) then it will
				append the <NodeList> to the first node and a clone to each subsequent node.

			Arguments:
				*nodes* (Element/array of Elements/NodeList/Selector)
				
				The node/nodes  to prepend the <NodeList> to.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// prepends 'Paragraph: ' to every paragraph
				glow.dom.create('<span>Paragraph: </span>').prependTo('p');
				(end)

			*/
			prependTo: function (nodes) {
				if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
				nodes.prepend(this);
				return this;
			},

			/*
			Method: after
				Insert nodes after each element in the <NodeList>. If there is more than one
				element in the node list, the nodes will be inserted after the first element
				and clones inserted after each subsequent node.
				
				(string parameters cannot be used when dealing with XML NodeLists)
				
			Arguments:
				*nodeSpec* (String/Element/array of Elements/NodeList)
				
				The node/nodes  to insert after each element.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// adds a paragraph after each heading
				glow.dom.get('h1, h2, h3').after('<p>...</p>');
				(end)
			*/
			after: function (nodeSpec) {
				var that = this,
					length = that.length,
					nodes,
					nodesLen,
					j,
					i = 1,
					cloned;
				
				if (length == 0) { return that; }
				
				nodes = typeof nodeSpec == "string" ? r.create(nodeSpec) :
					nodeSpec instanceof r.NodeList ? nodeSpec :
					r.get(nodeSpec);
					
				nodesLen = nodes.length;
				
				for (j = nodesLen - 1; j >= 0; j--) {
					that[0].parentNode.insertBefore(nodes[j], that[0].nextSibling);
				}
				for (; i < length; i++) {
					cloned = nodes.clone();
					
					for (j = nodesLen - 1; j >= 0; j--) {
						that[i].parentNode.insertBefore(cloned[j], that[i].nextSibling);
					}
				}
				return that;
			},

			/*
			Method: before
				Insert nodes before each element in the <NodeList>. If there is more than one
				element in the node list, the nodes will be inserted after the first element
				and clones inserted before each subsequent node.
				
				(string parameters cannot be used when dealing with XML NodeLists)
				
			Arguments:
				*nodeSpec* (String/Element/array of Elements/NodeList)
				
				The node/nodes  to insert before each element.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// adds a heading before each
				glow.dom.get('p').before('<h1>Paragraph:</h1>');
				(end)
			*/
			before: function (nodeSpec) {
				var that = this,
					length = that.length,
					j = 0,
					i = 1,
					nodes,
					nodesLen,
					cloned;
			
				if (length == 0) { return that; }
				
				nodes = typeof nodeSpec == "string" ? r.create(nodeSpec) :
					nodeSpec instanceof r.NodeList ? nodeSpec :
					r.get(nodeSpec);
				
				nodesLen = nodes.length;
				
				for (; j < nodesLen; j++) {
					that[0].parentNode.insertBefore(nodes[j], that[0]);
				}
				for (; i < length; i++) {
					cloned = nodes.clone();
					for (j = 0; j < nodesLen; j++) {
						that[i].parentNode.insertBefore(cloned[j], that[i]);
					}
				}
				return that;
			},

			/*
			Method: insertAfter
				Insert items in the <NodeList> after each of the given elements. If more than one
				element is passed in, the <NodeList> will be inserted after the first element
				and clones inserted after each subsequent element.

			Arguments:
				*nodes* (Selector/Element/array of Elements/NodeList)
				
				The node/nodes  to insert the <NodeList> after.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// adds a paragraph after each heading
				glow.dom.create('<p>...</p>').insertAfter('h1, h2, h3');
				(end)
			*/
			insertAfter: function (nodes) {
				if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
				nodes.after(this);
				return this;
			},

			/*
			Method: insertBefore
				Insert items in the <NodeList> before each of the given elements. If more than one
				element is passed in, the <NodeList> will be inserted before the first element
				and clones inserted before each subsequent element.

			Arguments:
				*nodes* (Selector/Element/array of Elements/NodeList)
				
				The node/nodes  to insert the <NodeList> before.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// adds a heading before each paragraph
				glow.dom.create('<h1>Paragraph:</h1>').insertBefore('p');
				(end)
			*/
			insertBefore: function (nodes) {
				if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
				nodes.before(this);
				return this;
			},

			/*
			Method: replaceWith
				Replace the contents of elements in the <NodeList> with the given nodes. If the
				<NodeList> has more than one element, the passed in nodes will go into the first
				element and clones will go into each subsequent element.
				
				(string parameters cannot be used when dealing with XML NodeLists)
				
			Arguments:
				*nodeSpec* (String/Element/array of Elements/NodeList)
				
				The element/elements to replace the contents with.

			Returns:
				The <NodeList>.

			Example:
				(code)
				// replace all elements with class 'topSecret' with an access denied message
				glow.dom.get('.topSecret').replaceWith('<span>ACCESS DENIED</span>');
				(end)
			*/
			replaceWith: function (nodeSpec) {
				var that = this,
					length = that.length,
					nodes,
					nodesLen,
					j = 0,
					i = 1,
					cloned;
			
				if (length == 0) { return that; }
				
				nodes =
					typeof nodeSpec == "string" ? r.create(nodeSpec) :
					nodeSpec instanceof r.NodeList ? nodeSpec :
					r.get(nodeSpec);
				
				that[0].innerHTML = "";
				
				nodesLen = nodes.length;
				
				for (; j < nodesLen; j++) {
					that[0].appendChild(nodes[j]);
				}
				for (; i < length; i++) {
					that[i].innerHTML = "";
					cloned = nodes.clone();
					for (j = 0; j < nodesLen; j++) {
						that[i].appendChild(cloned[j]);
					}
				}
				return that;
			},

			/*
			Method: get
				Get decendents of elements in the <NodeList> that match a CSS selector.

			Arguments:
				*selector* (CSS Selector)
				
				Selects the elements to return as a nodeList.

			Returns:
				A new <NodeList> containing the matched decendents.

			Example:
				(code)
				var myNodeList = glow.dom.create("<div><a href='http://news.bbc.co.uk'>News</a></div>");

				 // gets 'a' tags that are decendants of the current context
				myNodeList = myNodeList.get("a");
				(end)
			*/
			get: function() {
				
				/*
				PrivateFunction: compileSelector
					Compile a CSS selector to an AST.

				Arguments:
					sSelector - A string containing the CSS selector (or comma separated group of selectors).

				Returns:
					An array containing an AST for each selector in the group.
				*/
				function compileSelector(sSelector) {
					//return from cache if possible
					if (cssCache[sSelector]) {
						return cssCache[sSelector];
					}
					
					var r = [], //array of result objects
						ri = 0, //results index
						comb, //current combinator
						tagTmp,
						idTmp,
						aRx, //temp regex result
						matchedCondition, //have we matched a condition?
						sLastSelector, //holds last copy of selector to prevent infinite loop
						firstLoop = true;
					
					while (sSelector && sSelector != sLastSelector) {
						tagTmp = "";
						idTmp = "";
						//protect us from infinite loop
						sLastSelector = sSelector;
						
						//start by getting the scope (combinator)
						if (aRx = cssRegex.combinator.exec(sSelector)) {
							comb = aRx[1];
							sSelector = sSelector.slice(aRx[0].length);
						}
						//look for optimal id & tag searching
						if (aRx = cssRegex.tagName.exec(sSelector)) {
							tagTmp = aRx[1];
							sSelector = sSelector.slice(aRx[0].length);
						}
						if (aRx = cssRegex.classNameOrId.exec(sSelector)) {
							if (aRx[1] == "#") {
								idTmp = aRx[2];
								sSelector = sSelector.slice(aRx[0].length);
							}
						}
						if (!comb) { //use native stuff
							if (idTmp && firstLoop) {
								r[ri++] = [getByIdQuick, [idTmp.replace(/\\/g, ""), tagTmp || "*", null]];
							} else {
								r[ri++] = [getByTagName, [tagTmp || "*", null]];
								if (idTmp) {
									r[ri++] = [hasId, [idTmp.replace(/\\/g, ""), null]];
								}
							}
						} else if (comb == ">") {
							r[ri++] = [getChildren, [null]];
							if (idTmp) {
								r[ri++] = [hasId, [idTmp.replace(/\\/g, ""), null]];
							}
							if (tagTmp && tagTmp != "*") { //uses tag
								r[ri++] = [isTag, [tagTmp, null]];
							}
						}

						//other conditions can appear in any order, so here we go:
						matchedCondition = true;
						while (matchedCondition) {
							//look for class or ID
							if (sSelector.charAt(0) == "#" || sSelector.charAt(0) == ".") {
								if (aRx = cssRegex.classNameOrId.exec(sSelector)) {
									if (sSelector.charAt(0) == "#") { //is ID
										//define ID, remove escape chars
										r[ri++] = [hasId, [aRx[2].replace(/\\/g, ""), null]];
									} else { //is class
										r[ri++] = [hasClassName, [aRx[2].replace(/\\/g, ""), null]];
									}
									sSelector = sSelector.slice(aRx[0].length);
								} else {
									//make this more user friendly?
									throw new Error("Invalid Selector");
								}
							} else {
								matchedCondition = false;
							}
						}
						
						firstLoop = false;
					}
					
					if (sSelector !== "") {
						//make this more user friendly?
						throw new Error("Invalid Selector");
					}
					
					//add to cache and return
					return cssCache[sSelector] = r;
				}

				/*
				PrivateFunction: fetchElements
					Get elements which match array of compiled conditions based on
					an initial context.

				Arguments:
					a - (object) CSS selector AST object.
					initialContext - DOM Element or DOM Document to search within.
				
				Returns:
					An array of matching elements.
				*/
				function fetchElements(a, initialContext) {
					var context = initialContext; //elements to look within
					
					for (var i = 0, al = a.length; i < al; i++) {
						a[i][1][a[i][1].length - 1] = context;
						context = a[i][0].apply(this, a[i][1]);
					}
					return context;
				}
				
				/*
				PrivateFunction: getByIdQuick
					Get an element with a specific tag name, within a context.

				Arguments:
					id - (string) the id of the element.
					tagName - (string) the name of the element.
					context - DOM Element or DOM Document in which to find the element.

				Returns:
					A DOM Element matching the specified criteria.
				*/
				function getByIdQuick(id, tagName, context) {
					var r = [], ri = 0, notQuick = [], notQuicki = 0, tmpNode;
					for (var i = 0, length = context.length; i < length; i++) {
						if (context[i].getElementById) {
							tmpNode = context[i].getElementById(id);
							if (tmpNode && (tmpNode.tagName == tagName.toUpperCase() || tagName == "*" || tmpNode.tagName == tagName)) {
								r[ri++] = tmpNode;
							}
						} else {
							notQuick[notQuicki++] = context[i];
						}
					}
					//deal with the ones we couldn't do quick
					if (notQuick[0]) {
						notQuick = getByTagName(tagName, notQuick);
						notQuick = hasId(id, notQuick);
					}
					return r.concat(notQuick);
				}
				
				function getChildren(context) {
					var r = [];
					for (var i = 0, length = context.length; i < length; i++) {
						append(r, context[i].childNodes);
					}
					return r;
				}
				
				function hasId(id, context) {
					for (var i = 0, length = context.length; i < length; i++) {
						if (context[i].id == id) {
							//is this a safe optimisation?
							return [context[i]];
						}
					}
					return [];
				}
				
				/*
				PrivateFunction: isTag
					Get an array of elements within an array that have a given tag name.

				Arguments:
					tagName - (string) the name of the element.
					context - (array) elements to match.

				Returns:
					An array of matching elements.
				*/
				function isTag(tagName, context) {
					var r = [], ri = 0;
					for (var i = 0, length = context.length; i < length; i++) {
						if (context[i].tagName == tagName.toUpperCase() || context[i].tagName == tagName) {
							r[ri++] = context[i];
						}
					}
					return r;
				}

				/*
				PrivateFunction: hasClassName
					Get elements that have a given class name from a provided array of elements.

				Arguments:
					className - (string) the name of the class.
					context - (array) the DOM Elements to match.

				Returns:
					An array of matching DOM Elements.
				*/
				function hasClassName(className, context) {
					var r = [], ri = 0;
					for (var i = 0, length = context.length; i < length; i++) {
						if ((" " + context[i].className + " ").indexOf(" " + className + " ") != -1) {
							r[ri++] = context[i];
						}
					}
					return r;
				}
				
				/*
				PrivateFunction: getBySelector
					Get elements within a context by a CSS selector.

				Arugments:
					sSelector - (string) CSS selector.
					context - DOM Document or DOM Element to search within.

				Returns:
					An array of DOM Elements.
				*/
				function getBySelector(sSelector, context) {
					var aCompiledCSS; // holds current compiled css statement
					var r = [];
					//split multiple selectors up
					var aSelectors = sSelector.split(",");
					//process each
					for (var i = 0, nSelLen = aSelectors.length; i < nSelLen; i++) {
						aCompiledCSS = compileSelector(glow.lang.trim(aSelectors[i]));
						//get elements from DOM
						r = r.concat(fetchElements(aCompiledCSS, context));
					}
					return r;
				}
				
				/*
				PrivateFunction: getIfWithinContext
					Get elements from a set of elements that are within at least one of another
					set of DOM Elements.

				Arguments:
					nodes - DOM Elements to take the results from.
					context - DOM Elements that returned elements must be within.

				Returns:
					An array of DOM Elements.
				*/
				function getIfWithinContext(nodes, context) {
					nodes = nodes.length ? nodes : [nodes];
					var r = []; //to return
					var nl; 
					
					//loop through nodes
					for (var i = 0; nodes[i]; i++) {
						nl = glow.dom.get(nodes[i]);
						//loop through context nodes
						for (var n = 0; context[n]; n++) {
							if (nl.isWithin(context[n])) {
								r[r.length] = nl[0];
								break;
							}
						}
					}
					return r;
				}
				
				// main implementation
				return function(sSelector) {
					// no point trying if there's no current context
					if (!this.length) { return this; }
					var r = []; //nodes to return
					// decide what to do by arg
					for (var i = 0, argLen = arguments.length; i < argLen; i++) {
						if (typeof arguments[i] == "string") { // css selector string
							r = r.concat(getBySelector(arguments[i], this));
							// append(r, getBySelector(arguments[i], this));
						} else { // nodelist, array, node
							r = r.concat(getIfWithinContext(arguments[i], this));
							// append(r, getIfWithinContext(arguments[i], this));
						}
					}
					
					// strip out duplicates, wrap in nodelist
					return glow.dom.get(unique(r));
				};
			}()
		};
		return r;
	}
});
;/*&debug*/
/*
Namespace: glow.debug
*/
glow.module("glow.debug", "0.4.0", {
	require: [],
	implementation: function() {

		/*
		Module: glow.debug
			Do extended error checking in debug builds of the library.

		Synopsis:
			(code)
		    // place debug start comment here
			try {
				glow.debug.assertParameterObject(arguments[0], ['name'], ['email'], 'invalid parameters to createUser');
				glow.debug.assert(typeof arguments[0].name == 'string' && arguments[0].search(/\S/), "invalid name");
			} catch (e) {
				throw e;
			}
			// place debug end comment here
			(end)
		*/


		return {
			/*
			Method: glow.debug.assert
				Check that a condition is true.

			Arguments:
				condition - (boolean) the condition to check
				message - (string) the error message to use if the condition is false.

			Returns:
				No useful return value.

			Throws:
				If the condition is false then an exception is raised with the given message.

			Example:
				(code)
				// check that the first argument to a function was a glow.dom.NodeList
				glow.debug.assert(aruments[0] instanceof glow.dom.NodeList, 'nodeList must be a glow.dom.NodeList');
				(end)
			*/
			assert: function (condition, message) {
				if (! condition) { throw message; }
			},

			/*
			Method: glow.debug.assertParameterObject
				Check that a parameter object contains certain parameters.

			Arguments:
				parameters - (object) the parameter object to check.
				required - (array) the names of parameters that must be present.
				optional - (array) the names of parameters that can be present.
				message - (string) the error message to use (with more detailed information appended).

			Returns:
				No useful return value.

			Example:
				(code)
				// check that name is passed, and possibly email
				glow.debug.assertParameterObect(arguments[0], ['name'], ['email'], 'invalid parameters to addUser');
				(end)
			*/
			assertParameterObject: function (parameters, required, optional, message) {
				var i, length;
				var params = {};
				for (i = 0, length = required.length; i < length; i++) {
					params[required[i]] = 1;
					if (! (required[i] in parameters)) {
						throw message + ", required parameter '" + required[i] + "' not passed";
					}
				}
				for (i = 0, length = optional.length; i < length; i++) {
					params[optional[i]] = 1;
				}
				for (i in parameters) {
					if (! (i in params)) {
						throw message + ", unexpected parameter '" + i + "' passed";
					}
				}
			}
		};
	}
});

/*&enddebug*/
;/*
Namespace: glow.data
*/
glow.module("glow.data", "0.4.0", {
	require: ["glow.dom"],
	implementation: function() {
		//private

		/*
		PrivateProperty: TYPES
			hash of strings representing data types
		*/
		var TYPES = {
			UNDEFINED : "undefined",
			OBJECT    : "object",
			NUMBER    : "number",
			BOOLEAN   : "boolean",
			STRING    : "string",
			ARRAY     : "array",
			FUNCTION  : "function",
			NULL      : "null"
		}

		/*
		PrivateProperty: TEXT
			hash of strings used in encoding/decoding
		*/
		var TEXT = {
			AT    : "@",
			EQ    : "=",
			DOT   : ".",
			EMPTY : "",
			AND   : "&",
			OPEN  : "(",
			CLOSE : ")"
		}

		/*
		PrivateProperty: JSON
			nested hash of strings and regular expressions used in encoding/decoding Json
		*/
		var JSON = {
			HASH : {
				START     : "{",
				END       : "}",
				SHOW_KEYS : true
			},

			ARRAY : {
				START     : "[",
				END       : "]",
				SHOW_KEYS : false
			},

			DATA_SEPARATOR   : ",",
			KEY_SEPARATOR    : ":",
			KEY_DELIMITER    : "\"",
			STRING_DELIMITER : "\"",

			SAFE_PT1 : /^[\],:{}\s]*$/,
			SAFE_PT2 : /\\./g,
			SAFE_PT3 : /\"[^\"\\\n\r]*\"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g,
			SAFE_PT4 : /(?:^|:|,)(?:\s*\[)+/g
		}

		/*
		PrivateProperty: SLASHES
			hash of strings and regular expressions used in encoding strings
		*/
		var SLASHES = {
			TEST : /[\b\n\r\t\\\f\"]/g,
			B : {PLAIN : "\b", ESC : "\\b"},
			N : {PLAIN : "\n", ESC : "\\n"},
			R : {PLAIN : "\r", ESC : "\\r"},
			T : {PLAIN : "\t", ESC : "\\t"},
			F : {PLAIN : "\f", ESC : "\\f"},
			SL : {PLAIN : "\\", ESC : "\\\\"},
			QU : {PLAIN : "\"", ESC : "\\\""}
		}

		/*
		PrivateMethod: _getType
			Callback function for glow.lang.replace to escape appropriate characters

		Arguments:
			s - the regex match to be tested

		Returns:
			The escaped version of the input.
		*/
		function _replaceSlashes(s) {
			switch (s) {
				case SLASHES.B.PLAIN: return SLASHES.B.ESC;
				case SLASHES.N.PLAIN: return SLASHES.N.ESC;
				case SLASHES.R.PLAIN: return SLASHES.R.ESC;
				case SLASHES.T.PLAIN: return SLASHES.T.ESC;
				case SLASHES.F.PLAIN: return SLASHES.F.ESC;
				case SLASHES.SL.PLAIN: return SLASHES.SL.ESC;
				case SLASHES.QU.PLAIN: return SLASHES.QU.ESC;
				default: return s;
			}
		}

		/*
		PrivateMethod: _getType
			Returns the data type of the object

		Arguments:
			object - the object to be tested

		Returns:
			A one of the TYPES constant properties that represents the data type of the object.
		*/
		function _getType(object) {
			if((typeof object) == TYPES.OBJECT) {
				if (object == null) {
					return TYPES.NULL;
				} else {
					return (object instanceof Array)?TYPES.ARRAY:TYPES.OBJECT;
				}
			} else {
				return (typeof object);
			}
		}

		//public
		return {
			/*
			Method: encodeUrl
				Encodes an object for use as a query string.

			Arguments:
				*objectToEncode* (object)
				
				The object to be encoded. This must be a hash whose values can only be primitives or arrays of primitives.

			Returns:
				A string representing the object suitable for use as a query string, with all values suitably escaped.

				It does not include the initial question mark. Where the input field was an array, the key is repeated in the output.
			Example:
				(code)
				var getRef = glow.data.encodeUrl({foo: "Foo", bar: ["Bar 1", "Bar2"]});
				(end)
				The above code will return "foo=Foo&bar=Bar%201&bar=Bar2".
			*/
			encodeUrl : function (object) {
				var objectType = _getType(object);
				var paramsList = [];
				var listLength = 0;

				if (objectType != TYPES.OBJECT) {
					throw new Error("glow.data.encodeUrl: cannot encode item");
				} else {
					for (var key in object) {
						switch(_getType(object[key])) {
							case TYPES.FUNCTION:
							case TYPES.OBJECT:
								throw new Error("glow.data.encodeUrl: cannot encode item");
								break;
							case TYPES.ARRAY:
								for(var i = 0, l = object[key].length; i < l; i++) {
									switch(_getType(object[key])[i]) {
										case TYPES.FUNCTION:
										case TYPES.OBJECT:
										case TYPES.ARRAY:
											throw new Error("glow.data.encodeUrl: cannot encode item");
											break;
										default:
											paramsList[listLength++] = key + TEXT.EQ + encodeURIComponent(object[key][i]);
									}
								}
								break;
							default:
								paramsList[listLength++] = key + TEXT.EQ + encodeURIComponent(object[key]);
						}
					}

					return paramsList.join(TEXT.AND);
				}
			},

			/*
			Method: decodeUrl
				Decodes a query string into an object.

			Arguments:
				*textToDecode* (string)
				
				The query string to be decoded. It should not include the initial question mark.

			Returns:
				A object representing the data given by the query string, with all values suitably unescaped.

				All keys in the query string are keys of the object. Repeated keys result in an array.
			Example:
				(code)
				var getRef = glow.data.decodeUrl("foo=Foo&bar=Bar%201&bar=Bar2");
				(end)
				The above code will return the object {foo: "Foo",	bar: ["Bar 1", "Bar2"]}.
			*/
			decodeUrl : function (text) {
				if(_getType(text) != TYPES.STRING) {
					throw new Error("glow.data.decodeUrl: cannot decode item");
				} else if (text === "") {
					return {};
				}

				var result = {};
				var keyValues = text.split(TEXT.AND);

				var thisPair, key, value;

				for(var i = 0, l = keyValues.length; i < l; i++) {
					thisPair = keyValues[i].split(TEXT.EQ);
					if(thisPair.length != 2) {
						throw new Error("glow.data.decodeUrl: cannot decode item");
					} else {
						key = decodeURIComponent(thisPair[0]);
						value = decodeURIComponent(thisPair[1]);

						switch (_getType(result[key])) {
							case TYPES.ARRAY:
								result[key][result[key].length] = value;
								break;
							case TYPES.UNDEFINED:
								result[key] = value;
								break;
							default:
								result[key] = [result[key], value];
						}
					}
				}

				return result;
			},

			/*
			Method: encodeJson
				Encodes an object into a string Json representation.

			Arguments:
				*objectToEncode* (object)
				
				The object to be encoded. This can be arbitrarily nested, but must not contain functions or cyclical structures.

			Returns:
				A string representing the object as Json.

			Example:
				(code)
				var getRef = glow.data.encodeJson({foo: "Foo", bar: ["Bar 1", "Bar2"]});
				(end)
				The above code will return '{"foo": "Foo", "bar": ["Bar 1", "Bar2"]}'.
			*/
			encodeJson : function (object, options) {
				function _encode(object, options)
				{
					if(_getType(object) == TYPES.ARRAY) {
						var type = JSON.ARRAY;
					} else {
						var type = JSON.HASH;
					}

					var serial = [type.START];
					var len = 1;
					var dataType;
					var notFirst = false;

					for(var key in object) {
						dataType = _getType(object[key]);

						if(dataType != TYPES.UNDEFINED) { /* ignore undefined data */
							if(notFirst) {
								serial[len++] = JSON.DATA_SEPARATOR;
							}
							notFirst = true;

							if(type.SHOW_KEYS) {
								serial[len++] = JSON.KEY_DELIMITER;
								serial[len++] = key;
								serial[len++] = JSON.KEY_DELIMITER;
								serial[len++] = JSON.KEY_SEPARATOR;
							}

							switch(dataType) {
								case TYPES.FUNCTION:
									throw new Error("glow.data.encodeJson: cannot encode item");
									break;
								case TYPES.STRING:
								default:
									serial[len++] = JSON.STRING_DELIMITER;
									serial[len++] = glow.lang.replace(object[key], SLASHES.TEST, _replaceSlashes);
									serial[len++] = JSON.STRING_DELIMITER;
									break;
								case TYPES.NUMBER:
								case TYPES.BOOLEAN:
									serial[len++] = object[key];
									break;
								case TYPES.OBJECT:
								case TYPES.ARRAY:
									serial[len++] = _encode(object[key], options);
									break;
								case TYPES.NULL:
									serial[len++] = TYPES.NULL;
									break;
							}
						}
					}
					serial[len++] = type.END;

					return serial.join(TEXT.EMPTY);
				}

				options = options || {};
				var type = _getType(object);

				if((type == TYPES.OBJECT) || (type == TYPES.ARRAY)) {
					return _encode(object, options);
				} else {
					throw new Error("glow.data.encodeJson: cannot encode item");
				}
			},

			/*
			Method: decodeJson
				Decodes a string Json representation into an object.

			Arguments:
				*text* (string)
				
				The string to be decoded. Must be valid Json.
				
				*options* (object)
				
				An options object (see below)
				
				Options:
				
				An object that may contain the following :
				
					*safeMode* (boolean)
					
					Property which indicates if the string only be decoded it is deemed "safe". The json.org regular expression checks are used. 
					Default - False.

			Returns:
				A Javascript object that mirrors the data given.

			Example:
				(code)
				var getRef = glow.data.decodeJson('{foo: "Foo", bar: ["Bar 1", "Bar2"]}');
				(end)
				The above code will return {foo: "Foo", bar: ["Bar 1", "Bar2"]}.
				(code)
				var getRef = glow.data.decodeJson('foobar', {safeMode: true});
				(end)
				The above code will throw an error.
			*/
			decodeJson : function (text, options) {
				if(_getType(text) != TYPES.STRING) {
					throw new Error("glow.data.decodeJson: cannot decode item");
				}

				options = options || {};
				options.safeMode = options.safeMode || false;

				var canEval = true;

				if(options.safeMode) {
					canEval = (JSON.SAFE_PT1.test(text.replace(JSON.SAFE_PT2, TEXT.AT).replace(JSON.SAFE_PT3, JSON.ARRAY.END).replace(JSON.SAFE_PT4, TEXT.EMPTY)));
				}

				if(canEval) {
					try {
						return eval(TEXT.OPEN + text + TEXT.CLOSE);
					}
					catch(e) {/* continue to error */}
				}

				throw new Error("glow.data.decodeJson: cannot decode item");
			},

			/*
			Method: escapeHTML
				Get a copy of a string with HTML entities escaped.

			Arguments:
				*text* (string)
				
				The string to be escaped.
				
			Returns:
				A string with entities escaped.

			Example:
				(code)
				// useful for protecting against XSS attacks:
				var fieldName = '" onclick="alert(\'hacked\')" name="';

				// but should be used in all cases like this:
				glow.dom.create('<input name="' + glow.data.escapeHTML(untrustedString) + '"/>');
				(end)
			*/	
			escapeHTML : function (html) {
				return glow.dom.create('<div></div>').text(html).html();
			}		   
		};
	}
});
;/*
Namespace: glow.net
*/
glow.module("glow.net", "0.4.0", {
	require: ["glow.data"],
	implementation: function() {
		//private
		
		var STR = {
			XML_ERR:"Cannot get response as XML, check the mime type of the data",
			POST_DEFAULT_CONTENT_TYPE:'application/x-www-form-urlencoded;'
		},
		/*
		PrivateProperty: requests
			array of request objects
		*/
			requests = [];
		
		/*
		PrivateMethod: xmlHTTPRequest
			returns a request object appropriate for the user agent
		*/
		function xmlHTTPRequest() {
			if (window.XMLHttpRequest) {
				return (xmlHTTPRequest = function() { return new XMLHttpRequest(); })();
			} else if (glow.env.ie < 7) {
				var progIds = ['MSXML2.XMLHTTP.6.0','"Msxml2.XMLHTTP.3.0"'];
				for (var i = 0, len = progIds.length; i < len; i++) {
					try {
						new ActiveXObject(progIds[i]);
						return (xmlHTTPRequest = function() { return new ActiveXObject(progIds[i]); })();
					} catch(e) { }
				}
			}
			return null;
		}
		
		
		/*
		PrivateMethod: populateOptions
			takes an option object for get / post and returns an option object with the defaults filled in for ommited properties
		*/
		function populateOptions(opts) {
			/*&Review:1.0.0
				this is to get around an interface change, we should ditch this when we get to 1.0.0
			*/
			opts.load && (opts.onLoad = opts.load);
			opts.error && (opts.onError = opts.error);
			/*&EndReview*/
			return glow.lang.apply(
				{
					onLoad: function(){},
					onError: function(){},
					addToHistory: false,
					headers: {},
					async: true,
					useCache: false,
					data: null
				},
				opts
			);
		}
		
		/*
		PrivateMethod: noCacheUrl
			Adds random numbers to the querystring of a url so the browser doesn't use a cached version
		*/
		function noCacheUrl(url) {
			return [url, (/\?/.test(url) ? "&" : "?"), "a", new Date().getTime(), parseInt(Math.random()*100000)].join("");
		}
		
		/*
		PrivateMethod: request
			Makes an http request
		*/
		function request(method, url, opts) {
			var reqId, //ID of this request
				req = xmlHTTPRequest(), //request object
				data = opts.data && (typeof opts.data == "string" ? opts.data : glow.data.encodeUrl(opts.data));
			
			if (! opts.useCache) {
				url = noCacheUrl(url);
			}
			
			req.open(method, url, opts.async);
			
			//add custom headers
			for (var i in opts.headers) {
				req.setRequestHeader(i, opts.headers[i]); 
			}
			
			if (opts.async) {
				req.onreadystatechange = function() {
					if (req.readyState == 4) {
						var response = new Response(req);
						if (req.status == 200 || (req.status == 0 && req.responseText)) {
							opts.onLoad(response);
						} else {
							opts.onError(response);
						}
					}
				};
				requests[reqId = requests.length] = req;
				req.send(data);
				return reqId;
			} else {
				req.send(data);
				var response = new Response(req);
				if (req.status == 200 || (req.status == 0 && req.responseText)) {
					opts.onLoad(response);
				} else {
					opts.onError(response);
				}
				return response;
			}
		}
		
		//public
		var r = {}; //the module
		
		/*
		HiddenProperty: _jsonCbs
			object of script callbacks. The user shouldn't be messin' with these
		*/
		r._jsonCbs = {len:0};
		
		/*
		Method: get
			Makes an HTTP GET request to a given url

		Arguments:
			*url* (string)
			
			Url to make the request to. This can be a relative path. You cannot make requests for files on other domains, to do that you must put your data in a javascript file and use <loadScript> to fetch it.

			*opts* (object)
			
			An object of options
			
			Options:
			
			Opts may contain any of the following :
			
			*onLoad* (function)
			
			callback to execute when the request has sucessfully loaded. The callback is passed a <Response> object as its first parameter.

			*onError* (function)
			
			Callback to execute if the request was unsucessful. The callback is passed a <Response> object as its first parameter.
			
			*headers* (object)
			
			A hash of headers to send along with the request. Eg {"Accept-Language": "en-gb"}
			
			*async* (boolean)
			
			Should the request be performed asynchronously?
			
			Default - true
			
			*useCache* (boolean)
			
			If false, a random number is added to the query string to ensure a fresh version of the file is being fetched
			
			Default - false

		Returns:
			An integer identifying the async request, or the response object for sync requests

		Example:
			(code)
			var getRef = glow.net.get("myFile.html", {
				onLoad: function(response) {
					alert("Got file:\n\n" + response.text());
				},
				onError: function(response) {
					alert("Error getting file: " + response.statusText());
				}
			});
			(end)
		*/
		r.get = function(url, o) {
			o = populateOptions(o);
			return request('GET', url, o);
		};
		
		/*
		Method: post
			Makes an HTTP POST request to a given url

		Arguments:
			*url* (string)
			
			Url to make the request to. This can be a relative path. You cannot make requests for files on other domains, to do that you must put your data in a javascript file and use <loadScript> to fetch it.

			*data* (object/string)
			
			Data to post, either as a JSON-style object or a urlEncoded string
			
			*opts*  (object)
			
			An object of options. See <get> for details of options.

		Returns:
			An integer identifying the async request, or the response object for sync requests

		Example:
			(code)
			var postRef = glow.net.post("myFile.html",
				{key:"value", otherkey:["value1", "value2"]},
				{
					onLoad: function(response) {
						alert("Got file:\n\n" + response.text());
					},
					onError: function(response) {
						alert("Error getting file: " + response.statusText());
					}
				}
			);
			(end)
		*/
		r.post = function(url, data, o) {
			o = populateOptions(o);
			o.data = data;
			if (!o.headers["Content-Type"]) {
				o.headers["Content-Type"] = STR.POST_DEFAULT_CONTENT_TYPE;
			}
			return request('POST', url, o);
		};
		
		/*
		Method: abort
			Aborts an async http request

		Arguments:
			*id* (int)
			
			The identifier of a get / post request

		Returns:
			<glow.net>

		Example:
			(code)
			//make request
			var getRef = glow.net.get(...);
			//abort request
			glow.net.abort(getRef);
			(end)
		*/
		r.abort = function(id) {
			if (requests[id]) {
				requests[id].onreadystatechange = function(){};
				requests[id].abort();
			}
			return glow.net;
		}
		
		/*
		Method: loadScript
			Loads data by adding a script element to the end of the page. This can be used cross domain, but should only be used with trusted sources as any javascript included in the script will be executed.

		Arguments:
			*url* (string)
			
			Url of the script. Use "{callback}" in the querystring as the callback name if the data source supports it, then you can use onLoad in opts as your callback.

			*callback*
			
			Function to call when the data has loaded

		Returns:
			<glow.net>

		Example:
			(code)
			glow.net.loadScript("http://www.server.com/json/tvshows.php?jsoncallback={callback}",
				function(data) {
					alert("Data loaded");
				}
			);
			(end)
		*/
		r.loadScript = function(url, callback) {
			if (callback) {
				var callbackName = "c" + r._jsonCbs.len++;
				r._jsonCbs[callbackName] = callback;
				url = glow.lang.interpolate(url, {callback: "glow.net._jsonCbs." + callbackName});
			}
			var script = document.createElement("script");
			script.src = noCacheUrl(url);
			glow.ready(function() {
				document.body.appendChild(script);
			});
		}
		
		/*
		Class: glow.net.Response
		*/
		/*
		PrivateClass: Response
			Take a native xmlhttprequest object and builds a response object
		*/
		function Response(nativeResponse) {
			/*
			Property: nativeResponse
				The response object from the browser. This may not have the same properties and methods across user agents
			*/
			this.nativeResponse = nativeResponse;
			/*
			Property: status
				HTTP status code of the response
			*/
			this.status = nativeResponse.status;
		}
		Response.prototype = {
			/*
			Method: text
				Gets the body of the response as plain text
	
			Returns:
				String
			*/
			text: function() {
				return this.nativeResponse.responseText;
			},
			/*
			Method: xml
				Gets the body of the response as xml.
				
				You can use <glow.dom.get> to create a NodeList from the XML so
				you can transverse it using CSS selectors and filters.
	
			Returns:
				xml document
			*/
			xml: function() {
				//TODO: wrap this in debug stuff
				if (! this.nativeResponse.responseXML) {
					throw new Error(STR.XML_ERR);
				}
				return this.nativeResponse.responseXML;
			},
			
			/*
			Method: json
				Gets the body of the response as a json object
			
			Arguments:
				*safeMode* (boolean)
				
				(optional) If true, the response will be parsed using a string parser which will filter out non-JSON javascript, this will be slower but recommended if you do not trust the data source.

				Default - false
			
			Returns:
				Object
			*/
			json: function(safe) {
				return glow.data.decodeJson(this.text(), {safeMode:safe});
			},
			
			/*
			Method: header
				Gets a header from the response
		
			Arguments:
				name - the name of the header to get
				
			Returns:
				String header value
	
			Example:
				(code)
				var contentType = myResponse.header("Content-Type");
				(end)
			*/
			header: function(name) {
				return this.nativeResponse.getResponseHeader(name);
			},
			
			/*
			Method: statusText
				Gets the meaning of the <status>.
	
			Returns:
				String
			*/
			statusText: function() {
				return this.nativeResponse.statusText;
			}
		};
		
		return r;
	}
});

;/*
Namespace: glow.events
*/
glow.module("glow.events", "0.4.0", {
	require: [],
	implementation: function() {

		var r = {};
		var eventid = 1;
		var objid = 1;
		// object (keyed by obj id), containing object (keyed by event name), containing arrays of listeners
		var listenersByObjId = {};
		// object (keyed by ident) containing listeners
		var listenersByEventId = {};
		var domListeners = {};
		var psuedoPrivateEventKey = '__intGlowEventId' + Math.floor(Math.random() * 1337);
		var psuedoPreventDefaultKey = psuedoPrivateEventKey + 'PreventDefault';
		var psuedoStopPropagationKey = psuedoPrivateEventKey + 'StopPropagation';

		var topKeyListeners = {};
		var keyListenerId = 1;
		var keyListeners = {};
		var keyTypes = {};

		var CTRL = 1;
		var ALT = 2;
		var SHIFT = 4;

		var specialPrintables = {
			TAB      : '\t',
			SPACE    : ' ',
			ENTER    : '\n',
			BACKTICK : '`'
		};

		var keyNameAliases = {
			'96' : 223
		};

		var keyNameToCode = {
			CAPSLOCK : 20, NUMLOCK : 144, SCROLLLOCK : 145, BREAK : 19,
			BACKTICK : 223, BACKSPACE : 8, PRINTSCREEN : 44, MENU : 93, SPACE : 32,
			SHIFT : 16,  CTRL : 17,  ALT : 18,
			ESC   : 27,  TAB  : 9,   META     : 91,  RIGHTMETA : 92, ENTER : 13,
			F1    : 112, F2   : 113, F3       : 114, F4        : 115,
			F5    : 116, F6   : 117, F7       : 118, F8        : 119,
			F9    : 120, F10  : 121, F11      : 122, F12       : 123,
			INS   : 45,  HOME : 36,  PAGEUP   : 33,
			DEL   : 46,  END  : 35,  PAGEDOWN : 34,
			LEFT  : 37,  UP   : 38,  RIGHT    : 39,  DOWN      : 40
		};
		var codeToKeyName = {};
		for (var i in keyNameToCode) {
			codeToKeyName['' + keyNameToCode[i]] = i;
		}

		var operaBrokenChars = '0123456789=;\'\\\/#,.-';

		/*
		PrivateMethod: removeKeyListener
			Removes a listener for a key combination.
		
		Arguments:
			ident - identifier returned from addKeyListener.
		*/

		function removeKeyListener (ident) {
			var keyType = keyTypes[ident];
			if (! keyType) { return false; }
			var listeners = keyListeners[keyType];
			if (! listeners) { return false; }
			for (var i = 0, len = listeners.length; i < len; i++) {
				if (listeners[i][0] == ident) {
					listeners.splice(i, 1);
					return true;
				}
			}
			return false;
		}

		/*
		PrivateMethod: initTopKeyListner
			Adds an event listener for keyboard events, for key listeners added by addKeyListener.

		Arguments:
			type - press|down|up - the type of key event.
		*/

		function initTopKeyListener (type) {
			topKeyListeners[type] = r.addListener(document, 'key' + type, function (e) {
				var mods = 0;
				if (e.ctrlKey) { mods += CTRL; }
				if (e.altKey) { mods += ALT; }
				if (e.shiftKey) { mods += SHIFT; }
				var keyType = e.chr ? e.chr.toLowerCase() : e.key ? e.key.toLowerCase() : e.keyCode;
				var eventType = mods + ':' + keyType + ':' + type;
				var listeners = keyListeners[eventType] ? keyListeners[eventType].slice(0) : [];
				// if the user pressed shift, but event didn't specify that, include it
				if (e.shiftKey) { // upper-case letter, should match regardless of shift
					var shiftEventType = (mods & ~ SHIFT) + ':' + keyType + ':' + type;
					if (keyListeners[shiftEventType]) {
						for (var i = 0, len = keyListeners[shiftEventType].length; i < len; i++) {
							listeners[listeners.length] = keyListeners[shiftEventType][i];
						}
					}
				}
				if (! listeners) { return; }
				for (var i = 0, len = listeners.length; i < len; i++) {
					listeners[i][2].call(listeners[i][3] || this, e);
				}
			});
		}
		
		/*
		PrivateMethod: clearEvents
			Removes all current listeners to avoid IE mem leakage
		*/
		function clearEvents() {
			var ident;
			for (ident in listenersByEventId) {
				r.removeListener(ident);
			}
		}

		// for opera madness
		var previousKeyDownKeyCode;

		var operaResizeListener,
			operaDocScrollListener;

		/*
		PrivateMethod: addDomListener
			Adds an event listener to a browser object. Massages differences with certain events.

		Arguments:
			attachTo - the browser object to attach the event to.
			name - the generic name of the event (inc. mousewheel)
		*/
		function addDomListener (attachTo, name) {
			var wheelEventName;
			
			if (glow.env.opera) {
				if (name.toLowerCase() == 'resize' && !operaResizeListener && attachTo == window) {
					operaResizeListener = r.addListener(window.document.body, 'resize', function (e) { r.fire(window, 'resize', e); });
				} else if (name.toLowerCase() == 'scroll' && !operaDocScrollListener && attachTo == window) {
					operaDocScrollListener = r.addListener(window.document, 'scroll', function (e) { r.fire(window, 'scroll', e); });
				}
			}
			
			var callback = function (e) {
				if (! e) { e = window.event; }
				var event = new r.Event();
				event.nativeEvent = e;
				event.source = e.target || e.srcElement;
				//safari 1.3 registers clicks on text nodes, get the parent node
				if (event.source && event.source.nodeType != 1) {
					event.source = event.source.parentNode;
				}
				event.relatedTarget = e.relatedTarget || (name.toLowerCase() == "mouseover" ? e.fromElement : e.toElement);
			    event.button = glow.env.ie ? (e.button & 1 ? 0 : e.button & 2 ? 2 : 1) : e.button;
				if (e.pageX || e.pageY) {
					event.pageX = e.pageX;
					event.pageY = e.pageY;
				} else if (e.clientX || e.clientY) 	{
					event.pageX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
					event.pageY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
				}
				if (name.toLowerCase() == 'mousewheel') {
					// this works in latest opera, but have read that it needs to be switched in direction
					// if there was an opera bug, I can't find which version it was fixed in
					event.wheelDelta =
						e.wheelDelta ? e.wheelDelta / 120 :
						e.detail ? - e.detail / 3 :
							0;
					if (event.wheelDelta == 0) { return; }
				}
				if (name.toLowerCase().indexOf("key") != -1) {
					event.altKey = !! e.altKey;
					event.ctrlKey = !! e.ctrlKey;
					event.shiftKey = !! e.shiftKey;

					if (name == 'keydown') {
						previousKeyDownKeyCode = e.keyCode;
					}

					event.charCode = e.keyCode && e.charCode !== 0 ? undefined : e.charCode;

					if (name.toLowerCase() == 'keypress') {
						if (typeof(event.charCode) == 'undefined') {
							event.charCode = e.keyCode;
						}

						if (glow.env.opera && event.charCode && event.charCode == previousKeyDownKeyCode &&
							operaBrokenChars.indexOf(String.fromCharCode(event.charCode)) == -1
						) {
							event.charCode = undefined;
							event.keyCode = previousKeyDownKeyCode;
						}
					}
					
					// make things a little more sane in opera
					if (event.charCode && event.charCode <= 31) { event.charCode = undefined; }

					if (event.charCode) {
						event.chr = String.fromCharCode(event.charCode);
					}
					else if (e.keyCode) {
						event.charCode = undefined;
						event.keyCode = keyNameAliases[e.keyCode.toString()] || e.keyCode;
						event.key = codeToKeyName[event.keyCode];
						if (specialPrintables[event.key]) {
							event.chr = specialPrintables[event.key];
							event.charCode = event.chr.charCodeAt(0);
						}
					}

					if (event.chr) {
						event.capsLock =
							event.chr.toUpperCase() != event.chr ? // is lower case
								event.shiftKey :
							event.chr.toLowerCase() != event.chr ? // is upper case
								! event.shiftKey :
								undefined; // can only tell for keys with case
					}
				}

				r.fire(this, name, event);
				if (event.defaultPrevented()) { return false; }
			};
			
			if (attachTo.addEventListener && (!glow.env.webkit || glow.env.webkit > 418)) {
				attachTo.addEventListener(name.toLowerCase() == 'mousewheel' && glow.env.gecko ? 'DOMMouseScroll' : name, callback, false);
			} else {
				var onName = 'on' + name;
				var existing = attachTo[onName];
				if (existing) {
					attachTo[onName] = function () {
						existing.apply(this, arguments);
						callback.apply(this, arguments);
					};
				} else {
					attachTo[onName] = callback;
				}
			}
			attachTo = null;
		}
	
		/*
		Method: addListener
			Add an event listener to an object (e.g. a DOM Element).

		Arguments:
			*attachTo* (selector/nodeList)
			
			The object that the event listener will be attached to. If the
		   	parameter is a string, then it is treated as a CSS selector and
		   	the listener is attached to the first matching element. If no
		   	element is matched, or the <glow.dom> module is not loaded, then
		   	an exception is raised. If the parameter is a <glow.dom.NodeList>,
		   	then the listener is attached to the first element. If the
		   	NodeList (or NodeList resulting from a selector) is empty, then
		   	undefined is returned.
			
			*name* (string)
			
			The event name. Listeners for DOM events should not begin with 'on'
				   (i.e. 'click' rather than 'onclick')
				   
			*callback* (function)
			
			The function that should be called when the event fires.
			
			*context* (object)
			
			This is optional. The object that the callback should be invoked on. If
					  not passed then the callback invocant will be the target of the event.

		Returns:
			A unique identifier for the event suitable for passing to
			<glow.events.removeListener>. If an empty NodeList or CSS selector that
			returns no elements is passed, then undefined is returned.

		Example:
			(code)
			glow.events.addListener(
				'#nav',
				'click',
				function () { alert('nav clicked'); }
		    );

			glow.events.addListener(
				myLightBox,
				'close',
				this.showSurvey,
				this
			);
			(end)
		*/
		r.addListener = function (attachTo, name, callback, context) {
			if (! attachTo) { throw 'no attachTo paramter passed to addListener'; }

			if (typeof attachTo == 'string') {
				if (! glow.dom) { throw "glow.dom must be loaded to use a selector as the first argument to glow.events.addListener"; }
				if (! (attachTo = glow.dom.get(attachTo)[0])) { return undefined; }
				
			}
			else if (glow.dom && attachTo instanceof glow.dom.NodeList) {
				if (attachTo.length == 0) { return undefined; }
				attachTo = attachTo[0];
			}
			
			var objIdent;
			if (! (objIdent = attachTo[psuedoPrivateEventKey])) {
				objIdent = attachTo[psuedoPrivateEventKey] = objid++;
			}
			var ident = eventid++;
			var listener = [ objIdent, name, callback, context ];
			listenersByEventId[ident] = listener;

			var objListeners = listenersByObjId[objIdent];
			if (! objListeners) { objListeners = listenersByObjId[objIdent] = {}; }
			var objEventListeners = objListeners[name];
			if (! objEventListeners) { objEventListeners = objListeners[name] = []; }
			objEventListeners[objEventListeners.length] = listener;

			if ((attachTo.addEventListener || attachTo.attachEvent) && ! domListeners[objIdent + ':' + name]) {
				addDomListener(attachTo, name);
				domListeners[objIdent + ':' + name] = true;
			}
			return ident;
		};

		/*
		Method: removeListener
			Removes an event listener.

		Arguments:
			*ident* (int)
			
			A unique identifier returned from <glow.events.addListener>.

		Returns:
			Boolean indicating that an event was found and removed.

		Example:
			(code)
			var listener = glow.events.addListener(...);
			glow.events.removeListener(listener);
			(end)
		*/
		r.removeListener = function (ident) {
			if (ident && ident.toString().indexOf('k:') != -1) {
				return removeKeyListener(ident);
			}
			var listener = listenersByEventId[ident];
			if (! listener) { return false; }
			delete listenersByEventId[ident];
			var listeners = listenersByObjId[listener[0]][listener[1]];
			for (var i = 0, len = listeners.length; i < len; i++) {
				if (listeners[i] == listener) {
					listeners.splice(i, 1);
					break;
				}
			}
			if (! listeners.length) {
				delete listenersByObjId[listener[0]][listener[1]];
			}
			var listenersLeft = false;
			for (var i in listenersByObjId[listener[0]]) { listenersLeft = true; break;	}
			if (! listenersLeft) {
				delete listenersByObjId[listener[0]];
			}
			return true;
		};

		/*
		Method: fire
			Fire an event.

		Arguments:
			*attachedTo* (object)
			
			The object that the event is coming from (usually this).
			
			*name* (string)
			
			The name of the event (event names should not start with the word 'on').
			
			*event* (event object)
			
			(optional) the event object. If not specified, a generic event object is
			created with <glow.events.create>. Custom event objects should be created with
			<glow.events.create> and extended.

		Returns:
			The event object.

		Example:
			(code)
			LightBox.prototype.close = function () {
				// close the lightbox...
				var event = glow.events.fire(this, 'close');
				// listeners have been called
			};
			(end)
		*/
		r.fire = function (attachedTo, name, e) {
   			if (! attachedTo) throw 'glow.events.fire: required parameter attachedTo not passed (name: ' + name + ')';
   			if (! name) throw 'glow.events.fire: required parameter name not passed';
			if (! e) { e = new r.Event(); }
			e.type = name;
			e.attachedTo = attachedTo;
			if (! e.source) { e.source = attachedTo; }

			var objIdent = attachedTo[psuedoPrivateEventKey],
				objListeners = objIdent && listenersByObjId[objIdent],
				objEventListeners = objListeners && objListeners[name];
			
			if (! objEventListeners) { return e; }
			
			var listener, res;

			// we make a copy of the listeners before calling them, as the event handlers may
			// remove themselves (took me a while to track this one down)
			var listeners = objEventListeners.slice(0);
			for (var i = 0, len = listeners.length; i < len; i++) {
				listener = listeners[i];
				res = listener[2].call(listener[3] || attachedTo, e);
				if (typeof res == 'boolean' && ! res) {
					e.preventDefault();
				}
			}
			return e;
		};

		/*
		Method: addKeyListener
			Add an event listener for a keyboard event.

		Arguments:
			*key* (string)
			
			key/key combination that is responded to. See below for format of
			key specifier.
			
			*type* (string)
			
			The type of key press you are listening for
			
			_Possible Values_ :
			
			'press' - key is pressed (comparable to a mouse click)
			'down' - the key is pushed down
			'up' - the key is released
			
			*callback* (function)
			The function that should be called when the event fires.
			
			*context* (object)
			
			(optional) the object that the callback should be invoked on. If not passed
			then the callback invocant will be the target of the event.

		Returns:
			A unique identifier for the event suitable for passing to <glow.events.removeListener>.

		Format of Key Specifier:
			The 'key' parameter starts with modifier keys 'CTRL', 'ALT' and 'SHIFT'. Modifiers can
			appear in any order and are followed by a '+'. Following any modifiers is the key
			character. To specify a character code, use the appropriate escape sequence
			(e.g. "CTRL+\u0065" - CTRL+e"). To specify a special key, the key character should be
			replaced with a key identifier, as listed below (e.g. "RIGHT" specifies the right arrow
			key).

		Notes for Opera:
			It is currently impossible to differentiate certain key events in Opera (for example the
			RIGHT (the right arrow key) and the apostrope (') result in the same code). For this reason
			pressing either of these keys will result in key listeners specified as  "RIGHT" and/or "'"
			to be fired.

		Example:
			(code)
			glow.events.addKeyListener("CTRL+ALT+a", "press", 
		        function () { alert("CTRL+ALT+a pressed"); }
		    );
			glow.events.addKeyListener("SHIFT+\u00A9", "down",
				function () { alert("SHIFT+© pushed") }
			);
			(end)
		*/
		var keyRegex = /^((?:(?:ctrl|alt|shift)\+)*)(?:(\w+|.)|[\n\r])$/i;
		r.addKeyListener = function (key, type, callback, context) {
			type.replace(/^key/i, "");
			type = type.toLowerCase();
			if (! (type == 'press' || type == 'down' || type == 'up')) {
				throw 'event type must be press, down or up';
			}
			if (! topKeyListeners[type]) { initTopKeyListener(type); }
			var res = key.match(keyRegex),
				mods = 0,
				charCode;
			if (! res) { throw 'key format not recognised'; }
			if (res[1].toLowerCase().indexOf('ctrl') != -1)  { mods += CTRL;  }
			if (res[1].toLowerCase().indexOf('alt') != -1)   { mods += ALT;   }
			if (res[1].toLowerCase().indexOf('shift') != -1) { mods += SHIFT; }
			var eventKey = mods + ':' + (res[2] ? res[2].toLowerCase() : '\n') + ':' + type;
			var ident = 'k:' + keyListenerId++;
			keyTypes[ident] = eventKey;
			var listeners = keyListeners[eventKey];
			if (! listeners) { listeners = keyListeners[eventKey] = []; }
			listeners[listeners.length] = [ident, type, callback, context];
			return ident;
		};

		/*
		Class: glow.events.Event
			Prototype for event objects passed to listeners. When firing a custom event, create a <glow.events.Event>
			object and add properties and methods specific to your event. If you do not create an event, as standard
			<glow.events.Event> object will be created for you. For built in events (*click*, *keypress*, etc.),
			a normalised event object based on <glow.events.Event> is passed to	the event listener.
		
			To access the native event object, use the *nativeEvent* property on the event object.

		General Mouse Event Properties:
			pageX - contains the horizontal position in the page that the mouse pointer is.
			pageY - contains the vertical position in the page that the mouse pointer is.

		Mouse Button Event Properties:
			button - 0 for the left button, 1 for the middle button or 2 for the right button.

		Mouse Over/Out Event Properties (*mouseover* and *mouseout* event types):
			relatedTarget - the element that the mouse has come from or is going to.

		Mouse Wheel Event Properties (*mousewheel* event type):
			wheelDelta - the number of clicks up (positive) or down (negative) that the user moved the wheel.

		Keyboard Event Properties:
			ctrlKey - (boolean) was the ctrl key pressed during the key event.
			shiftKey - (boolean) was the shift key pressed during the key event.
			altKey - (boolean) was the alt key pressed during the key event.
			capsLock - (boolean|undefined) was caps-lock on during the key event. If the key is not alphabetic,
					   then this property will be undefined as it is not possible to tell if caps-lock is on.
			keyCode - (integer) an integer number representing the code of the keyboard key that was pressed.
			key - (string) a short identifier for the key for special keys (see Key Identifiers below),
				  or undefined if the key is not a special key.
			charCode - (integer) the unicode character code for a printable character, or undefined.
			chr - (character) a printable character, or undefined.

		Key Identifiers:
			SPACE ESC TAB ENTER BACKTICK
			CTRL SHIFT ALT META RIGHTMETA
			F1 F2 F3 F4	F5 F6 F7 F8 F9 F10 F11 F12
			INS HOME PAGEUP
			DEL	END PAGEDOWN
			LEFT RIGHT UP DOWN
			CAPSLOCK NUMLOCK SCROLLLOCK
			BREAK BACKSPACE PRINTSCREEN	MENU

		Notes for Opera:
			The information returned from Opera about key events does not allow certain keys to be differentiated.
			This mainly applies to special keys, such as the arrow keys, function keys, etc. which conflict with
			some printable characters. For keypress events, the event object will always contain the printable
			where there is a conflict. For keyup and keydown events, the keyCode property will contain the keyCode
			and the key property will contain the key identifier (see above) for the special key.
		*/
		r.Event = function () {};

		/*
		Method: preventDefault
			Prevent the default action for events. This can be called on an event object within
			code triggered by an event.

		Example:
			(code)
			// stop user clicking link with id "myLink"
			glow.events.addListener(
				'a#myLink',
				'click',
				function (e) { e.preventDefault(); }
		    );
			(end)
		*/
		r.Event.prototype.preventDefault = function () {
			if (this[psuedoPreventDefaultKey]) { return; }
			this[psuedoPreventDefaultKey] = true;
			if (this.nativeEvent && this.nativeEvent.preventDefault) {
				this.nativeEvent.preventDefault();
				this.nativeEvent.returnValue = false;
			}
		};

		/*
		Method: defaultPrevented
			Test if the default action has been prevented.
 
		Returns:
			true if the default action has been prevented.

		Example:
			(code)
			var e = glow.events.fire(...);
			if (! e.defaultPrevented()) {
				// perform the default action
			}
			(end)
		*/
		r.Event.prototype.defaultPrevented = function () {
			return !! this[psuedoPreventDefaultKey];
		};

		/*
		Method: stopPropagation
			Stop the event propagating. For DOM events, this stops the event bubbling up
			through event listeners added to parent elements. The event object is marked as
			having had propagation stopped (see <propagationStopped>).

		Example:
			(code)
			// catch all click events that are not links
			glow.events.addListener(
				document,
				'click',
				function () { alert('document clicked'); }
			);

			glow.events.addListener(
				'a',
				'click',
				function (e) { e.stopPropagation(); }
			);
			(end)
		*/
		r.Event.prototype.stopPropagation = function () {
			if (this[psuedoStopPropagationKey]) { return; }
			this[psuedoStopPropagationKey] = true;
			var e = this.nativeEvent;
			if (e) {
				e.cancelBubble = true;
				if (e.stopPropagation) { e.stopPropagation(); }
			}
		};

		/*
		Method: propagationStopped
			Test if the propagation has been stopped for this event.
 
		Returns:
			true if event propagation has been prevented.

		Example:
			(code)
			// see if anything handles the event on this, and if not fire it on this.overlay
			var e = glow.events.fire(this, "open");
			if (! e.propagationStopped()) {
				glow.events.fire(this.overlay, "open", e); 
			}
			(end)
		*/
		r.Event.prototype.propagationStopped = function () {
			return !! this[psuedoStopPropagationKey];
		};
		
		//cleanup to avoid mem leaks in IE
		r.addListener(window, "unload", clearEvents);

		return r;
	}
});
/*@end @*/