/*

jXpress 0.1

Copyright (c) 2008 Graham Bradley (http:/www.gbradley.co.uk)	
Licenced under the FreeBSD (http://www.freebsd.org/copyright/freebsd-license.html) licence.


/************

Class-based OOP

The majority of the library objects are instantiable classes. Some of these are standalone classes,
whereas others wrap base classes. While the library itself has no extended classes, it has a 
class-based framework which can be used elsewhere, for instance inside the FX and UI modules.

************/


/************

Selector API

The selector methods are pretty quick and flexible. They all begin with $:

	$			selects element(s) by ID string(s)
	$T			selects elements by tag name string(s)
	$C			selects elements by class name string(s)
	$CSS		selects element(s) by CSS3 selector string
	
$CSS supports CSS3 selectors, The more esoteric selectors (:hover etc) are not supported unless the
browser supports the W3C querySelectorAll methood.

Internally things are sped up by caching results, except for IE which can't flush them reliably. Tenatively
seems faster than jQuery, Dojo, EXT, prototype and YUI :D

************/

/************

DOM API

Manipulating the DOM is made easy with a bunch of NodeSet methods:

	parents(), children(), siblings(), nextSibling(), append(), prepend(), insertChild(), wrap(), unwrap() and more. Its also
	easy to create new nodes (including nested nodes) programmatically or from HTML strings.

************/

/************

Events and listeners

Event system kinda rocks. There are a ton of NodeSet functions that relate to common event types, allowing you to do stuff
like this:

nodeset.click(function(e){
	console.log(this,e);
	});
	
You can specify as many callback functions as you like in each call. Each callback gets passed the event object, and the 'this'
keyword points to a NodeSet containing the node upon which the event occured (not neccessarily the target).

Each event function takes an optional third object that you can use to change the value of 'this', or to specify key combinations such
as shift+f4 that must happen to fire the callbacks. You can register custom key types using static functions of the lib.Event object.
This object also lets you do things like prevent bubblng, access event targets, and set ascii keybindings.

The library normalises some events across browsers such as mouseenter, mouseleave and mousewheel. It also has custom events like
mousewheelup and mousewheeldown, and lets you easily define custom events.

Oh, and of course DOMContentLoaded is emulated where required, too. Its reduced down to this:

jxp.ready(function(){
	// do stuff
	});

************/

/************

Ajax

The HTTP object lets you send/receive data via XMLHttpRequest with a variety of options and callbacks, as well as
restricting and parsing JSON responses.

The XHTTP object is more basic, using script tags to send/recieve cross-domain requests. You can pass callbacks to it
just like the standard HTTP object, rather than having to specify callback names in parameters etc.

You can also create polling (X)HTTP objects, and also hijack submissions for gracefully-degrading forms.

************/

/************

Helper classes

There are several useful classes that you might want to use during development. These are:

	Hash			basically a prototype-safe Object, with filter(), map() functions similar to Arrays'
	Queue			allows you to queue-up and execute functions
	Scheduler		wraps a Queue, meaning you can execute its functions at timed intervals
	Observer		a basic publisher-subscriber system to control 'observable' events

************/

/************

XML, JSON

Um yeah, XML and JSON parsing utilities

************/

/************

Namespacing and pollution

The library tries to be as unobtrusive as possible, with a few exceptions:

	- automatically updates Array functions to JS1.8 (map(), reduce() etc) if required
	- adds expando properties to nodes to aid events / selector APIs
	
There are other extensions to the native Array, String and Function prototypes, but these can be supressed by passing a querystring
variable in the src url, e.g. src='jxpress-0.1.js?Array=false&String=false'

The default namespace is 'jxp', however you can specify an alternate namespace, again with a querystring value:

	src='jxpress-0.1.js?ns=foo'
	
You can also load the library directly as a property of an object (which will be created if it doesn't already exist):

	src='jxpress-0.1.js?ns=foo.bar.spam'
	
In this case, an extra global (a string 'foo.bar.spam') is created, to allow Modules to find the library without knowing the namespace.


************/

/************

Modules

jXpress Modules are sweet. You can load them from your HTML, from inside your scripts, or even set them
to be lazy-loading from a pre-defined URL. You can even store these URLs to another Module, the moduleLibrary, which
means you only need to pass the Module name to load it, on-demand, from an external resource.

Module delevopers can include dependency checks to prevent their module from loading in environments that don't support
the correct version of jXpress, or don't have a specific module installed. You can ensure that installation failures are
handled gracefully. And of course, Modules can be installed no matter your namespace.

************/

/************

FX

The FX module builds upon the basic NodeSet style methods to give animation effects. You can control the animation
of almost any CSS style with animate(), which accepts a variety of options including easing types and callbacks. In addition
there are simple predefined animation methods like shake() and bounce().

The Module also contains 3 classes to create in-page components; Accordion, Carousel and SlideBox.

************/


/************

UI

The UI module gives extensive drag n drop functionality. It contains a Draggable() class, with the ability to assign boundaries
and customisable drop targets to draggable elements. The Resizable class lets you assign handles to an element to resize it.

The Slider class allows you to create a customisable scrollbar, again with callbacks and customisble features.

************/


