//  Thanks to:
//  http://www.howtocreate.co.uk/tutorials/javascript/objects -- JavaScript objects, private/public properties
//  http://dreaminginjavascript.wordpress.com/2008/07/04/28/ -- Javascript operator tricks
//	http://www.hunlock.com/blogs/Closing_The_Book_On_Javascript_Closures -- Closures
//	http://www.thespanner.co.uk/2009/01/29/detecting-browsers-javascript-hacks/
/*

    OK. After several hours of frustrating trial-and-error, objects can now be
    "extended", while keeping their private bits private. Doing this in JavaScript
    was not hard, just unclear.
    
    Objects which can have functions dynamically added to them -- can be "extended" --
    have a meta Prototype object stored in the NPL object: NPL.Prototype.__ObjectName__
    
    You can use, "NPL.Prototype.__ObjectName__.myfunction =..." to add a function
    to that object's class. Note that any objects which are instantiated before
    the function is added will not have that function added to them. It doesn't
    work like Javascript's built-in prototype class that way.
    
    I can not find a way to get private methods and properties to work while
    keeping Javascript's built-in prototype functionality, so I have duplicated
    it.
    
    Also, dynamically-added functions do not have access to the object's private
    properties and methods. There does not appear to be a way to overcome this
    in Javascript, but it's considered correct behavior anyway.
    
    You can freely add public properties, however, which will stay local to the
    object that inherits them.

*/

/*

	STYLE GUIDE:
	
		1. i, j, k are all local iterator variables. These should only be used to index
			values in arrays, and they should never ever show up in the global scope.
		2. Internal variables and functions are lowercase and prepended with one underscore:
			_variable, _function
			These are items which might be globally or externally accessible, but aren't guaranteed
			to be supported in the future. Other scripts should avoid referencing them, but it's
			not forbidden.
		3. Private variables and methods are lowercase and prepended with two underscores:
			__variable, __function
			These should never appear in the global scope, or outside of any function or object.
		4. Local variables are mixed-case and prepended with an x:
			xVariable
			These should never appear in the global scope.
		5. Function parameters are mixed-case and always begin with a lowercase p:
			pParameter
			Functions should generally avoid modifying their parameters.
		6. SPEED, SPEED, SPEED. All code should have a _brutal_ emphasis on speed.
			That includes keeping the code concise, because the size of the code matters too.
		7. Comments should explain _what_ is being done (i.e., answer "why"), rather than "how".
		8. Use TODO to mark items that need to be taken care of in future versions.
		9. Use BUGFIX to mark items that produce errors, or potentially produce errors, and should
			be fixed soon.
		
*/

/*

	TODO:
		Switch to web workers (where available):
		1. Have Turbine handle script fragments, e.g.: /js/npl/[version]/effects/fade_in.js
		2. Spawn these as Worker threads
		3. Workers communicate with NPL core to manipulate DOM (...possible?)
		https://developer.mozilla.org/En/Using_web_workers
		PROGRESS: 0%
	
	TODO:
		Switch Channel.Open / Channel.Post to http_get & http_post
		1. Asynchronicity: Specifying a callback function should be optional; 
			if one is not specified, then the function won't return until data is received.
		PROGRESS: 0%
	
	TODO:
		jQuery selector compatibility
		PROGRESS: ~50%
	
	TODO:
		jQuery API compatibility
		PROGRESS: 0%
	
	TODO:
		Get Turbine on associatedtechs.com to handle NPL script requests like so:
			/js/npl/[version]&[module]&[module]&[module]&[module]...
		PROGRESS: 0%
	
	TODO:
		Get Turbine to minify the resulting JS, or not, depending:
			/js/npl/[version]&[module]&[module]&mini (default) or &debug (full version)
		PROGRESS: 0%
	
	TODO:
		Run tests, see if an entirely new selector engine would speed things up much.
		Try: using the tokenizer-state-parser approach from Shomi; and ridiculously complex RegEx.
		RegEx should be pre-compiled (i.e., create and store RegEx objects) for max performance.
		PROGRESS: 0%;
	
	TODO:
		Continued performance enhancements, shrinking code base
		1. document -> $._d; window -> $._w
		PROGRESS: epsilon.

*/

if ( typeof undef == 'undefined' ) { undef = function(p){return p === undefined} }
if ( typeof def == 'undefined' ) { def = function(p){return p !== undefined} }

