index now works in reverse when passed a selector or undefined. fixes #3971
[jquery.git] / src / core.js
1 // Will speed up references to window, and allows munging its name.
2 var window = this,
3
4         // Will speed up references to undefined, and allows munging its name.
5         undefined,
6
7         // Map over jQuery in case of overwrite
8         _jQuery = window.jQuery,
9
10         // Map over the $ in case of overwrite
11         _$ = window.$,
12
13         // Define a local copy of jQuery
14         jQuery,
15
16         // A central reference to the root jQuery(document)
17         rootjQuery,
18
19         jQuery = window.jQuery = window.$ = function( selector, context ) {
20                 // The jQuery object is actually just the init constructor 'enhanced'
21                 return selector === undefined ?
22                         rootjQuery :
23                         new jQuery.fn.init( selector, context );
24         },
25
26         // A simple way to check for HTML strings or ID strings
27         // (both of which we optimize for)
28         quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
29
30         // Is it a simple selector
31         isSimple = /^.[^:#\[\.,]*$/,
32
33         // Keep a UserAgent string for use with jQuery.browser
34         userAgent = navigator.userAgent.toLowerCase(),
35
36         // Save a reference to the core toString method
37         toString = Object.prototype.toString;
38
39 // Define the main jQuery method
40 jQuery = window.jQuery = window.$ = function( selector, context ) {
41         // The jQuery object is actually just the init constructor 'enhanced'
42         return new jQuery.fn.init( selector, context );
43 };
44
45 jQuery.fn = jQuery.prototype = {
46         init: function( selector, context ) {
47                 var match, elem, ret;
48
49                 // Handle $(""), $(null), or $(undefined)
50                 if ( !selector ) {
51                         this.length = 0;
52                         return this;
53                 }
54
55                 // Handle $(DOMElement)
56                 if ( selector.nodeType ) {
57                         this[0] = selector;
58                         this.length = 1;
59                         this.context = selector;
60                         return this;
61                 }
62
63                 // Handle HTML strings
64                 if ( typeof selector === "string" ) {
65                         // Are we dealing with HTML string or an ID?
66                         match = quickExpr.exec( selector );
67
68                         // Verify a match, and that no context was specified for #id
69                         if ( match && (match[1] || !context) ) {
70
71                                 // HANDLE: $(html) -> $(array)
72                                 if ( match[1] ) {
73                                         selector = jQuery.clean( [ match[1] ], context );
74
75                                 // HANDLE: $("#id")
76                                 } else {
77                                         elem = document.getElementById( match[3] );
78
79                                         // Handle the case where IE and Opera return items
80                                         // by name instead of ID
81                                         if ( elem && elem.id !== match[3] ) {
82                                                 return rootjQuery.find( selector );
83                                         }
84
85                                         // Otherwise, we inject the element directly into the jQuery object
86                                         ret = jQuery( elem || null );
87                                         ret.context = document;
88                                         ret.selector = selector;
89                                         return ret;
90                                 }
91
92                         // HANDLE: $(expr, $(...))
93                         } else if ( !context || context.jquery ) {
94                                 return (context || rootjQuery).find( selector );
95
96                         // HANDLE: $(expr, context)
97                         // (which is just equivalent to: $(context).find(expr)
98                         } else {
99                                 return jQuery( context ).find( selector );
100                         }
101
102                 // HANDLE: $(function)
103                 // Shortcut for document ready
104                 } else if ( jQuery.isFunction( selector ) ) {
105                         return rootjQuery.ready( selector );
106                 }
107
108                 // Make sure that old selector state is passed along
109                 if ( selector.selector && selector.context ) {
110                         this.selector = selector.selector;
111                         this.context = selector.context;
112                 }
113
114                 return this.setArray(jQuery.isArray( selector ) ?
115                         selector :
116                         jQuery.makeArray(selector));
117         },
118
119         // Start with an empty selector
120         selector: "",
121
122         // The current version of jQuery being used
123         jquery: "@VERSION",
124
125         // The number of elements contained in the matched element set
126         size: function() {
127                 return this.length;
128         },
129
130         // Get the Nth element in the matched element set OR
131         // Get the whole matched element set as a clean array
132         get: function( num ) {
133                 return num == null ?
134
135                         // Return a 'clean' array
136                         Array.prototype.slice.call( this ) :
137
138                         // Return just the object
139                         this[ num ];
140         },
141
142         // Take an array of elements and push it onto the stack
143         // (returning the new matched element set)
144         pushStack: function( elems, name, selector ) {
145                 // Build a new jQuery matched element set
146                 var ret = jQuery( elems || null );
147
148                 // Add the old object onto the stack (as a reference)
149                 ret.prevObject = this;
150
151                 ret.context = this.context;
152
153                 if ( name === "find" ) {
154                         ret.selector = this.selector + (this.selector ? " " : "") + selector;
155                 } else if ( name ) {
156                         ret.selector = this.selector + "." + name + "(" + selector + ")";
157                 }
158
159                 // Return the newly-formed element set
160                 return ret;
161         },
162
163         // Force the current matched set of elements to become
164         // the specified array of elements (destroying the stack in the process)
165         // You should use pushStack() in order to do this, but maintain the stack
166         setArray: function( elems ) {
167                 // Resetting the length to 0, then using the native Array push
168                 // is a super-fast way to populate an object with array-like properties
169                 this.length = 0;
170                 Array.prototype.push.apply( this, elems );
171
172                 return this;
173         },
174
175         // Execute a callback for every element in the matched set.
176         // (You can seed the arguments with an array of args, but this is
177         // only used internally.)
178         each: function( callback, args ) {
179                 return jQuery.each( this, callback, args );
180         },
181
182         // Determine the position of an element within
183         // the matched set of elements
184         index: function( elem ) {
185                 if ( !elem || typeof elem === "string" ) {
186                         return jQuery.inArray( this[0],
187                                 // If it receives a string, the selector is used
188                                 // If it receives nothing, the siblings are used
189                                 elem ? jQuery( elem ) : this.parent().children() );
190                 }
191                 // Locate the position of the desired element
192                 return jQuery.inArray(
193                         // If it receives a jQuery object, the first element is used
194                         elem.jquery ? elem[0] : elem, this );
195         },
196
197         is: function( selector ) {
198                 return !!selector && jQuery.multiFilter( selector, this ).length > 0;
199         },
200
201         // For internal use only.
202         // Behaves like an Array's method, not like a jQuery method.
203         push: [].push,
204         sort: [].sort,
205         splice: [].splice
206 };
207
208 // Give the init function the jQuery prototype for later instantiation
209 jQuery.fn.init.prototype = jQuery.fn;
210
211 jQuery.extend = jQuery.fn.extend = function() {
212         // copy reference to target object
213         var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;
214
215         // Handle a deep copy situation
216         if ( typeof target === "boolean" ) {
217                 deep = target;
218                 target = arguments[1] || {};
219                 // skip the boolean and the target
220                 i = 2;
221         }
222
223         // Handle case when target is a string or something (possible in deep copy)
224         if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
225                 target = {};
226         }
227
228         // extend jQuery itself if only one argument is passed
229         if ( length === i ) {
230                 target = this;
231                 --i;
232         }
233
234         for ( ; i < length; i++ ) {
235                 // Only deal with non-null/undefined values
236                 if ( (options = arguments[ i ]) != null ) {
237                         // Extend the base object
238                         for ( name in options ) {
239                                 src = target[ name ];
240                                 copy = options[ name ];
241
242                                 // Prevent never-ending loop
243                                 if ( target === copy ) {
244                                         continue;
245                                 }
246
247                                 // Recurse if we're merging object values
248                                 if ( deep && copy && typeof copy === "object" && !copy.nodeType ) {
249                                         target[ name ] = jQuery.extend( deep,
250                                                 // Never move original objects, clone them
251                                                 src || ( copy.length != null ? [ ] : { } ), copy );
252
253                                 // Don't bring in undefined values
254                                 } else if ( copy !== undefined ) {
255                                         target[ name ] = copy;
256                                 }
257                         }
258                 }
259         }
260
261         // Return the modified object
262         return target;
263 };
264
265 jQuery.extend({
266         noConflict: function( deep ) {
267                 window.$ = _$;
268
269                 if ( deep ) {
270                         window.jQuery = _jQuery;
271                 }
272
273                 return jQuery;
274         },
275
276         // See test/unit/core.js for details concerning isFunction.
277         // Since version 1.3, DOM methods and functions like alert
278         // aren't supported. They return false on IE (#2968).
279         isFunction: function( obj ) {
280                 return toString.call(obj) === "[object Function]";
281         },
282
283         isArray: function( obj ) {
284                 return toString.call(obj) === "[object Array]";
285         },
286
287         // check if an element is in a (or is an) XML document
288         isXMLDoc: function( elem ) {
289                 return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
290                         !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
291         },
292
293         // Evalulates a script in a global context
294         globalEval: function( data ) {
295                 if ( data && /\S/.test(data) ) {
296                         // Inspired by code by Andrea Giammarchi
297                         // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
298                         var head = document.getElementsByTagName("head")[0] || document.documentElement,
299                                 script = document.createElement("script");
300
301                         script.type = "text/javascript";
302                         if ( jQuery.support.scriptEval ) {
303                                 script.appendChild( document.createTextNode( data ) );
304                         } else {
305                                 script.text = data;
306                         }
307
308                         // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
309                         // This arises when a base node is used (#2709).
310                         head.insertBefore( script, head.firstChild );
311                         head.removeChild( script );
312                 }
313         },
314
315         nodeName: function( elem, name ) {
316                 return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
317         },
318
319         // args is for internal usage only
320         each: function( object, callback, args ) {
321                 var name, i = 0, length = object.length;
322
323                 if ( args ) {
324                         if ( length === undefined ) {
325                                 for ( name in object ) {
326                                         if ( callback.apply( object[ name ], args ) === false ) {
327                                                 break;
328                                         }
329                                 }
330                         } else {
331                                 for ( ; i < length; ) {
332                                         if ( callback.apply( object[ i++ ], args ) === false ) {
333                                                 break;
334                                         }
335                                 }
336                         }
337
338                 // A special, fast, case for the most common use of each
339                 } else {
340                         if ( length === undefined ) {
341                                 for ( name in object ) {
342                                         if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
343                                                 break;
344                                         }
345                                 }
346                         } else {
347                                 for ( var value = object[0];
348                                         i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
349                         }
350                 }
351
352                 return object;
353         },
354
355         trim: function( text ) {
356                 return (text || "").replace( /^\s+|\s+$/g, "" );
357         },
358
359         makeArray: function( array ) {
360                 var ret = [], i;
361
362                 if ( array != null ) {
363                         i = array.length;
364
365                         // The window, strings (and functions) also have 'length'
366                         if ( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval ) {
367                                 ret[0] = array;
368                         } else {
369                                 while ( i ) {
370                                         ret[--i] = array[i];
371                                 }
372                         }
373                 }
374
375                 return ret;
376         },
377
378         inArray: function( elem, array ) {
379                 for ( var i = 0, length = array.length; i < length; i++ ) {
380                         if ( array[ i ] === elem ) {
381                                 return i;
382                         }
383                 }
384
385                 return -1;
386         },
387
388         merge: function( first, second ) {
389                 // We have to loop this way because IE & Opera overwrite the length
390                 // expando of getElementsByTagName
391                 var i = 0, elem, pos = first.length;
392
393                 // Also, we need to make sure that the correct elements are being returned
394                 // (IE returns comment nodes in a '*' query)
395                 if ( !jQuery.support.getAll ) {
396                         while ( (elem = second[ i++ ]) != null ) {
397                                 if ( elem.nodeType !== 8 ) {
398                                         first[ pos++ ] = elem;
399                                 }
400                         }
401
402                 } else {
403                         while ( (elem = second[ i++ ]) != null ) {
404                                 first[ pos++ ] = elem;
405                         }
406                 }
407
408                 return first;
409         },
410
411         unique: function( array ) {
412                 var ret = [], done = {}, id;
413
414                 try {
415                         for ( var i = 0, length = array.length; i < length; i++ ) {
416                                 id = jQuery.data( array[ i ] );
417
418                                 if ( !done[ id ] ) {
419                                         done[ id ] = true;
420                                         ret.push( array[ i ] );
421                                 }
422                         }
423                 } catch( e ) {
424                         ret = array;
425                 }
426
427                 return ret;
428         },
429
430         grep: function( elems, callback, inv ) {
431                 var ret = [];
432
433                 // Go through the array, only saving the items
434                 // that pass the validator function
435                 for ( var i = 0, length = elems.length; i < length; i++ ) {
436                         if ( !inv !== !callback( elems[ i ], i ) ) {
437                                 ret.push( elems[ i ] );
438                         }
439                 }
440
441                 return ret;
442         },
443
444         map: function( elems, callback ) {
445                 var ret = [], value;
446
447                 // Go through the array, translating each of the items to their
448                 // new value (or values).
449                 for ( var i = 0, length = elems.length; i < length; i++ ) {
450                         value = callback( elems[ i ], i );
451
452                         if ( value != null ) {
453                                 ret[ ret.length ] = value;
454                         }
455                 }
456
457                 return ret.concat.apply( [], ret );
458         },
459
460         // Use of jQuery.browser is deprecated.
461         // It's included for backwards compatibility and plugins,
462         // although they should work to migrate away.
463         browser: {
464                 version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
465                 safari: /webkit/.test( userAgent ),
466                 opera: /opera/.test( userAgent ),
467                 msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
468                 mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
469         }
470 });
471
472 // All jQuery objects should point back to these
473 rootjQuery = jQuery(document);
474
475 function evalScript( i, elem ) {
476         if ( elem.src ) {
477                 jQuery.ajax({
478                         url: elem.src,
479                         async: false,
480                         dataType: "script"
481                 });
482         } else {
483                 jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
484         }
485
486         if ( elem.parentNode ) {
487                 elem.parentNode.removeChild( elem );
488         }
489 }
490
491 function now() {
492         return (new Date).getTime();
493 }