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