(function(){

	/* we'll start by bringing array support up to 1.8. Implimentations are common enough so this gets done no matter the pollution prefs. */

	if (!Array.prototype.indexOf){
		Array.prototype.indexOf=function(elt,from){
			var len=this.length, from=from || 0;
			for (;from<len;from++){
				if (from in this && this[from]===elt) return from;
				}
			return -1;
			};
		Array.prototype.lastIndexOf=function(elt,from){
			var len=this.length, from=from || 0;
			for (;from>-1;from--){
				if (from in this && this[from]===elt) return from;
				}
			return -1;
			};
		Array.prototype.forEach=function(fn){
			var len=this.length, thisp = arguments[1];
			for (var i=0;i<len;i++){
				if (i in this) fn.call(thisp, this[i], i, this);
				}
			};
		Array.prototype.every=function(fun){
			var len = this.length, thisp = arguments[1];
			for (var i = 0; i < len; i++){
				if (i in this && !fun.call(thisp, this[i], i, this)) return false;
				}
			return true;
			};
		Array.prototype.some=function(fn){
			var len = this.length, thisp = arguments[1];
			for (var i=0;i<len;i++){
				if (i in this && fn.call(thisp, this[i], i, this)) return true;
				}
			return false;
			};
		Array.prototype.filter=function(fn){
			var len = this.length, res = [], thisp = arguments[1];
			for (var i=0;i<len;i++){
				if (i in this){
					var val=this[i];
					if (fn.call(thisp, val, i, this)) res.push(val);
					}
				}
			return res;
			};
		Array.prototype.map=function(fn){
			var len=this.length, res=new Array(len), thisp=arguments[1];
			for (var i=0;i<len;i++){
				if (i in this) res[i] = fn.call(thisp, this[i], i, this);
				}
			return res;
			};
		}
		
	if (!Array.prototype.reduce) Array.prototype.reduce=function(fn){
		var len=this.length, i=0;
		if (arguments.length >= 2) var rv = arguments[1];
		else{
			while (true){
				if (i in this){
					rv=this[i++];
					break;
					}
				}
			}
		for (;i<len;i++){
			if (i in this) rv=fn.call(null, rv, this[i], i, this);
			}
		return rv;
		};
		
	/* define some info constants */
	
	var _info={
		version:0.1,
		author:'Graham Bradley',
		help:'http://jxpress.gbradley.co.uk/docs/'
		};
	
	/* the function namespace is also a privileged function to return info*/
	
	var lib=function(s){
		return _info[s];
		};
		
	/* add some Class helper functions */
		
	lib.Class={
		extend:function(Sub, Sup){
			var Inherit=function(){};
			Inherit.prototype=Sup.prototype;
			Sub.prototype=new Inherit();
			Sub.prototype.constructor=Sub;
			Sub.SuperClass=Sup;
			return this;
			},
		augment:function(){
			var a=[].slice.call(arguments), o=a.length ? a.shift() : {};
			a.forEach(function(p){ for (var y in p) o[y]=p[y]; });
			return this;
			}
		};
		
	/* add an Client singleton */
	
	var _ie;
	lib.Client=(function(){
		var _b, ua=navigator.userAgent;
		if (/firefox/i.test(ua)) _b={t:'firefox',mj:(ua.match(/firefox\/([\d]+)/i) || [0,0])[1],mi:(ua.match(/firefox\/[\d]+\.([\d\.]+)/i) || [0,0])[1]};
		else if (/msie/i.test(ua)) _b={t:'internet explorer',mj:(ua.match(/msie\s([\d]+)/i) || [0,0])[1],mi:(ua.match(/msie\s[\d]+\.([\d\.]+)/i) || [0,0])[1]};
		else if (/safari/i.test(ua)) _b={t:'safari',mj:(ua.match(/\/(\d)+[\d\.\s]+safari/i) || [0,0])[1],mi:(ua.match(/\/\d+\.([\d\.\s]+)safari/i) || [0,0])[1]};
		else if (/opera/i.test(ua)) _b={t:'opera',mj:(ua.match(/opera\/(\d+)/i) || [0,0])[1],mi:(ua.match(/opera\/\d+\.(\d+)/i) || [0,0])[1]};
		else _b={};
		_ie=_b.t=='internet explorer';
		return {
			browser:function(){return _b.t},
			browserMajorVersion:function(){return _b.ma},
			browserMinorVersion:function(){return _b.mi},
			quirks:function(){ return document.compatMode=='BackCompat'; },
			query:function(k){	// return querystring value or all as hash
				var _q=lib.String.toHash.call(window.location.search.replace(/^\?/,''));
				var fn=function(k){
					return k ? _q[k] : _q;
					};
				lib.Client.query=fn;
				return fn(k);
				},
			windowSize:function(){
				return document.all ? {w:document.body.clientWidth,h:document.body.clientHeight} : {w:innerWidth,h:innerHeight};
				},
			scroll:function(){
				return (typeof window.pageYOffset=='number') ? {x:window.pageXOffset,y:window.pageYOffset} : 
					document.body && (document.body.scrollLeft || document.body.scrollTop) ? {x:document.body.scrollLeft,y:document.body.scrollTop} : 
					document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop) ? {x:document.documentElement.scrollLeft,y:document.documentElement.scrollTop} : {x:0,y:0}
				}
			};
		})();
	
	/* OK. We'll start the proper stuff by adding some basic data methods */
	
	lib.Class.augment(lib,{
	
		/* The Array class is an object literal with extra array goodies */
		
		Array:{
			flatten:function(){		// flatten array of arrays; speed up for case of 1 item
				return this.length==1 ? this[0] : (!this.length ? [] : [].slice.call([].reduce.call(this,function(a,b){	// use Array.slice as native moz reduce points to wrong constructor? 
					return [].concat.call(a,b);
					})));
				},
				
			unique:function(){		// return a new array of unique items
				var a=[];
				this.forEach(function(x){
					if (a.indexOf(x)==-1) a.push(x);
					});
				return a;
				},
				
			hash:function(index){	// simulate a hash table
				var len=this.length;
				for (var i=len-1;i>=0;i--) this[this[i][index]]=this[i];
				},
				
			intersect:function(){	// like SQL's intersect
				var a=[], args=[].slice.call(arguments);
				this.forEach(function(x){
					if (args.every(function(y){
						return y.indexOf(x)>=0;
						})) a.push(x);
					});
				return a;
				},
				
			minus:function(){		// like SQL's minus
				var args=[].slice.call(arguments);
				return this.filter(function(x){
					return !(args.some(function(y){
						return y.indexOf(x)>=0;
						}));
					});
				},
				
			all:function(){			// test if all items evaluate to something
				return this.every(function(x){ return !!x; });
				},
				
			none:function(){			// test if all items evaluate to nothing
				return this.every(function(x){ return !x; });
				},
				
			any:function(){			// test if any items evaluate to something
				return this.some(function(x){ return !!x; });
				},
				
			clean:function(){			// remove items that evaluate to false (default)
				var args=[].slice.call(arguments);
				return this.filter(function(x){
					return args.length ? args.indexOf(x)<0 : !!x;
					});
				}
			},
			
		/* now we have the String class, again an object literal */
		
		String:{
			trim:function(){					// um, trim
				return this.replace(/^(\s*)|(\s*)$/g,'');
				},
			isEmail:function(){					// fairly extensive mail format check
				return ((/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-z]{2,7}$/i).test(this));
				},
			repeat:function(n){					// repeat the string n times
				var a=[];
				for (var i=0;i<n;i++) a.push(this);
				return a.join('');
				},
			chunk:function(n){					// split into n-length chunks
				var t=this.length, a=[];
				for (var i=0;i<t;i+=n) a.push(this.substring(i,i+n));
				return a;
				},
			stripHTML:function(){				// um, strip html
				var args=lib.util.getArgs(arguments);
				if (!args.length) return this.replace(/<[^>]*>/g,'');
				else return this.replace(new RegExp("<(\/?"+args.join("|")+")[^>]*>","gi"),'');
				},
			substitute:function(o,opt){			// substitute with {placeholders}
				var s=this, opt=opt || {};
				if (!'flags' in opt) opt.flags='g';
				for (var t in o) s=s.replace(new RegExp("{"+t.replace('$','\\$')+"}",opt.flags),o[t]);
				return s;
				},
			toCamelCase:function(){
				var s=this,  m=s.match(/-[^-]/g);
				if (m){
					for (var i=0;i<m.length;i++) s=s.replace(m[i],m[i].replace('-','').toUpperCase());
					}
				return s;
				},
			toProperCase:function(){
				var str=this.toLowerCase().split(" ");
				for (var i=0;i<str.length;i++) str[i]=str[i].substring(0,1).toUpperCase()+str[i].substring(1,str[i].length);
				return str.join(" ");
				},
			toRegEx:function(opt){				// for handling special chars to regex
				if (!opt) opt={};
				if (!opt.flags) opt.flags='g';
				var s=this.replace(new RegExp('\\\\','g'),'\\\\');
				'$^[]./+?*'.split('').forEach(function(c){
					s=s.replace(new RegExp('\\'+c,'gi'),'\\'+c);
					});
				return opt.raw ? s : new RegExp(s,opt.flags);
				},
			toNodeSet:function(){
				var p=document.createElement(this.toString().match(/<option/) ? "select" : "div");
				p.innerHTML=this.toString();
				return new lib.NodeSet(lib.DOM.filterByNodeType(p.childNodes));
				},
			toHash:function(opt){
				var p={};
				this.split("&").forEach(function(s){
					s=s.split("=");
					p[s[0]]=opt && opt.decode ? decodeURIComponent(s[1]) : s[1];
					});
				return new lib.Hash(p);
				},
			hexToRGB:function(){				// color conversion
				return this.replace(/^#/,'').chunk(2).map(function(h){
					return parseInt(h,16);
					}).join(",");
				},
			RGBToHex:function(){
				return '#'+this.replace(/rgb\(\)/ig,'').split(',').map(function(r){
					return (r*1).toString(16);
					}).join('');
				}
			},
			
		/* Function class, again an object literal */
		
		Function:{
			bind:function(context){
				var self=this;
				return function(){ self.apply(context,[].slice.call(arguments)); }
				},
			subscribe:function(observer,context){		// subscribe to an observer
				observer.subscribers.push(this);
				observer.context.push(context);
				return this;
				},
			unsubscribe:function(observer){		// unsubscribe from an observer
				var self=this, index=-1;
				observer.subscribers=observer.subscribers.filter(function(x,i){
					if (self==x) index=i;
					return self!=x;
					});
				if (index>-1) observer.context.splice(index,1);
				return self;
				}
			},
			
		/* some extra functions that don't really fit anywhere else */
			
		util:{
			getArgs:function(a){	// a utility method to flatten function arguments into one array
				var a=[].slice.call(a);
				return lib.Array.flatten.call(a.length && a[0].constructor==Array ? a : [a]);
				},
			toArray:function(a){	// convert an array-like object to an array
				var r=[];
				for (var i=0;i<a.length;i++) r[i]=a[i];
				return r;
				},
			copy:function(s,d,o){	// copy properties with optional overwrite arg
				for (var p in s){
					if (s.hasOwnProperty(p) && (o || !(p in d))) d[p]=s[p];
					}
				}
			},

		Hash:(function(){		/* the Hash class is instantiable */
			
			var me=function(){
				lib.Class.augment.apply(null,[this].concat([].slice.call(arguments)));
				};
				
			lib.Class.augment(me.prototype,{
				keys:function(){
					var a=[];
					for (var x in this){
						if (this.hasOwnProperty(x)) a.push(x);
						}
					return a;
					},
				values:function(){
					var a=[];
					for (var x in this){
						if (this.hasOwnProperty(x)) a.push(this[x]);
						}
					return a;
					},
				add:function(o){
					for (var x in o){
						if (o.hasOwnProperty(x)) this[x]=o[x];
						}
					return this;
					},
				forEach:function(fn,c){
					for (var x in this){
						if (this.hasOwnProperty(x)) fn.call(c, this[x], x, this);
						}
					return this;
					},
				map:function(fn,c){
					var d=new this.constructor({});
					for (var x in this){
						if (this.hasOwnProperty(x)) d[x]=fn.call(c, this[x], x, this);
						}
					return d;
					},
				filter:function(fn,c){
					var d=new this.constructor({});
					for (var x in this){
						if (this.hasOwnProperty(x) && fn.call(c, this[x], x, this)) d[x]=this[x];
						}
					return d;
					},
				some:function(fn,c){
					for (var x in this){
						if (this.hasOwnProperty(x) && fn.call(c, this[x], x, this)) return true;
						}
					return false;
					},
				every:function(fn,c){
					for (var x in this){
						if (this.hasOwnProperty(x) && !fn.call(c, this[x], x, this)) return false;
						}
					return true;
					},
				intersect:function(){
					var d=new this.constructor({}), args=Array.prototype.slice.call(arguments);
					this.forEach(function(v,p){
						if (args.every(function(o){
							return o.hasOwnProperty(p) && o[p]==v;
							})) d[p]=v;
						});
					return d;
					},
				minus:function(){
					var args=Array.prototype.slice.call(arguments);
					return this.filter(function(v,p){
						return !(args.some(function(o){
							return o.hasOwnProperty(p) && o[p]==v;
							}));
						});
					},
				clean:function(){
					var args=Array.prototype.slice.call(arguments);
					return this.filter(function(x){
						return args.length ? args.indexOf(x)<0 : !!x;
						});
					},
				toQuerystring:function(opt){
					var qs=[];
					this.forEach(function(v,p){
						if (typeof v=='string' || typeof v=='number') qs.push(p+"="+(opt && opt.encode ? encodeURIComponent(v) : v));
						});
					return qs.join("&");
					}
				});

			return me;
			})(),
		
		Observer:(function(){			/* set up an Observer class */
			var me=function(){
				this.subscribers=lib.util.getArgs(arguments);
				this.context=new Array(this.subscribers.length);
				};
			lib.Class.augment(me.prototype,{
				deliver:function(){
					var self=this, args=[].slice.call(arguments), context=args.length ? args.shift() : window;		// default context supplied as the first argument to deliver(). Look up context supplied during subscription, otherwise default, otherwise window.
					this.subscribers.forEach(function(x,i){ 
						x.apply(self.context[i] || context,args);
						});
					return this;
					},
				clear:function(){
					this.subscribers=[];
					this.context=[];
					return this;
					}
				});
			return me;
			})(),
			
		Queue:(function(){		/* the Queue class is used to operate arrays of functions */
			
			var me=function(){
				this.q=lib.util.getArgs(arguments);
				var opt=(!this.q.length || typeof this.q[this.q.length-1]=='function') ? {} : this.q.pop();	// opt object passed as final argument
				var self=this;
				['Next','Previous','Reset'].forEach(function(t){	// create observers for each method. One subscribing function per type can be added to the opt object.
					self['on'+t]=new lib.Observer(opt['on'+t] ? [opt['on'+t]] : []);
					});
				this.count=0;
				};
				
			var move=function(obj,dir,args){
				if (!obj.q.length) return;
				obj.count+=dir;
				var rv=obj.q[dir==1 ? 0 : obj.q.length-1].apply(obj,args);
				if (dir==1) obj.q.push(obj.q.shift());
				else obj.q.unshift(obj.q.pop());
				return rv;
				};
				
			lib.Class.augment(me.prototype,{
				next:function(){ return move(this,1,[].slice.call(arguments)); this.onNext.deliver(this)},
				previous:function(){ return move(this,-1,[].slice.call(arguments));  this.onPrevious.deliver(this)},
				reset:function(){
					var r=Math.abs(this.count % this.q.length);
					while (r--) this.q.unshift(this.q.pop());
					this.count=0;
					this.onReset.deliver(this);
					return this;
					}
				});
			
			return me;
			})(),
			
		Scheduler:(function(){	/* a wrapper around a Queue() instance, allows scheduled invoacation of queued functions */
			
			var me=function(){
				var q=[].slice.call(arguments);
				var opt=(q.length && typeof q[q.length-1]!='function') ? q.pop() : {}, self=this;	// this allows arguments as a lib.Queue instance, a sequence of functions, or an array of functions
				this.queue=q.length==1 ? (q[0].constructor==lib.Queue ? q[0] : new lib.Queue(q[0])) : new lib.Queue(q);
				this.repeat=(opt.repeat ? (opt.repeat==-1 ? Number.POSITIVE_INFINITY : opt.repeat) : 1)*this.queue.q.length, this.period=opt.period || 60, this.counter=0;
				['Start','Stop','Pause'].forEach(function(t){	// create observers for each method. One subscribing function per type can be added to the opt object.
					self['on'+t]=new lib.Observer(opt['on'+t] ? [opt['on'+t]] : []);
					});
				};
				
			var move=function(self){
				self.timer=setTimeout(function(){
					self.queue.next();
					if ((self.counter++) < self.repeat-1) move(self);
					else self.stop();
					},self.period*1000);
				};
				
			lib.Class.augment(me.prototype,{
				start:function(){
					this.onStart.deliver(this);
					move(this);
					},
				stop:function(){
					clearTimeout(this.timer);
					this.counter=0;
					this.queue.reset();
					this.onStop.deliver(this);
					},
				pause:function(){
					this.onPause.deliver(this);
					if (this.timer){
						clearTimeout(this.timer);
						this.timer=null;
						}
					else move(this);
					}
				});
			
			return me;
			})()
		});
			
		lib.NodeSet=(function(){
		
			var me=function(a){	/* nodeset takes an arg array; filters duplicate / non-existent nodes */
				var self=this, j=0, a=lib.Array.unique.call(a);
				for (var i=0;i<a.length;i++){
					if (a[i]) self[j++]=a[i];
					}
				this.length=j;
				};
				
			// these methods can just wrap array methods
			
			['indexOf','lastIndexOf','every','some'].forEach(function(x){
				me.prototype[x]=function(){ return Array.prototype[x].apply(this,arguments); };
				});
			['map','filter'].forEach(function(x){
				me.prototype[x]=function(){ return new me(Array.prototype[x].apply(this,arguments)); };
				});
			['unique','intersect','minus','clean'].forEach(function(x){
				me.prototype[x]=function(){ return new me(lib.Array[x].apply(this,arguments)); };
				});
				
			// these can wrap the DOM query methods we make later
				
			['$T','$C','$CSS'].forEach(function(x){
				me.prototype[x]=function(){ return lib[x].apply(this,arguments); };
				});
				
			// now we can add the rest
				
			lib.Class.augment(me.prototype,{
				
				// manipulation / conversion methods
				
				forEach:function(){ [].forEach.apply(this,arguments); return this;},
				mapArray:function(fn){ return [].map.apply(this,arguments.length ? arguments : [function(n){return n;}]); },	// passing no arguments to mapArray() just returns the nodes in a standard array
				concat:function(){
					return new me([].concat.apply(this.mapArray(),[].slice.call(arguments).map(function(x){
						return x.mapArray();
						})));
					},
					
				// selection methods
					
				item:function(){	// can accept multiple args: integer index (negative starts from end), or string pattern
					var all=[], self=this;
					lib.util.getArgs(arguments).forEach(function(i){
						if (typeof i=="number"){
							if (i<0) i=self.length+i;
							if (i>=0 && i<self.length) all.push(self[i]);
							}
						else if (typeof i=="string") all=all.concat(lib.DOM.filterByPattern(self,i));
						});
					return new me(all);
					},
				
				// DOM scripting methods
				
				parents:function(){
					return this.map(function(n){ return n.parentNode || false; });
					},
				children:function(){
					return new me(lib.Array.flatten.call(this.mapArray(function(n){ return n.children || lib.DOM.filterByNodeType(n.childNodes)})));
					},
				firstChild:function(){
					return new me(this.mapArray(function(n){
						var c=n.children || lib.DOM.filterByNodeType(n.childNodes);
						return c.length ? c[0] : false;
						}));
					},
				lastChild:function(){
					return new me(this.mapArray(function(n){
						var c=n.children || lib.DOM.filterByNodeType(n.childNodes);
						return c.length ? c[c.length-1] : false;
						}));
					},
				siblings:function(){
					return new me(lib.Array.flatten.call(this.mapArray(function(n){
						return lib.Array.minus.call(n.children || lib.DOM.filterByNodeType(n.parentNode.childNodes),[n]);
						})));
					},
				nextSibling:function(){
					return new me(this.mapArray(function(e){
						var n=e.nextSibling;
						while (n && n.nodeType!=1) n=n.nextSibling;
						return n;
						}));
					},
				previousSibling:function(){
					return new me(this.mapArray(function(e){
						var p=e.previousSibling;
						while (p && p.nodeType!=1) p=p.previousSibling;
						return p;
						}));
					},
				adjacentSiblings:function(){
					return this.previousSibling().concat(this.nextSibling()).unique();
					},
				positionedAncestor:function(){
					return this.mapArray(function(n){
						while (n.parentNode && n!=document.body){
							n=n.parentNode;
							var p=(new lib.NodeSet([n])).getStyle('position')[0];
							if (p=='absolute' || p=='relative') break;
							}
						return n;
						});
					},
				copy:function(opt){
					var opt=opt || {}, d=!(opt && opt.deep===false);
					return this.map(function(n){
						var c=n.cloneNode(d);
						if (opt.id && c.id) c.id=opt.id(c.id);	// accepts a modifier function
						return c;
						});
					},
				remove:function(){
					return this.forEach(function(n){ n.parentNode.removeChild(n); });
					},
				getHTML:function(){
					return this.mapArray(function(n){ return n.innerHTML; });
					},
				setHTML:function(h){
					return this.forEach(function(n){
						if (h && typeof h=="function") n.innerHTML=h(n.innerHTML);		// accepts string or modifier function
						else n.innerHTML=h || "";
						});
					},
				prepend:function(ns,opt){
					var ns=(typeof ns=="string" ? lib.String.toNodeSet.call(ns) : ns), copy=!(opt && opt.copy===false);
					return this.forEach(function(n){
						(copy ? ns.copy() : ns).forEach(function(x){ n.parentNode.insertBefore(x,n); });
						});
					},
				append:function(ns,opt){
					var ns=(typeof ns=="string" ? lib.String.toNodeSet.call(ns) : ns), copy=!(opt && opt.copy===false);
					return this.forEach(function(n){
						var z=copy ? ns.copy() : ns, s=new lib.NodeSet(lib.DOM.filterByNodeType(n.parentNode.childNodes)), l=s.length, i=s.indexOf(n);
						z.forEach(function(x){
							if ((i+1)>=s.length) n.parentNode.appendChild(x);
							else n.parentNode.insertBefore(x,s[i+1]);
							});
						});
					},
				insertChild:function(ns,i,opt){	// i is the sibling position of the new child; can be pos, neg, 0, or undefined (default, last child)
					var ns=(typeof ns=="string" ? lib.String.toNodeSet.call(ns) : ns), i=(i===0 ? 0 : (i ? i : null)), copy=!(opt && opt.copy===false);
					return this.forEach(function(n){
						var z=copy ? ns.copy() : ns, c=lib.DOM.filterByNodeType(n.childNodes);
						if (i==0 && c.length) new lib.NodeSet([c[0]]).prepend(z);
						else if (i===null || i>=c.length) z.forEach(function(x){ n.appendChild(x); });
						else new lib.NodeSet([i<0 ? (i*-1 < c.length-1 ? c[c.length+i] : c[0]) : c[i]]).prepend(z);
						});
					},
				wrap:function(t){	// tag type or simple HTML string
					t=t.match(/</) ? lib.String.toNodeSet.call(t) : lib.DOM.create(t);
					return this.forEach(function(n){
						n.parentNode.replaceChild(t.copy().insertChild(new lib.NodeSet([n]).copy())[0],n); // copy the node to be wrapped, add it as a child of the wrapping node, which then replaces the original node
						});
					},
				unwrap:function(t){	// tag type only
					return this.forEach(function(n){
						var p=n.parentNode;
						if (p && p.tagName.toLowerCase()==t && p.parentNode) p.parentNode.replaceChild(n,p);
						});
					},
					
				// style methods
				
				getStyle:(function(){
					var fn=window.getComputedStyle ? function(n,p){
						var comp=window.getComputedStyle(n,'');
						return comp[p] || null;
						} : function(n,p){
						return n.currentStyle ? (n.currentStyle[p] || null) : null;
						};
					return function(){
						var args=lib.util.getArgs(arguments);
						args=args.map(function(a){
							return lib.String.toCamelCase.call(a);
							});
						return this.mapArray(function(n){
							return args.map(function(a){
								return fn(n,a);
								});
							});
						};
					})(),
				setStyle:function(o){
					var h=new lib.Hash();
					for (var p in o){
						if (o.hasOwnProperty(p)) h[lib.String.toCamelCase.call(p)]=o[p];
						}
					return this.forEach(function(n){
						h.forEach(function(v,p){
							n.style[p]=v;
							});
						});
					},
				getClass:function(){
					return this.mapArray(function(n){
						return n.className.split(' ');
						});
					},
				setClass:function(){
					var a=lib.util.getArgs(arguments).join(' ');
					return this.forEach(function(n){
						n.className=a;
						});
					},
				addClass:function(){
					var a=lib.util.getArgs(arguments);
					return this.forEach(function(n){
						n.className=lib.Array.unique.call(n.className.split(' ').concat(a)).join(' ');
						});
					},
				removeClass:function(){
					var a=lib.util.getArgs(arguments);
					return this.forEach(function(n){
						n.className=lib.Array.minus.call(n.className.split(' '),a).join(' ');
						});
					},
				hasClass:function(){
					var a=lib.util.getArgs(arguments);
					var any=(a.length && typeof a[a.length-1]=='boolean' && typeof a[a.length-1]) ? true : false;
					return this.mapArray(function(n){
						var c=n.className, i=a.length;
						while (i--){
							if ((c.indexOf(a[i])==-1)!=any) return any;
							}
						return !any;
						});
					},
				hasAnyClass:function(){
					return this.hasClass.apply(this,lib.util.getArgs(arguments).concat([true]));
					},
				getAttr:function(){
					var args=lib.util.getArgs(arguments);
					return this.mapArray(function(n){
						return args.map(function(a){
							return n.getAttribute(a);
							});
						});
					},
				setAttr:function(o){
					return this.forEach(function(n){
						for (var p in o){
							if (o.hasOwnProperty(p)) n.setAttribute(p,o[p]);
							}
						});
					},
				getOpacity:function(){
					return this.mapArray(function(n){
						if (n.filters){
							try{ return (n.filters.alpha.opacity*1)/100; }
							catch(e){ return 1; }
							}
						else if (typeof n.style.opacity) return (n.style.opacity ? n.style.opacity : (new lib.NodeSet([n])).getStyle("opacity"))*1;
						return 1;
						});
					},
				setOpacity:function(o){
					return this.forEach(function(n){
						if (n.filters) n.style.setAttribute("filter", "alpha(opacity="+(o*100)+")");
						else n.style.opacity=o;
						});
					},
				show:function(t){
					t=t || 'block';
					return this.forEach(function(n){ n.style.display=t; });
					},
				hide:function(){
					return this.forEach(function(n){ n.style.display=''; });
					},
					
				// data methods
				
				getFormData:function(opt){
					opt=opt || {};
					return this.filter(function(f){
						return f.tagName.toLowerCase()=='form';
						}).mapArray(function(f){
						var data=new lib.Hash(), f=new lib.NodeSet([f]), v='';
						f.$T('textarea').forEach(function(t){ if (!!t.name && !t.disabled) data[t.name]=t.value; });
						f.$T('select').forEach(function(s){
							if (!!s.name && !s.disabled && s.options.length){
								if (!s.multiple) data[s.name]=s.options[s.selectedIndex || 0].value;
								else{
									var all=[];
									for (var i=0;i<s.options.length;i++){
										if (s.options[i].selected) all.push(s.options[i].value);
										}
									data[s.name]=all.join(',');
									}
								}
							});
						f.$T('input').forEach(function(i){
							if (!!i.name && !i.disabled){
								if (i.type=='text' || i.type=='file' || i.type=='password') data[i.name]=i.value;
								else if (i.type=='checkbox' && i.checked) data[i.name]='on';
								else if (i.type=='radio' && i.checked) data[i.name]=i.value;
								}
							});
						return opt.querystring ? data.toQuerystring(opt) : data;
						});
					},
				hijackForm:function(opt){	// sets a listener against all forms in the nodeset
					var re=document.domain ? new RegExp("^https?:\/\/[^\.]*"+lib.String.toRegEx.call(document.domain,{raw:1}),"i") : /^$/;
					this.filter(function(f){
						return f.tagName.toLowerCase()=='form';
						}).submit(function(e){
						lib.Event.cancel(e);
						var f=this;
						opt.method-f[0].method, opt.data=f;
						(new lib[f.action.match(re) ? 'HTTP' : 'XHTTP'](f[0].action || '',opt)).send();
						});
					return this;
					}
					
				});
				
			// some more style stuff that isn't added directly for whatever reason
			var adjBoxModel=(lib.Client.quirks() && _ie);
			var getDimension=function(ns,D){
				d=D.toLowerCase();
				
				var fn=function(n,display){
					var v=n['offset'+D] ? n['offset'+D] : (n.clip ? n.clip[d] : (n.style && n.style['pixel'+D] ? n.style['pixel'+D] : null));
					if ((v===null || v===undefined || v==='auto') && !isNaN(display)){
						n=new lib.NodeSet([n]), v=n.getStyle(d)[0][0].replace('px','');
						if (v=='auto' && n.getStyle('display')[0][0]=='none') v=fn(n.show()[0],n);	// if element is hidden, show, re-run and hide
						}
					else if (isNaN(display)) display.hide();
					return isNaN(v) ? v : v*1;
					};
				
				var r=ns.mapArray(fn);
				return r;
				};
				
			var getOuterDimension=function(ns,d){
				d=d=='Height' ? ['Top','Bottom'] : ['Left','Right'];
				return ns.mapArray(function(n){
					var t=0, n=new lib.NodeSet([n]);
					n.getStyle('border'+d[0]+'Width', 'border'+d[1]+'Width', 'padding'+d[0], 'padding'+d[1])[0].forEach(function(x,i){
						if (x=='thin') t+=_ie?2:1;
						else if (x=='medium'){
							if (i<2 && n.getStyle('border'+d[i]+'Style')[0]!='none') t+=_ie?4:3;
							}
						else if (x=='thick') t+=_ie?6:5;
						else t+=(x.replace('px','')*1);
						});
					return t;
					});
				};
				
			var setDimension=function(ns,D,v){
				d=D.toLowerCase();
				var o=adjBoxModel ? null : ns['getOuter'+D](), c=typeof v=='number' ? null : ns['get'+D]();
				ns.forEach(function(n,i){
					n.style[d]=((c ? c[i]+(v*1) : v)-(adjBoxModel ? 0 : o[i]))+'px';
					});
				return ns;
				};
				
			var setInnerDimension=function(ns,D,v){
				var o={}, d=D.toLowerCase();
				if (typeof v=='number'){
					o[d]=v+'px';
					ns.setStyle(o);
					}
				else{
					var c=ns['getInner'+D](), ns2=new lib.NodeSet([]);
					ns2.length=1;
					ns.forEach(function(n,i){
						ns2[0]=n;
						o[d]=(c[i]+(v*1))+'px';
						ns2.setStyle(o);
						});
					}
				return ns;
				};
			
			lib.Class.augment(me.prototype,{
				getHeight:function(){
					return getDimension(this,'Height');
					},
				setHeight:function(h){
					return setDimension(this,'Height',h);
					},
				getWidth:function(){
					return getDimension(this,'Width');
					},
				setWidth:function(w){
					return setDimension(this,'Width',w);
					},
				getSize:function(){
					var w=getDimension(this,'Width'), h=getDimension(this,'Height');
					return this.mapArray(function(n,i){ return {w:w[i],h:h[i]}; });
					},
				setSize:function(w,h){
					if (typeof w=='object') h=w.h, w=w.w;
					return this.setWidth(w).setHeight(h);
					},
				getOuterHeight:function(){ return getOuterDimension(this,'Height');},
				getOuterWidth:function(){return getOuterDimension(this,'Width');},
				getOuterSize:function(){
					var w=getOuterDimension(this,'Width'), h=getOuterDimension(this,'Height');
					return this.mapArray(function(n,i){ return {w:w[i],h:h[i]}; });
					},
				getInnerHeight:function(){
					var h=getOuterDimension(this,'Height'), H=getDimension(this,'Height');
					return H.map(function(n,i){ return n-h[i]});
					},
				setInnerHeight:function(h){
					return setInnerDimension(this,'Height',h);
					},
				getInnerWidth:function(){
					var w=getOuterDimension(this,'Width'), W=getDimension(this,'Width');
					return W.map(function(n,i){ return n-w[i]});
					},
				setInnerWidth:function(w){
					return setInnerDimension(this,'Width',w);
					},
				getInnerSize:function(){
					var w=this.getInnerWidth(), h=this.getInnerHeight();
					return w.map(function(n,i){ return {w:w[i],h:h[i]} });
					},
				setInnerSize:function(w,h){
					if (typeof w=='object') h=w.h, w=w.w;
					setInnerDimension(this,'Width',w);
					return setInnerDimension(this,'Height',h);
					},
				getCoord:function(o){
					o=o || {};
					if (!o.fromBody) o.p=['absolute','relative'];
					return this.mapArray(function(n){
						var c={x:0,y:0};
						if (n.offsetParent){
							c.y+=n.offsetTop;
							c.x+=n.offsetLeft;
							while (n=n.offsetParent){
								if (o.p && o.p.indexOf((new lib.NodeSet([n])).getStyle('position')[0][0])>=0) break;
								c.y+=n.offsetTop;
								c.x+=n.offsetLeft;
								}
							}
						return c;
						});
					},
				setCoord:function(x,y,o){
					if (typeof x=='object' && x!==null) o=y, y=x.y, x=x.x;
					o=o || {};
					var obj={'position':o.position || 'absolute'};
					if (o.fromBody){
						var self=this;
						this.positionedAncestor().forEach(function(a,i){
							var n=new lib.NodeSet([a]), c=n.getCoord({fromBody:true})[0];
							n[0]=self[i];	// save instantiation just by swapping in node
							if (x!=null) obj['left']=(-c.x+x)+'px';
							if (y!=null) obj['top']=(-c.y+y)+'px';
							n.setStyle(obj);
							});
						}
					else{
						if (x!=null) obj['left']=x+'px';
						if (y!=null) obj['top']=y+'px';
						this.setStyle(obj);
						}
					return this;
					}
				});

			var splitKey=function(k){
				k=k=='+' ? ['+'] : k.split('+').reverse();	// handle special case of '+'
				k[0]=/^[a-z]$/i.test(k[0]) ? k[0].charCodeAt(0) : lib.Event.keyBindings[k[0]] || '';	// grab alphabetic ascii or keybinding value
				return k.reverse().join('');
				};
				
			var getEvtArgs=function(){
				var a=lib.util.getArgs(arguments);
				var opt=a.length && typeof a[a.length-1]!='function' ? a.pop() : {};
				opt.key=opt.key ? splitKey(opt.key) : null;
				return [a,opt];
				};
			
			var eList=['click','dblclick','contextmenu','mouseover','mouseout','mousemove','mousedown','mouseup','keyup','keydown','keypress','scroll','load','unload','focus','blur','change','submit','reset','mousewheel'];
			if (_ie) eList=eList.concat(['mouseenter','mouseleave']);
			var w=lib.Client.browser()=='firefox' ? 'DOMMouseScroll' : 'mousewheel';
			
			eList.forEach(function(h){
				me.prototype[h]=function(){
					var x=getEvtArgs.apply(this,arguments);
					return this.forEach(function(n){
						x[0].forEach(function(fn){
							lib.Event.add(n,h=='mousewheel' ? w : h,fn,x[1]);
							});
						});
					};
				me.prototype['stop'+lib.String.toProperCase.call(h)]=function(){
					var x=getEvtArgs.apply(this,arguments);
					return this.forEach(function(n){
						if (x[0].length) x[0].forEach(function(fn){
							lib.Event.remove(n,h=='mousewheel' ? w : h,fn,x[1]);
							});
						else lib.Event.remove(n,h);
						});
					};
				});
		
			return me;
			})();

	/* add some basic DOM functions, for standalone use but also for the query funcs that follow */

	lib.DOM={
		create:function(){
			var args=lib.util.getArgs(arguments), el=document.createElement(args.shift());
			if (args[0]) (new lib.Hash(args[0])).forEach(function(v,p){ el[p]=v; });	// copy optional properties to node
			for (var i=1;i<args.length;i++){	// add optional child nodes (from Strings or NodeSets)
				(typeof args[i]=='string' ? lib.String.toNodeSet.call(args[i]) : args[i]).forEach(function(c){
					el.appendChild(c);
					});
				}
			return new lib.NodeSet([el]);
			},
		filterByNodeType:function(c,t,n){	// if a specific node n is supplied, an inclusion test returns a boolean. Otherwise, an array of matching nodes is returned.
			var all=[], t=t || 1;
			for (var i=0;i<c.length;i++){
				if (c[i] && c[i].nodeType==t){
					if (n) return true;
					else all.push(c[i]);
					}
				}
			return n ? false : all;
			},
		filterByTagName:function(c,t,n){	// if a specific node n is supplied, an inclusion test returns a boolean. Otherwise, an array of matching nodes is returned.
			var all=[], t=t.toLowerCase();
			for (var i=0;i<c.length;i++){
				if (c[i] && c[i].tagName && c[i].tagName.toLowerCase()==t){
					if (n) return true;
					else all.push(c[i]);
					}
				}
			return n ? false : all;
			},
		filterByPattern:function(c,p,n){	// if a specific node n is supplied, an inclusion test returns a boolean. Otherwise, an array of matching nodes is returned.
			var m=(p=='even' ? ["2n","2",0,0] : (p=='odd' ? ["2n","2","+",1] : !isNaN(p) ? ['0n+'+p,0,'+',p] : p.match(/^(\d*)n?(\+|\-)?(\d*)$/)));
			var i=-1+((m[2]?(m[2]=="+"?1:-1):1)*(m[3]?m[3]:0));
			m[1]*=1;
			if (!n) var all=[];
			for (var j=i;j<c.length;){
				if (j>=0){
					if (n==c[j]){
						if (n) return true;
						else all.push(c[j]);
						}
					}
				if (!m[1]) break;
				j=j+m[1];
				}
			return n ? false : all;
			},
		isDescendant:function(d,a){
			while (d && d!=document){
				d=d.parentNode;
				if (d==a) return true;
				}
			return false;
			}
		};
		
	/* now we have the core methods to build the main DOM query API */
	
	/* we'll start with some simple methods and work our way up */
	
	lib.Class.augment(lib,{
		$:function(){		// just a basic getById function, takes flexible args. Assumes unique IDs so always works from document node. Can also accept document and window.
			return new lib.NodeSet(lib.util.getArgs(arguments).map(function(i){ return (i==document || i==window) ? i : document.getElementById(i); }));
			},
		$T:function(){		// getByTag, Accepts multiple tag strings.
			var all=[], a=lib.util.getArgs(arguments);
			(this==lib ? [document] : this).forEach(function(b){
				a.forEach(function(n){
					all=all.concat(lib.util.toArray(b.getElementsByTagName(n)));
					});
				});
			return new lib.NodeSet(all);
			},
		$C:function(){	// works similar to native getElementsByClassName() so memoize it where available

			var fn=document.getElementsByClassName ? function(a,b,all){
				a.forEach(function(c){
					all=all.concat(lib.util.toArray(b.getElementsByClassName(c)));
					});
				return all;
				} : function(a,b,all){
				a.forEach(function(c){
					var c=c.split(" "), n=b.getElementsByTagName('*');
					for (var i=0;i<n.length;i++){
						if (!lib.Array.minus.call(c,n[i].className.split(" ")).length) all.push(n[i]);
						}
					});
				return new lib.NodeSet(all);
				};
				
			lib.$C=function(){
				var all=[], a=lib.util.getArgs(arguments);
				(this==lib ? [document] : this).forEach(function(b){
					all=all.concat(fn(a,b,all));
					});
				return new lib.NodeSet(all);
				};
			return lib.$C.apply(this,arguments);
			}
		});

	lib.$CSS=(function(){		// the beast, getByCSS. Accepts multiple CSS selector strings. Uses querySelectorAll() if available (can be suppressed to ensure common results). Can operate on various context nodes, document by default.
	
			// setup cache
			var cache={
				selector:{},
				node:[],
				empty:function(e){
					cache.selector={};
					cache.node.forEach(function(x,i){
						cache.node[i].c=null;
						cache.node[i].p=null;
						});
					}
				};
	
			// safe collection-to-array
			var copyColl=_ie ? function(c){
				var copy=[];
				for (var i=0;i<c.length;i++) copy[i]=c[i];
				return copy;
				} : function(c){
				return Array.prototype.slice.call(c);
				};
	
			var query=function(qu){
	
				var re={
					neg:/not\(([^\)]+)\)+/,				// negations
					tag:/^[\w\*]+/,						// tag
					id:/\#[\w\_-]+/,					// id
					cls:/\.[\w]+/g,						// class
					att:/\[[^\]]+\]/g,					// attribute
					pse:/:[\w-\_\(\)\+\-]+/g,			// pseudo-selector
					sng:/^[#\.\[:]?[^#\.\[:]+$/			// single selector
					};
			
				var helpers={
					empty:function(n){	// is node empty?
						n=n.firstChild;
						while (n){
							if (n.nodeType==3 && n.innerText!='\n') return false;
							n=n.nextSibling;
							}
						return true;
						},
					addToCache:function(p){
						var i=0, n=p.firstChild, tag={};
						while (n){							// cache child nodes
							while (n && n.nodeType!=1) n=n.nextSibling;
							if (!n) break;
							if (n.uid===undefined){	// if no UID then add it, and store reference in node cache
								n.uid=cache.node.length;
								cache.node[n.uid]={};
								}
							if (!tag[n.tagName.toLowerCase()]) tag[n.tagName.toLowerCase()]=0;
							cache.node[n.uid].c=[i++,tag[n.tagName.toLowerCase()]++];	// store the data [index,tagIndex]
							n=n.nextSibling;
							}
						if (p.uid===undefined){				// do the same for the parent node
							p.uid=cache.node.length;
							cache.node[p.uid]={};
							}
						cache.node[p.uid].p=[i,tag];
						},
					endChild:function(n,c,t){			// is node first/last
						if (n.uid===undefined || cache.node[n.uid].c==null) this.addToCache(n.parentNode);
						var i=cache.node[n.uid].c[t ? 1 : 0];
						return (c=='last') ? i==((t ? cache.node[n.parentNode.uid].p[1][n.tagName.toLowerCase()] : cache.node[n.parentNode.uid].p[0])-1) : !i;
						},
					onlyChild:function(n,t){			// is only child
						if (n.uid===undefined || cache.node[n.uid].c==null) this.addToCache(n.parentNode);
						return (t ? cache.node[n.parentNode.uid].p[1][n.tagName.toLowerCase()] : cache.node[n.parentNode.uid].p[0])==1;
						},
					nthChild:function(n,c,a,b,t){
						if (!a && !b) return true;		// is nth child
						if (n.uid===undefined || cache.node[n.uid].c==null) this.addToCache(n.parentNode);
						var i=cache.node[n.uid].c[t ? 1 : 0];
						if (c=='last'){
							i=(t ? cache.node[n.parentNode.uid].p[1][n.tagName.toLowerCase()] : cache.node[n.parentNode.uid].p[0])+1-i;
							}
						return a ? ((i-b) % a) : i+1==b;	
						},
					nextChild:function(n){
						var m=n.nextSibling;
						while (m && m.nodeType!=1) m=m.nextSibling;
						return m;
						},
					inPrevChildren:function(r,n,i){
						var p=n.parentNode;
						// do the node caching
						if (n.uid===undefined || cache.node[n.uid].c==null) this.addToCache(p);
						var x=cache.node[n.uid].c[0];
						for (var j=0;j<r.length;j++){
							if (r[j].parentNode!=p) continue;
							if (cache.node[r[j].uid].c[0] < cache.node[n.uid].c[0]) return true;
							}
						return false;
						
						},
					inPrevChildren2:function(r,n,i){
						var m=n.previousSibling, j=0, i=!i ? n.parentNode.childNodes.length : i;
						while (m && j<=i){
							while (m && m.nodeType!=1) m=m.previousSibling;
							if (!m) break;
							if (i==1) return r.indexOf(m)>-1;
							else if (r.indexOf(m)>-1) return true;
							j++;
							m=m.previousSibling;
							}
						return false;
						},
					byTag:function(base, tag, root, comb, neg){
						if (base.length) return base.filter(function(n){
							return n.tagName.toLowerCase()==tag != neg;
							});
						else{
							root.forEach(function(r){
								if (!comb || comb=='>'){
									var b=copyColl(r.getElementsByTagName(tag));
									if (neg) b=base.filter(function(x){
										return b.indexOf(x)==-1;
										});
									if (comb=='>') b=b.filter(function(x){
										return x.parentNode==r;
										});
									}
								else{
									var b=[], i=helpers.nextChild(r);
									while (i){
										if (i.tagName.toLowerCase()==tag != neg) b.push(i);
										i=helpers.nextChild(i);
										if (comb=='+') break;
										}
									}
								base=base.concat(b);
								});
							return base;
							}
						},
					simpleSelector:function(s){
						var r=s.replace(/even(?!\w)/gi,'2n').replace(/odd/gi,'2n+1'), t='tag', o={};
						if (!s.indexOf('#')) t='id';
						else if (!s.indexOf('.')) t='cls';
						else if (!s.indexOf('[')) t='att';
						else if (!s.indexOf(':not')) t='neg';
						else if (!s.indexOf(':')) t='pse';
						o[t]=[s];
						return [o,r];
						}
					};
		
				var reOrder=['tag','id','cls','att','pse','neg'];

				// first split on commas
				var qu=qu.split(','), results=[];
				qu.forEach(function(q, i){
					q=q.replace(/^(\s*)|(\s*)$/g,'');
				
					// for speed, we'll do a quick test for the case of a single ID; results aren't cached
			
					if (q.match(/^#[\w\-]+$/)){
						var result=document.getElementById(q.replace('#',''));
						if (result) results=results.concat(result);
						return;
						}
		
					var nq=[], nqp=[];
			
					// we're going to split on whitespace here. This is a flaw if the query contains quoted whitespace....will this happen?
	
					q.split(' ').forEach(function(s){
	
						if (re.sng.test(s)){
							// first check and return combinators
		
							if (s=='+' || s=='>' || s=='~'){
								nqp.push(s);
								nq.push(s);
								}
							else{
								var r=helpers.simpleSelector(s);
								nqp.push(r[0]);
								nq.push(r[1]);
								}
							}
						else{
		
							// the sequence we need is tag, ID, class, attribute, pseudo. Also normalise even/odd, and sort multiple selectors of the same type

							var o={}, m, r='';
							for (var x in re){
								while (m=s.match(re[x])){
									if (!o[x]) o[x]=[];
									o[x].push(x=='pse' ? m[0].replace(/even/gi,'2n').replace(/odd/gi,'2n+1') : m[0]);
									s=s.replace(m[0],'');
									}	
								}
			
							reOrder.forEach(function(x){
								if (o[x]) r+=(o[x].length==1 ? o[x][0] : o[x].sort().join(''));
								});
			
							nqp.push(o);
							nq.push(r);
	
							}
						});
	
					var n=[], partial=null, i=0;
					if (cache.selector){
						var q=nq.join(' ');
						if (q && cache.selector[q]){
							results=results.concat(cache.selector[q]);
							return;
							}
				
						// we should work backwards through the selector groups as we might find a partial match to work from
						var copy=nq.slice(), j=copy.length, l;
						while (j--){
							copy.pop();
							l=copy.join(' ');
							if (cache.selector[l]){
								n=cache.selector[l], i=j;
								break;
								}
							}
				
						if (j==-1){
			
							// we can also try to match a partial of the first group.
							// could this extensive checking be detrimental?
			
							var q2=q;
			
							if (!partial && nqp[0].pse){  // if there's pseudos, remove and try again...
								q2=q2.replace(/:[\w-\(\)]/g,'');
								if (cache[q2]){
									nqp[0].att=nqp[0].cls=nqp[0].tag=nqp[0].id=null;
									partial=cache.selector[q2];
									}
								}
			
							if (!partial && nqp[0].att){  // if there's attributes, remove and try again...
								q2=q2.replace(/\[[^\]]+\]/g,'');
								if (cache[q2]){
									nqp[0].cls=nqp[0].tag=nqp[0].id=null;
									partial=cache.selector[q2];
									}
								}
			
							if (!partial && nqp[0].cls){  // if there's classes, remove and try again...
								q2=q2.replace(/\.\w+/g,'');
								if (cache[q2]){
									nqp[0].tag=nqp[0].id=null;
									partial=cache.selector[q2];
									}
								}
				
							}
						}
			
					// use document.querySelectorAll is available, use it
				
					if (document.querySelectorAll){
						var result=copyColl(document.querySelectorAll(q));
						if (result.length) results=results.concat(result);
						return;
						}
			
			
					// main selector stuff. Now we can work through the selectors.
		
					var comb;			// the combinator type
		
					var check=function(root,base,comb,pid,pcls,ptag,patt,ppse,neg){
			
						if (pid){
				
							if (base && base.length) base=base.filter(function(b){
								b.getAttribute('id')==(typeof pid=='string' ? pid : pid[0]);
								});
							else{
								var id=(typeof pid=='string' ? [pid] : pid).map(function(x){
									return document.getElementById(x.replace('#',''));
									});
								if (!id[0]) id=[];
				
								if (id.length){
									if (root[0]==document){
										if (!neg) base=id;
										else base=copyColl(document.getElementsByTagName('*')).filter(function(x){
											return id.indexOf(x)==-1;
											});
										}
									else{
										var b=[];
										(neg ? base.filter(function(x){
											return id.indexOf(x)==-1;
											}) : id).forEach(function(x){
											var el=x;
											if (!comb){
												while (el=el.parentNode){
													var j=root.indexOf(el);
													if (j>-1){
														b.push(x);
														break;
														}
													}
												}
											else if (comb=='>'){
												if (root.indexOf(el.parentNode)>-1) b.push(x);
												}
											else if (comb=='+'){
												if (helpers.inPrevChildren(root,el,1)) b.push(x);
												}
											else if (comb=='~'){
												if (helpers.inPrevChildren(root,el)) b.push(x);
												}			
											});
										base=b;
										}
									}
								}
							if (!base.length) return false;
							}
			
						// what happens now depends on whether there is a native, therefore faster, getElementsByClassName
			
						if (document.getElementsByClassName){
			
							// if there's a native class selector function, its faster to do class stuff first then filter by tag
				
							if (pcls){	// there is a class
				
								var cls=pcls.join(' ').replace(/\./g,'').split(' ');
					
								if (base.length){ // handle case of existing base item
					
									base=base.filter(function(n){
										var cn=n.className+' ';
										return cls.every(function(c){
											return cn.indexOf(c+' ')>-1 != neg;
											});
										});
									if (!base.length) return false;
									}
								else{
									cls=!comb || comb=='>' ? cls.join(' ') : cls;
									root.forEach(function(r){
										if (!comb || comb=='>'){
											var b=copyColl(r.getElementsByClassName(cls));
											if (neg) b=base.filter(function(x){
												return b.indexOf(x)==-1;
												});
											if (comb=='>') b=b.filter(function(x){
												return x.parentNode==r;
												});
											}
										else{
											var b=[], i=helpers.nextChild(r);
											while (i){
												var c=i.className;
												if (cls.every(function(x){
													return c.indexOf(x)>-1 != neg;
													})) b.push(i);
												i=helpers.nextChild(i);
												if (comb=='+') break;
												}
											}
										base=base.concat(b);
										});
									if (!base.length) return false;
									}
							
								}
					
							if (ptag){
								base=helpers.byTag(base, ptag, root, comb, neg);
								if (!base.length) return false;
								}
			
							}
						else{
			
							// there's no native selector function, so instead we'll select by tag first, then filter by class
				
							if (ptag){
								base=helpers.byTag(base, ptag, root, comb, neg);
								if (!base.length) return false;
								}

							if (pcls){	// there is a class
					
								if (!base.length) root.forEach(function(r){
									base=base.concat(copyColl((comb=='+' || comb=='~' ? r.parentNode : r).getElementsByTagName("*")));
									});

								var cls=pcls.join(' ').replace(/\./g,'').split(' ');
								base=base.filter(function(n){
					
									if (comb=='>'){
										if (root.indexOf(n.parentNode)==-1) return false;
										}
									else if (comb){
										if (!helpers.inPrevChildren(root,n,comb=='+' ? 1 : false)) return false;
										}
						
									var cn=n.className+' ';
									return cls.every(function(c){
										return cn.indexOf(c+' ')>-1 != neg;
										});
									});
								if (!base.length) return false;
					
								}
			
							}
					
						if (!base.length) root.forEach(function(r){
							base=base.concat(copyColl((comb=='+' || comb=='~' ? r.parentNode : r).getElementsByTagName("*")));
							});
				
						if (patt){ // test attributes
			
							var att=patt.map(function(a){
								return a.match(/(\w+)([=~\|\$\^\*]*)?((\w*))?/);
								});
							base=base.filter(function(n){
				
								if (comb=='>'){
									if (root.indexOf(n.parentNode)==-1) return false;
									}
								else if (comb){
									if (!helpers.inPrevChildren(root,n,comb=='+' ? 1 : false)) return false;
									}

								return att.every(function(a){
									var v=n.getAttribute(a[1]);
									if (v===null) return neg;
									return ((!a[2] || (a[2]=='|=' && (v==a[4] || !(v.indexOf(a[4]+'-')))) || (a[2]=='=' && v==a[4]) || (a[2]=='~=' && ((v+' ').indexOf(a[4]+' ')>-1)) || (a[2]=='^=' && (!v.indexOf(a[4]))) || (a[2]=='$=' && (!(v.length-a[4].length-v.lastIndexOf(a[4])))) || (a[2]=='*=' && (v.indexOf(a[4])>-1))) != neg);
									});
								});
							if (!base.length) return false;
			
							}
				
						if (ppse){ // test pseudos
				
							var pse=ppse.map(function(a){
								a=a.match(/:([\w-]+)\(?([\w\+-]*)\)?/);
								if (a[1]=='target') a[2]=window.location.hash.replace("#","");
								else if (!a[1].indexOf('nth')){
									a[2]=isNaN(a[2]) ? (a[2]=='even' ? '2n' : ( a[2]=='odd' ? '2n+1' : a[2])).match(/^(\d*)n?(\+|\-)?(\d*)$/) : [a[2],0,0,a[2]*1];
									a[2][1]=a[2][1] ? a[2][1]*1 : 0;
									a[2][3]=a[2][3] ? (a[2][3]*(a[2][2]=='-' ? -1 : 1)) : 0;
									}
								return a;
								});
					
							base=base.filter(function(n){
				
								if (comb=='>'){
									if (root.indexOf(n.parentNode)==-1) return false;
									}
								else if (comb){
									if (!helpers.inPrevChildren(root,n,comb=='+' ? 1 : false)) return false;
									}
				
								return pse.every(function(a){
									return (
										(a[1]=='target' && (n.getAttribute('target')!=a[2])) ||
										(a[1]=='root' && (n.tagType.toLowerCase()!='html')) ||
										(a[1]=='disabled' && !n.disabled) ||
										(a[1]=='enabled' && n.disabled) ||
										(a[1]=='checked' && !n.checked) ||
										(a[1]=='empty' && !helpers.empty(n))  ||
										(a[1]=='contains' && (n.innerHTML || n.textContent).indexOf(a[2])==-1) ||
										(a[1]=='first-child' && !helpers.endChild(n,'first')) ||
										(a[1]=='first-of-type' && !helpers.endChild(n,'first',n.tagName.toLowerCase())) ||
										(a[1]=='last-child' && !helpers.endChild(n,'last')) ||
										(a[1]=='last-of-type' && !helpers.endChild(n,'last',n.tagName.toLowerCase())) ||
										(a[1]=='only-child' && !helpers.onlyChild(n)) ||
										(a[1]=='only-of-type' && !helpers.onlyChild(n,n.tagName.toLowerCase())) ||
										(a[1]=='nth-child' && !helpers.nthChild(n,'first',a[2][1],a[2][3])) ||
										(a[1]=='nth-of-type' && !helpers.nthChild(n,'first',a[2][1],a[2][3],n.tagName.toLowerCase())) ||
										(a[1]=='nth-last-child' && !helpers.nthChild(n,'last',a[2][1],a[2][3])) ||
										(a[1]=='nth-last-of-type' && !helpers.nthChild(n,'last',a[2][1],a[2][3],n.tagName.toLowerCase()))
										)==neg;
									});
								});
							if (!base.length) return false;
							}
						return base;
						};
		
		
					for (;i<nqp.length;i++){			// loop for each step in the selector
						var root=n.length ? n : [document];
						var base=partial || [], p=nqp[i];
			
						if (typeof p=='string'){
							comb=p;
							continue;
							}
					
						var result=check(root,base,comb,p.id, p.cls, p.tag, p.att, p.pse, false);
						if (result===false) return false;
				
						if (p.neg){
							var negp={};
							p.neg.forEach(function(x){
								x=helpers.simpleSelector(x.replace(/^:?not\(|\)$/g,''))[0];
								for (var k in x){
									if (!negp[k]) negp[k]=[];
									negp[k].push(x[k][0]);
									}
								});
						
							var result=check(root,result,comb,negp.id, negp.cls, negp.tag, negp.att, negp.pse, true);
							if (result===false) return false;
							}
				
						// still to test pseudos and negations
			
						n=result;
						partial=null;
						comb=false;
						}

					n=n.unique();
					if (cache.selector) cache.selector[nq.join(' ')]=n;
					results=results.concat(n);
					});
		
				if (_ie) cache.empty();
				return new lib.NodeSet(results);
				};
	
		
			if (!_ie){
				document.addEventListener('DOMAttrModified',cache.empty,false);
				document.addEventListener('DOMNodeInserted',cache.empty,false);
				document.addEventListener('DOMNodeRemoved',cache.empty,false);
				}
		
			return query;

			})();
		
	/* next, the HTTP class for ajaxy goodness */
	
	var _XHR=function(){
		var fn=window.XMLHttpRequest ? function(){
			return new XMLHttpRequest();
			} : window.ActiveXObject ? function(){
			return new ActiveXObject("Microsoft.XMLHTTP");
			} : function(){return null};
		lib.HTTP.XHR=fn;
		return fn();
		};
	
	lib.HTTP=(function(url,o){
		
		var me=function(url,o){
			this.xhr=new _XHR();
			this.method=o.method || 'get';
			this.async=o.async || true;
			this.data=o.data || '';
			this.timeout=o.timeout || null;
			this.json=o.json || false;
			this.contentType=o.contentType || false;
			this.url=url;
			this.requests=0;
			var self=this;
			['Start','Complete','Timeout','Error'].forEach(function(x){
				self['on'+x]=new lib.Observer();
				if (o['on'+x]) lib.Function.subscribe.call(o['on'+x],self['on'+x]);
				});
			};
			
		me.get=function(url,fn,data){ (new lib.HTTP(url,{onComplete:fn,data:data})).send(); };
		me.getJSON=function(url,fn,data){ (new lib.HTTP(url,{onComplete:fn,data:data,json:true})).send(); };
		me.post=function(url,fn,data){ (new lib.HTTP(url,{onComplete:fn,method:'post',data:data})).send(); };

		var _readyState=function(r){
			if (r.timeout && ((r.timeout*1000)+(new Date()).getTime())>r.startTime){
				clearInterval(r.statechange);
				r.statechange=null;
				r.xhr.abort();
				r.onTimeout.deliver(r);
				r.onError.deliver(r,'Operation timed out');
				if (--r.requests) r.send(1);
				}
			else if (r.xhr.readyState==4){
				clearInterval(r.statechange);
				r.statechange=null;
				if (r.xhr.status==200){
					if (!r.contentType || r.xhr.getResponseHeader('Content-type')==r.contentType){
						if (!r.json) r.onComplete.deliver(r,r.xhr.responseText,r.xhr.responseXml);
						else r.onComplete.deliver(r,lib.Data.JSON.parse(r.xhr.responseText));
						}
					else r.onError.deliver(this,'Unexpected content-type returned');
					}
				else r.onError.deliver(r,'Server returned a '+r.xhr.status+' status code');
				if (--r.requests) r.send(1);
				}
			};
				
		me.prototype.send=function(callback){
			if (!callback) this.requests++;	// increment the request counter when used directly
			var self=this;
			if (!this.statechange){
				if (this.data.toQuerystring) var data=this.data.toQuerystring({encode:true});
				else if (this.data.getFormData){
					var data=this.data.getFormData({querystring:true,encode:true});
					data=data.length ? data[0] : '';
					}
				else { var data=this.data; }
				if (this.method=='get') data+=(data ? '&' : '')+lib('namespace')+'nocache='+(new Date()).getTime();
				var url=(this.method=='get') ? this.url+(this.data ? (this.url.match(/\?/) ? '&' : '?')+data : '') : this.url;
				this.xhr.open(this.method, url, this.async);
				if (this.method=='post'){
					this.xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
					this.xhr.setRequestHeader("Content-length",this.data.length);
					this.xhr.setRequestHeader("Connection","Close");
					}
				else data='';
				this.statechange=setInterval(function(){ _readyState(self); },10);
				this.onStart.deliver(this);
				this.startTime=(new Date()).getTime();
				this.xhr.send(data);
				}
			return this;
			};
			
		me.prototype.poll=function(opt){	// returns a Scheduler instance that calls send() when invoked
			var self=this;
			return new lib.Scheduler(function(){
				self.send();
				},opt);
			};
			
		return me;
		})();
		
	lib.XHTTP=(function(){
	
		var me=function(url,opt){
			opt=opt || {};
			this.url=url;
			this.id=opt.id || lib('namespace')+'xhttp';
			this.data=opt.data || '';
			this.callbackname=opt.callbackname || 'callback';
			this.onComplete=new lib.Observer();
			if (opt.onComplete) opt.onComplete.subscribe(this.onComplete);
			};
			
		me.get=function(url,fn,data){ (new lib.XHTTP(url,{onComplete:fn,data:data})).send(); };
		
		me.requestCache=[];
		var _addToCache=function(fn){
			return me.requestCache.push(fn)-1;
			};
			
		me.prototype.send=function(){
			var url=this.url, data=this.data;
			if (data){
				if (data.toQuerystring) data=data.toQuerystring({encode:true});
				else if (data.getFormData) data=data.getFormData({querystring:true,encode:true});
				url+=(url.match(/\?/) ? '&' : '?')+data;
				}
			var self=this;
			url+=(url.match(/\?/) ? '&' : '?')+lib('namespace')+'nocache='+(new Date()).getTime()+'&'+this.callbackname+'='+lib('namespace')+'.XHTTP.requestCache['+_addToCache(function(r){
				self.onComplete.deliver(self,r);
				var f=arguments.callee;	// this function deletes itself from the cache, removing the closure
				me.requestCache.forEach(function(fn,i){
					if (f==fn) me.requestCache[i]=null;
					});
				})+']';
			lib.$(this.id).remove();
			lib.$T('head').insertChild(lib.DOM.create('script',{id:this.id,type:'text/javascript',src:url}));
			};
			
		me.prototype.poll=function(opt){	// returns a Scheduler instance that calls send() when invoked
			var self=this;
			return new lib.Scheduler(function(){
				self.send();
				},opt);
			};
			
		return me;
	
		})();
		
	lib.Data={
		JSON:{
			serialize:function(o){
				if (!o) return false;
				var item=function(v){
					if (typeof v=='number') return isFinite(v) ? v : 'null';
					else if (typeof v=='string') return '"'+v.replace(/(["\\])/g,"\\$1")+'"';
					else if (typeof v=='boolean') return v.toString();
					else if (v===null || typeof v==='function') return "null";
					else if (v.constructor==Array) return "["+v.map(item).join(",")+"]";
					else if (typeof v==='object' && v.constructor===Object){
						var r=[];
						for (var p in v){
							if (v.hasOwnProperty(p)) r.push('"'+p+'":'+item(v[p]));

							}
						return r.length ? "{"+r.join(",")+"}" : "null";
						}
					else return "null";
					};
				var r=[];
				for (var x in o){
					if (o.hasOwnProperty(x)) r.push('"'+x+'":'+item(o[x]));
					}
				return "{"+r.join(",")+"}";
				},
			parse:function(s){	// Douglas Crockfod's JSON.parse
				if (!s) return false;
				var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
				cx.lastIndex=0;
				if (cx.test(s)) s=s.replace(cx,function(a){
					return '\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);
					});
				return ((/^[\],:{}\s]*$/).test(s.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) ? eval('(' + s + ')') : false;
				}
			},
		XML:{
			serialize:function(xml){
				lib.Data.XML.serialize=(window.XMLSerializer) ? function(xml){
					var o=new XMLSerializer();
					return o.serializeToString(xml);
					} : function(xml){
					return xml.xml?xml.xml:false;
					};
				return lib.Data.XML.serialize(xml);
				},
			parse:function(str){
				lib.Data.XML.parse=(window.DOMParser) ? function(str){
					var xmlObj=new DOMParser();
					var xml=xmlObj.parseFromString(str,"text/xml");
					return xml;
					} : ((window.ActiveXObject) ? function(str){
					var xml=new ActiveXObject("Microsoft.XMLDOM");
					xml.async="false";
					xml.loadXML(str);
					return xml;
					} : function(){return false});
				return lib.Data.XML.serialize(str);
				}
			}
		};
		

		
	/* now lets do the Event stuff */
	
	var _ContentLoaded=function(f){	// Modified ContentLoaded by Diego Perini, http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
		var w=window, d=w.document, D='DOMContentLoaded', u=w.navigator.userAgent.toLowerCase(), v=parseFloat(u.match(/.+(?:rv|it|ml|ra|ie)[\/: ]([\d.]+)/)[1]);
		var init=function(e){
			if (!document.loaded){
				document.loaded=true;
				f(e);
				};
			};
		if (/webkit\//.test(u) && v < 525.13) (function(){			// safari < 525.13
				if (/complete|loaded/.test(d.readyState)) init('khtml-poll');
				else setTimeout(arguments.callee, 10);
			})();
		else if (/msie/.test(u) && !w.opera){
			d.attachEvent('onreadystatechange',function(e){  // internet explorer all versions
				if (d.readyState=='complete'){
					d.detachEvent('on'+e.type, arguments.callee);
					init(e);
					}
				});
			if (w == top) (function(){
				try { d.documentElement.doScroll('left'); }
				catch (e) { setTimeout(arguments.callee, 10); return;}
				init('msie-poll');
				})();
			}
		else if (d.addEventListener && (/opera\//.test(u) && v > 9) || (/gecko\//.test(u) && v >= 1.8) || (/khtml\//.test(u) && v >= 4.0) || (/webkit\//.test(u) && v >= 525.13)) d.addEventListener(D,function(e){
			d.removeEventListener(D, arguments.callee, false);
			init(e);
			},false);
		else{
			var oldonload=w.onload;
			w.onload=function(e){
				if (typeof oldonload=='function') oldonload(e || w.event);
				init(e||w.event);
				};
			}
		};
		
	var _customEvts={};
	
	lib.Event={
		get:function(e){ return e=(e) ? e : ((window.event) ? event : null); },
		target:function(e){
			e=this.get(e);	// get the target of an event
			return new lib.NodeSet([e ? e.target ? e.target : ((e.srcElement) ? e.srcElement : null) : null]);
			},
		coord:function(e){
			e=this.get(e);
			var c={x:0,y:0};
			if ('pageX' in e) return {x:e.pageX,y:e.pageY};
			else if (e.clientX){
				c.x=e.clientX+document.body.scrollLeft-document.body.clientLeft;
				c.y=e.clientY+document.body.scrollTop-document.body.clientTop;
				if (document.body.parentElement && document.body.parentElement.clientLeft){
					var bP=document.body.parentElement;
					c.x+=bP.scrollLeft-bP.clientLeft;
					c.y+=bP.scrollTop-bP.clientTop;
					}
				}
			return c;
			},
		ascii:function(e){	// get the chdar/key code from an event
			return e.which ? e.which : (!isNaN(e.charCode) ? e.charCode : (!isNaN(e.keyCode) ? e.keyCode : null));
			},				// map some keynames to ascii values
		keyBindings:new lib.Hash({'enter':13,'escape':27,'leftArrow':37,'rightArrow':39,'upArrow':38,'downArrow':40,'f1':112,'f2':113,'f3':114,'f4':115,'f5':116,'f6':117,'f7':118,'f8':119,'f9':120,'f10':121,'f11':122,'f12':123}),
		cancel:function(e){	// cancel event bubbling
			e=this.get(e);
			if (e.stopProgagation) e.stopPropagation();
			else e.cancelBubble=true;
			if (e.preventDefault) e.preventDefault();
			else e.returnValue=false;
			},
		defineCustom:(function(){
			var fn=function(name,evt,fn){
				_customEvts[name]={name:name,fn:fn};
				lib.NodeSet.prototype[name]=function(){
					var a=[].slice.call(arguments), o=_customEvts[name];
					if (a.length && typeof a[a.length-1]!='function') a[a.length-1].customEvt=o;
					else a.push({customEvt:o});
					this[evt].apply(this,a);
					};
				lib.NodeSet.prototype['stop'+lib.String.toProperCase.call(name)]=function(){
					var a=[].slice.call(arguments), o=_customEvts[name];
					if (a.length && typeof a[a.length-1]!='function') a[a.length-1].customEvt=o;
					else a.push({customEvt:o});
					this['stop'+lib.String.toProperCase.call(evt)].apply(this,a);
					};
				return this;
				};
				
			if (!_ie){
				fn('mouseenter','mouseover',function(n,e){
					return n[0]==lib.Event.target(e)[0];
					});
				fn('mouseleave','mouseout',function(n,e){
					return n[0]==lib.Event.target(e)[0];
					});
				}
				
			fn('mousewheelup','mousewheel',function(n,e){
				return (e.wheelDelta ? e.wheelDelta*-1 : e.detail)<0;
				});
				
			fn('mousewheeldown','mousewheel',function(n,e){
				return (e.wheelDelta ? e.wheelDelta*-1 : e.detail)>0;
				});
				
			return fn;
			})(),
		add:function(obj,type,fn,opt){
			opt=opt || {};
			if (obj){
				var key=opt.customEvt ? opt.customEvt.name : (opt.key ? (opt.key+'').split('+').join('') : 'none'), n=false;	// invert the key
				if (!obj.evtCache) obj.evtCache={};
				if (!obj.evtCache[type]){
					obj.evtCache[type]={};
					n=true;
					}
				if (!obj.evtCache[type][key]) obj.evtCache[type][key]=new lib.Observer();	// create an observer for this evt/key combo
				if (n){
					if (obj==document && type=='load'){		// handle special case for contentloaded
						_ContentLoaded(function(x){
							document.evtCache['load']['none'].deliver(document,x);
							});
						}
					else {
						var f=function(e){		// otherwise attach a handler
							e=lib.Event.get(e);
							var n=new lib.NodeSet([this]), c=this.evtCache[e.type];
							if (c.none) c.none.deliver(n,e);							// deliver simple evt type fns
							if (_customEvts[key]){										// deliver custom event if it passes the test
								if (_customEvts[key].fn(n,e)) c[key].deliver(n,e);
								}
							else if (/key/.test(e.type)){								// if its a key evt
								var k=lib.Event.ascii(e);
								if (k && c[k]) c[k].deliver(n,e);						// deliver any evt/key combo evts
								['shift','ctrl','alt','cmd'].forEach(function(m){		// deliver any evt/key/modifier combo evts
									if (e[m+'Key'] && c[m+k]) c[m+k].deliver(n,e);
									});
								}
							};
						if (obj.addEventListener) obj.addEventListener(type,f,false);
						else if (obj.attachEvent){
							var oldF=f;	// wrap in a closure to maintain 'this';
							f=function(){oldF.call(obj,window.event)};
							obj.attachEvent('on'+type,f);
							}
						else obj[on+'type']=f;
						obj['fn'+type]=f;
						}
					};
				if (obj==document && type=='load' && document.loaded) fn.call(document);
				else lib.Function.subscribe.call(fn,obj.evtCache[type][key],opt.context);
				}
			return this;
			},
		remove:function(obj,type,fn,opt){
			try{
			if (!obj.evtCache) return;
			opt=opt || {};
			var key=opt.customEvt ? opt.customEvt.name : (opt.key ? (opt.key+'').split('+').join('') : 'none');	// invert key
			for (var x in obj.evtCache){
				if (!type || x==type){
					if (!type || !fn){
						var f=obj[fn+'x'];
						if (obj.removeEventListener) obj.removeEventListener(x,f,false);
						else if (obj.detachEvent) obj.detachEvent('on'+x,f);
						else obj['on'+x]=null;
						obj.evtCache[x]=obj['fn'+x]=null;
						}
					else if (obj.evtCache[x][key]) lib.Function.unsubscribe.call(fn,obj.evtCache[x][key]);	// remove fns for evt/key combo
					}
				}
			if (!type) delete obj.evtCache;
			}
			catch(e){}
			}
		};
		
	/* the module class */
	
	lib.Module=(function(){
			
		var _onload={}, _loadError=function(module,msg){
			if (_onload[module.name].onError) _onload[module.name].onError(msg);
			return false;
			};
			
		var self={
			loadOnDemand:false,			// modules arent loaded automatically by default
			library:new lib.Hash({'moduleLibrary':'http://192.168.2.174/dump/gb/modules/moduleLibrary.js'}), // the library itself is just a hash
			install:function(module){		// this method is called by the module itself
				if (module.requires){
					var self=this;
					if (module.requires.version && module.requires.version > lib('version')) return _loadError(module,'The "'+module.name+'" module was not loaded because it requires jXpress '+module.requires.version+'.');
					else if (module.requires.modules){
						var p=module.requires.modules.filter(function(p){	// filter installed modules
							return !self[p];
							});
						if (p.length) return _loadError(module,'The "'+module.name+'" module was not loaded because it requires the following uninstalled modules: "'+p.join('", "')+'".');
						}
					}
				if (module.init){
					this[module.name]=module.init(lib);	// if all is ok, save the module function as a property of the singleton
					if (_onload[module.name]){	// call and remove any onComplete callbacks
						_onload[module.name].onComplete(this[module.name]);
						delete _onload[module.name];
						}
					return true;
					}
				else return false;
				},
			get:function(name,opt){		// use this method to safely get a module, even if its not installed
				opt=opt || {};
				if (this[name]) opt.onComplete(this[name]);	// already loaded
				else if (this.loadOnDemand){	// only load dynamically if requested
					if (this.library[name]){	// if this module address is registered, fetch it
						_onload[name]=opt;
						(new lib.XHTTP(this.library[name])).send();
						}
					else if (!this.moduleLibrary){	// try requesting the full library if it doesn't exist
						var self=this;
						this.get('moduleLibrary',{onComplete:function(pLib){	// redfine the callback to request the original module
							pLib();
							self.get(name,opt);
							},onError:opt.onError});
						}
					else if (opt.onError) opt.onError('The "'+name+'" module is not registered in the Module Library.');
					}
				else if (opt.onError) opt.onError('The "'+name+'" module is not loaded, and Module.loadOnDemand is set to false.');
				}
			};
		return self;
		})();
		
	/* add a shortcut for document loading and set onunload to cleanup JS objects from the DOM */
	
	lib.ready=function(fn){
		lib.Event.add(document,'load',fn);
		};
		
	lib.Event.add(window,'unload',function(){
		lib.$T('*').forEach(function(n){
			lib.Event.remove(n);
			});
		});
		
	/* get init parameters, copy prototypes and setup namespace */
	
	var init=lib.$T('script').filter(function(s){ return (/gb\.js/i).test(s.src); });
	var params=(init && init.length ? lib.String.toHash.call(init[0].src.match(/\.js\??(.*)$/i)[1]) : new lib.Hash());
	['Array','String','Function'].forEach(function(c){
		if (!params[c] || params[c]!='false') lib.Class.augment(window[c].prototype,lib[c]);
		});
	
	var obj=window;
	(params.ns ? params.ns.split(".") : ['jxp']).forEach(function(p,i,a){
		if (!obj[p]) obj[p]={};
		if (i==a.length-1) obj[p]=lib;
		else obj=obj[p];
		});
	_info.namespace=params.ns || 'jxp';
	if (_info.namespace.indexOf('.')!=-1) window[_info.namespace]=lib;	// add a global pointer if the library is buried in a nested namespace
		
	// done :)
	
	})();