1 (function(window, global) {
  2 	/**	@exports self as puredom */
  3 
  4 	// node:
  5 	if (typeof process==='object' && process.argv && process.argv[0]==='node') {
  6 		window = require('jsdom').jsdom().parentWindow;
  7 	}
  8 
  9 	var document = window.document,
 10 		navigator = window.navigator;
 11 
 12 	var previousSelf = window.puredom;
 13 
 14 	if (typeof Date.now!=='function') {
 15 		/**	@ignore */
 16 		Date.now = function() {
 17 			return new Date().getTime();
 18 		};
 19 	}
 20 
 21 	/**	When called as a function, acts as an alias of {@link puredom.el}.<br />
 22 	 *	If a <code>Function</code> is passed, it is registered as a DOMReady handler. <br />
 23 	 *	Otherwise, all arguments are passed on to {@link puredom.el}.
 24 	 *	@version 1.9.1
 25 	 *	@namespace Core functionality
 26 	 *	@function
 27 	 *	@param {Function|Any} arg	If a <code>Function</code> is passed, it is registered as a DOMReady handler. Otherwise, all arguments are passed on to {@link puredom.el}
 28 	 *	@name puredom
 29 	 *	@public
 30 	 */
 31 	var self = function(){
 32 			return priv.puredom.apply(priv, arguments);
 33 		},
 34 		/**	@private */
 35 		baseSelf = {
 36 			version : '1.9.1',
 37 			templateAttributeName : 'data-tpl-id',
 38 			baseAnimationInterval : 20,
 39 			allowCssTransitions : true,
 40 			easingMethods : {
 41 				ease : function(f) {
 42 					return (Math.sin(f*Math.PI - Math.PI/2) + 1) / 2;
 43 				},
 44 				'ease-in-out' : function(f) {
 45 					return this.ease(f);
 46 				}
 47 			}
 48 		},
 49 		initialized = false,
 50 		vendorCssPrefix,
 51 		vendorCssPrefixJS,
 52 		objConstructor = baseSelf.constructor,
 53 		textContentProperty,
 54 		getSupportedTextContentProperty,
 55 
 56 		/**	@private */
 57 		priv = {
 58 			oninit : [],
 59 			animationTimes : {
 60 				fast	: 150,
 61 				medium	: 450,
 62 				slow	: 1000
 63 			},
 64 			animationTimeScale : 1,
 65 			registeredEventCount : 0,
 66 			html5elements : 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
 67 			support : {
 68 				html5 : true,
 69 				filters : false,
 70 				querySelectorAll : 'querySelectorAll' in document,
 71 				webkitMultitouch : 'ontouchstart' in window && navigator.maxTouchPoints!==0 && navigator.msMaxTouchPoints!==0
 72 			},
 73 			regex : {
 74 				css3AutoPrefix : /([\s\;\/\*])(transform|transition|perspective|box\-sizing|box\-shadow|border\-radius)\:([^\;]*)(\;|$)/gim,		// |text\-shadow
 75 				css3VendorPrefix : /\b\-(moz|webkit|ms|o|vendor)\-/gim,
 76 				templateFieldToken : /([^\\]?)\{([a-z0-9A-Z\$_\.]+)(\|[^\}]*?)?\}/gm,
 77 				parseObjectNameFromString : /^\[object ([^\s]+)\]$/gim,
 78 				autoDetectHTMLContent : /(<[a-z]|&[a-z#0-9]{1,10};)/gim,
 79 				whitespaceCharacters : /\s/,
 80 				getNumericCSSValue : /[^0-9\.\-]/gm,
 81 				getCSSValueUnits : /([a-z]+|%)$/,
 82 				getNonIntegerCharsSigned : /[^0-9\.\-]/gm,
 83 				getUpperCaseAlphaChars : /[A-Z]/gm
 84 			}
 85 		};
 86 
 87 	/** @ignore */
 88 	function noop(){}
 89 
 90 	/** @ignore */
 91 	self.support = priv.support;
 92 
 93 	if (navigator.userAgent.match(/\b(webkit|applewebkit|chrome|chromium|khtml)\b/gim)) {
 94 		vendorCssPrefix = '-webkit';
 95 		vendorCssPrefixJS = 'Webkit';
 96 	}
 97 	else if (navigator.userAgent.match(/\bopera\b/gim)) {
 98 		vendorCssPrefix = '-o';
 99 		vendorCssPrefixJS = 'O';
100 	}
101 	else if (navigator.userAgent.match(/\bgecko\b/gim)) {
102 		vendorCssPrefix = '-moz';
103 		vendorCssPrefixJS = 'Moz';
104 	}
105 	else if (navigator.userAgent.match(/\bmsie\s*?(8|9|[1-9][0-9]+)\b/gim)) {
106 		vendorCssPrefix = '-ms';
107 		vendorCssPrefixJS = 'Ms';
108 	}
109 	else if (navigator.userAgent.match(/\bmsie\s*?[4-8]\b/gim)) {
110 		priv.support.filters = true;
111 		priv.support.filterProperty = 'filter';
112 	}
113 
114 
115 	/**	@ignore */
116 	(function(div, i) {
117 		div = document.createElement('div');
118 		div.innerHTML = '<nav></nav>';
119 		priv.support.html5 = div.childNodes.length>0;
120 		if (!priv.support.html5) {
121 			priv.html5frag = document.createDocumentFragment();
122 			for (i=priv.html5elements.length; i--; ) {
123 				priv.html5frag.createElement(priv.html5elements[i]);
124 			}
125 			priv.html5div = document.createElement('div');
126 			priv.html5frag.appendChild(priv.html5div);
127 		}
128 	}());
129 
130 
131 	/**	Note: this function removes itself, and should only ever be run once.
132 	 *	@ignore
133 	 */
134 	getSupportedTextContentProperty = function() {
135 		var d = document.body;
136 		textContentProperty = (d.textContent!==undefined && "textContent") || (d.innerText && 'innerText') || "innerHTML";
137 		getSupportedTextContentProperty = null;
138 		return textContentProperty;
139 	};
140 
141 
142 
143 	/** Extend/augment a base object with the properties of one or more additional objects.<br />
144 	 *	<strong>Note:</strong> all additional arguments are treated as additional Objects to copy properties from.
145 	 *	@param {Object} base	The object to extend. For cloning, use an object literal.
146 	 *	@param {Object} props	An Object to copy properties from.
147 	 *	@param {Object} [...]	Additional arguments also get copied onto base.
148 	 *	@returns {Object} base
149 	 *	@example
150 	 *		var clonedObj = puredom.extend({}, originalObj);
151 	 *		puredom.extend(MyClass.prototype, prototypeAsHash);
152 	 */
153 	self.extend = function(base) {
154 		var i, j, ext;
155 		base = base || {};
156 		for (i=1; i<arguments.length; i++) {
157 			ext = arguments[i];
158 			if (ext) {
159 				for (j in ext) {
160 					if (ext.hasOwnProperty(j)) {
161 						base[j] = ext[j];
162 					}
163 				}
164 				// IE never reports toString as an "own property", so manually check if it was copied and fix if required:
165 				if (typeof ext.toString==='function' && ext.toString!==Object.prototype.toString) {		// ext.toString!==obj.toString &&
166 					base.toString = ext.toString;
167 				}
168 			}
169 		}
170 		return base;
171 	};
172 
173 
174 	/** Mix functionality from one object into another. <br />
175 	 *	<strong>Note:</strong> all additional arguments are treated as additional Objects to copy properties from. <br />
176 	 *	<strong>Alternative Signature:</strong> <code>mixin(true, [props, ...], base)</code>
177 	 *	@param {Object} base	The object to extend. For cloning, use an object literal.
178 	 *	@param {Object} props	An Object to copy properties from, unless base already has a property of the same name.
179 	 *	@returns {Object} base
180 	 *	@example
181 	 *		// standard:
182 	 *		puredom.mixin(myObj, decorator1, decorator2);
183 	 *
184 	 *		// alternative, decorator-first style:
185 	 *		puredom.mixin(true, decorator1, decorator2, myObj);
186 	 */
187 	self.mixin = function(base) {
188 		var i, j, ext,
189 			mix = Array.prototype.slice.call(arguments, 1);
190 		if (base===true) {
191 			base = mix.pop();
192 		}
193 		base = base || {};
194 		for (i=0; i<mix.length; i++) {
195 			if ( (ext=mix[i]) ) {
196 				for (j in ext) {
197 					if (ext.hasOwnProperty(j) && !base.hasOwnProperty(j)) {
198 						base[j] = ext[j];
199 					}
200 				}
201 			}
202 		}
203 		return base;
204 	};
205 
206 
207 	/**	Strip an object of all of its properties.<br />
208 	 *	<strong>Note:</strong> Sets property values to null, doesn't actually delete them.
209 	 *	@param {Object} obj					An object to strip all properties from
210 	 *	@param {Boolean} [andProto=false]	If <code>true</code>, nullifies entire prototype chain.
211 	 */
212 	self.strip = function(obj, andProto) {
213 		for (var i in obj) {
214 			if (andProto===true || obj.hasOwnProperty(i)) {
215 				obj[i] = null;			// faster than delete.
216 			}
217 		}
218 	};
219 
220 
221 	/** Get a value from within a nested object. "Deep keys" use dot notation.
222 	 *	@param {Object} obj		The object to delve into.
223 	 *	@param {String} path	A dot-notated key to find within <code>obj</code>
224 	 *	@param {Boolean} [discardFirst=false]			If <code>true</code>, the first segment of <code>path</code> will be discarded.
225 	 *	@param {Boolean} [allowIncompleteMatch=false]	If <code>true</code>, returns the deepest reachable value, even if it is not a full path.
226 	 */
227 	self.delve = function(obj, path, discardFirst, allowIncompleteMatch) {
228 		var i = 0;
229 		if (path==='.' || (path==='this' && !obj.hasOwnProperty('this'))) {
230 			return obj;
231 		}
232 		path = path.split('.');
233 		if (discardFirst===true) {
234 			path.splice(0, 1);
235 		}
236 		while (i<path.length && obj && obj.hasOwnProperty(path[i])) {
237 			obj = obj[path[i]];
238 			i += 1;
239 		}
240 		if (i>=path.length || (allowIncompleteMatch===true && i>0)) {
241 			return obj;
242 		}
243 	};
244 
245 
246 	/**	Flatten a nested Object using underscore-delimited keys. (<code>foo_bar_baz</code>)
247 	 *	@param {Object} obj		The nested/deep object to flatten
248 	 *	@returns {Object} flat
249 	 */
250 	self.flattenObj = function(obj, prefix, depth, flat) {
251 		var i, p;
252 		prefix = prefix || '';
253 		depth = depth || 0;
254 		flat = flat || {};
255 		for (i in obj) {
256 			if (obj.hasOwnProperty(i)) {
257 				p = prefix ? (prefix+'_'+i) : i;
258 				if (self.isScalar(obj[i])) {
259 					flat[p] = obj[i];
260 				}
261 				else {
262 					self.flattenObj(obj[i], p, depth+1, flat);
263 				}
264 			}
265 		}
266 		if (!depth) {
267 			return flat;
268 		}
269 	};
270 
271 
272 	/** Inject arbitrarily nested template fields into a string of text. <br />
273 	 *	Fields are referenced like this:  {foo.bar.baz|truncate:300,byWord}<br />
274 	 *	<em><strong>Note:</strong> keys are CaSe-SeNsItIvE.</em>
275 	 *	@param {String} text				The text to template
276 	 *	@param {Object} fields				An object containing (nested) keys for replacement
277 	 *	@param {Boolean} [allowI18n=true]	Allow Internationalization using the engine referenced by {@link puredom.l18n}?
278 	 *	@returns {String} The templated text
279 	 */
280 	self.template = function(text, fields, allowI18n) {
281 		var templated,
282 			i18n;
283 		if (allowI18n!==false && self.i18n) {
284 			i18n = self.i18n;
285 		}
286 		templated = (text+'').replace(priv.regex.templateFieldToken, function(str, pre, id, filters) {
287 			var val;
288 			if (pre!=='\\' && id) {
289 				val = self.delve(fields, id);
290 				if (val) {
291 					if (i18n) {
292 						val = i18n(val) || val;
293 					}
294 					if (filters && filters.substring(0,1)==='|') {
295 						val = self.text.filter(val, filters.substring(1));
296 					}
297 					str = pre + val;
298 				}
299 				else {
300 					str = pre;
301 				}
302 			}
303 			else {
304 				str = pre;
305 			}
306 			return str;
307 		});
308 		return templated;
309 	};
310 
311 
312 	/** Simple prototypal inheritance.
313 	 *	@param {Function} baseClass		The base (child) class.
314 	 *	@param {Function} superClass	A class to inherit from.
315 	 *	@returns {Function} baseClass, for convenience
316 	 *	@example
317 	 * puredom.inherits(puredom.ControllerManager, puredom.EventEmitter);
318 	 */
319 	self.inherits = function(base, superClass) {
320 		function F(){}
321 		F.prototype = superClass.prototype;
322 		var proto = base.prototype;
323 		base.prototype = new F();
324 		puredom.extend(base.prototype, proto);
325 		base.prototype.constructor = base;
326 		base.prototype.__super = superClass;
327 	};
328 
329 
330 	/** Get the <strong>lowercase</strong> type (constructor name) of an object.<br />
331 	 *	<em><strong>Important Note:</strong> Unlike many other typeOf implementations, this method returns the name of an Object's constructor, rather than just "object".</em>
332 	 *	@param {Any} what		An object to analyze
333 	 *	@returns {String} type
334 	 *	@example
335 	 * puredom.typeOf({}) === 'object'
336 	 * puredom.typeOf([]) === 'array'
337 	 * puredom.typeOf(new Audio) === 'audio'
338 	 */
339 	self.typeOf = function(what) {
340 		if (what===undefined) {
341 			return 'undefined';
342 		}
343 		else if (what===null) {
344 			return 'null';
345 		}
346 		else if (what) {
347 			if (what.constructor===objConstructor) {
348 				return 'object';
349 			}
350 			else if (self.isArray(what)) {
351 				return 'array';
352 			}
353 		}
354 		//return String(typeof what).toLowerCase();
355 		return Object.prototype.toString.call(what).replace(priv.regex.parseObjectNameFromString,'$1').toLowerCase();
356 	};
357 
358 
359 	/** Determines if the passed object is scalar.
360 	 *	@param {Any} what		An object to analyze
361 	 *	@returns {Boolean} isScalar
362 	 */
363 	self.isScalar = function(what) {
364 		var type = self.typeOf(what);
365 		if (type==='undefined' || type==='null' || type==='number' || type==='string' || type==='boolean') {
366 			return true;
367 		}
368 		return false;
369 	};
370 
371 
372 	/* Index of an element within an array */
373 	if (!Array.prototype.indexOf || ([self]).indexOf(self)!==0) {
374 		try {
375 			/**	@ignore */
376 			Array.prototype.indexOf = function(what) {
377 				for (var x=0; x<this.length; x++) {
378 					if (this[x]===what) {
379 						return x;
380 					}
381 				}
382 				return -1;
383 			};
384 		}catch(arrayIndexErr){}
385 	}
386 
387 
388 	/**	Convert an Array-like object (having a length and numeric properties) into an Array.
389 	 *	@param {Any} obj		An Array-like object to convert
390 	 *	@returns {Array} array	The converted <code>Array</code> on success, or the original object <code>obj</code> on failure.
391 	 */
392 	self.toArray = function(obj) {
393 		var arr = [],
394 			len = obj && obj.length,
395 			x, p;
396 		if (len || len===0) {
397 			for (x=len; x--; ) {
398 				arr[x] = obj[x];
399 			}
400 		}
401 		else {
402 			x = 0;
403 			while (true) {
404 				if (obj.hasOwnProperty && obj.hasOwnProperty(x)) {
405 					arr.push(obj[x]);
406 				}
407 				else if (obj.hasOwnProperty && obj.hasOwnProperty(x+'')) {
408 					arr.push(obj[x+'']);
409 				}
410 				else {
411 					break;
412 				}
413 				x += 1;
414 			}
415 		}
416 		return arr;
417 	};
418 
419 
420 	/** Determine if the argument is an Array
421 	 *	@function
422 	 *	@param {Any} what		An object to analyze
423 	 *	@returns {Boolean} isArray	<code>true</code> if the object is an <code>Array</code>, otherwise <code>false</code>.
424 	 */
425 	self.isArray = Array.isArray ? function(what) {
426 		return Array.isArray(what);
427 	} : function(what) {
428 		return Object.prototype.toString.call(what)==="[object Array]";
429 	};
430 
431 
432 	/** Determine if an object has a direct property with the given name.
433 	 *	@param {Any} obj		An object to test
434 	 *	@param {String} prop	A property name to test
435 	 *	@returns {Boolean} hasOwnProperty
436 	 */
437 	self.hasOwnProp = function(obj, prop) {
438 		return Object.prototype.hasOwnProperty.call(obj, prop);
439 	};
440 
441 
442 	/** Iterate over an object, calling an <code>iterator</code> function on each value.
443 	 *	@name puredom.forEach
444 	 *	@function
445 	 *	@param {Object|Array} obj		An object to iterate over.
446 	 *	@param {Function} iterator		A function to call for each value. Gets passed <code>(value, key)</code>.
447 	 *	@returns obj
448 	 */
449 	self.forEach = function(obj, iterator) {
450 		var i, r;
451 		if (self.isArray(obj)) {
452 			for (i=0; i<obj.length; i++) {
453 				r = iterator(obj[i], i);
454 				if (r===false) {
455 					break;
456 				}
457 			}
458 		}
459 		else {
460 			for (i in obj) {
461 				if (obj.hasOwnProperty(i)) {
462 					r = iterator(obj[i], i);
463 					if (r===false) {
464 						break;
465 					}
466 				}
467 			}
468 		}
469 		return obj;
470 	};
471 
472 	/**	@ignore */
473 	self.foreach = self.forEach;
474 
475 
476 	/**	Set the innerHTML of an element, with fixes for various browser bugs
477 	 *	@private
478 	 *	@param {HTMLElement} el			An element whose content should be set
479 	 *	@param {String} html			The content to set
480 	 */
481 	self.setInnerHTML = function(el, html) {
482 		var frag, i;
483 		if (priv.support.html5) {
484 			el.innerHTML = html || '';
485 		}
486 		else {
487 			el.innerHTML = '';
488 			priv.html5div.innerHTML = html || '';
489 			frag = document.createDocumentFragment();
490 			for (i=priv.html5div.childNodes.length; i--; ) {
491 				frag.appendChild(priv.html5div.firstChild);
492 			}
493 			el.appendChild(frag);
494 		}
495 	};
496 
497 
498 	/** Create a DOM node from an Object description
499 	 *	@private
500 	 *	@param {Object} options			An object that describes how to construct the node
501 	 *	@param {HTMLElement} [parent]	Optional parent node to inject the newly constructed element into.
502 	 *	@returns {HTMLElement} node		Returns the created HTML element.
503 	 */
504 	self.createElement = function(options, parent) {
505 		var el, x, i, childFrag, processProp, insertedBefore;
506 		if (typeof options==='string') {
507 			childFrag = document.createElement('div');
508 			childFrag.innerHTML = options;
509 			for (i=0; i<childFrag.childNodes.length; i++) {
510 				el = childFrag.childNodes[i];
511 				if (el.nodeType===1) {
512 					if (parent) {
513 						parent.appendChild(el);
514 					}
515 				}
516 			}
517 			return el;
518 		}
519 		options = options || {};
520 		el = document.createElement(options.type || "div");
521 		parent = parent || options.parent;
522 		if (options.insertBefore && options.insertBefore.constructor===self.NodeSelection) {
523 			options.insertBefore = options.insertBefore._nodes[0];
524 		}
525 		if (!parent && options.insertBefore) {
526 			parent = options.insertBefore.parentNode;
527 		}
528 		for (x in options) {
529 			if (self.hasOwnProp(options, x)) {
530 				if ((x+"").substring(0,2).toLowerCase()==="on") {
531 					self.addEvent(el, x.substring(2), options[x]);
532 				}
533 				else if (x==="css" || x==="cssText") {
534 					if (vendorCssPrefix) {
535 						options[x] = options[x].replace(priv.regex.css3AutoPrefix, '$1'+vendorCssPrefix+'-$2:$3; $2:$3;');
536 						options[x] = options[x].replace(priv.regex.css3VendorPrefix, '-'+vendorCssPrefix+'-');
537 						el.style.cssText = options[x];
538 					}
539 					else {
540 						el.style.cssText = options[x];
541 					}
542 				}
543 				else if (x==='html' || x==='innerHTML') {
544 					self.setInnerHTML(el, options[x]);
545 				}
546 				else if (x==="attributes") {
547 					for (i in options[x]) {
548 						if (self.hasOwnProp(options[x], i)) {
549 							el.setAttribute(i, options[x][i]);
550 						}
551 					}
552 				}
553 				else if (x!=='parent' && x!=='children' && x!=='insertBefore' && x!=='type' && x!=='children' && x!=='html' && x!=='innerHTML') {
554 					if (document.all) {
555 						try {
556 							el[x] = options[x];
557 						}catch(err){
558 							self.log(x);
559 						}
560 					}
561 					else {
562 						el[x] = options[x];
563 					}
564 				}
565 				else {
566 					//self.log('Skipping "'+x+'" property in create()', options[x]);
567 				}
568 			}
569 		}
570 		if (parent) {
571 			if (options.insertBefore) {
572 				try {
573 					parent.insertBefore(el, options.insertBefore);
574 					insertedBefore = true;
575 				}catch(err) {
576 					insertedBefore = false;
577 				}
578 			}
579 			if (!insertedBefore) {
580 				parent.appendChild(el);
581 			}
582 		}
583 
584 		if (options.children && self.isArray(options.children)) {
585 			childFrag = document.createDocumentFragment();
586 			for (x=0; x<options.children.length; x++) {
587 				self.createElement(options.children[x], childFrag);
588 			}
589 			el.appendChild(childFrag);
590 		}
591 		return el;
592 	};
593 
594 
595 	/**	Creates a new selection containing the elements of <code>nodes</code>. <br />
596 	 *	This class is not generally instantiated directly - instead, use the puredom()
597 	 *	function to query for elements or wrap an Array of elements with a selection.
598 	 *	@class Represents a collection of DOM Elements. <br />
599 	 *	Puredom methods that work with DOM elements generally return an instance of this.
600 	 *	@name puredom.NodeSelection
601 	 *	@param {Array} nodes		An array of raw DOM nodes to wrap in a selection.
602 	 */
603 	self.NodeSelection = function NodeSelection(nodes) {
604 		var x;
605 		this._results = [];
606 		this._animations = [];
607 		if (nodes) {
608 			if (self.isArray(nodes)) {
609 				this._nodes = nodes = nodes.slice();
610 				for (x=nodes.length; x--; ) {
611 					if (!nodes[x]) {
612 						nodes.splice(x, 1);
613 					}
614 					else if (nodes[x] instanceof NodeSelection) {
615 						nodes = nodes.concat(nodes.splice(x, 1)[0]._nodes);
616 					}
617 				}
618 			}
619 			else {
620 				this._nodes = [nodes];
621 			}
622 		}
623 		else {
624 			this._nodes = [];
625 		}
626 	};
627 
628 	self.extend(self.NodeSelection.prototype, /** @lends puredom.NodeSelection# */ {
629 
630 		/**	@private */
631 		_results : [],
632 
633 		/**	@private */
634 		_nodes : [],
635 
636 		/**	@private */
637 		_animations : [],
638 
639 
640 		/**	Get an Array of String representations of each element in the selection. <br />
641 		 *	For a more logging-friendly option, see {@link puredom.NodeSelection#describe}.
642 		 */
643 		describe : function() {
644 			var p = [];
645 			this.each(function(node) {
646 				var str = '<' + node.nodeName(),
647 					id = node.prop('id'),
648 					className = node.prop('className'),
649 					cn, i, g;
650 				if (id) {
651 					str += ' id="' + id + '"';
652 				}
653 				if (className) {
654 					str += ' class="' + className + '"';
655 				}
656 				str += '>';
657 				if (node._nodes[0].childNodes.length===1 && node._nodes[0].childNodes[0].nodeType===3) {
658 					str += node.text().replace(/(\r|\n)/gim,decodeURIComponent("%E2%86%A9")).replace(/\t/gim,decodeURIComponent("%E2%86%92"));
659 				}
660 				else {
661 					str += '[' + node.children().count() + ' children]';
662 				}
663 				str += '</' + node.nodeName() + '>';
664 				p.push(str);
665 			});
666 			return p;
667 		},
668 
669 		/**	Get a String representation of the selection's current contents. <br />
670 		 *	For the raw Array description, see {@link puredom.NodeSelection#describe}.
671 		 */
672 		toString : function() {
673 			return this.describe().join(', ');
674 		},
675 
676 		/**	@private */
677 		toSource : function() {
678 			return this._nodes;
679 		},
680 
681 		/**	Get the result of the previous operation. <br />
682 		 *	Many puredom methods return the selection they were called on rather than a standard return value.
683 		 *	This method gets the equivalent return value of the most recent selection method call.
684 		 *	@param {Number} [reverseIndex=0]		Optionally get an older return value. This value is a 0-based offset.
685 		 *	@returns {Any} returnValue, or <code>undefined</code> if no value was returned.
686 		 */
687 		getResult : function(reverseIndex) {
688 			reverseIndex = Math.round(reverseIndex) || 0;
689 			return this._results[this._results.length - reverseIndex - 1];
690 		},
691 
692 		/**	@private */
693 		pushResult : function(result) {
694 			this._results.push(result);
695 			return this;
696 		},
697 
698 		/**	Call an iterator function on each element in the selection, wrapping each in a new {@link puredom.NodeSelection}.<br />
699 		 *	<strong>Note:</strong> Return <code>false</code> from within <code>iterator</code> to break out of the loop.
700 		 *	@param {Function} iterator	Gets passed <code>(element, index)</code> for each element in the selection. The value of <code>this</code> is the selection itself.
701 		 *	@returns {this}
702 		 */
703 		each : function(action) {
704 			return this._each(action, true);
705 		},
706 
707 		/**	Call an iterator function on each <strong>raw DOM node</strong> in the selection.<br />
708 		 *	<strong>Note:</strong> Return <code>false</code> from within <code>iterator</code> to break out of the loop.
709 		 *	@param {Function} iterator	Gets passed <code>(node, index)</code> for each element in the selection. The value of <code>this</code> is the selection itself.
710 		 *	@returns {this}
711 		 */
712 		_each : function(action, asSelection, inReverse) {
713 			var nodes = this._nodes.slice(0,this._nodes.length),
714 				x, y, node, ret;
715 			for (x=0; x<nodes.length; x++) {
716 				y = x;
717 				if (inReverse===true) {
718 					y = nodes.length-y-1;
719 				}
720 				node = nodes[y];
721 				if (asSelection===true) {
722 					node = new self.NodeSelection(node);
723 				}
724 				ret = action.call(this, node, y);
725 				if (ret===false) {
726 					break;
727 				}
728 			}
729 			return this;
730 		},
731 
732 		/**	Call a function on the selection in the future.
733 		 *	@param {Number} millis		The number of milliseconds to wait before calling <code>callback</code>.
734 		 *	@param {Function} callback	The function to call in <code>millis</code> milliseconds. Gets called on the selection, so the value of <code>this</code> is the selection itself.
735 		 *	@returns {this}
736 		 */
737 		wait : function(millis, callback) {
738 			var self = this;
739 			if (callback) {
740 				setTimeout(function() {
741 					callback.apply(self);
742 					self = callback = null;
743 				}, Math.abs(millis));
744 			}
745 			return this;
746 		},
747 
748 		/**	Get the <strong>lower-case</strong> nodeName of an element.<br />
749 		 *	<em><strong>Note:</strong> Only returns a UUID for the first element in a selection.</em> <br />
750 		 *	<strong>Note:</strong> In puredom, the <code>window</code> Object is given a nodeName of "#window".
751 		 *	@returns {String} nodeName
752 		 */
753 		nodeName : function() {
754 			var node = this._nodes[0],
755 				nodeName = node && node.nodeName && node.nodeName.toLowerCase();
756 			if (node===window) {
757 				return '#window';
758 			}
759 			else if (nodeName) {
760 				return nodeName;
761 			}
762 			return null;
763 		},
764 
765 		/**	Get a globally unique identifier for an element. <br />
766 		 *	<em><strong>Note:</strong> Only returns a UUID for the first element in a selection.</em>
767 		 *	@returns {String} uuid
768 		 */
769 		uuid : function() {
770 			return this._nodes[0] && priv.nodeToId(this._nodes[0]) || null;
771 		},
772 
773 		/**	Get or set the textual content of elements. Omit <code>text</code> to retrieve the textual value instead of setting it.
774 		 *	@param {String} [text]		If set, replaces the textual content of elements.
775 		 *	@returns {String} text		The textual contents of the first element in the selection, or the selection itself if <code>text</code> was not set.
776 		 */
777 		text : function(text) {
778 			if (arguments.length===0) {
779 				return this._nodes[0] && this._nodes[0][textContentProperty || getSupportedTextContentProperty()] || '';
780 			}
781 			text = text + "";
782 			this._each(function(el) {
783 				el[textContentProperty || getSupportedTextContentProperty()] = text;
784 			});
785 			return this;
786 		},
787 
788 		/**	Set the HTML contents of elements.
789 		 *	@param {String} [content]			If set, updates the content of the elements. If not set, returns the HTML content of the first element.
790 		 *	@param {Boolean} [asText=auto]		If <code>true</code>, content will be treated as textual, if <code>false</code> content will be treated as HTML. Defaults to auto-detection of HTML.
791 		 *	@returns {String} text		The HTML or textual contents of the first element in the selection, or the selection itself if <code>html</code> was not set.
792 		 */
793 		html : function(content, asText) {
794 			if (arguments.length===0) {
795 				return this._nodes[0] && this._nodes[0].innerHTML || '';
796 			}
797 			content = content + "";
798 			priv.regex.autoDetectHTMLContent.lastIndex = 0;
799 			if ((priv.regex.autoDetectHTMLContent).test(content) && asText!==true) {
800 				//self.log("Detected HTML in .html() insertion, using innerHTML...", {content:content});
801 				//alert('setting HTML: ' + content);
802 				this._each(function(el) {
803 					self.setInnerHTML(el, content);
804 				});
805 			}
806 			else {
807 				//alert('setting PLAIN: ' + content);
808 				//self.log("Detected plain text in .html() insertion, using textContent or equivalent...", {content:content});
809 				this._each(function(el) {
810 					el[textContentProperty || getSupportedTextContentProperty()] = content;
811 				});
812 			}
813 			return this;
814 		},
815 
816 		/**	Apply CSS to elements.
817 		 *	@param {String|Object} css				CSS to apply to the elements in the selection. Either a CSS-string, or an Object where the keys are CSS properties and the values are the corresponding values to apply.
818 		 *	@param {Object} [options]				Options
819 		 *	@param {Number|String} [options.tween]	Animate the application of the given CSS styles. Numeric values are treated as durations, String values must be a comma-separated value of the format: "duration,easing-method".
820 		 *	@param {Function} [options.callback]	A function to call once the styles have been applied. If tweening/animating, gets called once the animation has completed.
821 		 *	@param {Function} callback				Same as <code>options.callback</code>, takes precidence when used.
822 		 *	@returns {this}
823 		 */
824 		css : function(css, options, callback) {
825 			var type,
826 				selection = this;
827 			options = options || {};
828 			if (typeof options==='string' || typeof options==='number') {
829 				options = {tween:options};
830 			}
831 			if (!callback && options.callback) {
832 				callback = options.callback;
833 			}
834 			if (!callback || !callback.call) {
835 				callback = noop;
836 			}
837 			type = self.typeOf(options.tween);
838 			if (typeof css==='string') {
839 				css = priv.parseCSS(css);
840 			}
841 			if ((type==='string' && options.tween!=='none') || (type==='number' && options.tween>0)) {
842 				var tween = (options.tween + '').replace(priv.regex.whitespaceCharacters,'').split(','),
843 					x,
844 					cb,
845 					total = 0,
846 					completed = 0;
847 				cb = function(node) {
848 					completed += 1;
849 					if (completed>=total) {
850 						cb = null;
851 						if (callback) {
852 							callback.call(selection, selection);		// node
853 						}
854 						selection = callback = node = null;
855 					}
856 				};
857 				for (x in css) {
858 					if (css.hasOwnProperty(x)) {
859 						total += 1;
860 						this.animateCss(x, css[x], tween[0], tween[1], cb);
861 					}
862 				}
863 			}
864 			else {
865 				this._each(function(el) {
866 					self.applyCss(el, css);
867 				});
868 
869 				if (callback) {
870 					setTimeout(function(){
871 						callback.call(selection, selection);
872 						selection = callback = null;
873 					}, 1);
874 				}
875 				else {
876 					selection = null;
877 				}
878 			}
879 			return this;
880 		},
881 
882 		/** Show elements.
883 		 *	@returns {this}
884 		 */
885 		show : function() {
886 			this.css({
887 				display		: '',
888 				visibility	: 'visible'
889 			});
890 			// ensure the new active style is not display:none
891 			this._each(function(node) {
892 				if (node.style.display==='none' || self.nodeStyle(node, 'display')==='none') {
893 					node.style.display = 'block';
894 				}
895 			});
896 			return this;
897 		},
898 
899 		/** Hide elements.
900 		 *	@param {Boolean} [andIgnore=true]		If <code>false</code>, triggers "visibility:hidden" CSS, instead of "display:none".
901 		 *	@returns {this}
902 		 */
903 		hide : function(andIgnore) {
904 			/*
905 			var disp = this.getStyle('display',true);
906 			if (disp && disp!=='none') {
907 				this._previousDisplayStyle = disp;
908 			}
909 			*/
910 			return this.css(
911 				andIgnore===false ? {visibility:'hidden'} : {display:'none'}
912 			);
913 		},
914 
915 		/**	This function tries quite hard to guess what particular fade effect is needed. <br />
916 		 *	If the element that is already semi-transparent, it fades from the current opacity. <br />
917 		 *	If the element that is hidden but not explicitly transparent, it fades from opacity=0 (hidden). <br />
918 		 *	If the element is already 100% opaque (non-transparent), no animation is performed, and the callback is fired after a very small delay (to enforce async). <br />
919 		 *	Arguments are interchangeable for backward compatibility.
920 		 *	@param {Number|String} tween	A tween value. Can be a <code>{Number} duration</code>, or <code>{String} "duration,easing-method"</code>.
921 		 *	@param {Function} [callback]	A function to call once the fade has completed. Gets passed the selection.
922 		 *	@returns {this}
923 		 */
924 		fadeIn : function(tween, callback) {
925 			var originalOpacity = parseFloat(this.getStyle('opacity') || '0') || 0,
926 				targetOpacity = 1;
927 			if (this.getStyle('display')==='none' || this.getStyle('visibility')==='hidden') {
928 				if (this.getStyle('opacity') && originalOpacity>0 && originalOpacity<1) {
929 					targetOpacity = originalOpacity;
930 				}
931 				originalOpacity = 0;
932 				this.css({
933 					opacity : 0
934 				});
935 			}
936 			// arguments can be in reverse order
937 			if (self.typeOf(tween)==='function') {
938 				callback = tween;
939 				tween = arguments[1];
940 			}
941 			if (originalOpacity>=1 || tween===0 || tween===false) {
942 				this.css({
943 					opacity : targetOpacity
944 				}).show();
945 				if (callback && callback.call) {
946 					setTimeout(callback, 0);
947 				}
948 				return this;
949 			}
950 			this.css({
951 				opacity : targetOpacity
952 			}, {tween:tween || 'medium', callback:function(selection) {
953 				if (callback) {
954 					callback(selection);
955 				}
956 				tween = callback = null;
957 			}}).show();
958 			return this;
959 		},
960 
961 		/**	The opposite of fadeIn(). Makes several guesses about the desired effect. */
962 		fadeOut : function(tween, callback, andIgnore) {
963 			var originalOpacity = parseFloat(this.getStyle('opacity') || '1') || 1;
964 			if (self.typeOf(tween)==='function') {
965 				callback = tween;
966 				tween = arguments[1];
967 			}
968 			if (self.typeOf(callback)==='boolean') {
969 				andIgnore = callback;
970 				callback = null;
971 				if (self.typeOf(arguments[2])==='function') {
972 					callback = arguments[2];
973 				}
974 			}
975 			if (originalOpacity<=0 || this.getStyle('display')==='none' || this.getStyle('visibility')==='hidden' || tween===0 || tween===false) {
976 				this.css({
977 					opacity : 0
978 				}).hide(andIgnore);
979 				setTimeout(callback, 0);
980 				return this;
981 			}
982 			this.css({
983 				opacity : 0
984 			}, {tween:tween || 'medium', callback:function(selection) {
985 				selection.hide(andIgnore).css({
986 					opacity : originalOpacity
987 				});
988 				if (callback) {
989 					callback(selection);
990 				}
991 				tween = callback = null;
992 			}});
993 			return this;
994 		},
995 
996 		/**	Automatically detects and uses CSS3 transitions.
997 		 *	@private
998 		 */
999 		animateCSS : (function() {
1000 			var manual, cssTransition, supportsCssTransition, checkCssTransitionSupport;
1001 
1002 			/**	@ignore */
1003 			manual = function(cssProp, targetValue, duration, easing, callback) {
1004 				var startValues = [],
1005 					perNodeProperties = [],
1006 					numericTargetValue, units, s;
1007 
1008 				cssProp = cssProp.toLowerCase();
1009 
1010 				if (targetValue!=='auto') {
1011 					numericTargetValue = parseFloat((targetValue + '').replace(priv.regex.getNumericCSSValue,'')) || 0;
1012 					s = self.typeOf(targetValue)==='string' && targetValue.match(priv.regex.getCSSValueUnits);
1013 					units = (s && s[0]) || 'px';
1014 					if (cssProp==='opacity') {
1015 						units = '';
1016 					}
1017 				}
1018 				else {
1019 					units = cssProp==='opacity' ? '' : 'px';
1020 				}
1021 
1022 				this._each(function(node, i) {
1023 					var ts, tss, testCssObj={}, iprop, vis;
1024 
1025 					startValues[i] = parseFloat((self.nodeStyle(node, cssProp) + '').replace(priv.regex.getNonIntegerCharsSigned,'')) || 0;
1026 
1027 					if (targetValue==='auto' || targetValue==='') {
1028 						vis = node.style.visibility || '';
1029 						testCssObj[cssProp] = targetValue;
1030 						testCssObj.visibility = 'hidden';
1031 						self.applyCss(node, testCssObj);
1032 						ts = self.nodeStyle(node, cssProp);
1033 						if (ts===targetValue || ts.indexOf('px')<ts.length-3) {
1034 							iprop = cssProp.substring(0,1).toUpperCase() + cssProp.substring(1).toLowerCase();
1035 							ts = node['offset'+iprop] + 'px';
1036 						}
1037 						tss = self.typeOf(ts)==='string' && ts.match(priv.regex.getCSSValueUnits);
1038 						perNodeProperties[i] = {
1039 							_actualTarget : targetValue,
1040 							numericTargetValue : parseFloat((ts + '').replace(priv.regex.getNumericCSSValue,'')) || 0,
1041 							units : cssProp==='opacity' ? '' : (tss && tss[0] || 'px')
1042 						};
1043 						setTimeout(function() {
1044 							node.style.visibility = vis;
1045 							node = null;
1046 						}, 51);
1047 						testCssObj = ts = tss = null;
1048 					}
1049 					else {
1050 						perNodeProperties[i] = {
1051 							numericTargetValue : numericTargetValue,
1052 							units : units
1053 						};
1054 					}
1055 				});
1056 
1057 				return this.animate(function(fraction, anim) {
1058 					this._each(function(node, i) {
1059 						var cssObj = {},
1060 							value = (fraction * (perNodeProperties[i].numericTargetValue-startValues[i]) + startValues[i]),
1061 							floatVal,
1062 							units = perNodeProperties[i].units;
1063 						if (units==='px') {
1064 							value = Math.round(value);
1065 						}
1066 						else {
1067 							floatVal = parseFloat(value);
1068 							if (floatVal%1===0) {
1069 								value = Math.round(floatVal);
1070 							}
1071 							else {
1072 								value = floatVal.toFixed(2);
1073 							}
1074 						}
1075 						cssObj[cssProp] = value + units;
1076 						self.applyCss(node, cssObj);
1077 					});
1078 				}, duration, easing, function(sel) {
1079 					sel._each(function(node, i) {
1080 						var cssObj = {};
1081 						if (perNodeProperties[i]._actualTarget) {
1082 							cssObj[cssProp] = perNodeProperties[i]._actualTarget;
1083 							self.applyCss(node, cssObj);
1084 						}
1085 					});
1086 					callback.apply(sel, arguments);
1087 				});
1088 			};
1089 
1090 			/**	@ignore */
1091 			cssTransition = function(cssProp, targetValue, duration, easing, callback) {
1092 				var anim = this._createAnimationObj(function(){}, duration, easing, callback),
1093 					me = this,
1094 					transition = {},
1095 					css = {};
1096 
1097 				cssProp = self.getStyleAsProperty(cssProp);
1098 				if (self.typeOf(targetValue)==='number' && (cssProp+'').toLowerCase()!=='opacity') {
1099 					targetValue = targetValue + 'px';
1100 				}
1101 
1102 				transition[self.getStyleAsCSS(cssProp)] = {
1103 					duration : anim.duration,
1104 					timingFunction : anim.easing
1105 				};
1106 
1107 				css[cssProp] = targetValue;
1108 
1109 				setTimeout(function() {
1110 					/**	@ignore */
1111 					me._each(function(node) {
1112 						self.updateCssTransitions(node, transition);
1113 						self.applyCss(node, css);
1114 						priv.incrementAnimationCount(node);
1115 					});
1116 					/**	@ignore */
1117 					anim._cb = function() {
1118 						if (anim) {
1119 
1120 							/** remove CSS transition definitions from the generated CSS:
1121 							 *	@ignore
1122 							 */
1123 							var nullTransition = {};
1124 							nullTransition[cssProp] = null;
1125 							me._each(function(node) {
1126 								self.updateCssTransitions(node, nullTransition);
1127 								priv.decrementAnimationCount(node);
1128 							});
1129 
1130 							if (anim.callback) {
1131 								anim.callback.call(me, me);
1132 							}
1133 							for (var x in anim) {
1134 								if (anim.hasOwnProperty(x)) {
1135 									try{ delete anim[x]; }catch(err){}
1136 								}
1137 							}
1138 						}
1139 						anim = css = callback = me = null;
1140 					};
1141 					setTimeout(anim._cb, (parseInt(anim.duration,10) || 0)+20);
1142 				}, 10);
1143 			};
1144 
1145 			/**	@ignore */
1146 			checkCssTransitionSupport = function() {
1147 				supportsCssTransition = document.body.style[vendorCssPrefixJS+'Transition']!==undefined || document.body.style.transition!==undefined;
1148 				return supportsCssTransition;
1149 			};
1150 
1151 			return function(cssProp, targetValue, duration, easing, callback) {
1152 				var iosCompat=false, x;
1153 				if (self.typeOf(supportsCssTransition)!=='boolean') {
1154 					checkCssTransitionSupport();
1155 				}
1156 				if ((self.allowCssTransitions!==false || iosCompat===true) && supportsCssTransition) {
1157 					cssTransition.apply(this, arguments);
1158 				}
1159 				else {
1160 					manual.apply(this, arguments);
1161 				}
1162 				return this;
1163 			};
1164 		}()),
1165 
1166 		animate : function(animator, duration, easing, callback) {
1167 			if (animator) {
1168 				var nodeSelection = this,
1169 					anim = this._createAnimationObj.apply(this, arguments),
1170 					frame;
1171 
1172 				this._each(function(node) {
1173 					priv.incrementAnimationCount(node);
1174 				});
1175 
1176 				frame = function(now) {
1177 					anim.frameTime = now;
1178 					anim.position = anim.frameTime - anim.start;
1179 					anim.fraction = anim.position / anim.duration;
1180 					if (anim.position>=anim.duration) {
1181 						anim.fraction = 1;
1182 						anim.position = anim.duration;
1183 					}
1184 					else if (anim.easingMethod) {
1185 						anim.fraction = anim.easingMethod.call(self.easingMethods, anim.fraction, anim);
1186 					}
1187 
1188 					anim.animator.call(nodeSelection, anim.fraction, anim);
1189 
1190 					if (anim.fraction===1) {
1191 						for (var x=nodeSelection._animations.length; x--; ) {
1192 							if (nodeSelection._animations[x]===anim) {
1193 								nodeSelection._animations.splice(x, 1);
1194 								break;
1195 							}
1196 						}
1197 						if (anim.callback) {
1198 							setTimeout(function() {
1199 								nodeSelection._each(function(node) {
1200 									priv.decrementAnimationCount(node);
1201 								});
1202 								anim.callback.call(nodeSelection, nodeSelection, anim);
1203 								nodeSelection = anim = null;
1204 							}, 10);
1205 						}
1206 					}
1207 					else {
1208 						anim.timer = self.animationFrame.getTimer(frame, self.baseAnimationInterval || 10);
1209 					}
1210 				};
1211 
1212 				self.animationFrame.getTimer(frame, self.baseAnimationInterval || 10);
1213 
1214 				this._animations.push(anim);
1215 			}
1216 			return this;
1217 		},
1218 
1219 		/**	@private */
1220 		_createAnimationObj : function(animator, duration, easing, callback) {
1221 			var anim = {
1222 				animator	: animator,
1223 				duration	: duration,
1224 				easing		: self.typeOf(easing)==='string' ? easing : 'ease',
1225 				callback	: callback,
1226 				start		: self.animationFrame.getStartTime(),
1227 				frameTime	: null
1228 			};
1229 
1230 			if (self.typeOf(anim.duration)==='string') {
1231 				switch (anim.duration.toLowerCase()) {
1232 					case 'long':
1233 					case 'slow':
1234 						anim.duration = priv.animationTimes.slow;
1235 						break;
1236 					case 'short':
1237 					case 'fast':
1238 						anim.duration = priv.animationTimes.fast;
1239 						break;
1240 					default:
1241 						anim.duration = parseInt(anim.duration, 10) || priv.animationTimes.medium;
1242 				}
1243 			}
1244 			else {
1245 				anim.duration = Math.round(anim.duration) || priv.animationTimes.medium;
1246 			}
1247 
1248 			if (priv.animationTimeScale) {
1249 				anim.duration *= priv.animationTimeScale;
1250 			}
1251 
1252 			if (anim.easing && self.easingMethods.hasOwnProperty(anim.easing)) {
1253 				anim.easingMethod = self.easingMethods[anim.easing];
1254 			}
1255 			else {
1256 				anim.easing = null;
1257 			}
1258 			return anim;
1259 		},
1260 
1261 		/**	Add a CSS class to the selection. <br />
1262 		 *	Pass an Array and/or multiple arguments to add multiple classes.
1263 		 *	@param {String} className		A CSS class to add.
1264 		 *	@returns {this}
1265 		 */
1266 		classify : function(className) {
1267 			var classes = self.isArray(className) ? className : self.toArray(arguments);
1268 			this._each(function(el) {
1269 				self.addClass(el, classes);
1270 			});
1271 			return this;
1272 		},
1273 
1274 		/**	Remove a CSS class to the selection. <br />
1275 		 *	Pass an Array and/or multiple arguments to remove multiple classes.
1276 		 *	@param {String} className		A CSS class to remove.
1277 		 *	@returns {this}
1278 		 */
1279 		declassify : function(className) {
1280 			var classes = self.isArray(className) ? className : self.toArray(arguments);
1281 			this._each(function(el) {
1282 				self.removeClass(el, classes);
1283 			});
1284 			return this;
1285 		},
1286 
1287 		/** Check if the selection contains only nodes with the given CSS class.
1288 		 *	@param {String} className		The CSS class to check for
1289 		 *	@param {Boolean} [ifAny=false]	If `true`, returns `true` only if *and* nodes have the given CSS class
1290 		 *	@returns {Boolean}
1291 		 */
1292 		hasClass : function(className, ifAny) {
1293 			var result = ifAny!==true;
1294 			this._each(function(node) {
1295 				var exists = node.classList ? node.classList.contains(className) : (' '+node.className+' ').indexOf(' '+className+' ')>-1;
1296 				if (ifAny===true) {
1297 					if (exists) {
1298 						result = true;
1299 						return false;
1300 					}
1301 				}
1302 				else if (!exists) {
1303 					result = false;
1304 				}
1305 			});
1306 			return result;
1307 		},
1308 
1309 		/** Set the opacity of each node in the selection.
1310 		 *	@param {Number} opacity		A value from 0 to 1
1311 		 *	@returns {this}
1312 		 */
1313 		setOpacity : function(opacity) {
1314 			this._each(function(el) {
1315 				self.setOpacity(el, opacity);
1316 			});
1317 			return this;
1318 		},
1319 
1320 		/** Call a method on each node in the selection and sum the results.
1321 		 *	@returns {Number}
1322 		 */
1323 		sumOf : function(method) {
1324 			var total = 0,
1325 				args = Array.prototype.slice.call(arguments, 1);
1326 			if (this.constructor.prototype.hasOwnProperty(method)) {
1327 				this._each(function(node) {
1328 					node = new self.NodeSelection(node);
1329 					total += node[method].apply(node, args);
1330 				});
1331 			}
1332 			return total;
1333 		},
1334 
1335 		height : function(height, options) {
1336 			var units,
1337 				node,
1338 				matches,
1339 				offsetHeight = 0;
1340 			if (self.typeOf(height)==='object' && !options) {
1341 				options = height;
1342 				height = null;
1343 			}
1344 			options = options || {};
1345 			if (height || height===0) {
1346 				height = height + '';
1347 				if (height==='auto') {
1348 					units = '';
1349 				}
1350 				else {
1351 					matches = (/^([\-0-9\.]+)(.*?)$/).exec(height);
1352 					height = Math.round(matches && matches[1] || height) || 0;
1353 					units = matches && matches[2] || 'px';
1354 				}
1355 				this.css({
1356 					height : height + units
1357 				});
1358 				return this;
1359 			}
1360 			this._each(function(node) {
1361 				offsetHeight += parseInt(node.offsetHeight, 10) || 0;
1362 				if (options.border!==true) {
1363 					offsetHeight -= parseInt( (self.nodeStyle(node, 'border-top-width')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1364 					offsetHeight -= parseInt( (self.nodeStyle(node, 'border-bottom-width')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1365 				}
1366 				if (options.margin===true) {
1367 					offsetHeight += parseInt( (self.nodeStyle(node, 'margin-top')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1368 					offsetHeight += parseInt( (self.nodeStyle(node, 'margin-bottom')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1369 				}
1370 				if (options.padding===false) {
1371 					offsetHeight -= parseInt( (self.nodeStyle(node, 'padding-top')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1372 					offsetHeight -= parseInt( (self.nodeStyle(node, 'padding-bottom')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1373 				}
1374 			});
1375 			return offsetHeight;
1376 		},
1377 		width : function(width, options) {
1378 			var units,
1379 				node,
1380 				matches,
1381 				offsetWidth = 0;
1382 			if (self.typeOf(width)==='object' && !options) {
1383 				options = width;
1384 				width = null;
1385 			}
1386 			options = options || {};
1387 			if (width || width===0) {
1388 				width = width + '';
1389 				if (width==='auto') {
1390 					units = '';
1391 				}
1392 				else {
1393 					matches = (/^([\-0-9\.]+)(.*?)$/).exec(width);
1394 					width = matches && matches[1] || width;
1395 					units = matches && matches[2] || 'px';
1396 				}
1397 				this.css({
1398 					width : width + units
1399 				});
1400 				return this;
1401 			}
1402 			this._each(function(node) {
1403 				offsetWidth += parseInt(node.offsetWidth, 10) || 0;
1404 				if (options.border!==true) {
1405 					offsetWidth -= parseInt( (self.nodeStyle(node, 'border-left-width')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1406 					offsetWidth -= parseInt( (self.nodeStyle(node, 'border-right-width')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1407 				}
1408 				if (options.margin===true) {
1409 					offsetWidth += parseInt( (self.nodeStyle(node, 'margin-left')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1410 					offsetWidth += parseInt( (self.nodeStyle(node, 'margin-right')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1411 				}
1412 				if (options.padding===false) {
1413 					offsetWidth -= parseInt( (self.nodeStyle(node, 'padding-left')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1414 					offsetWidth -= parseInt( (self.nodeStyle(node, 'padding-right')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0;
1415 				}
1416 			});
1417 			return offsetWidth;
1418 		},
1419 		x : function(absolute, mark) {
1420 			var node = this._nodes[0],
1421 				pos = null,
1422 				posProp;
1423 			if (node) {
1424 				pos = node.offsetLeft;
1425 				if (absolute===true) {
1426 					while((node=node.parentNode) && node!==document) {
1427 						pos += parseFloat(node.offsetLeft) || 0;
1428 					}
1429 				}
1430 			}
1431 			return pos;
1432 		},
1433 		y : function(absolute) {
1434 			var node = this._nodes[0],
1435 				pos = null;
1436 			if (node) {
1437 				pos = node.offsetTop;
1438 				if (absolute===true) {
1439 					while((node=node.parentNode) && node!==document) {
1440 						pos += parseFloat(node.offsetTop) || 0;
1441 					}
1442 				}
1443 			}
1444 			return pos;
1445 		},
1446 		position : function(x, y, tween, units) {
1447 			var css;
1448 			units = units || 'px';
1449 			if (arguments.length<1 || (arguments.length===1 && arguments[0]===true)) {
1450 				return {
1451 					x : this.x(arguments[0]===true),
1452 					y : this.y(arguments[0]===true)
1453 				};
1454 			}
1455 			else {
1456 				css = {};
1457 				if (puredom.typeOf(x)==='number') {
1458 					css.left = x + units;
1459 				}
1460 				else if (puredom.typeOf(x)==='string') {
1461 					css.left = x;
1462 				}
1463 				if (puredom.typeOf(y)==='number') {
1464 					css.top = y + units;
1465 				}
1466 				else if (puredom.typeOf(y)==='string') {
1467 					css.top = y;
1468 				}
1469 				this.css(css, tween);
1470 				return this;
1471 			}
1472 		},
1473 		scrollLeft : function(value) {
1474 			if (value || value===0) {
1475 				if (self.typeOf(value)!=='number') {
1476 					value = Math.round((value+'').replace(priv.regex.getNonIntegerCharsSigned,''));
1477 				}
1478 				this._each(function(node) {
1479 					node.scrollLeft = value;
1480 				});
1481 			}
1482 			else {
1483 				return this._nodes && this._nodes[0] && this._nodes[0].scrollLeft || 0;
1484 			}
1485 		},
1486 		scrollTop : function(value) {
1487 			if (value || value===0) {
1488 				if (self.typeOf(value)!=='number') {
1489 					value = Math.round((value+'').replace(priv.regex.getNonIntegerCharsSigned,''));
1490 				}
1491 				this._each(function(node) {
1492 					node.scrollTop = value;
1493 				});
1494 			}
1495 			else {
1496 				return this._nodes && this._nodes[0] && this._nodes[0].scrollTop || 0;
1497 			}
1498 		},
1499 		focus : function() {
1500 			this._each(function(node) {
1501 				if (node.focus) {
1502 					node.focus();
1503 				}
1504 			});
1505 			return this;
1506 		},
1507 		blur : function() {
1508 			this._each(function(node) {
1509 				if (node.blur) {
1510 					node.blur();
1511 				}
1512 			});
1513 			return this;
1514 		},
1515 		selectAll : function() {
1516 			this._each(function(node) {
1517 				if (node.SelectAll) {
1518 					node.SelectAll();
1519 				}
1520 				if (node.select) {
1521 					node.select();
1522 				}
1523 				if (node.selectionStart && node.hasOwnProperty('value')) {
1524 					node.selectionStart = 0;
1525 					node.selectionEnd = node.value.length;
1526 				}
1527 			});
1528 		},
1529 		getStyle : function(prop, returnValue) {
1530 			var props = [];
1531 			this._each(function(node) {
1532 				props.push( self.nodeStyle(node, prop) || null );
1533 			});
1534 			if (returnValue===false) {
1535 				this.pushResult(props);
1536 				return this;
1537 			}
1538 			return props.length<=1 ? props[0] : props;
1539 		},
1540 		value : function(newValue, options) {
1541 			options = options || {};
1542 			if (newValue!==null && newValue!==undefined && arguments.length>0) {
1543 				// set the value
1544 				this._each(function(node) {
1545 					var name = (node.nodeName+'').toLowerCase(),
1546 						type = (node.getAttribute('type') || '').toLowerCase();
1547 
1548 					if (name==='input' && (type==='checkbox' || type==='radio')) {
1549 						node.checked = !!newValue;
1550 					}
1551 					else {
1552 						node.value = newValue;
1553 					}
1554 
1555 					if (options.fireChange!==false) {
1556 						self.fireEvent({
1557 							type : 'change',
1558 							target : node,
1559 							value : newValue
1560 						});
1561 					}
1562 				});
1563 
1564 				return this;
1565 			}
1566 			else {
1567 				// get and return the value
1568 				var values = [];
1569 				this._each(function(node) {
1570 					var name = (node.nodeName+'').toLowerCase(),
1571 						type = (node.getAttribute('type') || '').toLowerCase(),
1572 						value;
1573 					if (name==='input' && (type==='checkbox' || type==='radio')) {
1574 						values.push(!!node.checked);
1575 					}
1576 					else if (name==='select') {
1577 						value = (node.multiselect || node.multiSelect) ? [] : null;
1578 						self.el(node).query('option')._each(function(option) {
1579 							if (option.selected || option.checked) {
1580 								if (self.isArray(value)) {
1581 									value.push(option.value);
1582 								}
1583 								else {
1584 									value = option.value;
1585 								}
1586 							}
1587 						});
1588 						values.push(value);
1589 					}
1590 					else {
1591 						values.push(node.value);
1592 					}
1593 				});
1594 				return values.length<2 ? values[0] : values;
1595 			}
1596 		},
1597 		attr : function(key, value, returnValue) {
1598 			var attrs = [], i, k;
1599 			if (arguments.length===0) {
1600 				attrs = {};
1601 				for (i=this._nodes[0].attributes.length; i--; ) {
1602 					k = this._nodes[0].attributes[i];
1603 					// skip over non-user-specified attributes in IE
1604 					if (k.specified) {
1605 						attrs[k.name || k.nodeName] = k.value || k.nodeValue;
1606 					}
1607 				}
1608 				return attrs;
1609 			}
1610 			else if (arguments.length>1) {
1611 				if (self.typeOf(key)==='object') {
1612 					for (i in key) {
1613 						if (typeof i==='string' && key.hasOwnProperty(i)) {
1614 							this.attr(attrs, key[i]);
1615 						}
1616 					}
1617 					return this;
1618 				}
1619 				return this._each(function(node) {
1620 					try {
1621 						node.setAttribute(key, value);
1622 						if (node.removeAttribute && (value===null || value===undefined)) {
1623 							node.removeAttribute(key);
1624 						}
1625 					} catch(err) {}
1626 				});
1627 			}
1628 			else {
1629 				this._each(function(node) {
1630 					var a = node.getAttribute(key);
1631 					if (typeof a!=='string') {
1632 						a = null;
1633 					}
1634 					attrs.push( a );
1635 				});
1636 				if (returnValue===false) {
1637 					this.pushResult(attrs);
1638 					return this;
1639 				}
1640 				return attrs.length<=1 ? attrs[0] : attrs;
1641 			}
1642 		},
1643 		prop : function(key, value, returnValue) {
1644 			var props = [];
1645 			if (arguments.length>1) {
1646 				return this._each(function(node) {
1647 					node[key] = value;
1648 					if (value===undefined) {
1649 						try{ delete node[key]; }catch(err){}
1650 					}
1651 				});
1652 			}
1653 			else {
1654 				this._each(function(node) {
1655 					var val;
1656 					try {
1657 						val = node[key];
1658 					}catch(err){
1659 						self.log('NodeSelection#prop('+key+') :: Access Error', err);
1660 					}
1661 					props.push( val || null );
1662 				});
1663 				if (returnValue===false) {
1664 					this.pushResult(props);
1665 					return this;
1666 				}
1667 				return props.length<=1 ? props[0] : props;
1668 			}
1669 		},
1670 		enable : function() {
1671 			this.attr('disabled', null);
1672 			return this;
1673 		},
1674 		disable : function() {
1675 			this.attr('disabled', 'disabled');
1676 			return this;
1677 		},
1678 		enabled : function(newValue) {
1679 			if (newValue===true || newValue===false) {
1680 				this[newValue?'enable':'disable']();
1681 				return this;
1682 			}
1683 			else {
1684 				return this.attr('disabled')!=='disabled' && this.prop('disabled')!==true;
1685 			}
1686 		},
1687 
1688 		/**	Register an event handler. <br />
1689 		 *	When an event of the given type is triggered, the handler function is called.
1690 		 *	@param {String} type			An event type to listen for
1691 		 *	@param {String} [selector]		Optionally fire only if the event target matches a CSS selector
1692 		 *	@param {Function} handler		A handler to call in response to the event
1693 		 *	@example
1694 		 *		function clickHandler(e){ alert(e.button); }
1695 		 *		foo.addEvent("click", clickHandler);
1696 		 *	@returns {this}
1697 		 */
1698 		on : function(type, selector, handler) {
1699 			this._each(function(el) {
1700 				self.addEvent(el, type, selector, handler);
1701 			});
1702 			return this;
1703 		},
1704 
1705 		/**	Un-register an event handler.
1706 		 *	@param {String} type			The event type
1707 		 *	@param {String} [selector]		Optionally fire only if the target matches a CSS selector
1708 		 *	@param {Function} handler		The handler to remove
1709 		 *	@example
1710 		 *		foo.removeEvent("click", clickHandler);
1711 		 *	@returns {this}
1712 		 */
1713 		off : function(type, selector, handler) {
1714 			this._each(function(el) {
1715 				self.removeEvent(el, type, selector, handler);
1716 			});
1717 			return this;
1718 		},
1719 
1720 		/**	Fire an event on the selection.
1721 		 *	@param {String} type		An event type
1722 		 *	@param {Object|Event} e		The event data
1723 		 *	@returns {this}
1724 		 */
1725 		trigger : function(type, e) {
1726 			this._each(function(node) {
1727 				self.fireEvent(self.extend({}, e || {}, {
1728 					type : type,
1729 					target : node
1730 				}));
1731 			});
1732 			return this;
1733 		},
1734 
1735 		/**	@private */
1736 		_removeAllEvents : function(deep) {
1737 			var children;
1738 			this._each(function(node) {
1739 				priv.wrappedEventListener.destroyObjHandlers(node, true);
1740 			});
1741 			children = this.children();
1742 			if (deep===true && children.count()>0) {
1743 				children._removeAllEvents(true);
1744 			}
1745 			children = deep = null;
1746 			return this;
1747 		},
1748 
1749 		/**	Append an element to the DOM.
1750 		 *	@param {puredom.NodeSelection|HTMLElement} child	An element or a Selection of elements to append
1751 		 *	@returns {this}
1752 		 */
1753 		appendChild : function(child) {
1754 			if (child && this._nodes.length>0) {
1755 				if (child.constructor===this.constructor) {
1756 					var parent = this._nodes[0];
1757 					child._each(function(node) {
1758 						parent.appendChild(node);
1759 					});
1760 				}
1761 				else {
1762 					this._nodes[0].appendChild(child);
1763 				}
1764 			}
1765 			return this;
1766 		},
1767 
1768 		/**	Insert an element into the DOM before a given reference element.
1769 		 *	@param {puredom.NodeSelection|HTMLElement} child	An element or a Selection of elements to insert
1770 		 *	@param {puredom.NodeSelection|HTMLElement} before	An element or a Selection to insert <code>child</code> before
1771 		 *	@returns {this}
1772 		 */
1773 		insertBefore : function(child, before) {
1774 			if (child && this._nodes.length>0) {
1775 				if (before && before.constructor===this.constructor) {
1776 					before = before._nodes[0];
1777 				}
1778 				if (!before || before.parentNode!==this._nodes[0]) {
1779 					return this.appendChild(child);
1780 				}
1781 				if (child.constructor===this.constructor) {
1782 					var parent = this._nodes[0];
1783 					child._each(function(node) {
1784 						parent.insertBefore(node, before);
1785 					}, null, true);
1786 				}
1787 				else {
1788 					this._nodes[0].insertBefore(child, before);
1789 				}
1790 			}
1791 			return this;
1792 		},
1793 
1794 		/**	Insert all elements in the Selection into a given parent. <br />
1795 		 *	Uses document fragments to improve performance when inserting a Selection containing multiple nodes.
1796 		 *	@param {puredom.NodeSelection|HTMLElement} what		A parent into which the selection should be inserted
1797 		 *	@returns {this}
1798 		 */
1799 		insertInto : function(what) {
1800 			var frag;
1801 			if (what && this.count()>0) {
1802 				if (what.constructor===this.constructor) {
1803 					what = what._nodes[0];
1804 				}
1805 				if (this.count()===1) {
1806 					what.appendChild(this._nodes[0]);
1807 				}
1808 				else {
1809 					frag = document.createDocumentFragment();
1810 					this._each(function(node) {
1811 						frag.appendChild(node);
1812 					}, null, true);
1813 					what.appendChild(frag);
1814 				}
1815 			}
1816 			return this;
1817 		},
1818 
1819 		/**	Remove all elements in the Selection from the DOM.
1820 		 *	@returns {this}
1821 		 */
1822 		remove : function() {
1823 			this.fireEvent('remove');
1824 			this._each(function(node) {
1825 				if (node.parentNode) {
1826 					node.parentNode.removeChild(node);
1827 				}
1828 			});
1829 			return this;
1830 		},
1831 
1832 		/**	Remove all elements in the Selection from the DOM, and <strong>destroy them</strong>. <br />
1833 		 *	<strong>Note:</strong> This also removes all elements from the Selection.
1834 		 *	@returns {this}
1835 		 */
1836 		destroy : function() {
1837 			this.remove();
1838 			this.fireEvent('destroy');
1839 			this._removeAllEvents(true);
1840 			this._nodes.splice(0, this._nodes.length);
1841 			return this;
1842 		},
1843 
1844 		/**	Search for elements within the Selection's tree that match the given CSS selector. <br />
1845 		 *	<strong>Note:</strong> This is a scoped version of puredom(selector).
1846 		 *	@param {String} selector	A CSS selector
1847 		 *	@param {Object} [options]	Options, which get passed to puredom()
1848 		 *	@returns {puredom.NodeSelection} results
1849 		 */
1850 		query : function(selector, options) {
1851 			var results = [];
1852 			if (this._nodes.length>0) {
1853 				options = puredom.extend({}, options || {});
1854 				this._each(function(node) {
1855 					var r;
1856 					options.within = node;
1857 					r = self.selectorEngine.query(selector, options);
1858 					if (self.isArray(r) && r.length>0) {
1859 						results = results.concat(r);
1860 					}
1861 				});
1862 			}
1863 			return new self.NodeSelection(results);
1864 		},
1865 
1866 		/**	Clone the selection, optionally into a new parent node.
1867 		 *	@param {Boolean} [deep=true]	Perform a deep clone, which clones all descendent nodes.
1868 		 *	@param {Object} [newParent]		Optionally inject into a new parentNode. Pass <code>true</code> to clone into the same parent.
1869 		 *	@returns {puredom.NodeSelection} clonedSelection
1870 		 */
1871 		clone : function(deep, newParent) {
1872 			var sel = [];
1873 			if (newParent===true) {
1874 				newParent = this.parent();
1875 			}
1876 			this._each(function(node) {
1877 				sel.push( node.cloneNode(deep!==false) );
1878 			});
1879 			sel = new self.NodeSelection(sel);
1880 			if (newParent) {
1881 				newParent.appendChild(sel);
1882 			}
1883 			sel._each(priv.removeNodeUID);
1884 			if (deep!==false) {
1885 				sel.query('*')._each(priv.removeNodeUID);
1886 			}
1887 			return sel;
1888 		},
1889 
1890 		/**	Get the number of elements in the Selection.
1891 		 *	@returns {Number} count
1892 		 */
1893 		count : function() {
1894 			return this._nodes.length;
1895 		},
1896 
1897 		/**	Check if the selection contains at least one element.
1898 		 *	@returns {Boolean} exists
1899 		 */
1900 		exists : function() {
1901 			return this.count()>0;
1902 		},
1903 
1904 		/**	Get a new Selection containing both the next and previous sibling elements of each element in the current Selection.
1905 		 *	@returns {puredom.NodeSelection} siblings
1906 		 */
1907 		siblings : function() {
1908 			var newSelection = new self.NodeSelection();
1909 			this._each(function(node) {
1910 				var n = node;
1911 				while ((n=n.previousSibling)) {
1912 					if (n.nodeName!=='#text' && n.nodeName!=='#comment' && n.nodeType!==3) {
1913 						newSelection._nodes.push(n);
1914 					}
1915 				}
1916 				n = node;
1917 				while ((n=n.nextSibling)) {
1918 					if (n.nodeName!=='#text' && n.nodeName!=='#comment' && n.nodeType!==3) {
1919 						newSelection._nodes.push(n);
1920 					}
1921 				}
1922 			});
1923 			return newSelection;
1924 		},
1925 
1926 		/**	Get a new Selection containing the next sibling elements of each element in the current Selection.
1927 		 *	@returns {puredom.NodeSelection} nextSiblings
1928 		 */
1929 		next : function() {
1930 			var sib = this._nodes[0] && this._nodes[0].nextSibling;
1931 			while (sib && sib.nodeType===3) {
1932 				sib = sib.nextSibling;
1933 			}
1934 			return new self.NodeSelection(sib && [sib] || null);
1935 		},
1936 
1937 		/**	Get a new Selection containing the previous sibling elements of each element in the current Selection.
1938 		 *	@returns {puredom.NodeSelection} previousSiblings
1939 		 */
1940 		prev : function() {
1941 			var sib = this._nodes[0] && this._nodes[0].previousSibling;
1942 			while (sib && sib.nodeType===3) {
1943 				sib = sib.previousSibling;
1944 			}
1945 			return new self.NodeSelection(sib && [sib] || null);
1946 		},
1947 
1948 		/**	Alais of {@link puredom.NodeSelection#prev} */
1949 		previous : function() {
1950 			return this.prev.apply(this,arguments);
1951 		},
1952 
1953 		/**	Get a new Selection containing the first direct child element of the current Selection.
1954 		 *	@returns {puredom.NodeSelection} firstChild
1955 		 */
1956 		firstChild : function() {
1957 			return this.children().first();
1958 		},
1959 
1960 		/**	Get a new Selection containing the last direct child element of the current Selection.
1961 		 *	@returns {puredom.NodeSelection} firstChild
1962 		 */
1963 		lastChild : function() {
1964 			return this.children().last();
1965 		},
1966 
1967 		/**	Get a new Selection containing the direct child element of the current Selection at index <code>n</code>.
1968 		 *	@param {Number} n		A 0-based index.
1969 		 *	@returns {puredom.NodeSelection} nthChild
1970 		 */
1971 		nthChild : function(n) {
1972 			return this.children().index(n);
1973 		},
1974 
1975 		/**	Get a new Selection containing only the first element in the current Selection.
1976 		 *	@returns {puredom.NodeSelection} first
1977 		 */
1978 		first : function(n) {
1979 			return new self.NodeSelection( this._nodes.slice(0, n || 1) );
1980 		},
1981 
1982 		/**	Get a new Selection containing only the last element in the current Selection.
1983 		 *	@returns {puredom.NodeSelection} last
1984 		 */
1985 		last : function(n) {
1986 			return new self.NodeSelection( this._nodes.slice(this._nodes.length-(n || 1)) );
1987 		},
1988 
1989 		/**	Get a new Selection containing only the <code>n</code> element(s) in the current Selection starting at index <code>i</code>.
1990 		 *	@param {Number} i		A 0-based index of the elements(s) to return
1991 		 *	@param {Number} [n=1]	The number of elements to return
1992 		 *	@returns {puredom.NodeSelection} slice
1993 		 */
1994 		index : function(i, n) {
1995 			return new self.NodeSelection(self.typeOf(i)==='number' && this._nodes.slice(i, i + (n || 1)) || null);
1996 		},
1997 
1998 		/**	Get a new Selection containing the de-duped parent elements of the current Selection.
1999 		 *	@returns {puredom.NodeSelection} parents
2000 		 */
2001 		parent : function() {
2002 			var nodes=[], parent;
2003 			this._each(function(node) {
2004 				parent = node.parentNode;
2005 				// Note: all newly created elements are placed into a document fragment in IE.
2006 				// Unfortunately, this means parentNodes that are #document-fragment's can't be considered valid (lowest-common).
2007 				if (parent && nodes.indexOf(parent)<0 && parent.nodeType!==11) {
2008 					nodes.push(parent);
2009 				}
2010 			});
2011 			return new self.NodeSelection(nodes);
2012 		},
2013 
2014 		/**	Get a new Selection containing all direct child elements of the current Selection.
2015 		 *	@returns {puredom.NodeSelection} children
2016 		 */
2017 		children : function() {
2018 			var children = [],
2019 				x, y;
2020 			if (this._nodes.length>0) {
2021 				for (x=0; x<this._nodes.length; x++) {
2022 					if (this._nodes[x].childNodes) {
2023 						for (y=0; y<this._nodes[x].childNodes.length; y++) {
2024 							if (this._nodes[x].childNodes[y].nodeType!==3 && this._nodes[x].childNodes[y].nodeName!=='#text' && this._nodes[x].childNodes[y].nodeName!=='#comment') {
2025 								children.push(this._nodes[x].childNodes[y]);
2026 							}
2027 						}
2028 					}
2029 				}
2030 			}
2031 			return new self.NodeSelection(children);
2032 		},
2033 
2034 		/**	Submit any forms in the Selection.
2035 		 *	@returns {this}
2036 		 */
2037 		submit : function() {
2038 			return this._each(function(node) {
2039 				var evt = self.fireEvent({
2040 					type : 'submit',
2041 					target : node
2042 				});
2043 				if (evt.rval!==false && evt.returnValue!==false && evt.preventDefault!==true) {
2044 					if (node.submit) {
2045 						node.submit();
2046 					}
2047 				}
2048 			});
2049 		},
2050 
2051 		/** Get or set the selected text of an input element. <br />
2052 		 *	<strong>Note:</strong> This only operates on the first element in the selection.
2053 		 *	@param {Number} start			A character index at which to start text selection
2054 		 *	@param {Number} [end=start]		A character index at which to end text selection
2055 		 *	@returns {Object} An object with <code>start</code>, <code>end</code> and <code>text</code> properties correspdonding to the current selection state.
2056 		 */
2057 		selection : function(start, end) {
2058 			var el = this._nodes[0],
2059 				value, sel, before, endMax, range;
2060 			if (start && typeof start!=='number' && start.start) {
2061 				end = start.end;
2062 				start = start.start;
2063 			}
2064 			if (typeof start==='number') {
2065 				if (start<0) {
2066 					start = 0;
2067 				}
2068 				endMax = el.value.length;
2069 				if (end>endMax) {
2070 					end = endMax;
2071 				}
2072 				if (start>end) {
2073 					start = end;
2074 				}
2075 				else if (end<start) {
2076 					end = start;
2077 				}
2078 
2079 				if(window.getSelection) {
2080 					el.selectionStart = start;
2081 					el.selectionEnd = end;
2082 				}
2083 				else if (el.selectionEnd || el.selectionEnd===0) {
2084 					el.selectionStart = start;
2085 					el.selectionEnd = end;
2086 				}
2087 				else if (el.createTextRange) {
2088 					range = el.createTextRange();
2089 					range.collapse(true);
2090 					range.moveStart('character', start);
2091 					range.moveEnd('character', end);
2092 					range.select();
2093 				}
2094 			}
2095 			else {
2096 				if (window.getSelection) {					// Stanadards
2097 					value = typeof el.value==='string' ? el.value : el.innerHTML;
2098 					sel = window.getSelection();
2099 					return {
2100 						start	: el.selectionStart+0,
2101 						end		: el.selectionEnd+0,
2102 						text	: value.substring(sel.start, sel.end)
2103 					};
2104 				}
2105 				else if (document.selection) {				// IE
2106 					sel = document.selection.createRange();
2107 					before = document.body.createTextRange();
2108 					before.moveToElementText(el);
2109 					before.setEndPoint("EndToStart", sel);
2110 					return {
2111 						start	: before.text.length,
2112 						end		: before.text.length + sel.text.length,
2113 						text	: sel.text
2114 					};
2115 				}
2116 				else {
2117 					self.log("Selection retrieval is not supported in this browser.");
2118 					return {
2119 						start	: 0,
2120 						end		: 0,
2121 						text	: ''
2122 					};
2123 				}
2124 			}
2125 			return this;
2126 		},
2127 
2128 		/**	Template the Selection and all descendants based on <code>data-tpl-id</code> attributes. <br />
2129 		 *	Each element with a <code>data-tpl-id</code> attribute will have it's contents updated with the corresponding value of that attribute when interpreted as a dot-notated key path within <code>templateFields</code>. <br />
2130 		 *	<code>data-tpl-id</code> attribute values can optionally include a pipe-separated list of "filters". <br />
2131 		 *	<strong>Example:</strong> <br />
2132 		 *		<pre><span data-tpl-id="article.title|ucWords|truncate:byWord,300|htmlEntities"></span></pre> <br />
2133 		 *	<em><strong>Note:</strong> the "htmlEntities" filter is already added for you where needed.</em>
2134 		 *	@param {Object} [templateFields={}]		Template data fields. Nested Objects/Arrays are addressable via dot notation.
2135 		 *	@returns {this}
2136 		 */
2137 		template : function(templateFields) {
2138 			var attrName = self.templateAttributeName,
2139 				getFilters;
2140 			templateFields = templateFields || {};
2141 
2142 			getFilters = function(filters, htmlEntities) {
2143 				for (var i=filters.length; i--; ) {
2144 					if (filters[i]==='htmlEntities') {
2145 						filters.splice(i, 1);
2146 					}
2147 				}
2148 				return filters;
2149 			};
2150 
2151 			this.query('['+attrName+']').each(function(node) {
2152 				var nodeName = node.nodeName(),
2153 					tpl = node.attr(attrName),
2154 					key, tplField, tplValue, tplFilters, nType, keyMatches;
2155 
2156 				tpl = ~tpl.indexOf(':') ? priv.parseCSS(tpl, false) : { 'set':tpl };
2157 
2158 				for (key in tpl) {
2159 					if (tpl.hasOwnProperty(key)) {
2160 
2161 						tplField = tpl[key].split('|');
2162 						tplFilters = getFilters(tplField.slice(1));
2163 						tplField = tplField[0];
2164 
2165 						tplValue = puredom.delve(templateFields, tplField);
2166 
2167 						if (tplValue!==null && tplValue!==undefined) {
2168 							if ((tplValue instanceof Date || tplValue.constructor.name==='Date') && tplValue.toLocaleString) {
2169 								tplValue = tplValue.toLocaleString();
2170 							}
2171 							if (tplFilters && tplFilters.length) {
2172 								tplValue = self.text.filter(tplValue, tplFilters.join('|'));
2173 							}
2174 
2175 							keyMatches = key.match(/^([a-z]+)\-(.+)$/i);
2176 
2177 							if (keyMatches && typeof node[keyMatches[1]]==='function') {
2178 								node[keyMatches[1]](keyMatches[2], tplValue);
2179 							}
2180 							else if (typeof node[key]==='function') {
2181 								node[key](tplValue);
2182 							}
2183 							else if ( (nType = node.attr('data-tpl-prop')) ) {
2184 								node.prop(nType, tplValue);
2185 							}
2186 							else {
2187 								switch (nodeName) {
2188 									case 'select':
2189 									case 'input':
2190 									case 'textarea':
2191 									case 'meter':
2192 									case 'progress':
2193 										node.value(tplValue);
2194 										break;
2195 
2196 									case 'img':
2197 									case 'video':
2198 									case 'audio':
2199 									case 'iframe':
2200 										node.attr('src', tplValue);
2201 										break;
2202 
2203 									default:
2204 										//node.html(self.text.htmlEntities(tplValue));
2205 										node.text(tplValue);
2206 										break;
2207 								}
2208 							}
2209 						}
2210 					}
2211 				}
2212 			});
2213 			templateFields = null;
2214 			return this;
2215 		}
2216 	});
2217 
2218 	/**	Alias of {@link puredom.NodeSelection#trigger}
2219 	 *	@function
2220 	 */
2221 	self.NodeSelection.prototype.fireEvent = self.NodeSelection.prototype.trigger;
2222 
2223 	/**	Alias of {@link puredom.NodeSelection#trigger}
2224 	 *	@function
2225 	 */
2226 	self.NodeSelection.prototype.emit = self.NodeSelection.prototype.trigger;
2227 
2228 	/**	Alias of {@link puredom.NodeSelection#on}
2229 	 *	@function
2230 	 */
2231 	self.NodeSelection.prototype.addEvent = self.NodeSelection.prototype.on;
2232 
2233 	/**	Alias of {@link puredom.NodeSelection#off}
2234 	 *	@function
2235 	 */
2236 	self.NodeSelection.prototype.removeEvent = self.NodeSelection.prototype.off;
2237 
2238 	/**	@ignore */
2239 	self.NodeSelection.prototype.animateCss = self.NodeSelection.prototype.animateCSS;
2240 
2241 
2242 
2243 	/**	@private */
2244 	priv.incrementAnimationCount = function(node) {
2245 		node._puredom_animationCount = priv.getAnimationCount(node) + 1;
2246 		if (node._puredom_animationCount===1) {
2247 			self.addClass(node, '_puredom_animating');
2248 		}
2249 	};
2250 	/**	@private */
2251 	priv.decrementAnimationCount = function(node) {
2252 		var current = Math.max(0, priv.getAnimationCount(node));
2253 		if (current>1) {
2254 			node._puredom_animationCount = current - 1;
2255 		}
2256 		else {
2257 			node._puredom_animationCount = null;
2258 			self.removeClass(node, '_puredom_animating');
2259 		}
2260 	};
2261 	/**	@private */
2262 	priv.getAnimationCount = function(node) {
2263 		return parseInt(node._puredom_animationCount, 10) || 0;
2264 	};
2265 
2266 
2267 
2268 	/**	Destroy and cleanup puredom.
2269 	 *	@private
2270 	 */
2271 	priv.unload = function() {
2272 		priv.wrappedEventListener.reset();
2273 		self.selectorEngine.clearCache();
2274 		priv._nodeToIdList = {};
2275 		setTimeout(function() {
2276 			window.puredom = priv = objConstructor = getSupportedTextContentProperty = null;
2277 		}, 10);
2278 	};
2279 
2280 
2281 	/**	Create or retrieve one or more elements based on a query. <br />
2282 	 *	If query begins with "<" or is an object, a new element is contructed based on that information. <br />
2283 	 *	If the query is a CSS selector, DOM nodes matching that selector are returned.
2284 	 *	@param {String|Object} query	A CSS selector (retrieval), or a DOM description (creation).
2285 	 *	@param {Boolean} [log=false]	If true, query process will be logged to the console.
2286 	 *	@returns {puredom.NodeSelection} selection
2287 	 */
2288 	self.el = function(query, log) {
2289 		var results, type;
2290 		if (query) {
2291 			type = typeof query;
2292 			if (type==='string' && query.charAt(0)!=='<') {
2293 				if (log===true) {
2294 					self.log('query is a CSS selector', query, type);
2295 				}
2296 				if (query==='body') {
2297 					results = document.body;
2298 				}
2299 				else if (query==='html') {
2300 					results = document.documentElement || document;
2301 				}
2302 				else {
2303 					results = self.selectorEngine.query(query, arguments[1]);
2304 				}
2305 			}
2306 			else if (self.isArray(query)) {
2307 				results = [];
2308 				for (var x=0; x<query.length; x++) {
2309 					Array.prototype.splice.apply(results, [0,0].concat(self.el(query[x])._nodes) );
2310 				}
2311 			}
2312 			else if (type==='string' || (type==='object' && !query.nodeName && query!==window)) {
2313 				if (log===true) {
2314 					self.log('query is an HTML fragment', query, type);
2315 				}
2316 				results = self.createElement.apply(self, arguments);
2317 			}
2318 			else if (query.constructor===self.NodeSelection) {
2319 				if (log===true) {
2320 					self.log('query is already a NodeSelection', query.constructor+'', query.constructor.name);
2321 				}
2322 				return query;
2323 			}
2324 			else if (query.nodeName || query===window) {
2325 				if (log===true) {
2326 					self.log('query is an HTML element', query, type);
2327 				}
2328 				if (query===window) {
2329 					query = document.documentElement || document;
2330 				}
2331 				results = query;
2332 			}
2333 		}
2334 		return new self.NodeSelection(results);
2335 	};
2336 
2337 
2338 
2339 	/**	Get a selection ({@link puredom.NodeSelection}) containing the node with the given UUID. <br />
2340 	 *	UniqueIDs can be retrieved using {@link puredom.NodeSelection#uuid}.
2341 	 *	@param {String} uuid						Unique node ID, such as one derived from {@link puredom.NodeSelection#uuid}.
2342 	 *	@returns {puredom.NodeSelection} selection	A selection ({@link puredom.NodeSelection}) containing the referenced node.
2343 	 */
2344 	self.node = function(uuid) {
2345 		return new self.NodeSelection(priv.idToNode(uuid));
2346 	};
2347 
2348 
2349 	// Events
2350 	function cancelEvent(e) {
2351 		if (e.stopPropagation) {
2352 			e.stopPropagation();
2353 		}
2354 		try {
2355 			if (e.cancelBubble && e.cancelBubble.call) {
2356 				e.cancelBubble();
2357 			}
2358 			else {
2359 				e.cancelBubble = true;
2360 			}
2361 		} catch(err) {}
2362 	}
2363 
2364 	/**	@class Represents a DOM event.
2365 	 *	@name puredom.DOMEvent
2366 	 */
2367 	self.DOMEvent = function DOMEvent(type) {
2368 		if (type) {
2369 			this.type = type.replace(/^on/gi,'');
2370 		}
2371 	};
2372 	self.DOMEvent.displayName = 'puredom.DOMEvent';
2373 
2374 	self.extend(self.DOMEvent.prototype, /** @lends puredom.DOMEvent# */ {
2375 
2376 		/**	Which mouse button or key generated the action (if applicable)
2377 		 *	@type Number
2378 		 */
2379 		which	: null,
2380 
2381 		/**	The triggered event type (with no "on"-prefix)
2382 		 *	@type String
2383 		 */
2384 		type	: '',
2385 
2386 		/**	The DOM node that originated the event.
2387 		 *	@type {Element}
2388 		 */
2389 		target	: null,
2390 
2391 		/**	When available, refers to a DOM node that aided in originating the event (such as the DOM node the mouse was *previously* overtop of).
2392 		 *	@type {Element}
2393 		 */
2394 		relatedTarget : null,
2395 
2396 		/**	Prevent the event's browser-default action from occurring.
2397 		 *	@function
2398 		 */
2399 		preventDefault : function() {
2400 			this.defaultPrevented = true;
2401 		},
2402 
2403 		/**	Stop bubbling.
2404 		 *	@function
2405 		 */
2406 		stopPropagation : function(){
2407 			this.propagationStopped = true;
2408 			this.bubblingCancelled = true;
2409 		},
2410 
2411 		/**	Stop bubbling, prevent the browser-default action and set the event's returned value to false.
2412 		 *	@function
2413 		 */
2414 		cancel : function() {
2415 			this.preventDefault();
2416 			this.stopPropagation();
2417 			this.returnValue = false;
2418 			return false;
2419 		},
2420 
2421 		/**	Represents the handler's return value.
2422 		 *	@type Boolean
2423 		 */
2424 		returnValue : true,
2425 
2426 		/**	The contained raw DOM Event.
2427 		 *	@type DOMEvent
2428 		 */
2429 		originalEvent : null,
2430 
2431 		/**	The timestamp when the event was triggered.
2432 		 *	@type Number
2433 		 */
2434 		timeStamp : null
2435 	});
2436 
2437 	/**	Alias of {@link puredom.DOMEvent#stopPropagation}, provided only for backward compatibility.
2438 	 *	@function
2439 	 */
2440 	self.DOMEvent.prototype.cancelBubble = self.DOMEvent.prototype.stopPropagation;
2441 
2442 	/**	Alias of {@link puredom.DOMEvent#cancel}, provided only for compatibility with other notable DOM libraries.
2443 	 *	@function
2444 	 */
2445 	self.DOMEvent.prototype.stop = self.DOMEvent.prototype.cancel;
2446 
2447 	/**	@deprecated
2448 	 *	@private
2449 	 */
2450 	self.DOMEvent.prototype.prevent = self.DOMEvent.prototype.cancel;
2451 
2452 
2453 
2454 
2455 	/**	@private */
2456 	priv.wrappedEventListener = {
2457 		list : [],
2458 		none : {},
2459 
2460 		/**	@private */
2461 		summary : function() {
2462 			for (var x=0; x<this.list.length; x++) {
2463 				self.log( priv.idToNode(this.list[x].target), '.on', this.list[x].type, ' -> ', (this.list[x].handler.displayName || this.list[x].handler.name) );
2464 			}
2465 		},
2466 
2467 		/**	@private */
2468 		reset : function(removeEvents) {
2469 			var i, evt;
2470 			if (removeEvents===true) {
2471 				for (i=this.list.length; i--; ) {
2472 					evt = this.list[i];
2473 					this.list[i] = this.none;
2474 					self.removeEvent(priv.idToNode(evt.target), evt.type, evt.selector, evt.wrappedHandler);
2475 					this.unsetRefs(evt);
2476 					window.killCount = (window.killCount || 0) + 1;
2477 				}
2478 			}
2479 			this.list.splice(0, this.list.length);
2480 		},
2481 
2482 		/**	@private */
2483 		destroyObjHandlers : function(obj) {
2484 			var i, evt,
2485 				objId = priv.nodeToId(obj);
2486 			for (i=this.list.length; i--; ) {
2487 				evt = this.list[i];
2488 				if (evt.target===objId) {
2489 					this.unsetRefs(evt);
2490 					this.list.splice(i, 1);
2491 					self.removeEvent(obj, evt.type, evt.selector, evt.wrappedHandler);
2492 					window.killCount = (window.killCount || 0) + 1;
2493 				}
2494 			}
2495 		},
2496 
2497 		/**	@private */
2498 		get : function(type, handler, obj, selector, andDestroy) {
2499 			var i, evt;
2500 			selector = selector || null;
2501 			obj = priv.nodeToId(obj);
2502 			for (i=this.list.length; i--; ) {
2503 				evt = this.list[i];
2504 				if (evt.target===obj && evt.handler===handler && evt.selector===selector && evt.type===type) {
2505 					handler = evt.wrappedHandler;
2506 					if (andDestroy===true) {
2507 						this.list.splice(i, 1);
2508 						window.killCount = (window.killCount || 0) + 1;
2509 						this.unsetRefs(evt);
2510 					}
2511 					break;
2512 				}
2513 			}
2514 			// fall back to the original handler
2515 			return handler;
2516 		},
2517 
2518 		/**	@private */
2519 		unsetRefs : function(item) {
2520 			item.wrappedHandler.type = null;
2521 			item.wrappedHandler.handler = null;
2522 			item.wrappedHandler.target = null;
2523 		},
2524 
2525 		/**	@private */
2526 		internalFireEvent : function(event) {
2527 			var target = priv.nodeToId(event.target),
2528 				type = event.type.replace(/^on/gm,''),
2529 				i, item, returnValue;
2530 			for (i=this.list.length; i--; ) {
2531 				item = this.list[i];
2532 				if (item.target===target && item.type===type) {
2533 					returnValue = item.handler.call(event.target, event);
2534 					if (returnValue===false) {
2535 						break;
2536 					}
2537 				}
2538 			}
2539 		},
2540 
2541 		/**	@private */
2542 		create : function(type, handler, obj, selector) {
2543 			selector = selector || null;
2544 			var evt = {
2545 				type	: type,
2546 				target	: priv.nodeToId(obj),
2547 				selector : selector,
2548 				handler	: handler,
2549 				/**	@ignore */
2550 				wrappedHandler : function wrappedHandler(e) {
2551 					var handler = wrappedHandler.handler,
2552 						type = (wrappedHandler.type || e.type).toLowerCase().replace(/^on/,''),
2553 						originalTarget = this!==window ? this : (priv && priv.idToNode(wrappedHandler.target)),
2554 						fireTarget, event, i,
2555 						d = {
2556 							isInSelf : false,
2557 							doPreventDefault : false,
2558 							doCancelBubble : false,
2559 							doStopPropagation : false,
2560 							e : e || window.event,
2561 							ret : null,
2562 							/**	@ignore */
2563 							end : function() {
2564 								d.end = null;
2565 								e = event = d = handler = type = originalTarget = null;
2566 								return this.ret;
2567 							}
2568 						};
2569 					if (!priv || !priv.idToNode) {
2570 						self.log("target:<"+e.target.nodeName+' class="'+e.target.className+'" id="'+e.target.id+'"' + "> , type:"+type+"/"+e.type);
2571 					}
2572 					e = d.e;
2573 
2574 
2575 					event = self.extend(new self.DOMEvent(type), {
2576 						which	: e.which,
2577 						target	: e.target || e.srcElement || originalTarget || document.body,
2578 						relatedTarget : e.relatedTarget || (type==='mouseout' ? e.toElement : e.fromElement),
2579 						returnValue : true,
2580 						originalEvent : e,
2581 						timeStamp : e.timeStamp || Date.now()
2582 					});
2583 
2584 					// NOTE: For convenience, copy extra properties from the original event.
2585 					// This is mostly used for custom events to pass custom properties.
2586 					for (i in e) {
2587 						if (!event.hasOwnProperty(i) && typeof e[i]!=='function' && i!==i.toUpperCase() && i!=='layerX' && i!=='layerY') {
2588 							event[i] = e[i];
2589 						}
2590 					}
2591 
2592 					if (!event.target) {
2593 						self.log('Event target doesn\'t exist for type "'+event.type+'": ',event.target,', srcElement=',e.srcElement);
2594 					}
2595 
2596 					if (e.type==='touchend' && priv._lastTouchPos) {
2597 						event.pageX = priv._lastTouchPos.pageX;
2598 						event.pageY = priv._lastTouchPos.pageY;
2599 					}
2600 					else if (e.touches && e.touches[0]) {
2601 						event.pageX = e.touches[0].pageX;
2602 						event.pageY = e.touches[0].pageY;
2603 						priv._lastTouchPos = {
2604 							pageX : event.pageX,
2605 							pageY : event.pageY
2606 						};
2607 					}
2608 					else if (e.pageX || e.pageX===0 || e.clientX || e.clientX===0) {
2609 						event.pageX = e.pageX || (e.clientX+document.body.offsetLeft);
2610 						event.pageY = e.pageY || (e.clientY+document.body.offsetTop);
2611 					}
2612 					if (type.indexOf('key')>-1 || e.keyCode || e.charCode) {
2613 						event.keyCode = e.keyCode || e.which;
2614 						event.charCode = e.keyCode || e.charCode || e.which;			// this should never be used
2615 						event.which = e.which;
2616 					}
2617 					if (type.indexOf('mouse')>-1 || type.indexOf('click')>-1 || (e.button!==null && e.button!==undefined)) {
2618 						event.button = typeof e.button=='number' ? e.button : e.which;
2619 					}
2620 
2621 					// fix safari #textnode target bug
2622 					if (event.target && event.target.nodeType===3 && originalTarget.nodeType!==3) {
2623 						event.target = event.target.parentNode;
2624 					}
2625 
2626 					// allow filtering by CSS selector
2627 					var sel = wrappedHandler.selector,
2628 						selEls, isInSelector;
2629 					if (sel && typeof sel==='string') {
2630 						selEls = self.selectorEngine.query(sel, {
2631 							within : originalTarget
2632 						});
2633 					}
2634 
2635 					// is the capturing node within the original handler context?
2636 					d.searchNode = !selEls && event.relatedTarget || event.target;
2637 					do {
2638 						if (selEls) {
2639 							if (selEls.indexOf(d.searchNode) !== -1 ) {
2640 								isInSelector = true;
2641 								fireTarget = d.searchNode;
2642 								break;
2643 							}
2644 							continue;
2645 						}
2646 						if (d.searchNode===originalTarget) {
2647 							d.isInSelf = true;
2648 							break;
2649 						}
2650 					} while(d.searchNode && (d.searchNode=d.searchNode.parentNode) && d.searchNode!==document);
2651 
2652 					if (selEls && !isInSelector) {
2653 						return;
2654 					}
2655 
2656 					// Don't fire mouseout events when the mouse is moving in/out a child node of the handler context element
2657 					if ((type!=='mouseover' && type!=='mouseout') || !d.isInSelf) {
2658 						if (handler && handler.call) {
2659 							event.currentTarget = fireTarget || originalTarget;
2660 							d.handlerResponse = handler.call(fireTarget || originalTarget, event);
2661 						}
2662 						else {
2663 							// NOTE: Turn this on and fix the IE bug.
2664 							//console.log('Handler not a function: ', self.typeOf(handler), ' handler=', handler, ' type=', type);
2665 						}
2666 
2667 						event.returnValue = d.handlerResponse!==false && event.returnValue!==false;
2668 						if (event.defaultPrevented) {
2669 							event.returnValue = e.returnValue = false;
2670 							if (e.preventDefault) {
2671 								e.preventDefault();
2672 							}
2673 						}
2674 						if (event.bubblingCancelled===true || event.propagationStopped===true || event.cancelBubble===true) {
2675 							cancelEvent(e);
2676 						}
2677 						d.ret = event.returnValue;
2678 					}
2679 					else {
2680 						d.ret = true;
2681 					}
2682 					return d.end();
2683 				}
2684 			};
2685 			evt.wrappedHandler.displayName = 'wrappedEventHandler_'+type;
2686 			evt.wrappedHandler.handler = handler;
2687 			evt.wrappedHandler.type = type;
2688 			evt.wrappedHandler.target = evt.target;		// an ID, not the node itself
2689 			evt.wrappedHandler.selector = selector;
2690 			this.list.push(evt);
2691 			obj = type = handler = evt = null;
2692 			return this.list[this.list.length-1].wrappedHandler;
2693 		}
2694 
2695 	};
2696 
2697 
2698 
2699 
2700 	/** Get a String description of the subject for an event operation
2701 	 *	@private
2702 	 *	@param {Any} subject		An object of any type.
2703 	 */
2704 	priv.getSubjectDescription = function(obj) {
2705 		return (obj.nodeName ? (self.el(obj)+'') : (obj.constructor && obj.constructor.name || obj.name || obj)) + '';
2706 	};
2707 
2708 
2709 	/**	Automatically translate DOM event types from [key] to [value] when registering or removing listeners. <br />
2710 	 *	Also falsifies corresponding puredom-wrapped events' type fields.
2711 	 *	@object
2712 	 */
2713 	self.eventTypeMap = {};
2714 	if (priv.support.webkitMultitouch) {
2715 		self.extend(self.eventTypeMap, {
2716 			'mousedown'	: 'touchstart',
2717 			'mousemove'	: 'touchmove',
2718 			'mouseup'	: 'touchend'
2719 		});
2720 	}
2721 
2722 	/**	Add an event listener to a DOM node for the given event type.
2723 	 *	@private
2724 	 *	@param {HTMLElement} obj			An element to add the event listener to.
2725 	 *	@param {String} type				A type of event to register the listener for.
2726 	 *	@param {String} [selector]			Optionally call handler only if the target matches a CSS selector.
2727 	 *	@param {Function} handler			The listener function to register. Gets passed <code>({Event} event)</code>.
2728 	 */
2729 	self.addEvent = function(obj, type, selector, fn) {
2730 		var x, origType;
2731 		if (typeof selector==='function') {
2732 			fn = selector;
2733 			selector = null;
2734 		}
2735 		if (obj) {
2736 			if (self.typeOf(type)==='string' && type.indexOf(',')>-1) {
2737 				type = type.replace(/\s/gm,'').split(',');
2738 			}
2739 			if (self.isArray(type)) {
2740 				for (x=0; x<type.length; x++) {
2741 					self.addEvent(obj, type[x], selector, fn);
2742 				}
2743 				return true;
2744 			}
2745 			origType = type = (type+'').toLowerCase().replace(/^\s*(on)?(.*?)\s*$/gim,'$2');
2746 
2747 			if (typeof type!=='string' || !fn || !fn.call) {
2748 				self.log('Attempted to add event with invalid type or handler:', {
2749 					type : type,
2750 					handler : fn+'',
2751 					subject : priv.getSubjectDescription(obj)
2752 				});
2753 				return;
2754 			}
2755 
2756 			if (self.eventTypeMap.hasOwnProperty(type)) {
2757 				type = self.eventTypeMap[type];
2758 			}
2759 
2760 			fn = priv.wrappedEventListener.create(origType, fn, obj, selector);
2761 			if (obj.attachEvent) {
2762 				obj.attachEvent('on' + type, fn);
2763 			}
2764 			else if (obj.addEventListener) {
2765 				obj.addEventListener(type, fn, false);
2766 				self._eventCount = (self._eventCount || 0) + 1;
2767 			}
2768 		}
2769 	};
2770 
2771 
2772 	/**	Remove an event listener from a DOM node.
2773 	 *	@private
2774 	 *	@param {Element} obj				An element to remove the event listener from.
2775 	 *	@param {String} type				The event type of the listener to be removed.
2776 	 *	@param {String} [selector]			Optionally call handler only if the target matches a CSS selector.
2777 	 *	@param {Function} handler			The listener function to remove.
2778 	 */
2779 	self.removeEvent = function(obj, type, selector, fn) {
2780 		var x, origType;
2781 		if (typeof selector==='function') {
2782 			fn = selector;
2783 			selector = null;
2784 		}
2785 		if (obj) {
2786 			if (self.typeOf(type)==='string' && type.indexOf(',')>-1) {
2787 				type = type.replace(/\s/gm,'').split(',');
2788 			}
2789 			if (self.isArray(type)) {
2790 				for (x=0; x<type.length; x++) {
2791 					self.removeEvent(obj, type[x], selector, fn);
2792 				}
2793 				return true;
2794 			}
2795 			origType = type = (type+'').toLowerCase().replace(/^\s*(on)?(.*?)\s*$/gim,'$2');
2796 
2797 			if (typeof type!=='string' || !fn || !fn.call) {
2798 				self.log('Attempted to remove event with invalid type or handler:', {
2799 					type : type,
2800 					handler : fn+'',
2801 					subject : priv.getSubjectDescription(obj)
2802 				});
2803 				return;
2804 			}
2805 
2806 			if (self.eventTypeMap.hasOwnProperty(type)) {
2807 				type = self.eventTypeMap[type];
2808 			}
2809 
2810 			fn = priv.wrappedEventListener.get(origType, fn, obj, selector, true);
2811 			if (obj.detachEvent) {
2812 				obj.detachEvent('on' + type, fn);
2813 			}
2814 			else if (obj.removeEventListener) {
2815 				try {
2816 					obj.removeEventListener(type, fn, false);
2817 					self._eventCount = (self._eventCount || 0) - 1;
2818 				} catch(err) {}
2819 			}
2820 		}
2821 	};
2822 
2823 
2824 	/**	When called from within an event handler and passed the DOM Event, cancels the event, prevents the event's default action, and returns false.<br />
2825 	 *	<strong>Note:</strong> puredom-wrapped Event objects have a cancel() method that does this for you.
2826 	 *	@private
2827 	 *	@param {Event} event	A DOM Event.
2828 	 *	@returns false
2829 	 */
2830 	self.cancelEvent = function(e) {
2831 		e = e || window.event;
2832 		if (e) {
2833 			if (e.preventDefault) {
2834 				e.preventDefault();
2835 			}
2836 			if (e.stopPropagation) {
2837 				e.stopPropagation();
2838 			}
2839 			try {
2840 				e.cancelBubble = true;
2841 			} catch(err) {}
2842 			e.returnValue = false;
2843 		}
2844 		return false;
2845 	};
2846 
2847 
2848 	/*
2849 	priv.checkEventTypeSupport = function(s, type) {
2850 		var da = !!('ondataavailable' in s),
2851 			support = false,
2852 			handler;
2853 		handler = function(e) {
2854 			support = e && e.test_prop==='test_val';
2855 		};
2856 		self.addEvent(type, handler);
2857 		self.fireEvent({
2858 			target : s,
2859 			type : type,
2860 			test_prop : 'test_val'
2861 		});
2862 		self.removeEvent(type, handler);
2863 		handler = s = type = null;
2864 		return support;
2865 	};
2866 	priv.supportsCustomEventTypes = priv.checkEventTypeSupport(document.createElement('span'), 'custom:event_typetest');
2867 	*/
2868 
2869 
2870 	/**	Fire an event on a DOM node.
2871 	 *	@private
2872 	 *	@param {Object} options				An event options object, having at least a "type" and "target".
2873 	 *	@param {String} [options.type]		The event type to fire. "on"- prefixes get stripped.
2874 	 *	@param {String} [options.target]	A DOM node to fire the event on.
2875 	 *	@returns {Object} result description, with {Boolean} "rval" (false if any handler returned false), and {Boolean} "preventDefault" (default was prevented).
2876 	 */
2877 	self.fireEvent = function (options) {
2878 		var evt, rval, p, preventDefault, initError;
2879 		options = options || {};
2880 		if (document.createEventObject) {       // IE
2881 			options = self.extend({}, options);
2882 			options.type = 'on'+options.type.toLowerCase().replace(/^on/,'');
2883 			//priv.checkEventTypeSupport(options.target, options.type);
2884 
2885 			try {
2886 				evt = document.createEventObject();
2887 			}catch(err) {
2888 				initError = true;
2889 			}
2890 			if (!initError) {
2891 				for (p in options) {
2892 					if (options.hasOwnProperty(p)) {
2893 						try {
2894 							evt[p] = options[p];
2895 						}catch(err){}
2896 					}
2897 				}
2898 				try {
2899 					rval = options.target.fireEvent(options.type, evt);
2900 					preventDefault = evt.preventDefault===true;
2901 				} catch(err) {
2902 					initError = true;
2903 				}
2904 			}
2905 			if (initError) {
2906 				//self.log('Error: Could not fire "' + options.type + '" event in IE. Falling back to internal implementation.');
2907 				if (priv.wrappedEventListener.internalFireEvent) {
2908 					priv.wrappedEventListener.internalFireEvent(options);
2909 				}
2910 			}
2911 
2912 		}
2913 		else {                                  // Everything else
2914 			evt = document.createEvent("HTMLEvents");
2915 			evt.initEvent(options.type.toLowerCase().replace(/^on/,''), true, true);
2916 			for (p in options) {
2917 				if (options.hasOwnProperty(p)) {
2918 					try {
2919 						evt[p] = options[p];
2920 					} catch(err) {}
2921 				}
2922 			}
2923 			rval = !options.target.dispatchEvent(evt);
2924 			preventDefault = evt.preventDefault===true;
2925 		}
2926 
2927 		return {
2928 			evt             : evt,
2929 			preventDefault  : preventDefault,
2930 			rval            : rval
2931 		};
2932 	};
2933 
2934 
2935 
2936 
2937 
2938 	/**	@private */
2939 	priv._nodeToIdIndex = 0;
2940 
2941 	/**	@private */
2942 	priv._nodeToIdList = {};
2943 
2944 	/**	Get the UUID value for a given node. If the node does not yet have a UUID, it is assigned one.
2945 	 *	@private
2946 	 */
2947 	priv.nodeToId = function(el) {
2948 		var search, id;
2949 		if (el===window) {
2950 			return '_td_autoid_window';
2951 		}
2952 		else if (el===document.documentElement) {
2953 			return '_td_autoid_html';
2954 		}
2955 		else if (el===document.body) {
2956 			return '_td_autoid_body';
2957 		}
2958 		search = (/\s_td_autoid_([0-9]+)\s/gm).exec(' ' + el.className + ' ');
2959 		if (search && search[1]) {
2960 			id = search[1];
2961 		}
2962 		else {
2963 			priv._nodeToIdIndex += 1;
2964 			id = priv._nodeToIdIndex + '';
2965 			self.addClass(el, '_td_autoid_'+id);
2966 		}
2967 		priv.ensureNodeIdListing(el, id);
2968 		return id;
2969 	};
2970 
2971 	/**	Get the node with the given UUID.
2972 	 *	@private
2973 	 */
2974 	priv.idToNode = function(id) {
2975 		var search,
2976 			listed = priv._nodeToIdList[id+''],
2977 			node;
2978 		if (id==='_td_autoid_window') {
2979 			return window;
2980 		}
2981 		else if (id==='_td_autoid_html') {
2982 			return document.documentElement;
2983 		}
2984 		else if (id==='_td_autoid_body') {
2985 			return document.body;
2986 		}
2987 		if (listed) {
2988 			node = self.selectorEngine.query(listed);
2989 			if (!(/\s_td_autoid_[0-9]+\s/gm).exec(' ' + node.className + ' ')) {
2990 				node = null;
2991 			}
2992 		}
2993 		if (!node) {
2994 			search = self.selectorEngine.query('._td_autoid_'+id);
2995 			node = search && search[0];
2996 			if (node) {
2997 				priv.ensureNodeIdListing(node, id);
2998 			}
2999 		}
3000 		return node || false;
3001 	};
3002 
3003 	/**	@private */
3004 	priv.ensureNodeIdListing = function(node, id) {
3005 		var idAttr;
3006 		if (node.getAttribute) {
3007 			idAttr = node.getAttribute('id');
3008 			if (!idAttr) {
3009 				idAttr = '_td_autoid_'+id;
3010 				node.setAttribute('id', idAttr);
3011 			}
3012 			priv._nodeToIdList[id] = '#'+idAttr;
3013 		}
3014 	};
3015 
3016 	/**	@private */
3017 	priv.removeNodeUID = function(node) {
3018 		var id = node.getAttribute('id');
3019 		if (id && id.match(/^_td_autoid_[0-9]+$/g)) {
3020 			if (node.removeAttribute) {
3021 				node.removeAttribute('id');
3022 			}
3023 			else {
3024 				node.setAttribute('id', '');
3025 			}
3026 		}
3027 		if (node.className) {
3028 			node.className = node.className.replace(/(^|\b)_td_autoid_[0-9]+(\b|$)/gim,'');
3029 		}
3030 	};
3031 
3032 
3033 
3034 	/**	@namespace Shim for HTML5's animationFrame feature.
3035 	 *	@name puredom.animationFrame
3036 	 *	@public
3037 	 */
3038 	self.animationFrame = (function() {
3039 		/** @ignore */
3040 		var self = /** @lends puredom.animationFrame */ {
3041 				nativeSupport : true,
3042 				manualFramerate : 11
3043 			},
3044 			perf = window.performance,
3045 			prefix;
3046 
3047 		if (window.requestAnimationFrame) {
3048 			prefix = '';
3049 		}
3050 		else if (window.mozRequestAnimationFrame) {
3051 			prefix = 'moz';
3052 		}
3053 		else if (window.webkitRequestAnimationFrame) {
3054 			prefix = 'webkit';
3055 		}
3056 		else {
3057 			self.nativeSupport = false;
3058 		}
3059 
3060 		/** @ignore */
3061 		function now() {
3062 			if (perf && perf.now) {
3063 				return perf.now();
3064 			}
3065 			return Date.now();
3066 		}
3067 
3068 		if (self.nativeSupport) {
3069 
3070 			/**	Defer execution of an animation function so it occurs during the next rendering cycle.
3071 			 *	@param {Function} f		A function to call during the next animation frame.
3072 			 *	@name puredom.animationFrame.getTimer
3073 			 *	@function
3074 			 */
3075 			self.getTimer = function(f) {
3076 				return window[ (prefix ? (prefix+'R') : 'r') + 'equestAnimationFrame'](f);
3077 			};
3078 
3079 			/**	Unregister a deferred animation function.
3080 			 *	@param {String} identifier		A timer identifier, such as one obtained from {@link puredom.animationFrame.getTimer}.
3081 			 *	@name puredom.animationFrame.cancelTimer
3082 			 *	@function
3083 			 */
3084 			self.cancelTimer = function(t) {
3085 				window[ (prefix ? (prefix+'C') : 'c') + 'ancelRequestAnimationFrame'](t);
3086 			};
3087 
3088 			/**	Get the start time (timestamp, in milliseconds) of the current animation.
3089 			 *	@param {String} identifier		A timer identifier, such as one obtained from {@link puredom.animationFrame.getTimer}.
3090 			 *	@name puredom.animationFrame.getStartTime
3091 			 *	@function
3092 			 */
3093 			self.getStartTime = function(t) {
3094 				return window[ (prefix ? (prefix+'A') : 'a') + 'nimationStartTime'] || now();
3095 			};
3096 
3097 		}
3098 		else {
3099 
3100 			/**	@ignore */
3101 			self.getTimer = function(f) {
3102 				return setTimeout(function() {
3103 					f( now() );
3104 					f = null;
3105 				}, self.manualFramerate);
3106 			};
3107 
3108 			/**	@ignore */
3109 			self.cancelTimer = function(t) {
3110 				clearTimeout(t);
3111 			};
3112 
3113 			/**	@ignore */
3114 			self.getStartTime = function(t) {
3115 				return now();
3116 			};
3117 
3118 		}
3119 
3120 		return self;
3121 	}());
3122 
3123 
3124 
3125 
3126 
3127 	/**	Set the opacity of an element.
3128 	 *	@private
3129 	 *	@param {HTMLElement} el		A DOM node to which an opacity value should be applied.
3130 	 *	@param {Number} opac		An integer opacity between 0 and 100, or a decimal value between 0.01 and 1.
3131 	 */
3132 	self.setOpacity = function(el, opac) {
3133 		if (opac<=1 && opac>0) {
3134 			opac = opac * 100;
3135 		}
3136 		opac = Math.round(opac);
3137 		if (opac<100) {
3138 			el.style.opacity = parseFloat(opac/100).toFixed(2);
3139 			el.style.filter = "alpha(opacity=" + opac + ")";
3140 		}
3141 		else {
3142 			el.style.opacity = null;
3143 			el.style.filter = null;
3144 		}
3145 	};
3146 
3147 
3148 
3149 	/**	Apply a Microsoft filter value to an element, retaining existing applied filters. <br />
3150 	 *	See: {@link http://msdn.microsoft.com/en-us/library/ms532853(v=vs.85).aspx}
3151 	 *	@private
3152 	 *	@param {HTMLElement} el		An element to apply a filter to.
3153 	 *	@param {String} type		A valid filter type.
3154 	 *	@param {String} value		The value to set for the given filter type.
3155 	 */
3156 	self.applyMsFilter = function(el, type, value) {
3157 		var item, filters,
3158 			valueStr = '',
3159 			ch = priv.support.filterProperty==='MsFilter' ? '"' : '',
3160 			p, a, i;
3161 		type = type.charAt(0).toUpperCase() + type.substring(1);
3162 		if (typeof value==='string') {
3163 			valueStr = value;
3164 			value = {};
3165 			a = valueStr.replace(/\s*(,|=)\s*      /gm,'$1').split(',');
3166 			for (p=0; p<a.length; p++) {
3167 				i = a[p].indexOf('=');
3168 				value[a[p].substring(0,i)] = a[p].substring(i+1);
3169 			}
3170 		}
3171 		else {
3172 			for (p in value) {
3173 				if (value.hasOwnProperty(p)) {
3174 					valueStr += ', '+p+'='+value[p];
3175 				}
3176 			}
3177 			valueStr = valueStr.replace(',','');
3178 		}
3179 		try {
3180 			item = el.filters && el.filters.item && (el.filters.item('DXImageTransform.Microsoft.'+type) || el.filters.item(type));
3181 		}catch(err){}
3182 		if (item) {
3183 			for (p in value) {
3184 				if (value.hasOwnProperty(p) && p!=='enabled') {
3185 					item[p] = value[p];
3186 				}
3187 			}
3188 			item.enabled = value.enabled?1:0;
3189 		}
3190 		else {
3191 			filters = el.style.MsFilter || el.style.filter || '';
3192 			filters += ' ' + (self.nodeStyle(el, 'MsFilter') || self.nodeStyle(el, 'filter') || '');
3193 			if (filters) {
3194 				filters = filters.replace(new RegExp('(^|\\s|\\))"?((progid\\:)?DXImageTransform\\.Microsoft\\.)?'+type+'\\s*?\\(.*?\\)"?\\s*?','gim'),'$1') + ' ';
3195 			}
3196 			el.style[priv.support.filterProperty] = filters + ch+'progid:DXImageTransform.Microsoft.'+type+'('+valueStr+')'+ch;
3197 		}
3198 		// hasLayout
3199 		if (!el.style.zoom) {
3200 			el.style.zoom = 1;
3201 		}
3202 	};
3203 
3204 
3205 
3206 
3207 	/**	@private */
3208 	var cssPropCache = {};
3209 	/**	@private */
3210 	function getPrefixedCssProperty(prop) {
3211 		var ret = prop,
3212 			p = cssPropCache[prop];
3213 		if (p) {
3214 			return p;
3215 		}
3216 		if (vendorCssPrefixJS && prop.substring(0, vendorCssPrefixJS.length)!==vendorCssPrefixJS) {
3217 			p = vendorCssPrefixJS + prop.charAt(0).toUpperCase() + prop.substring(1);
3218 			if (p in document.body.style) {
3219 				ret = p;
3220 			}
3221 		}
3222 		cssPropCache[prop] = ret;
3223 		return ret;
3224 	}
3225 
3226 
3227 	/**	Apply key-value CSS styles to an element.
3228 	 *	@param {HTMLElement} el		An element whose style should be updated.
3229 	 *	@param {Object} properties	An Object where keys are CSS properties and values are the corresponding CSS values to apply.
3230 	 *	@private
3231 	 */
3232 	self.applyCss = function(el, properties) {
3233 		var x, cx, d, p, ieOpac, vp;
3234 		properties = properties || {};
3235 		for (x in properties) {
3236 			if (properties.hasOwnProperty(x)) {
3237 				cx = self.getStyleAsCSS(x);
3238 				cx = cx.replace(/^\-(moz|webkit|ms|o|vendor)\-/gim, vendorCssPrefix+'-');
3239 				cx = self.getStyleAsProperty(cx);
3240 				cx = getPrefixedCssProperty(cx);
3241 				if (!priv.support.filters) {
3242 					el.style[cx] = properties[x];
3243 				}
3244 				else {
3245 					if (cx==='opacity') {
3246 						ieOpac = Math.round( parseFloat(properties[x])*100 );
3247 						if (ieOpac<100) {
3248 							self.applyMsFilter(el, 'alpha', {
3249 								enabled : true,
3250 								opacity : ieOpac
3251 							});
3252 						}
3253 						else {
3254 							self.applyMsFilter(el, 'alpha', {
3255 								enabled : false
3256 							});
3257 						}
3258 					}
3259 					else if (cx==='--box-shadow') {
3260 						d = properties[x].match(/\b(\#[0-9af]{3}[0-9af]{3}?|rgba?\([0-9\,\s]+\))\b/gim);
3261 						d = d && d[0] || '';
3262 						p = (' '+properties[x]+' ').replace(d,'').replace(/\s+/m,' ').split(' ').slice(1,4);
3263 						self.applyMsFilter(el, 'glow', {
3264 							Color : d,
3265 							Strength : Math.round(p[3].replace(/[^0-9\-\.]/gim,''))
3266 						});
3267 					}
3268 				}
3269 			}
3270 		}
3271 	};
3272 
3273 	/**	Convert a CSS property name to it's CamelCase equivalent.
3274 	 *	@private
3275 	 */
3276 	self.getStyleAsProperty = function(style) {
3277 		if (typeof style!=='string') {
3278 			return null;
3279 		}
3280 		style = style.replace(/\-[a-z0-9]/gim, priv.styleAsPropReplacer);
3281 		// fixes "webkitFoo" -> "WebkitFoo" etc
3282 		style = style.replace(/^(webkit|moz|ms|o)[A-Z]/gm, priv.styleAsPropVendorPrefixReplacer);
3283 		return style;
3284 	};
3285 	/**	@private */
3286 	priv.styleAsPropReplacer = function(s) {
3287 		return s.charAt(1).toUpperCase();
3288 	};
3289 	/**	@private */
3290 	priv.styleAsPropVendorPrefixReplacer = function(s) {
3291 		return s.charAt(0).toUpperCase()+s.substring(1);
3292 	};
3293 
3294 	/**	Convert a CSS property name to it's css-dash-separated equivalent.
3295 	 *	@private
3296 	 */
3297 	self.getStyleAsCSS = function(style) {
3298 		return typeof style==='string' && style.replace(/\-*([A-Z])/gm, '-$1').toLowerCase() || null;
3299 	};
3300 
3301 	/**	Parse a CSS String and return an Object representation, converting `-css-keys` to `jsKeys`.
3302 	 *	@param {String} css
3303 	 *	@param {Boolean} [camelKeys=true]	If `false`, keys will be left untouched.
3304 	 *	@private
3305 	 */
3306 	priv.parseCSS = function(css, camelKeys) {
3307 		var tokenizer = /\s*([a-z\-]+)\s*:\s*([^;]*?)\s*(?:;|$)/gi,
3308 			obj, token, key;
3309 		if (css) {
3310 			obj = {};
3311 			tokenizer.lastIndex = 0;
3312 			while ((token=tokenizer.exec(css))) {
3313 				key = token[1];
3314 				if (camelKeys!==false) {
3315 					key = self.getStyleAsProperty(key);
3316 				}
3317 				obj[key] = token[2];
3318 			}
3319 		}
3320 		return obj;
3321 	};
3322 
3323 	self._parseCss = priv.parseCSS;
3324 
3325 	/** Some intense CSS3 transitions wrapping, needed in order to support animating multiple
3326 	 *	properties asynchronously with interjected transition modifications
3327 	 *	@private
3328 	 */
3329 	self.getCssTransitions = function(el) {
3330 		var transitions = {},
3331 			current = {
3332 				properties			: '-vendor-transition-property',
3333 				durations			: '-vendor-transition-duration',
3334 				timingFunctions		: '-vendor-transition-timing-function'
3335 			},
3336 			p, x, durationStr, duration;
3337 		for (p in current) {
3338 			if (current.hasOwnProperty(p)) {
3339 				current[p] = (self.nodeStyle(el, current[p]) || '').replace(/\s/,'').split(',');
3340 			}
3341 		}
3342 		for (x=0; x<current.properties.length; x++) {
3343 			if (current.properties[x] && current.properties[x]!=='null' && !(current.properties[x]==='all' && current.durations[x].match(/^[0\.ms]*$/))) {
3344 				durationStr = current.durations[x] || current.durations[current.durations.length-1];
3345 				duration = parseFloat(durationStr.replace(/[^\-0-9\.]/gim,'')) || 0;
3346 				if (!durationStr.match(/ms$/i)) {
3347 					duration *= 1000;
3348 				}
3349 				transitions[self.getStyleAsProperty(current.properties[x])] = {
3350 					duration : duration,
3351 					timingFunction : current.timingFunctions[x] || current.timingFunctions[current.timingFunctions.length-1] || 'ease'
3352 				};
3353 			}
3354 		}
3355 
3356 		return transitions;
3357 	};
3358 
3359 	/** @private */
3360 	self.setCssTransitions = function(el, transitions) {
3361 		var css = {
3362 				'-vendor-transition-property'			: [],
3363 				'-vendor-transition-duration'			: [],
3364 				'-vendor-transition-timing-function'	: []
3365 			},
3366 			p;
3367 
3368 		for (p in transitions) {
3369 			if (transitions.hasOwnProperty(p) && transitions[p]) {
3370 				css['-vendor-transition-property'].push(p.toLowerCase());
3371 				css['-vendor-transition-duration'].push((transitions[p].duration/1000).toFixed(3) + 's');
3372 				css['-vendor-transition-timing-function'].push(transitions[p].timingFunction || 'ease');
3373 			}
3374 		}
3375 
3376 		for (p in css) {
3377 			if (css.hasOwnProperty(p)) {
3378 				css[p] = css[p].join(', ');
3379 			}
3380 		}
3381 
3382 		self.applyCss(el, css);
3383 	};
3384 
3385 	/** @private */
3386 	self.updateCssTransitions = function(el, transitionsToUpdate) {
3387 		var transitions, p;
3388 		if (transitionsToUpdate) {
3389 			transitions = self.getCssTransitions(el);
3390 			for (p in transitionsToUpdate) {
3391 				if (transitionsToUpdate.hasOwnProperty(p)) {
3392 					if (transitionsToUpdate[p]) {
3393 						transitions[p] = transitionsToUpdate[p];
3394 					}
3395 					else {
3396 						delete transitions[p];
3397 					}
3398 				}
3399 			}
3400 			self.setCssTransitions(el, transitions);
3401 		}
3402 	};
3403 
3404 
3405 	/** @private */
3406 	self.addClass = function(el, classes, remove) {
3407 		var modified = false,
3408 			list, index, i;
3409 		if (classes) {
3410 			if (classes.length===1) {
3411 				classes = classes[0].split(' ');
3412 			}
3413 			else if (!self.isArray(classes)) {
3414 				classes = classes.split(' ');
3415 			}
3416 			if (el.classList) {
3417 				el.classList[remove ? 'remove' : 'add'].apply(el.classList, classes);
3418 				return;
3419 			}
3420 
3421 			list = (el.className || '').split(/\s+/);
3422 			for (i=0; i<classes.length; i++) {
3423 				index = list.indexOf(classes[i]);
3424 				if (remove!==true && index===-1) {
3425 					modified = true;
3426 					list.push( classes[i] );
3427 				}
3428 				else if (remove===true && index>-1) {
3429 					modified = true;
3430 					list.splice(index, 1);
3431 				}
3432 			}
3433 			if (modified) {
3434 				el.className = list.join(' ');
3435 			}
3436 		}
3437 	};
3438 
3439 	/** @private */
3440 	self.removeClass = function(el, classes) {
3441 		return self.addClass(el, classes, true);
3442 	};
3443 
3444 	/** Get the current value of a CSS property from the given node.
3445 	 *	@private
3446 	 */
3447 	self.nodeStyle = function(node, property) {
3448 		var dashed = (property+'').replace(/[A-Z]/g, '-$0').toLowerCase(),
3449 			camelized,
3450 			filter,
3451 			s,
3452 			style;
3453 
3454 		dashed = dashed.replace(/^\-(moz|webkit|ms|o|vendor)\-/gim, vendorCssPrefix+'-');
3455 		camelized = dashed.replace(/\-[a-z]/gim, function (s) {return s.substring(1).toUpperCase();});
3456 
3457 		if (dashed==='opacity' && priv.support.filters) {
3458 			return node.filters.alpha && node.filters.alpha.enabled!==false && Math.round(node.filters.alpha.opacity)/100;
3459 		}
3460 
3461 		if (node.style && node.style[camelized]) {
3462 			style = node.style[camelized] || '';
3463 		}
3464 
3465 		else if (node.currentStyle && node.currentStyle[camelized]) {
3466 			style = node.currentStyle[camelized] || node.currentStyle[dashed] || '';
3467 		}
3468 
3469 		else if (document.defaultView && document.defaultView.getComputedStyle) {
3470 			s = document.defaultView.getComputedStyle(node, null);
3471 			style = s && (s.getPropertyValue(dashed) || s.getPropertyValue(camelized)) || '';
3472 		}
3473 
3474 		else if (window.getComputedStyle) {
3475 			s = window.getComputedStyle(node, null);
3476 			style = s && (s.getPropertyValue(dashed) || s.getPropertyValue(camelized)) || '';
3477 		}
3478 
3479 		return style;
3480 	};
3481 
3482 
3483 	/**	Old alias of puredom.text.html()
3484 	 *	@private
3485 	 */
3486 	self.htmlEntities = function(str, stripTags) {
3487 		var filters = ['htmlEntities'];
3488 		if (stripTags===true) {
3489 			filters.push('stripTags');
3490 		}
3491 		return self.text.filter(str, filters);
3492 	};
3493 
3494 
3495 	/**	Log to the browser console, if it exists.
3496 	 */
3497 	self.log = function() {
3498 		var c = global.console;
3499 		if (c && c.log) {
3500 			c.log.apply(c, arguments);
3501 		}
3502 	};
3503 
3504 
3505 
3506 	/**	Add a new plugin method to {puredom.NodeSelection}. <br />
3507 	 *	When called, a plugin function gets passed the arguments supplied by the caller. <br />
3508 	 *	The value of <code>this</code> within the function is the selection ({@link puredom.NodeSelection}) it was called on.
3509 	 *	@param {String} name		A method name to define on {puredom.NodeSelection}
3510 	 *	@param {Function} func		The plugin method to define.
3511 	 */
3512 	self.addNodeSelectionPlugin = function(name, func) {
3513 		if (!self.NodeSelection.prototype.hasOwnProperty(name)) {
3514 			self.NodeSelection.prototype[name] = function() {
3515 				var ret = func.apply(this, arguments);
3516 				if (ret===null || ret===undefined) {
3517 					return this;
3518 				}
3519 				return ret;
3520 			};
3521 		}
3522 	};
3523 
3524 
3525 	/**	Called on DOM ready.
3526 	 *	@private
3527 	 */
3528 	self.init = function() {
3529 		if (!initialized) {
3530 			initialized = true;
3531 
3532 			self.forEach(priv.oninit, function(i) {
3533 				i.call(self, self);
3534 			});
3535 
3536 			self.fireEvent({
3537 				target : document.body,
3538 				type : "puredomready",
3539 				puredom : self
3540 			});
3541 		}
3542 	};
3543 	self.addEvent(document, "DOMContentLoaded", self.init);
3544 	self.addEvent(window, "load", self.init);
3545 	self.addEvent(window, "unload", priv.unload);
3546 
3547 
3548 	/**	Allows extensions to be included before the core.
3549 	 *	@ignore
3550 	 */
3551 	(function() {
3552 		if (previousSelf) {
3553 			for (var x in previousSelf) {
3554 				if (previousSelf.hasOwnProperty(x)) {
3555 					self[x] = previousSelf[x];
3556 				}
3557 			}
3558 			previousSelf = null;
3559 		}
3560 	}());
3561 
3562 	/**	@private */
3563 	priv.puredom = function(i) {
3564 		if (self.typeOf(i)==='function') {
3565 			if (initialized===true) {
3566 				i.call(self, self);
3567 			}
3568 			else {
3569 				priv.oninit.push(i);
3570 			}
3571 			return self;
3572 		}
3573 		else {
3574 			return self.el.apply(self, arguments);
3575 		}
3576 	};
3577 
3578 	self.extend(self, baseSelf);
3579 	self.toString = function(){return 'function puredom(){}';};
3580 
3581 	this.puredom = global.puredom = self;
3582 
3583 	if (typeof define==='function' && define.amd) {
3584 		define('puredom', function(){ return self; });
3585 	}
3586 	if (typeof module==='object') {
3587 		module.exports = self;
3588 	}
3589 }(this, typeof global==='object' ? global : this));
3590