1 // Save a reference to some core methods
  2 var toString = Object.prototype.toString,
  3 	hasOwn = Object.prototype.hasOwnProperty,
  4 	push = Array.prototype.push,
  5 	slice = Array.prototype.slice,
  6 	indexOf = Array.prototype.indexOf,
  7 	trim = String.prototype.trim,
  8 	UNDEF,
  9 	WIN = window,
 10 	DOC = document,
 11 	RCAPITALISE = /\b(\w)(\w+)\b/g,
 12 	/** @private */
 13 	fcapitalise = function( all, first, rest ){
 14 		return first.toUpperCase() + rest.toLowerCase();
 15 	},
 16 	FIRST_SPACES = /^\s*/,
 17 	LAST_SPACES = /\s*$/,
 18 	DOMLOADED = "DOMContentLoaded",
 19 	READYSTATE = "onreadystatechange",
 20 	SCRIPT = "script",
 21 	OPACITY = "opacity",
 22 	TOP = "top",
 23 	LEFT = "left",
 24 	COMPLETE = "complete",
 25 	// The ready event handler
 26 	DOMContentLoaded,
 27 	// Has the ready events already been bound?
 28 	readyBound = false,
 29 	// The functions to execute on DOM ready
 30 	readyList = [];
 31 /**
 32  * @description used to instantiate the Simples object
 33  * @param {String|Element} selector element is used by object and string is used to select Element(s), see Simples.Selector for more information
 34  * @param {Element} context element used to provide context
 35  **/
 36 function Simples( selector, context ) {
 37 	return new Simples.fn.init( selector, context );
 38 }      
 39 
 40 /**
 41  * @description used to test the Constructor / Class of an object
 42  * @param {Object} object the object to test
 43  * @param {String} objectClass the class name to test
 44  * @param {Boolean} doCapitalisation if you don't want to force the capitalisation of the className
 45  **/
 46 Simples.isConstructor = function( obj, className, mustCapitalise ){
 47 	if( obj !== null && obj !== UNDEF ){
 48 		return toString.call( obj ) === "[object "+( mustCapitalise ? className.replace(RCAPITALISE,fcapitalise) : className )+"]";
 49 	}
 50 	return false;
 51 };
 52 /**
 53  * @description used to test the Constructor / Class of an object
 54  * @param {Object} the object to test
 55  **/
 56 Simples.getConstructor = function( obj ){
 57 	if( obj !== null && obj !== UNDEF ){
 58 		return toString.call( obj ).replace("[object ","").replace("]","");
 59 	}
 60 	return false;
 61 };
 62 
 63 /**
 64  * @description used to merge objects onto the first specfied object
 65  * @param {Object} target native javascript object to be merged
 66  * @param {Object|Array} obj1, obj2.... native javascript object or array to be merged onto first
 67  **/
 68 Simples.merge = function(first /* obj1, obj2..... */ ) {
 69     // if only 1 argument is passed in assume Simples is the target
 70     var target = (arguments.length === 1 && !(this === WIN || this === DOC)) ? this: Simples.isConstructor( first, "Object" ) ? first : {};
 71     // set i to value based on whether there are more than 1 arguments
 72     var i = arguments.length > 1 ? 1: 0;
 73     // Loop over arguments
 74     for (var l = arguments.length; i < l; i++) {
 75         // if object apply directly to target with same keys
 76         var klass = Simples.getConstructor( arguments[i] );
 77         if ( klass === "Object" ) {
 78             for (var key in arguments[i]) {
 79                 if (hasOwn.call(arguments[i], key)) {
 80                     target[key] = arguments[i][key];
 81                 }
 82             }
 83         } else if ( klass === "Array" ) {
 84             // if array apply directly to target with numerical keys
 85             push.apply(target, arguments[i]);
 86         }
 87     }
 88 
 89     return target;
 90 };
 91 
 92 Simples.merge( /** @lends Simples */ {
 93 	/**
 94 	 * @description used to add functionality to the Simples instance object
 95 	 * @param {Object|String} addMethods list of names of functions and the function in a opbject, when 2 arguments provided the first should be the name of the function and the function to be added.
 96 	 */
 97 	extend : function( addMethods ){
 98 		// Detect whether addMethods is an object to extend onto subClass
 99 		var klass = Simples.getConstructor( addMethods );
100 		if( klass === "Object" ){
101 			for (var key in addMethods) {
102 		        if ( hasOwn.call( addMethods, key ) ) {
103 		            Simples.fn[key] = addMethods[key];
104 		        }
105 		    }
106 		} else if( klass === "String" && typeof arguments[1] === "function" ){
107 			Simples.fn[ arguments[0] ]= arguments[1];
108 		}
109 	},
110 	/**
111 	 * @description used to coerce a NodeList, HTMLElementCollection or Object into Array
112 	 * @param {NodeList|HTMLElementCollection|Object} object to be coerced
113 	 * @param {Array|Object} output object to have coerced object added to
114 	 */
115 	makeArray : function( array, results ) {
116 		results = results || [];
117 		push.apply( results, slice.call( array || [], 0 ) );
118 		return results;
119 	},
120 	/**
121 	 * @description used to check an object to see whether it is empty
122 	 * @param {Object} obj object to check whether is empty
123 	 */
124 	isEmptyObject : function( obj ) {
125 		for ( var name in obj ) { return false; }
126 		return true;
127 	},
128 	/**
129 	 * @description a value to indicate whether the browser has been triggered as ready
130 	 */
131 	isReady : false,
132 	/**
133 	 * @description used to add functions to browser ready queue and are triggered when DOMContentLoaded has been fired or latest window.onload
134 	 * @param {Function} callback the function to be fired when ready and or fired if already ready
135 	 */
136 	ready: function( callback ) {
137 		// Attach the listeners
138 		Simples.bindReady();
139 		
140 		// If the DOM is already ready
141 		if ( Simples.isReady ) {
142 			// Execute the function immediately
143 			callback.call( DOC, Simples.Event( 'ready' ) );
144 
145 		// Otherwise, remember the function for later
146 		} else if ( readyList ) {
147 			// Add the function to the wait list
148 			readyList.push( callback );
149 		}
150 	},
151 	/** @private Handle when the DOM is ready */
152 	readyHandler : function() {
153 		// Make sure that the DOM is not already loaded
154 		if ( !Simples.isReady ) {
155 			// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
156 			if ( !DOC.body ) {
157 				return WIN.setTimeout( Simples.readyHandler, 13 );
158 			}
159 
160 			// Remember that the DOM is ready
161 			Simples.isReady = true;
162 
163 			// If there are functions bound, to execute
164 			if ( readyList ) {
165 				// Execute all of them
166 				var fn, i = 0;
167 				while ( (fn = readyList[ i++ ]) ) {
168 					fn.call( DOC, Simples );
169 				}
170 
171 				// Reset the list of functions
172 				readyList = null;
173 			}
174 		}
175 	},
176 	/** @private To setup the event listeners for the ready event */
177 	bindReady : function(){
178 		if ( readyBound ) { return; }
179 
180 		readyBound = true;
181 
182 		// Catch cases where $(DOC).ready() is called after the
183 		// browser event has already occurred.
184 		if ( DOC.readyState === COMPLETE ) {
185 			return Simples.readyHandler();
186 		}
187 
188 		// Mozilla, Opera and webkit nightlies currently support this event
189 		if ( DOC.addEventListener ) {
190 			// Use the handy event callback
191 			DOC.addEventListener( DOMLOADED, DOMContentLoaded, false );
192 
193 			// A fallback to WIN.onload, that will always work
194 			WIN.addEventListener( "load", Simples.readyHandler, false );
195 
196 		// If IE event model is used
197 		} else if ( DOC.attachEvent ) {
198 			// ensure firing before onload,
199 			// maybe late but safe also for iframes
200 			DOC.attachEvent( READYSTATE, DOMContentLoaded);
201 
202 			// A fallback to WIN.onload, that will always work
203 			WIN.attachEvent( "onload", Simples.readyHandler );
204 
205 			// If IE and not a frame
206 			// continually check to see if the DOC is ready
207 			var toplevel = false;
208 
209 			try {
210 				toplevel = WIN.frameElement === null || WIN.frameElement === UNDEF;
211 			} catch(e) {}
212 
213 			if ( DOC.documentElement.doScroll && toplevel ) {
214 				doScrollCheck();
215 			}
216 		}
217 	},
218 	/**
219 	 * @description used to set the context on a function when the returned function is executed
220 	 * @param {Object} context object to use as the execution context
221 	 * @param {Function} func the function you want to call with the given context
222 	 */
223 	setContext : function( context, func ){
224 		return function(){
225 			return func.apply( context, arguments );
226 		};
227 	},
228 	/**
229 	 * @description Use native String.trim function wherever possible, Otherwise use our own trimming functionality
230 	 * @param {String} text String to trim
231 	 */
232 	trim : function( text ) {
233 		text = text === null || text === UNDEF ? "" : text;
234 		return trim ? trim.call( text ) : text.toString().replace( FIRST_SPACES, "" ).replace( LAST_SPACES, "" );
235 	},
236 	/**
237 	 * @description an empty function to use as noop function
238 	 */
239 	noop : function(){}
240 });
241 
242 // Perform a simple check to determine if the browser is capable of
243 // converting a NodeList to an array using builtin methods.
244 // Also verifies that the returned array holds DOM nodes
245 // (which is not the case in the Blackberry browser)
246 /** @ignore */
247 try {
248 	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
249 
250 // Provide a fallback method if it does not work
251 } catch( e ) {
252 	/** @ignore */
253 	Simples.makeArray = function( array, results ) {
254 		array = array || [];
255 		var i = 0,
256 			ret = results || [];
257 
258 		if( Simples.isConstructor(array,"Array") ){
259 			push.apply( ret, array );
260 		} else {
261 			if ( typeof array.length === "number" ) {
262 				for ( var l = array.length; i < l; i++ ) {
263 					ret.push( array[i] );
264 				}
265 			} else {
266 				for ( ; array[i]; i++ ) {
267 					ret.push( array[i] );
268 				}
269 			}
270 		}
271 
272 		return ret;
273 	};
274 }
275 
276 // Cleanup functions for the DOC ready method
277 /** @private */
278 if ( DOC.addEventListener ) {
279 	/** @private */
280 	DOMContentLoaded = function() {
281 		DOC.removeEventListener( DOMLOADED, DOMContentLoaded, false );
282 		Simples.readyHandler();
283 	};
284 
285 } else if ( DOC.attachEvent ) {
286 	/** @private */
287 	DOMContentLoaded = function() {
288 		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
289 		if ( DOC.readyState === COMPLETE ) {
290 			DOC.detachEvent( READYSTATE, DOMContentLoaded );
291 			Simples.readyHandler();
292 		}
293 	};
294 } 
295 
296 /** @private The DOM ready check for Internet Explorer */
297 function doScrollCheck() {
298 	if ( Simples.isReady ) {
299 		return;
300 	}
301 
302 	try {
303 		// If IE is used, use the trick by Diego Perini
304 		// http://javascript.nwbox.com/IEContentLoaded/
305 		DOC.documentElement.doScroll(LEFT);
306 	} catch(e) {
307 		WIN.setTimeout( doScrollCheck, 1 );
308 		return;
309 	}
310 
311 	// and execute any waiting functions
312 	Simples.readyHandler();
313 }
314 
315 /**
316  * @namespace Simples.fn
317  * @description the instance of a Simples object functions / instance methods
318  */
319 Simples.fn = Simples.prototype = {
320 	/**  @ignore */	 
321 	constructor : Simples,
322 	/**
323 	 * @constructs
324 	 * @description to initialize the Simples constructor
325 	 * @param {String|Element} selector Query String to find in the DOM and add to Simples instance or the Element use as the selected object
326 	 * @param {Object} context The document or element used to do the query on 
327 	 * @returns {Simples} the simples onject with the results of the selector
328 	 */
329 	init : function( selector, context ){
330 
331 		// Handle $(""), $(null), or $(UNDEF)
332 		if ( !selector ){
333 			return this;
334 		} else if( selector.selector !== UNDEF ){
335 			return selector;
336 		}
337 
338 		// Handle $(DOMElement)
339 		if ( selector.nodeType || ( selector.document && selector.document.nodeType ) ) {
340 			this.context = this[0] = selector;
341 			this.length = 1;
342 			return this;
343 		}
344 		
345 		// The body element only exists once, optimize finding it
346 		if ( selector === "body" && !context ) {
347 			this.context = DOC;
348 			this[0] = DOC.body;
349 			this.selector = "body";
350 			this.length = 1;
351 			return this;
352 		}
353 
354 		var klass = Simples.getConstructor( selector );
355 		if( klass === "String" ){
356 			this.context = context;
357 			this.selector = selector;
358 		
359 			Simples.Selector( selector, context, this );
360 
361 		} else if( klass === "HTMLCollection" || klass === "NodeList" || ( klass === "Array" && context === true ) ){
362 
363 			Simples.makeArray( selector, this );
364 		} else {
365 			Simples.makeArray( selector, this );
366 			this.filter(function(){ return !!this.nodeType || !!this.document; });
367 		}
368 		return this;		
369 	},
370 	/**
371 	 * @name Simples.fn.length
372 	 * @description The count of items on the Simples object 
373 	 */
374 	length : 0,
375 	/**
376 	 * @name Simples.fn.selector	
377 	 * @description The selector used to create the Simples object
378 	 */
379 	selector : "",
380 	/**
381 	 * @name Simples.fn.version	
382 	 * @description The version of the Simples library
383 	 */
384 	version : '@VERSION',
385 	// For internal use only.
386 	// Behaves like an Array's method, not like a Simples method. For hooking up to Sizzle.
387 	/** @private */
388 	push: push,
389 	/** @private */
390 	sort: [].sort,
391 	/** @private */
392 	splice: [].splice
393 };      
394 
395 Simples.fn.init.prototype = Simples.fn;
396 
397 Simples.extend( /** @lends Simples.fn */ {
398 	/**
399 	 * @name Simples.fn.each
400 	 * @function
401 	 * @description To loop over each item in the Simples object
402 	 * @param {Function} callback The function to call with each item, this is current item, arguments[ item, index, object ]
403 	 */
404 	each : function( callback ){
405 		var i=0,l=this.length;
406 		while(i<l){
407 			callback.call( this[i], this[i], i++, this );
408 		}
409 		return this;
410 	},
411 	/**
412 	 * @name Simples.fn.filter
413 	 * @function
414 	 * @description To filter the selected elements on the Simples object 
415 	 * @param {Function} testFn The function to call with each item, this is current item, arguments[ item, index, object ], need to return true from callback to retain element all other return values will remove the element
416 	 */
417 	filter : function( testFn ){
418 		var i = 0,c = 0,l = this.length;
419 		while( i<l ){
420 			if( testFn.call( this[c], this[c], i, this ) !== true ){ 
421 				this.splice( c--, 1 );
422 			}
423 			c++; i++;
424 		}
425 		return this;
426 	},
427 	/**
428 	 * @name Simples.fn.find
429 	 * @function
430 	 * @description used to find elements off of the elements currently on the Simples object 
431 	 * @param {String} selector Selector string to find elements
432 	 */
433 	find: function( selector ){ 
434 		var results = Simples(), i=0,l=this.length;
435 		while(i<l){
436 			Simples.Selector( selector, this[i++], results );
437 		}
438 		return results;
439 	},
440 	/**
441 	 * @name Simples.fn.add
442 	 * @function
443 	 * @description used to add more elements to the current Simples object
444 	 * @param {Elements} elems An array or Simples object of elements to concatenate to the current simples Object
445 	 */
446 	add : function( elems ){
447 		Simples.makeArray( Simples( elems ), this );
448 		return this;
449 	},
450 	/**
451 	 * @name Simples.fn.makeArray
452 	 * @function
453 	 * @description convert the current Simples object into an Array
454 	 */	
455 	makeArray : function(){
456 		return Simples.makeArray( this );
457 	}
458 });