Merge branch 'bug7531' of https://github.com/csnover/jquery into csnover-bug7531
[jquery.git] / src / manipulation.js
1 (function( jQuery ) {
2
3 var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
4         rleadingWhitespace = /^\s+/,
5         rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
6         rtagName = /<([\w:]+)/,
7         rtbody = /<tbody/i,
8         rhtml = /<|&#?\w+;/,
9         rnocache = /<(?:script|object|embed|option|style)/i,
10         // checked="checked" or checked (html5)
11         rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
12         raction = /\=([^="'>\s]+\/)>/g,
13         rbodystart = /^\s*<body/i,
14         rbodyend = /<\/body>\s*$/i,
15         wrapMap = {
16                 option: [ 1, "<select multiple='multiple'>", "</select>" ],
17                 legend: [ 1, "<fieldset>", "</fieldset>" ],
18                 thead: [ 1, "<table>", "</table>" ],
19                 tr: [ 2, "<table><tbody>", "</tbody></table>" ],
20                 td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
21                 col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
22                 area: [ 1, "<map>", "</map>" ],
23                 _default: [ 0, "", "" ]
24         };
25
26 wrapMap.optgroup = wrapMap.option;
27 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
28 wrapMap.th = wrapMap.td;
29
30 // IE can't serialize <link> and <script> tags normally
31 if ( !jQuery.support.htmlSerialize ) {
32         wrapMap._default = [ 1, "div<div>", "</div>" ];
33 }
34
35 jQuery.fn.extend({
36         text: function( text ) {
37                 if ( jQuery.isFunction(text) ) {
38                         return this.each(function(i) {
39                                 var self = jQuery( this );
40
41                                 self.text( text.call(this, i, self.text()) );
42                         });
43                 }
44
45                 if ( typeof text !== "object" && text !== undefined ) {
46                         return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
47                 }
48
49                 return jQuery.text( this );
50         },
51
52         wrapAll: function( html ) {
53                 if ( jQuery.isFunction( html ) ) {
54                         return this.each(function(i) {
55                                 jQuery(this).wrapAll( html.call(this, i) );
56                         });
57                 }
58
59                 if ( this[0] ) {
60                         // The elements to wrap the target around
61                         var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
62
63                         if ( this[0].parentNode ) {
64                                 wrap.insertBefore( this[0] );
65                         }
66
67                         wrap.map(function() {
68                                 var elem = this;
69
70                                 while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
71                                         elem = elem.firstChild;
72                                 }
73
74                                 return elem;
75                         }).append(this);
76                 }
77
78                 return this;
79         },
80
81         wrapInner: function( html ) {
82                 if ( jQuery.isFunction( html ) ) {
83                         return this.each(function(i) {
84                                 jQuery(this).wrapInner( html.call(this, i) );
85                         });
86                 }
87
88                 return this.each(function() {
89                         var self = jQuery( this ),
90                                 contents = self.contents();
91
92                         if ( contents.length ) {
93                                 contents.wrapAll( html );
94
95                         } else {
96                                 self.append( html );
97                         }
98                 });
99         },
100
101         wrap: function( html ) {
102                 return this.each(function() {
103                         jQuery( this ).wrapAll( html );
104                 });
105         },
106
107         unwrap: function() {
108                 return this.parent().each(function() {
109                         if ( !jQuery.nodeName( this, "body" ) ) {
110                                 jQuery( this ).replaceWith( this.childNodes );
111                         }
112                 }).end();
113         },
114
115         append: function() {
116                 return this.domManip(arguments, true, function( elem ) {
117                         if ( this.nodeType === 1 ) {
118                                 this.appendChild( elem );
119                         }
120                 });
121         },
122
123         prepend: function() {
124                 return this.domManip(arguments, true, function( elem ) {
125                         if ( this.nodeType === 1 ) {
126                                 this.insertBefore( elem, this.firstChild );
127                         }
128                 });
129         },
130
131         before: function() {
132                 if ( this[0] && this[0].parentNode ) {
133                         return this.domManip(arguments, false, function( elem ) {
134                                 this.parentNode.insertBefore( elem, this );
135                         });
136                 } else if ( arguments.length ) {
137                         var set = jQuery(arguments[0]);
138                         set.push.apply( set, this.toArray() );
139                         return this.pushStack( set, "before", arguments );
140                 }
141         },
142
143         after: function() {
144                 if ( this[0] && this[0].parentNode ) {
145                         return this.domManip(arguments, false, function( elem ) {
146                                 this.parentNode.insertBefore( elem, this.nextSibling );
147                         });
148                 } else if ( arguments.length ) {
149                         var set = this.pushStack( this, "after", arguments );
150                         set.push.apply( set, jQuery(arguments[0]).toArray() );
151                         return set;
152                 }
153         },
154         
155         // keepData is for internal use only--do not document
156         remove: function( selector, keepData ) {
157                 for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
158                         if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
159                                 if ( !keepData && elem.nodeType === 1 ) {
160                                         jQuery.cleanData( elem.getElementsByTagName("*") );
161                                         jQuery.cleanData( [ elem ] );
162                                 }
163
164                                 if ( elem.parentNode ) {
165                                          elem.parentNode.removeChild( elem );
166                                 }
167                         }
168                 }
169                 
170                 return this;
171         },
172
173         empty: function() {
174                 for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
175                         // Remove element nodes and prevent memory leaks
176                         if ( elem.nodeType === 1 ) {
177                                 jQuery.cleanData( elem.getElementsByTagName("*") );
178                         }
179
180                         // Remove any remaining nodes
181                         while ( elem.firstChild ) {
182                                 elem.removeChild( elem.firstChild );
183                         }
184                 }
185                 
186                 return this;
187         },
188
189         clone: function( events ) {
190                 // Do the clone
191                 var ret = this.map(function() {
192                         if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
193                                 // IE copies events bound via attachEvent when
194                                 // using cloneNode. Calling detachEvent on the
195                                 // clone will also remove the events from the orignal
196                                 // In order to get around this, we use innerHTML.
197                                 // Unfortunately, this means some modifications to
198                                 // attributes in IE that are actually only stored
199                                 // as properties will not be copied (such as the
200                                 // the name attribute on an input).
201                                 var html = this.outerHTML,
202                                         ownerDocument = this.ownerDocument;
203                                 if ( !html ) {
204                                         var div = ownerDocument.createElement("div");
205                                         div.appendChild( this.cloneNode(true) );
206                                         html = div.innerHTML;
207                                 } else if ( rbodystart.test(html) && rbodyend.test(html) ) {
208                                         html = html.replace( rbodystart, "<div>" ).replace( rbodyend, "</div>" );
209                                 }
210
211                                 return jQuery.clean([html.replace(rinlinejQuery, "")
212                                         // Handle the case in IE 8 where action=/test/> self-closes a tag
213                                         .replace(raction, '="$1">')
214                                         .replace(rleadingWhitespace, "")], ownerDocument)[0];
215                         } else {
216                                 return this.cloneNode(true);
217                         }
218                 });
219
220                 // Copy the events from the original to the clone
221                 if ( events === true ) {
222                         cloneCopyEvent( this, ret );
223                         cloneCopyEvent( this.find("*"), ret.find("*") );
224                 }
225
226                 // Return the cloned set
227                 return ret;
228         },
229
230         html: function( value ) {
231                 if ( value === undefined ) {
232                         return this[0] && this[0].nodeType === 1 ?
233                                 this[0].innerHTML.replace(rinlinejQuery, "") :
234                                 null;
235
236                 // See if we can take a shortcut and just use innerHTML
237                 } else if ( typeof value === "string" && !rnocache.test( value ) &&
238                         (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
239                         !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
240
241                         value = value.replace(rxhtmlTag, "<$1></$2>");
242
243                         try {
244                                 for ( var i = 0, l = this.length; i < l; i++ ) {
245                                         // Remove element nodes and prevent memory leaks
246                                         if ( this[i].nodeType === 1 ) {
247                                                 jQuery.cleanData( this[i].getElementsByTagName("*") );
248                                                 this[i].innerHTML = value;
249                                         }
250                                 }
251
252                         // If using innerHTML throws an exception, use the fallback method
253                         } catch(e) {
254                                 this.empty().append( value );
255                         }
256
257                 } else if ( jQuery.isFunction( value ) ) {
258                         this.each(function(i){
259                                 var self = jQuery( this );
260
261                                 self.html( value.call(this, i, self.html()) );
262                         });
263
264                 } else {
265                         this.empty().append( value );
266                 }
267
268                 return this;
269         },
270
271         replaceWith: function( value ) {
272                 if ( this[0] && this[0].parentNode ) {
273                         // Make sure that the elements are removed from the DOM before they are inserted
274                         // this can help fix replacing a parent with child elements
275                         if ( jQuery.isFunction( value ) ) {
276                                 return this.each(function(i) {
277                                         var self = jQuery(this), old = self.html();
278                                         self.replaceWith( value.call( this, i, old ) );
279                                 });
280                         }
281
282                         if ( typeof value !== "string" ) {
283                                 value = jQuery( value ).detach();
284                         }
285
286                         return this.each(function() {
287                                 var next = this.nextSibling,
288                                         parent = this.parentNode;
289
290                                 jQuery( this ).remove();
291
292                                 if ( next ) {
293                                         jQuery(next).before( value );
294                                 } else {
295                                         jQuery(parent).append( value );
296                                 }
297                         });
298                 } else {
299                         return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value );
300                 }
301         },
302
303         detach: function( selector ) {
304                 return this.remove( selector, true );
305         },
306
307         domManip: function( args, table, callback ) {
308                 var results, first, fragment, parent,
309                         value = args[0],
310                         scripts = [];
311
312                 // We can't cloneNode fragments that contain checked, in WebKit
313                 if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
314                         return this.each(function() {
315                                 jQuery(this).domManip( args, table, callback, true );
316                         });
317                 }
318
319                 if ( jQuery.isFunction(value) ) {
320                         return this.each(function(i) {
321                                 var self = jQuery(this);
322                                 args[0] = value.call(this, i, table ? self.html() : undefined);
323                                 self.domManip( args, table, callback );
324                         });
325                 }
326
327                 if ( this[0] ) {
328                         parent = value && value.parentNode;
329
330                         // If we're in a fragment, just use that instead of building a new one
331                         if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
332                                 results = { fragment: parent };
333
334                         } else {
335                                 results = jQuery.buildFragment( args, this, scripts );
336                         }
337                         
338                         fragment = results.fragment;
339                         
340                         if ( fragment.childNodes.length === 1 ) {
341                                 first = fragment = fragment.firstChild;
342                         } else {
343                                 first = fragment.firstChild;
344                         }
345
346                         if ( first ) {
347                                 table = table && jQuery.nodeName( first, "tr" );
348
349                                 for ( var i = 0, l = this.length; i < l; i++ ) {
350                                         callback.call(
351                                                 table ?
352                                                         root(this[i], first) :
353                                                         this[i],
354                                                 i > 0 || results.cacheable || this.length > 1  ?
355                                                         fragment.cloneNode(true) :
356                                                         fragment
357                                         );
358                                 }
359                         }
360
361                         if ( scripts.length ) {
362                                 jQuery.each( scripts, evalScript );
363                         }
364                 }
365
366                 return this;
367         }
368 });
369
370 function root( elem, cur ) {
371         return jQuery.nodeName(elem, "table") ?
372                 (elem.getElementsByTagName("tbody")[0] ||
373                 elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
374                 elem;
375 }
376
377 function cloneCopyEvent(orig, ret) {
378         var i = 0;
379
380         ret.each(function() {
381                 if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) {
382                         return;
383                 }
384
385                 var oldData = jQuery.data( orig[i++] ),
386                         curData = jQuery.data( this, oldData ),
387                         events = oldData && oldData.events;
388
389                 if ( events ) {
390                         delete curData.handle;
391                         curData.events = {};
392
393                         for ( var type in events ) {
394                                 for ( var handler in events[ type ] ) {
395                                         jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
396                                 }
397                         }
398                 }
399         });
400 }
401
402 jQuery.buildFragment = function( args, nodes, scripts ) {
403         var fragment, cacheable, cacheresults,
404                 doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
405
406         // Only cache "small" (1/2 KB) strings that are associated with the main document
407         // Cloning options loses the selected state, so don't cache them
408         // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
409         // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
410         if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
411                 !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
412
413                 cacheable = true;
414                 cacheresults = jQuery.fragments[ args[0] ];
415                 if ( cacheresults ) {
416                         if ( cacheresults !== 1 ) {
417                                 fragment = cacheresults;
418                         }
419                 }
420         }
421
422         if ( !fragment ) {
423                 fragment = doc.createDocumentFragment();
424                 jQuery.clean( args, doc, fragment, scripts );
425         }
426
427         if ( cacheable ) {
428                 jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
429         }
430
431         return { fragment: fragment, cacheable: cacheable };
432 };
433
434 jQuery.fragments = {};
435
436 jQuery.each({
437         appendTo: "append",
438         prependTo: "prepend",
439         insertBefore: "before",
440         insertAfter: "after",
441         replaceAll: "replaceWith"
442 }, function( name, original ) {
443         jQuery.fn[ name ] = function( selector ) {
444                 var ret = [],
445                         insert = jQuery( selector ),
446                         parent = this.length === 1 && this[0].parentNode;
447                 
448                 if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
449                         insert[ original ]( this[0] );
450                         return this;
451                         
452                 } else {
453                         for ( var i = 0, l = insert.length; i < l; i++ ) {
454                                 var elems = (i > 0 ? this.clone(true) : this).get();
455                                 jQuery( insert[i] )[ original ]( elems );
456                                 ret = ret.concat( elems );
457                         }
458                 
459                         return this.pushStack( ret, name, insert.selector );
460                 }
461         };
462 });
463
464 jQuery.extend({
465         clean: function( elems, context, fragment, scripts ) {
466                 context = context || document;
467
468                 // !context.createElement fails in IE with an error but returns typeof 'object'
469                 if ( typeof context.createElement === "undefined" ) {
470                         context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
471                 }
472
473                 var ret = [];
474
475                 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
476                         if ( typeof elem === "number" ) {
477                                 elem += "";
478                         }
479
480                         if ( !elem ) {
481                                 continue;
482                         }
483
484                         // Convert html string into DOM nodes
485                         if ( typeof elem === "string" && !rhtml.test( elem ) ) {
486                                 elem = context.createTextNode( elem );
487
488                         } else if ( typeof elem === "string" ) {
489                                 // Fix "XHTML"-style tags in all browsers
490                                 elem = elem.replace(rxhtmlTag, "<$1></$2>");
491
492                                 // Trim whitespace, otherwise indexOf won't work as expected
493                                 var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
494                                         wrap = wrapMap[ tag ] || wrapMap._default,
495                                         depth = wrap[0],
496                                         div = context.createElement("div");
497
498                                 // Go to html and back, then peel off extra wrappers
499                                 div.innerHTML = wrap[1] + elem + wrap[2];
500
501                                 // Move to the right depth
502                                 while ( depth-- ) {
503                                         div = div.lastChild;
504                                 }
505
506                                 // Remove IE's autoinserted <tbody> from table fragments
507                                 if ( !jQuery.support.tbody ) {
508
509                                         // String was a <table>, *may* have spurious <tbody>
510                                         var hasBody = rtbody.test(elem),
511                                                 tbody = tag === "table" && !hasBody ?
512                                                         div.firstChild && div.firstChild.childNodes :
513
514                                                         // String was a bare <thead> or <tfoot>
515                                                         wrap[1] === "<table>" && !hasBody ?
516                                                                 div.childNodes :
517                                                                 [];
518
519                                         for ( var j = tbody.length - 1; j >= 0 ; --j ) {
520                                                 if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
521                                                         tbody[ j ].parentNode.removeChild( tbody[ j ] );
522                                                 }
523                                         }
524
525                                 }
526
527                                 // IE completely kills leading whitespace when innerHTML is used
528                                 if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
529                                         div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
530                                 }
531
532                                 elem = div.childNodes;
533                         }
534
535                         if ( elem.nodeType ) {
536                                 ret.push( elem );
537                         } else {
538                                 ret = jQuery.merge( ret, elem );
539                         }
540                 }
541
542                 if ( fragment ) {
543                         for ( i = 0; ret[i]; i++ ) {
544                                 if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
545                                         scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
546                                 
547                                 } else {
548                                         if ( ret[i].nodeType === 1 ) {
549                                                 ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
550                                         }
551                                         fragment.appendChild( ret[i] );
552                                 }
553                         }
554                 }
555
556                 return ret;
557         },
558         
559         cleanData: function( elems ) {
560                 var data, id, cache = jQuery.cache,
561                         special = jQuery.event.special,
562                         deleteExpando = jQuery.support.deleteExpando;
563                 
564                 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
565                         if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
566                                 continue;
567                         }
568
569                         id = elem[ jQuery.expando ];
570                         
571                         if ( id ) {
572                                 data = cache[ id ];
573                                 
574                                 if ( data && data.events ) {
575                                         for ( var type in data.events ) {
576                                                 if ( special[ type ] ) {
577                                                         jQuery.event.remove( elem, type );
578
579                                                 } else {
580                                                         jQuery.removeEvent( elem, type, data.handle );
581                                                 }
582                                         }
583                                 }
584                                 
585                                 if ( deleteExpando ) {
586                                         delete elem[ jQuery.expando ];
587
588                                 } else if ( elem.removeAttribute ) {
589                                         elem.removeAttribute( jQuery.expando );
590                                 }
591                                 
592                                 delete cache[ id ];
593                         }
594                 }
595         }
596 });
597
598 function evalScript( i, elem ) {
599         if ( elem.src ) {
600                 jQuery.ajax({
601                         url: elem.src,
602                         async: false,
603                         dataType: "script"
604                 });
605         } else {
606                 jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
607         }
608
609         if ( elem.parentNode ) {
610                 elem.parentNode.removeChild( elem );
611         }
612 }
613
614 })( jQuery );