if ( NPL !== undefined )
{
    if ( _NPL_Dbg !== undefined ) { _NPL_Dbg('NPLib: "NPL" already defined.'); }
}
else
{
    //  NPL is a singleton which uses closures for some private properties and methods.
    //  There _is_ a way to instantiate another instance of this thing, but that would be a silly thing to do.
    //  All of the NPL functions are encapsulated here to protect them from namespace collisions.
    //  This little library is fully buzzword compliant. :-(
    var NPL = function ()
    {
        //  Private properties.
        var _uidcount = 1;
        var __assumePixelUnits = {'top': true, 'bottom': true, 'left': true, 'right': true, 'height': true, 'width': true, 'max-height': true, 'max-width': true, 'margin-right': true, 'margin-left': true, 'margin-top': true, 'margin-bottom': true, 'text-indent': true};
        //  Private methods.
        var __select = function (pSelector, pElements) {
			if ( typeof pSelector == 'object' )
			{
				if ( pSelector._npl !== undefined ) { return pSelector.elements(); }
				return [pSelector];
			}
			if ( typeof pSelector == 'string' )
			{
				if ( pElements !== undefined )
				{
					//	Restrict our search to descendants of pElements; pElements can either be
					//	another ElementSet or an array of document elements.
					var xElements = ( pElements['_npl'] !== undefined && pElements['_npl'] == true ) ? pElements.elements() : pElements;
				}
				else
				{
					var xElements = [document];
				}
				var _id = '';
				var _class = '';
				var _tag = '';
				var _matches = 0;
				var _attrs = {};
				var _attrsLength = 0;
				var _iesucks = true;		//	Yes, yes it does.
				var x = 0;
				pSelector += ' ';	//	Append a space; used in the for loop below on the last selector.
				for ( var i = 0; i < pSelector.length; i++ )
				{
					switch ( pSelector.charAt(i) )
					{
						case '#':
							_id = pSelector.substring(++i, i = pSelector.indexOf(' ', i));
							i--;
							break;
						case '.':
							_class = pSelector.substring(++i, i = pSelector.indexOf(' ', i));
							i--;
							break;
						case '[':
							//	attribute selector.
							x = pSelector.indexOf(']', i);
							if ( !x )
							{
								//	Selector looks like: "tag[attr". Throw it out.
								i = pSelector.indexOf(' ', i) - 1;
							}
							else
							{
								var xAttr = pSelector.substring(++i, i = x);
								var xValue = '', xModifier = '';
								//	See if the attribute selector specifies a value.
								x = xAttr.indexOf('=');
								if ( x > 0 )
								{
									xValue = xAttr.substring(x+1);
									xAttr = xAttr.substring(0, x);
									//	Should now have a key-value pair.
									//	The last character of xAttr may be "|" or "~" which modify the way that attributes match.
									switch (xAttr.charAt(xAttr.length-1))
									{ case '|':case'~':
										xModifier = xAttr.charAt(xAttr.length-1);
										//	I don't think the next line does anything!
										//xAttr = xAttr.substring(0, xAttr.length);
									}
								}
								else if ( x == 0 )
								{
									//	Selector looks like: "tag[=..". Throw it out.
									break;
								}
								//	Trim quotes.
								if ( xAttr.charAt(0) == '"' && xAttr.charAt(xAttr.length-1) == '"' ) { xAttr = xAttr.substring(1,xAttr.length-1); }
								if ( xValue.charAt(0) == '"' && xValue.charAt(xValue.length-1) == '"' ) { xValue = xValue.substring(1,xValue.length-1); }
								if ( xAttr.length > 0 )
								{
									//	The selection code later on will look for a modifier at the beginning of xValue.
									if ( xModifier != '' ) { xValue = xModifier + xValue; }
									_attrs[xAttr] = xValue;
									_attrsLength++;
								}
							}
							break;
						case ' ':
							if ( _id )
							{
								//  Enforce unique element IDs. See also: http://www.w3.org/TR/REC-html40/struct/global.html#h-7.5.2
								xElements = [];
								_matches = document.getElementById(_id);
								if ( _matches )
								{
									//	Verify element's tag, if specified.
									if ( (!_tag) || (_tag && (_matches.tagName.toLowerCase() == _tag.toLowerCase())) )
									{
										xElements.push(_matches);
									}
								}
							}
							else if ( _class )
							{
								//  First get all of the descendant elements of each of the currently-matched
								//  elements whose tag match xSearchTag.
								_matches = [];
								if ( ! _tag ) { _tag = '*'; }
								for ( x in xElements )
								{
									_iesucks = xElements[x].getElementsByTagName(_tag);
									if ( typeof _iesucks == 'object' )
									{
										//	IE sucks. :-(
										//	See also http://www.codingforums.com/archive/index.php/t-152936.html
										//	and http://www.nczonline.net/blog/2007/12/13/ie-com-reers-its-ugly-head/
										//	Even better, I also apparently can not use "for ( y in _iesucks )",
										//	because Internet Explorer does indeed munch much ass.
										for ( var y = 0; y < _iesucks.length; y++ ) { _matches.push(_iesucks[y]); }
									}
									else
									{
										_matches = _matches.concat(Array.prototype.slice.call(_iesucks));
									}
								}
								//  Now clear out the old xElements heap.
								xElements = [];
								//  ...And rebuild it from the new set of matches, where the xSearchClass matches what we're looking for.
								var n, xClass;
								for ( x in _matches )
								{
									//	Match elements with multiple classes.
									n = _matches[x]['className'].indexOf(_class);
									if ( n != -1 )
									{
										xClass = _matches[x]['className'];
										if ( (n == 0 && ((xClass.length == _class.length) || (xClass.charAt(_class.length) == ' '))) ||
											 (xClass.charAt(n-1) == ' ' && (xClass.length == n + _class.length || xClass.charAt(n + _class.length) == ' ')) )
										{
											xElements.push(_matches[x]);
										}
									}
								}
								//  Whew.
							}
							else if ( _tag )
							{
								//  Similar to above.
								_matches = [];
								for ( x = 0; x < xElements.length; x++ )
								{
									//	TODO:
									//		Convert this to a private function in the NPL object;
									//		have the function rewrite itself the first time it's called
									//		to remove the "if (typeof..."
									_iesucks = xElements[x].getElementsByTagName(_tag);
									if ( typeof _iesucks == 'object' )
									{
										for ( y = 0; y < _iesucks.length; y++ ) { _matches.push(_iesucks[y]); }
									}
									else
									{
										_matches = _matches.concat(Array.prototype.slice.call(_iesucks));
									}
									//	...and that's how the frog learned how to jump!
								}
								//	Support for CSS attribute selectors. Mmm, spicy. Attribute selectors are parsed up above.
								if ( _attrsLength > 0 )
								{
									xElements = [];
									var xAttrValue, xElemAttrs, z, xMatch;
									//	Each match must have the attributes in the _attrs array.
									for ( x = 0; x < _matches.length; x++ )
									{
										xMatch = true;
										for ( y in _attrs )
										{
											//	If the element doesn't have this attribute at all, then skip it.
											//	Can't use hasAttribute here; it's supported by everyone except Internet Explorer.
											//	IE 8 will only support it in "IE 8 mode", not "IE 7 mode"...
											//	Also, pretty much every browser will return null, but they're supposed to be returning "", so
											//	now they're changing.
											xMatch = _matches[x].getAttribute(y);
											if ( xMatch == null || xMatch == '' ) { xMatch = false; break; }
											xMatch = true;
											//	Now check to see if the attribute specifies a value.
											if ( _attrs[y] != '' )
											{
												//	Attribute specifies a value which must be matched to the element's attribute.
												if ( _attrs[y].charAt(0) == '~' )
												{
													xAttrValue = _attrs[y].substring(1);
													//	Yuck-ee.
													//	Attribute value must be part of a space-separated list:
													//	<div language="en fr de">
													xElemAttrs = _matches[x].getAttribute(y).split(' ');
													xMatch = false;
													for ( z in xElemAttrs )
													{
														if ( xElemAttrs[z] == xAttrValue ) { xMatch = true; break; }
													}
													if ( ! xMatch ) { break; }
												}
												else if ( _attrs[y].charAt(0) == '|' )
												{
													xAttrValue = _attrs[y].substring(1);
													//	Attribute must be exactly matched, or must match the selector followed by a -. e.g.:
													//	<div language="en-US"> will match div[language|=en]
													if ( _matches[x].getAttribute(y) != xAttrValue && _matches[x].getAttribute(y).indexOf(xAttrValue+'-') < 0 )
													{
														xMatch = false;
														break;
													}
												}
												else
												{
													//	Exact match.
													if ( _matches[x].getAttribute(y) != _attrs[y] )
													{
														xMatch = false;
														break;
													}
												}
											}
										}
										if ( xMatch ) { xElements.push(_matches[x]); }
									}
								}
								else
								{
									xElements = _matches.slice(0);
								}
							}				
							_id = _class = _tag = '';
							break;
						default:
							_tag += pSelector.charAt(i);
					}
				}
			}
			//  Do not return the document element.
			return ( (xElements.length == 1) && (xElements[0] == document) ) ? [] : xElements;
        };
        //  Public:
        return {
        
        	Browser: function ()
        	{
        		//	With help from http://www.thespanner.co.uk/2009/01/29/detecting-browsers-javascript-hacks/
        		//	I hate browser sniffing too, but occasionally it's helpful.
        		//	For example, we have to determine the correct way to find the browser window's width before the
        		//	page is fully loaded, which can cause unpredictable results.
        		//	Otherwise, whenever possible, feature detection is done instead.
        		//	Other possible tests:
        		//		Webkit, Mozilla support array-style access on a string (string[char]); others do not.
    			var _mozilla=false, _mozilla2=false, _mozilla3=false, _mozilla3_5=false, _ie=false, _devsbane=false, _webkit=false, _chrome=false, _opera=false;
        		if ( /a/[-1]=='a' )
        		{
        			_mozilla = true;
        			if ( (function x(){})[-6]=='x' ) { _mozilla2 = true; }
        			else if ( (function x(){})[-5]=='x' ) { _mozilla3 = true; }
        			else if ( Object.getPrototypeOf ) { _mozilla3_5 = true; }
        		}
        		else if ( '\v'=='v' )
        		{
        			// Internet Explorer 6 lands here too.
        			if ( XMLHttpRequest === undefined ) { _devsbane = true; }
        			_ie = true;
        		}
        		else if ( /a/.__proto__=='//' )
        		{
        			_webkit = true;
        		}
        		else if ( /source/.test((/a/.toString+'')) )
        		{
        			_chrome = true;
        		}
        		else if  ( /^function \(/.test([].sort) )
        		{
        			//	Doesn't appear to work on Opera 8.
        			_opera = true;
        		}
        		else if ( window.opera )
        		{
        			//	Works on all versions of Opera?
        			_opera = true;
        		}
        		else
        		{
        			if ( XMLHttpRequest === undefined ) { _devsbane = true; }
        		}
        		return {
        			Firefox:    function(){return _mozilla;},
        			Firefox2:   function(){return _mozilla2;},
        			Firefox3:   function(){return _mozilla3;},
        			Firefox3_5: function(){return _mozilla3_5;},
        			IE:         function(){return _ie;},
        			IE6:        function(){return _devsbane;},
        			Safari:     function(){return _webkit;},
        			Chrome:     function(){return _chrome;},
        			Opera:		function(){return _opera;}
        		}
        	}(),
        
            Mouse: function ()
            {
                var _x = 0, _y = 0;
                __get_Mouse_XY = function ( pEvent )
                {
                    if ( pEvent === undefined )
                    {
                        if ( window.event !== undefined )
                        {
                            if ( window.event.clientX !== undefined )
                            {
                            	if ( document.body.scrollLeft !== undefined )
                            	{
									__get_Mouse_XY = function()
									{
										_x = window.event.clientX+document.body.scrollLeft+document.documentElement.scrollLeft;
										_y = window.event.clientY+document.body.scrollTop+document.documentElement.scrollTop;
									}
								}
								else
								{
									__get_Mouse_XY = function()
									{
										_x = window.event.clientX+document.documentElement.scrollLeft;
										_y = window.event.clientY+document.documentElement.scrollTop;
									}
								}
                            }
                        }
                    }
                    else
                    {
                        if ( pEvent.pageX !== undefined ) { __get_Mouse_XY = function(e){_x=e.pageX;_y=e.pageY;} }
                    }
                };
                return {
                
                    x: function() { return _x; },
                    
                    y: function() { return _y; },
                    
                    update: function ( pEvent ) { __get_Mouse_XY(pEvent); }
                
                }
            }(),
            
            Page: function ()
            {
                //  Private properties.
                var _onload = window.onload; window.onload = function () {
                	document.onmousemove = function (pEvent) { NPL.Mouse.update(pEvent) };
                	if ( window.captureEvents !== undefined ) { window.captureEvents(Event.MOUSEMOVE); }
                	NPL.Page.LaunchInits();
                };
                var _loaded = false, _initfunctions = [], _resizefunctions = [], _watchresize = false, _windowlastx = 0, _windowlasty = 0, _windowchanged = 0, _windowlastchg = 0;
                return {
                
                	_WindowChanged: function ()
                	{
                		if ( document.body.clientHeight !== undefined && document.body.clientWidth !== undefined && _windowlastx == 0 && _windowlasty == 0 )
                		{
                			_windowlastx = NPL.Window.Width();
                			_windowlasty = NPL.Window.Height();
                			NPL.Page._WindowChanged = function(){
                				if ( _windowlastx != NPL.Window.Width() || _windowlasty != NPL.Window.Height() )
                				{
                					_windowlastchg = (new Date()).getTime();
                					_windowlastx = NPL.Window.Width();
                					_windowlasty = NPL.Window.Height();
                					if ( _windowchanged == 0 )
                					{
                						_windowchanged = 1;
                						//	Signal that window has *begun* changing.
                						if ( _resizefunctions['begin'] !== undefined ) { for (var i=0;i<_resizefunctions['begin'].length;i++){_resizefunctions['begin'][i]('begin');}}
                					}
                					else
                					{
	                					//	Signal that window is *still* changing.
	                					if ( _resizefunctions['active'] !== undefined ) { for (var i=0;i<_resizefunctions['active'].length;i++){_resizefunctions['active'][i]('active');}}
	                				}
                				}
                				else if ( _windowchanged == 0 )
                				{
                					//	Signal that window has not changed.
                					if ( _resizefunctions['quiet'] !== undefined ) { for (var i=0;i<_resizefunctions['quiet'].length;i++){_resizefunctions['quiet'][i]('quiet');}}
                				}
                				else if ( (new Date()).getTime() - _windowlastchg > 3000 )
                				{
                					_windowchanged = 0;
                					//	Signal that window has *stopped* changing.
                					if ( _resizefunctions['end'] !== undefined ) { for (var i=0;i<_resizefunctions['end'].length;i++){_resizefunctions['end'][i]('end');}}
                				}
                			}
                		}
                	},
                                	
                    OnLoad: function ( pInitFunction )
                    {
                    	if ( ! _loaded )
                    	{
                        	_initfunctions[_initfunctions.length] = pInitFunction;
                        }
                        else
                        {
                        	pInitFunction();
                        }
                    },

					OnResize: function ( pResizeCode, pResizeFunction )
					{
						switch ( pResizeCode )
						{ case 'begin':case 'end':case 'active':case 'quiet':
							if ( _resizefunctions[pResizeCode] === undefined ) { _resizefunctions[pResizeCode] = []; }
							_resizefunctions[pResizeCode][_resizefunctions[pResizeCode].length] = pResizeFunction;
							if ( _watchresize == false )
							{
								_watchresize = true;
								setInterval(function(){NPL.Page._WindowChanged()}, 1000);
							}
						}
					},
										
                    LaunchInits: function ()
                    {
                        if ( ! _loaded )
                        {
                            for ( var i = 0; i < _initfunctions.length; i++ ) { _initfunctions[i](); }
                            delete _initfunctions; 
                            if ( _onload ) { _onload(); }
                            delete _onload;
                            _loaded = true;
                        }
                    },
                    
                    Width: function ()
                    {
                    	//	Bugfix 10/12/2009: Safari's braindead Javascript engine returns "NaN" if you
                    	//	try to turn the following code into a terniary operation:
                    	//	return document.width ? parseInt(document.width) : parseInt(document.body.clientWidth);
                    	if ( document.width !== undefined ) { return parseInt(document.width); }
                        return parseInt(document.body.clientWidth);
                    }
                }
            }(),
            
            Window: function()
            {
            	return {
					Width: function ()
					{
						if ( window.innerWidth !== undefined ) { this.Width = function(){return window.innerWidth} }
						else if ( document.documentElement !== undefined ) { this.Width = function(){return document.documentElement.clientWidth == 0 ? document.getElementsByTagName('body')[0].clientWidth : document.documentElement.clientWidth} }
						else if ( document.body !== undefined && document.body.clientWidth !== undefined ) { this.Width = function(){return document.body.clientWidth} }
						else return 0;
						return this.Width();
					},
					
					Height: function ()
					{
						if ( window.innerHeight !== undefined ) { this.Height = function(){return window.innerHeight} }
						else if ( document.documentElement !== undefined ) { this.Height = function(){return document.documentElement.clientHeight == 0 ? document.getElementsByTagName('body')[0].clientHeight : document.documentElement.clientHeight} }
						else if ( document.body !== undefined && document.body.clientHeight !== undefined ) { this.Height = function(){return document.body.clientHeight} }
						else return 0;
						return this.Height();
					}
				}
            	
            }(),
                                
            NewUID: function () { return (++_uidcount).toString() + (new Date()).getTime().toString().substring(6) + (1000 + ~~(Math.random() * 1000)).toString(); },
            
            Select: function ( pSelector, pElements ) { return new ElementSet(__select(pSelector, pElements)); },
            
            Prototype: function () { return { __ElementSet__: new Object }; }(),
            
            ParseDescriptor: function ( pDescriptor )
            {
            	//	Standardizes "descriptors" used by various functions -- a way of passing
            	//	dynamically-described parameters to functions that can use them.
            	//	Takes a string like, "x:200; y: 300;levitate", and returns an object like,
            	//	{ x: 200, y: 300, levitate }
            	var i = pDescriptor.length;
            	var xWhitespace = { ' ':true, "\t":true, "\r":true, "\n":true };
            	var xReturnValue = {};
            	var xIsValue = false;
            	var xValue = '';
            	var j = 0;
            	var n;
            	while ( --i >= 0 )
            	{
					//	Eat trailing spaces and semicolons and things.
					while ( (xWhitespace[pDescriptor.charAt(i)] !== undefined || pDescriptor.charAt(i) == ';') && i-- >= 0 ) ;
					n = 0;
					//	Eat text.
					j = i;
					while ( pDescriptor.charAt(i) != ':' && pDescriptor.charAt(i) != ';' && i >= 0 )
					{
						//	'n' is used to count the number of spaces between a chunk of text and a delimiter.
						if ( xWhitespace[pDescriptor.charAt(i)] !== undefined )
						{
							n = i;
							while ( xWhitespace[pDescriptor.charAt(i)] !== undefined && i-- >= 0 ) ;
							n -= i++;
						}
						i--;
					}
					//	Should now have a range of characters for either a property or its value.
					if ( pDescriptor.charAt(i) == ':' )
					{
						xValue = pDescriptor.substr(i + n + 1, j - i - n);
					}
					else if ( pDescriptor.charAt(i) == ';' )
					{
						xReturnValue[pDescriptor.substr(i + n + 1, j - i - n)] = xValue;
						xValue = '';
					}
					else if ( i <= 0 )
					{
						//	Reached beginning of string.
						xReturnValue[pDescriptor.substr(n, j - i - n)] = xValue;
					}
					else
					{
						xReturnValue[pDescriptor.substr(i + n, j - i - n)] = '';
					}
            	}
            	return xReturnValue;
            },
            
            Image: function ( pImagePath )
            {
            	var xImage = document.createElement('img');         //  Can't use "new Image()", Safari will choke on it later.
            	xImage.src = pImagePath;
        		//	Try attaching the image to the document tree?
        		//	Internet Explorer will mysteriously shrink the image down
        		//	to 28x28 if the image is attached to an element at any point in its
        		//	load process. *sigh*
				//	Some browsers (ahem, Safari) do an annoying job of never supplying any
				//	information on properties for an object unless it's been attached to the document
				//	tree. Sooooo .... LoadImage now has its very own hidden element to pre-attach images.
				//	Can't set display to 'none' or Safari 2 will still refuse to set properties for dynamically loaded images.
				var xMagic = NPL.Select('#__NPL__LoadImageMagic');
            	if ( xMagic.count == 0 )
            	{
        			xMagic = document.createElement('div');
        			xMagic.setAttribute('id', '__NPL__LoadImageMagic');
        			//	Try moving it offscreen instead.
        			//xMagic.setAttribute('style', 'left: -5000px; position: absolute');
        			//	That appears to muck with some browsers. :-(
        			xMagic.setAttribute('style', 'top: -5000px; position: absolute');
        			document.body.appendChild(xMagic);
        			xMagic = NPL.Select(xMagic);
					//	Safari will not handle style operations on an element until it's attached to the document.
					xMagic.left(-5000).width(0).height(0).style('overflow', 'hidden');
        		}
				xMagic.element().appendChild(xImage);
            	return new ElementSet(__select(xImage));
            },
            
            Channel: function ()
            {
            	return {
            		Open: function(pURI, pCallback){
            			var xChannel = {};
            			xChannel.xhr = new XMLHttpRequest();
            			xChannel.xhr.onreadystatechange = function () {
            				if ( xChannel.xhr.readyState == 4 )
            				{
            					xChannel.callback(xChannel);
            				}
            			};
            			xChannel.xhr.parent = xChannel;
            			xChannel.uri = pURI;
            			xChannel.callback = pCallback;
            			xChannel.xhr.open('GET', pURI, true);
            			xChannel.xhr.send('');
            			return xChannel;
            		},
            		
            		Post: function(pURI, pParams, pCallback){
            			var xChannel = {};
            			xChannel.xhr = new XMLHttpRequest();
            			xChannel.xhr.onreadystatechange = function () {
            				if ( xChannel.xhr.readyState == 4 )
            				{
            					xChannel.callback(xChannel);
            				}
            			};
            			xChannel.xhr.parent = xChannel;
            			xChannel.uri = pURI;
            			xChannel.callback = pCallback;
            			xChannel.xhr.open('POST', pURI, true);
            			xChannel.xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
            			xChannel.xhr.setRequestHeader('Content-length', pParams.length);
            			xChannel.xhr.setRequestHeader('Connection', 'close');
            			xChannel.xhr.send(pParams);
            			return xChannel;
            		}
            	}
            }(),
            
            Tasks: function ()
            {
            	return {
            		
            		NewTask: function(pFunction) {
            		
						var __taskFunction = pFunction;
						var __taskTimeoutID = 0;
						
						var __taskObject = {
						
							Start: function (pDelay)
							{
								__taskObject.Run = function () {
									var xTimeWait = __taskFunction.call(__taskObject);
									if ( xTimeWait > 0 ) { __taskTimeoutID = setTimeout(__taskObject.Run, xTimeWait); }
									else { __taskObject.Run = function(){}; }
								};
								__taskTimeoutID = setTimeout(__taskObject.Run, pDelay === undefined ? 5 : pDelay);
							},
							
							Run: function(){},
							
							Stop: function () { __taskObject.Run = function(){} },
							
							End: function() { __taskObject.Run = function(){} }
						};
						
						return __taskObject;
            		}
            		
            	}
            }(),
            
            Trim: function (pStrings, pTrimChars)
            {
            	//	pStrings can be a string or an array of strings; pTrimChars can be a string of chars or
            	//	an array of chars to be removed from each pString.
            	var xStart, xEnd, xChar, xStrings, xTrims;
            	//	Set up the trims array.
            	xStrings = typeof pTrimChars == 'string' ? pTrimChars.split('') : pTrimChars;
            	xTrims = {};
            	for ( var i = 0; i < xStrings.length; i++ ) { xTrims[xStrings[i]] = 1; }
            	xStrings = typeof pStrings == 'string' ? [pStrings] : pStrings;
            	for ( i = 0; i < xStrings.length; i++ )
            	{
					xStart = 0; xEnd = xStrings[i].length - 1; xChar = xStrings[i].charAt(0);
					while ( xTrims[xChar] !== undefined && xStart < xEnd ) { xChar = xStrings[i].charAt(++xStart); }
					if ( xStart < xEnd )
					{
						//	Trim spaces and quotes from the end of the string.
						xChar = xStrings[i].charAt(xEnd);
						while ( xTrims[xChar] !== undefined && xStart < xEnd ) { xChar = xStrings[i].charAt(--xEnd); }
					}
					//	Yes, yes, I know I shouldn't do things like this. They're horrible. Arrest me.
					//	Also: .substring is "half-inclusive"; it includes the character at xStart but excludes the character at xEnd. *le sigh*
					xStrings[i] = xStart < xEnd ? xStrings[i].substring(xStart, ++xEnd) : (xStart > xEnd || xTrims[xChar] !== undefined) ? '' : xChar;
				}
				return typeof pStrings == 'string' ? xStrings[0] : xStrings;
            },

            PercentStrCompare: function (pString1, pString2, pCaseSensitive)
            {
            	//	Return a percentage value for a match between two strings.
            	//	The percentage of the characters that match are calculated for each string, and their average is returned.
            	//	The strings must match from the first character forward; 'abcde' & '-abcde' will get a value of 0.
            	//	This might be changed in a future version of this function.
            	if ( pCaseSensitive )
            	{
            		if ( pString1.length <= pString2.length )
            		{
	            		var xShortString = pString1;
	            		var xLongString = pString2;
	            	}
	            	else
	            	{
	            		var xShortString = pString2;
	            		var xLongString = pString1;
	            	}
            	}
            	else
            	{
            		if ( pString1.length <= pString2.length )
            		{
	            		var xShortString = pString1.toLowerCase();
	            		var xLongString = pString2.toLowerCase();
	            	}
	            	else
	            	{
	            		var xShortString = pString2.toLowerCase();
	            		var xLongString = pString1.toLowerCase();
	            	}
            	}
            	//	Bail immediately if the first character doesn't match -- shortcut for now.
            	if ( xShortString[0] != xLongString[0] ) { return 0; }
            	//	Next shortcut: quick return if the strings match neatly.
            	if ( xShortString.substr(0) == xLongString.substr(0, xShortString.length) )
            	{
            		return ((1 + (xShortString.length / xLongString.length))/2);
            	}
            	//	Let's do a binary search against the characters in xShortString.
            	//	It's about the fastest approach we can hope for.
            	var xMin = 0;
            	var xMax = xShortString.length - 1;
            	var xChar;
            	while ( true )
            	{
            		if ( xMax - xMin < 2 )
            		{
            			xMin++;
            			return (((xMin / xShortString.length) + (xMin / xLongString.length)) / 2);
            		}
            		xChar = parseInt((xMax - xMin)/2) + xMin;
            		if ( xShortString[xChar] == xLongString[xChar] )
            		{
            			if ( xShortString.substr(0, xChar) == xLongString.substr(0, xChar) )
            			{
            				xMin = xChar;
            				continue;
            			}
            		}
        			xMax = xChar;
            	}
            },
            
            _camelCases: {width:'width', height:'height', float:'cssFloat'},
            _camelizer: /\-(.)/g,
			_camelCaser: function (p)
			{
				//  Notes on below:
				//  The anonymous function in replace accepts the string (x) and the first sub string match (y), and then returns
				//  the string to replace the match with. This is due to IE's (correct) use of camelCase for its css selectors. 
				//  http://www.quirksmode.org/dom/getstyles.html
				//  Replace: https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/String/Replace
				//	Note: According to David Mark, this use of replace() (using a function for the replacer) breaks compatibility with Safari 2.
				//	Probably not worth fixing in this case, but maybe revisit later.
				return NPL._camelCases[p] !== undefined ? NPL._camelCases[p] : NPL._camelCases[p] = p.toLowerCase().replace(NPL._camelizer, function(x, y){return y.toUpperCase()});
			},

            __set_E_Style: function(e,a,v)     //  element, attribute, value
            {
				if ( document.defaultView && document.defaultView.getComputedStyle )
				{
				    //  Notes on below:
				    //  Tested with Firefox 1 -> 3.5. There was a previous note here that Firefox didn't
				    //	use camelCase, but that appears to be incorrect.
				    //  (Some versions of) Safari will literally hang if getComputedStyle returns null.
				    //	Safari v5 has apparently abandoned getComputedStyle for font-size and who-knows-what-else.
					NPL.__get_E_Style = function(e,a){
						a=NPL._camelCaser(a);
						if (a=='fontSize') { return e.style[a]; }
						var s=document.defaultView.getComputedStyle(e, null);
						return s[a] === undefined ? '' : s[a]
					};
				}
				else if ( e.currentStyle )
				{
				    NPL.__get_E_Style = function(e,a){a=NPL._camelCaser(a);return e.currentStyle[a] ? e.currentStyle[a] : ''};
				}
				else
				{
				   NPL.__get_E_Style = function(e,a){return ''};
				}
				NPL.__set_E_Style = function(e,a,v){
					if(e.style)
					{
						a = NPL._camelCaser(a);
						//	Assume pixel-size declarations for any numbers without units.
						e.style[a] = ( (__assumePixelUnits[a] && !isNaN(v) && isFinite(v)) ) ? v + 'px' : v;
					}
				};
				if ( v === undefined ) { return NPL.__get_E_Style(e,a); }
				NPL.__set_E_Style(e,a,v);
			},
			__get_E_Style: function(e,a){return NPL.__set_E_Style(e,a)},
			
			__set_E_Opacity: function(e,o)
			{
				if ( e.style !== undefined )
                {
                    if ( e.style.opacity !== undefined )
                    {
                        NPL.__get_E_Opacity = function(e){return e.style ? e.style.opacity ? e.style.opacity : 1 : 1};
                        NPL.__set_E_Opacity = function(e,o){if (e.style){e.style.opacity = o}};
                    }
                    else if ( e.style.MozOpacity !== undefined )
                    {
                        NPL.__get_E_Opacity = function(e){return e.style ? e.style.MozOpacity ? e.style.MozOpacity : 1 : 1};
                        NPL.__set_E_Opacity = function(e,o){if (e.style){e.style.MozOpacity = o}};
                    }
                    else if ( e.style.filter !== undefined )
                    {
                        NPL.__get_E_Opacity = function(e){if (e.style){var xO=e.style.filter.match(/alpha\(opacity=([0-9]+)\)/); return (xO && xO.length > 1) ? xO[1] / 100 : 1;}};
                        NPL.__set_E_Opacity = function(e,o){if (e.style){e.style.filter = "alpha(opacity=" + o*100 + ")"}};
                    }
                    else
                    {
                        NPL.__get_E_Opacity = function(e){return 1};
                        NPL.__set_E_Opacity = function(e,o){};
                    }
                    if ( o === undefined ) { return NPL.__get_E_Opacity(e); }
                    NPL.__set_E_Opacity(e,o);
                }
			},
			__get_E_Opacity: function(e){return NPL.__set_E_Opacity(e)},
			
			__set_E_Class: function(e,c)
	   		{
				if ( e.className !== undefined )
				{
					NPL.__get_E_Class = function(e){return e.className};
					NPL.__set_E_Class = function(e,c){e.className=c};
					NPL.__add_E_Class = function(e,c)
					{
						e.className = e.className == '' ? c : e.className + ' ' + c;
					};
				}
				else if ( e.getAttribute !== undefined )
				{
					NPL.__get_E_Class = function(e){return e.getAttribute('class')};
					NPL.__set_E_Class = function(e,c){e.setAttribute('class', c)};
				}
				else
				{
					NPL.__get_E_Class = function(e){return ''};
					NPL.__set_E_Class = function(e,c){};
				}
				if ( c === undefined ) { return NPL.__get_E_Class(e); }
				NPL.__set_E_Class(e,c);
			},
			__get_E_Class: function(e){return NPL.__set_E_Class(e)},
			__add_E_Class: function(e,c){
				var z = NPL.__get_E_Class(e);
				NPL.__set_E_Class(e, z == '' ? c : z + ' ' + c);
			},
			__drop_E_Class: function(e,c){
				c = c.split(' ');
				var x = {};
				var y = NPL.__get_E_Class(e).split(' ');
				var z = new Array();
				for ( var i = 0; i < c.length; i++ ) { x[c[i]] = true; }
				for ( i = 0; i < y.length; i++ )
				{
					if ( typeof x[y[i]] == 'undefined' ) { z.push(y[i]); }
				}
				NPL.__set_E_Class(e, z.join(' '));
			},
			
		   	__get_E_Loaded: function(e)
		   	{
				this.__get_E_Loaded = e.complete !== undefined ? function(e){return e.complete} : function(e){return true};
				return this.__get_E_Loaded(e);
			},
			
			__get_E_FrameInnerHeight: function(e)
			{
				if ( e.contentDocument !== undefined )
				{
					this.__get_E_FrameInnerHeight = function(e){return parseInt(e.contentDocument.body.scrollHeight)};
				}
				else if ( e.document !== undefined )
				{
					this.__get_E_FrameInnerHeight = function(e){return parseInt(e.document.body.clientHeight)};
				}
				return this.__get_E_frameInnerHeight(e);
			}

        }
    }();
    
    if ( typeof $ == 'undefined' ) { $ = NPL.Select; }
}

function ElementSet ( pElements )
{
	this._npl = true;				//	Magic for object detection.
	this._elements = pElements;
	this._counter = 0;

	// Merge in dynamic functions from the prototype for this object.
    for ( var x in NPL.Prototype.__ElementSet__ ) { this[x] = NPL.Prototype.__ElementSet__[x]; }

	//     _for_all_do and _for_all_get are meta functions with horrible syntax
	//     and wonderful utility. Pass them a function as the first parameter and
	//     up to four other parameters, and they'll call the function with the
	//     four parameters on each of the elements in the set.
	this._for_all_do = function(f)
	{
		var xF = f;
		var n = this._elements.length;
		for ( var i = 0; i < n; i++ ) { arguments[0] = this._elements[i]; xF.apply(this, arguments); }
		return this;
	};
	
	this._for_all_get = function(f)
	{
	   var xReturnValue = [];
	   var xF = f;
	   var n = this._elements.length;
	   for ( var i = 0; i < n; i++ ) { arguments[0] = this._elements[i]; xReturnValue.push(xF.apply(this, arguments)); }
	   return xReturnValue.length == 1 ? xReturnValue[0] : xReturnValue;
	};

    this.length = this.count = this._elements.length;
	
    this.element = function ( pElementIndex )
    {
       if ( pElementIndex !== undefined ) { this._counter = pElementIndex; }
       if ( this._elements.length == 1 ) { return this._elements[0]; }
       if ( this._counter < this._elements.length ) { return this._elements[this._counter++]; }
       this._counter = 0;
       return null;
    };
    
    /*
    this.width = function ( pWidth )
    {
        //  http://www.quirksmode.org/dom/getstyles.html
        if ( pWidth === undefined ) {
        	return this._for_all_get(function(e){
        		//	Avoid calling __get_E_Style if possible because
        		//	__get_E_Style uses getComputedStyle() in Firefox which
        		//	is HOLY MOSES SLOW.
        		return e.offsetWidth;
        	});
        }
        if ( typeof pWidth == 'number' )
        {
        	pWidth = parseInt(pWidth);
        	if ( pWidth < 0 ) { return this; }
        	pWidth += 'px';
        }
        return this._for_all_do(function(e, v){NPL.__set_E_Style(e, 'width', v)}, pWidth);
    };
    */

    this.width = function ( pWidth )
    {
    	if ( pWidth === undefined )
    	{
    		return this._for_all_get(function(e){
        		//	Avoid calling __get_E_Style if possible because
        		//	__get_E_Style uses getComputedStyle() in Firefox which
        		//	is HOLY MOSES SLOW.
    			return e.offsetWidth;
    		});
    	}
    	pWidth = parseInt(pWidth);
    	if ( pWidth < 0 ) { return this; }
    	return this._for_all_do(function(e, v){
    		NPL.__set_E_Style(e, 'width', v+'px');
    		//	The goal here is to ensure that e.offsetWidth matches v.
    		//	CSS/JS sucks. There is no way to automatically set a width
    		//	which will match a subsequent width() query without dinking
    		//	around with the element a bit, because getComputedStyle() is
    		//	slow as balls and none of e.offsetWidth or e.clientWidth or
    		//	any of the other properties actually ever match the width
    		//	set by element style. Laaaaaame.
    		if ( v - e.offsetWidth != 0 )
    		{
    			NPL.__set_E_Style(e, 'width', 2*v - e.offsetWidth);
    		}
    	}, pWidth);
    };
    
    /*
    	TODO: separate 'width' and 'height' out into 'displayWidth', 'displayHeight', 'innerWidth', and 'innerHeight',
    	to clear up ambiguities between various issues with various browsers.
    */
    
    /*
    
    	A brief rant about height values (in Firefox, not tested for other browsers yet):
    	Elements expose clientHeight, offsetHeight, and scrollHeight values -- and NONE of these
    	match the height value returned from a style attribute. The height returned by the style
    	query does not include the element's padding.
    
    */
    
    //	Set or return the style height of the element.
    this.height = function ( pHeight )
    {
    	/*
    		Sigh. Add offsetHeight to the list of crap in Firefox which is unacceptably slow.
    		There does not seem to be any alternative.
    	*/
        if ( pHeight === undefined ) {
        	return this._for_all_get(function(e){return e.offsetHeight});
        }
        return this._for_all_do(function(e, v){NPL.__set_E_Style(e, 'height', v)}, typeof pHeight == 'number' ? pHeight + 'px' : pHeight);
    };
    
    //	Set or return the height of the element, including borders, not including the entire scrollable area.
    this.borderedHeight = function ( pHeight )
    {
    	if ( pHeight === undefined )
    	{
    	}
    	return this;
    }
    
    //	Set or return the height of the element, not including borders, including the entire scrollable area.
    this.innerHeight = function ( pHeight )
    {
    	if ( pHeight === undefined )
    	{
			return this._for_all_get(function(e,o){
				switch (e.tagName.toLowerCase())
				{
					case 'iframe':
						return NPL.__get_E_FrameInnerHeight(e);
					case 'div': default:
						return e.scrollHeight;
				}
			},this);
		}
		return this;
    };
    
    //	Set or return the height of the element, not including borders, not including the entire scrollable area.
    this.displayHeight = function ( pHeight )
    {
    	if ( pHeight === undefined )
    	{
			return this._for_all_get(function(e,o){
				switch(e.tagName.toLowerCase())
				{
					case 'div': default:
						return e.clientHeight;
				}
			},this);
		}
		return this._for_all_do(function(e,v){
			//	Set the displayHeight for the element. Need to calculate it according to the element's border height.
			if ( typeof pHeight != 'number' ) { pHeight = parseInt(pHeight); }
			switch(e.tagName.toLowerCase())
			{
				case 'div': default:
					//	When setting height by the style attribute, the value should be the height
					//	not including padding or border:
					NPL.__set_E_Style(e,'height', v - e.clientHeight + parseInt(NPL.__get_E_Style(e,'height')) + 'px');
			}
		}, pHeight);
    };
    
    this.left = function ( pLeft )
    {
    	if ( pLeft === undefined ) { return this._for_all_get(function(e){var _left=e.offsetLeft; while(e.offsetParent !== undefined && (e=e.offsetParent)){_left += e.offsetLeft}; return _left}); }
    	if ( typeof pLeft == 'number' ) { pLeft = parseInt(pLeft) + 'px'; }
    	return this._for_all_do(function(e,l){e.style.left = l}, pLeft);
    };
    
    this.right = function ( pRight )
    {
    	if ( pRight === undefined ) { return this._for_all_get(function(e){var _left=e.offsetLeft; _width = e.offsetWidth; while(e.offsetParent !== undefined && (e=e.offsetParent)){_left += e.offsetLeft}; return _left + _width}); }
    	if ( typeof pRight == 'number' ) { pRight = parseInt(pRight) + 'px'; }
    	return this._for_all_do(function(e,l){e.style.right = l}, pRight);
    };

    this.top = function ( pTop )
    {
    	if ( pTop === undefined ) { return this._for_all_get(function(e){var _top=e.offsetTop; while(e.offsetParent !== undefined && (e=e.offsetParent)){_top += e.offsetTop}; return _top}); }
    	if ( typeof pTop == 'number' ) { pTop = parseInt(pTop) + 'px'; }
    	return this._for_all_do(function(e,t){e.style.top = t}, pTop);
    };
    
    this.bottom = function ()
    {
    	return this._for_all_get(function(e){return NPL.Select(e).top() + NPL.Select(e).height()});
    };
    
    this.move = function ( pX, pY )
    {
    	return this._for_all_do(function(e,x,y){
    		var _x = parseInt(e.style.left);
    		var _y = parseInt(e.style.top);
    		e.style.left = isNaN(_x) ? x+'px' : _x+x+'px';
    		e.style.top = isNaN(_y) ? y+'px' : _y+y+'px';
    	}, pX, pY);
    };

    this.style = function ( pStyleAttr, pStyleValue )
    {
        //  Return (or set) the values for pStyleAttr for each element.
        //  Note that IE will return "auto" for the CSS width of any element
        //  that has not actually had its width set by CSS. You might want to use
        //  the width() function instead.
        if ( pStyleAttr === undefined )
        {
        	//	Return all of the style settings as a string instead.
        	return this._for_all_get(function(e){
        		var x = e.getAttribute('style');
        		if ( typeof x == 'object' )
        		{
        			return '{' + e.style.cssText + '}';
        		}
        		return '{' + x + '}';
        	});
        }
        if ( pStyleValue === undefined )
        {
			if ( pStyleAttr.charAt(0) == '{' )
			{
				var xStyles = NPL.ParseDescriptor(NPL.Trim(pStyleAttr, "\"'{} "));
				for ( var i in xStyles )
				{
					this._for_all_do(function(e, a, v){NPL.__set_E_Style(e, a, v)}, i, xStyles[i]);
				}
				return this;
			}
			else
			{
				return this._for_all_get(function(e, a){return NPL.__get_E_Style(e, a)}, pStyleAttr);
			}
		}
		if ( (!isNaN(parseFloat(pStyleValue)) && isFinite(pStyleValue)) )
		{
			pStyleValue = parseInt(pStyleValue);
		}
		return this._for_all_do(function(e, a, v){NPL.__set_E_Style(e, a, v)}, pStyleAttr, pStyleValue);
    };
    
    this.setAttr = function ( pAttr, pValue )
    {
    	if ( pValue === undefined ) { return this._for_all_get(function(e,a){return e.getAttribute(a)}, pAttr); }
    	return this._for_all_do(function(e,a,v){e.setAttribute(a,v)}, pAttr, pValue);
    }; this.getAttr = this.setAttr;
    
    //	Temporary; backwards compatibility.
    this.set = this.setAttr;
    this.get = this.getAttr;
    
    this.setValue = function ( pAttr, pValue )
    {
    	if ( pValue === undefined ) { return this._for_all_get(function(e){return e[pAttr]}); }
    	return this._for_all_do(function(e,a,v){e[a]=v}, pAttr, pValue);
    };
    
    this.opacity = function ( pOpacity )
    {
    	return pOpacity === undefined ?
    		this._for_all_get(function(e){return NPL.__get_E_Opacity(e)}) : 
    			this._for_all_do(function(e,o){NPL.__set_E_Opacity(e,o)}, pOpacity);
    };
    
    this.classname = function ( pClass )
    {
        if ( pClass === undefined ) { return this._for_all_get(function(e){return NPL.__get_E_Class(e)}); }
        return this._for_all_do(function(e,c){NPL.__set_E_Class(e,c)}, pClass);
    };
    
    this.addClass = function ( pClass )
    {
    	return this._for_all_do(function(e,c){NPL.__add_E_Class(e,c)}, pClass);
    };
    
    this.removeClass = function ( pClass )
    {
    	return this._for_all_do(function(e,c){NPL.__drop_E_Class(e,c)}, pClass);
    };

	this.elements		= function () {return this._elements.length > 0 ? this._elements.slice(0) : []};
	this.select			= function (pSelector) {return NPL.Select(pSelector, this)};
    this.id				= function (pID) {return this.setAttr('id', pID)};
    this.onclick		= function (pFunction) {return this.setValue('onclick', pFunction)};
    this.onmouseover	= function (pFunction) {return this.setValue('onmouseover', pFunction)};
    this.onmouseout		= function (pFunction) {return this.setValue('onmouseout', pFunction)};
    this.onmousedown	= function (pFunction) {return this.setValue('onmousedown', pFunction)};
    this.onmouseup		= function (pFunction) {return this.setValue('onmouseup', pFunction)};
    this.innerHtml		= function (pHTML) {return this.setValue('innerHTML', pHTML)};
    this.click			= function (){return this._for_all_do(function(e){e.onclick()})};
    this.scrollWidth	= function (){return this._for_all_get(function(e){return e.scrollWidth})};
    this.scrollHeight	= function (){return this._for_all_get(function(e){return e.scrollHeight})};
    this.htmlTag		= function (){return this._for_all_get(function(e){return e.tagName.toLowerCase()})};
    this.href			= function (pHREF) { return this.setValue('href', pHREF)};

	this.clone = function ()
	{
		return this._for_all_get(function(e){
			e = NPL.Select(e);
			var xE = NPL.Select(document.createElement(e.htmlTag()));
			xE.id(e.id());
			xE.classname(e.classname());
			xE.onclick(e.onclick());
			xE.onmouseover(e.onmouseover());
			xE.onmouseout(e.onmouseout());
			xE.onmouseup(e.onmouseup());
			if ( e.htmlTag() != 'iframe' )
			{
				//	Internet Explorer will throw an "unknown runtime error" if this is attempted
				//	on an iframe. >:-(
				xE.innerHtml(e.innerHtml());
			}
			xE.style(e.style());
			xE.top(e.element().offsetTop);
			xE.left(e.left());
			xE.width(e.width());	//	Required by IE 8
			return xE;
		});
	};

    this.onload = function ( pFunction )
    {
    	if ( this.isImage() )
    	{
    		if ( this.loaded() )
    		{
    			pFunction(this);
    		}
    		else
    		{
    			var xTask = NPL.Tasks.NewTask(function(){if (this._image.loaded()){this._onloadf(this._image);return 0;}return 50;});
    			xTask._image = this;
    			xTask._onloadf = pFunction;
    			xTask.Start();
    		}
    	}
    	else if ( this.htmlTag() == 'iframe' )
    	{
    		if ( this.element().attachEvent !== undefined )
    		{
    			//	Internet Explorer ... I WILL TAPDANCE ON YOUR GRAVE.
    			this.element().attachEvent('onload', pFunction);
    		}
    		else
    		{
    			this.element().onload = pFunction;
    		}
    	}
    	else
    	{
    		pFunction(this);
    	}
    	return this;
    };
            
    this.isImage = function ()
    {
    	var xReturnValue = this._for_all_get(function(e){return e.tagName.toLowerCase() == 'img'});
    	if ( typeof xReturnValue == 'boolean' ) { return xReturnValue; }
    	if ( xReturnValue.length !== undefined )
    	{
    		for ( var x in xReturnValue )
    		{
    			if ( xReturnValue[x] == false ) { return false; }
    		}
    		return true;
    	}
    	return false;
    };
    
    this.loaded = function ()
    {
    	if ( this.isImage() )
    	{
    		var xReturnValue = this._for_all_get(function(e){return NPL.__get_E_Loaded(e)});
    		if ( typeof xReturnValue == 'boolean' ) { return xReturnValue; }
    		if ( xReturnValue.length !== undefined )
    		{
    			for ( var x in xReturnValue )
    			{
    				if ( xReturnValue[x] == false ) { return false; }
    			}
    		}
    	}
    	return true;
    };
    
    this.replace = function ( pElements, pEffect )
    //	Replace the content of these elements with the content from some other elements on the page.
    //	pEffect is optional and requires the effects lib.
    {
    	if ( pEffect !== undefined && this.npfx_replace !== undefined )
    	{
    		this.npfx_replace(pElements, pEffect);
    	}
    	else if ( typeof pElements == 'string' )
    	{
    		this.innerHtml(pElements);
    	}
    	else
    	{
			if ( pElements.count == 1 )
			{
				this.innerHtml(pElements.innerHtml());
			}
			else if ( pElements.count == this.count )
			{
				var xElementSrc = pElements.element();
				var xElementDst = this.element();
				while ( xElementSrc != null && xElementDst != null )
				{
					NPL.Select(xElementDst).innerHtml(NPL.Select(xElementSrc).innerHtml());
				}
			}
		}
    	return this;
    };
    
    this.remove = function () { return this._for_all_do(function(e){e.parentNode.removeChild(e)}) };
    
    return this;
}

function NoIE6 ()
{
	//	Optional function provided for web developers that want to use it (me) to encourage users to upgrade.
	//	Most -- almost all? -- of NPL is compatible with IE 6; still, the only way IE 6 will go away is if we
	//	force it to, and it has other issues which are costly for developers to deal with.
	if ( NPL.Browser.IE6() )
	{
		var xNo6Text = "<h1>Sorry, your browser is no longer supported</h1>The website developer has gone to great efforts to make this website compatible with as many different web browsers as time and money allowed.<br>\n<br>\nUnfortunately, Internet Explorer 6 is both the most difficult and the most restrictive browser to develop for.<br>\n<br>\nYou are seeing this message because this website uses features which are supported by almost all other web browsers, except for Internet Explorer 6.<br>\n<br>\n<br>\nYour options are:<ul><li>Email the website developer: <a href='mailto:rob@associatedtechs.com'>rob@associatedtechs.com</a></li><br>\n<li><a href='http://getfirefox.com/'>Get Firefox</a>: A faster, more secure, widely-used alternative to Internet Explorer. Enjoy advanced website features, and if you also install <a href='http://adblockplus.org/'>AdBlock Plus</a>, you won't even have to download or view advertising on websites or be tricked again into downloading a virus.</li><br>\n<li>Upgrade to <a href='http://www.microsoft.com/windows/internet-explorer/ie7/'>Internet Explorer 7</a> or <a href='http://www.microsoft.com/windows/Internet-explorer/default.aspx'>Internet Explorer 8</a>.</li><br>\n</ul><br><br>I sincerely apologize for the trouble; unfortunately, it's no longer practical to continue to support this browser. There are widespread efforts underway to get the remaining Internet Explorer 6 users to upgrade.";
		NPL.Select('body').innerHtml(xNo6Text);
		NPL.Select('body').style('padding', '40px').style('font-size', '14px').style('font-family', 'Verdana, Geneva, sans-serif').style('width', '80%').style('height', '80%');
	}
}
