jquery core: Fixed #5202. Fixing selector generation when a manipulation function...
[jquery.git] / src / manipulation.js
1 var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
2         rleadingWhitespace = /^\s+/,
3         rxhtmlTag = /(<(\w+)[^>]*?)\/>/g,
4         rselfClosing = /^(?:abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i,
5         rtagName = /<(\w+)/,
6         rtbody = /<tbody/i,
7         fcloseTag = function(all, front, tag){
8                 return rselfClosing.test(tag) ?
9                         all :
10                         front + "></" + tag + ">";
11         },
12         wrapMap = {
13                 option: [ 1, "<select multiple='multiple'>", "</select>" ],
14                 legend: [ 1, "<fieldset>", "</fieldset>" ],
15                 thead: [ 1, "<table>", "</table>" ],
16                 tr: [ 2, "<table><tbody>", "</tbody></table>" ],
17                 td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
18                 col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
19                 _default: [ 0, "", "" ]
20         };
21
22 wrapMap.optgroup = wrapMap.option;
23 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
24 wrapMap.th = wrapMap.td;
25
26 // IE can't serialize <link> and <script> tags normally
27 if ( !jQuery.support.htmlSerialize ) {
28         wrapMap._default = [ 1, "div<div>", "</div>" ];
29 }
30
31 jQuery.fn.extend({
32         text: function( text ) {
33                 if ( typeof text !== "object" && text !== undefined )
34                         return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
35
36                 var ret = "";
37
38                 jQuery.each( text || this, function(){
39                         jQuery.each( this.childNodes, function(){
40                                 if ( this.nodeType !== 8 ) {
41                                         ret += this.nodeType !== 1 ?
42                                                 this.nodeValue :
43                                                 jQuery.fn.text( [ this ] );
44                                 }
45                         });
46                 });
47
48                 return ret;
49         },
50
51         wrapAll: function( html ) {
52                 if ( jQuery.isFunction( html ) ) {
53                         return this.each(function() {
54                                 jQuery(this).wrapAll( html.apply(this, arguments) );
55                         });
56                 }
57
58                 if ( this[0] ) {
59                         // The elements to wrap the target around
60                         var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone();
61
62                         if ( this[0].parentNode ) {
63                                 wrap.insertBefore( this[0] );
64                         }
65
66                         wrap.map(function(){
67                                 var elem = this;
68
69                                 while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
70                                         elem = elem.firstChild;
71                                 }
72
73                                 return elem;
74                         }).append(this);
75                 }
76
77                 return this;
78         },
79
80         wrapInner: function( html ) {
81                 return this.each(function(){
82                         jQuery( this ).contents().wrapAll( html );
83                 });
84         },
85
86         wrap: function( html ) {
87                 return this.each(function(){
88                         jQuery( this ).wrapAll( html );
89                 });
90         },
91
92         append: function() {
93                 return this.domManip(arguments, true, function(elem){
94                         if ( this.nodeType === 1 ) {
95                                 this.appendChild( elem );
96                         }
97                 });
98         },
99
100         prepend: function() {
101                 return this.domManip(arguments, true, function(elem){
102                         if ( this.nodeType === 1 ) {
103                                 this.insertBefore( elem, this.firstChild );
104                         }
105                 });
106         },
107
108         before: function() {
109                 return this.domManip(arguments, false, function(elem){
110                         this.parentNode.insertBefore( elem, this );
111                 });
112         },
113
114         after: function() {
115                 return this.domManip(arguments, false, function(elem){
116                         this.parentNode.insertBefore( elem, this.nextSibling );
117                 });
118         },
119
120         clone: function( events ) {
121                 // Do the clone
122                 var ret = this.map(function(){
123                         if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
124                                 // IE copies events bound via attachEvent when
125                                 // using cloneNode. Calling detachEvent on the
126                                 // clone will also remove the events from the orignal
127                                 // In order to get around this, we use innerHTML.
128                                 // Unfortunately, this means some modifications to
129                                 // attributes in IE that are actually only stored
130                                 // as properties will not be copied (such as the
131                                 // the name attribute on an input).
132                                 var html = this.outerHTML, ownerDocument = this.ownerDocument;
133                                 if ( !html ) {
134                                         var div = ownerDocument.createElement("div");
135                                         div.appendChild( this.cloneNode(true) );
136                                         html = div.innerHTML;
137                                 }
138
139                                 return jQuery.clean([html.replace(rinlinejQuery, "")
140                                         .replace(rleadingWhitespace, "")], ownerDocument)[0];
141                         } else {
142                                 return this.cloneNode(true);
143                         }
144                 });
145
146                 // Copy the events from the original to the clone
147                 if ( events === true ) {
148                         var orig = this.find("*").andSelf(), i = 0;
149
150                         ret.find("*").andSelf().each(function(){
151                                 if ( this.nodeName !== orig[i].nodeName ) { return; }
152
153                                 var events = jQuery.data( orig[i], "events" );
154
155                                 for ( var type in events ) {
156                                         for ( var handler in events[ type ] ) {
157                                                 jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
158                                         }
159                                 }
160
161                                 i++;
162                         });
163                 }
164
165                 // Return the cloned set
166                 return ret;
167         },
168
169         html: function( value ) {
170                 if ( value === undefined ) {
171                         return this[0] ?
172                                 this[0].innerHTML.replace(rinlinejQuery, "") :
173                                 null;
174
175                 // See if we can take a shortcut and just use innerHTML
176                 } else if ( typeof value === "string" && !/<script/i.test( value ) &&
177                         !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
178
179                         for ( var i = 0, l = this.length; i < l; i++ ) {
180                                 // Remove element nodes and prevent memory leaks
181                                 if ( this[i].nodeType === 1 ) {
182                                         cleanData( this[i].getElementsByTagName("*") );
183                                 }
184
185                                 this[i].innerHTML = value;
186                         }
187
188                 } else {
189                         this.empty().append( value );
190                 }
191
192                 return this;
193         },
194
195         replaceWith: function( value ) {
196                 return this.after( value ).remove();
197         },
198
199         detach: function( selector ) {
200                 return this.remove( selector, true );
201         },
202
203         domManip: function( args, table, callback ) {
204                 var results, first, value = args[0], scripts = [];
205
206                 if ( jQuery.isFunction(value) ) {
207                         return this.each(function() {
208                                 args[0] = value.call(this);
209                                 return jQuery(this).domManip( args, table, callback );
210                         });
211                 }
212
213                 if ( this[0] ) {
214                         // If we're in a fragment, just use that instead of building a new one
215                         if ( args[0] && args[0].parentNode && args[0].parentNode.nodeType === 11 ) {
216                                 results = { fragment: args[0].parentNode };
217                         } else {
218                                 results = buildFragment( args, this[0], scripts );
219                         }
220
221                         first = results.fragment.firstChild;
222
223                         if ( first ) {
224                                 table = table && jQuery.nodeName( first, "tr" );
225
226                                 for ( var i = 0, l = this.length; i < l; i++ ) {
227                                         callback.call(
228                                                 table ?
229                                                         root(this[i], first) :
230                                                         this[i],
231                                                 results.cacheable || this.length > 1 || i > 0 ?
232                                                         results.fragment.cloneNode(true) :
233                                                         results.fragment
234                                         );
235                                 }
236                         }
237
238                         if ( scripts ) {
239                                 jQuery.each( scripts, evalScript );
240                         }
241                 }
242
243                 return this;
244
245                 function root( elem, cur ) {
246                         return jQuery.nodeName(elem, "table") ?
247                                 (elem.getElementsByTagName("tbody")[0] ||
248                                 elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
249                                 elem;
250                 }
251         }
252 });
253
254 function buildFragment(args, nodes, scripts){
255         var fragment, cacheable, cached, cacheresults, doc;
256
257         if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && args[0].indexOf("<option") < 0 ) {
258                 cacheable = true;
259                 cacheresults = jQuery.fragments[ args[0] ];
260                 if ( cacheresults ) {
261                         if ( cacheresults !== 1 ) {
262                                 fragment = cacheresults;
263                         }
264                         cached = true;
265                 }
266         }
267
268         if ( !fragment ) {
269                 doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
270                 fragment = doc.createDocumentFragment();
271                 jQuery.clean( args, doc, fragment, scripts );
272         }
273
274         if ( cacheable ) {
275                 jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
276         }
277
278         return { fragment: fragment, cacheable: cacheable };
279 }
280
281 jQuery.fragments = {};
282
283 jQuery.each({
284         appendTo: "append",
285         prependTo: "prepend",
286         insertBefore: "before",
287         insertAfter: "after",
288         replaceAll: "replaceWith"
289 }, function(name, original){
290         jQuery.fn[ name ] = function( selector ) {
291                 var ret = [], insert = jQuery( selector );
292
293                 for ( var i = 0, l = insert.length; i < l; i++ ) {
294                         var elems = (i > 0 ? this.clone(true) : this).get();
295                         jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
296                         ret = ret.concat( elems );
297                 }
298                 return this.pushStack( ret, name, insert.selector );
299         };
300 });
301
302 jQuery.each({
303         // keepData is for internal use only--do not document
304         remove: function( selector, keepData ) {
305                 if ( !selector || jQuery.multiFilter( selector, [ this ] ).length ) {
306                         if ( !keepData && this.nodeType === 1 ) {
307                                 cleanData( this.getElementsByTagName("*") );
308                                 cleanData( [ this ] );
309                         }
310
311                         if ( this.parentNode ) {
312                                  this.parentNode.removeChild( this );
313                         }
314                 }
315         },
316
317         empty: function() {
318                 // Remove element nodes and prevent memory leaks
319                 if ( this.nodeType === 1 ) {
320                         cleanData( this.getElementsByTagName("*") );
321                 }
322
323                 // Remove any remaining nodes
324                 while ( this.firstChild ) {
325                         this.removeChild( this.firstChild );
326                 }
327         }
328 }, function(name, fn){
329         jQuery.fn[ name ] = function(){
330                 return this.each( fn, arguments );
331         };
332 });
333
334 jQuery.extend({
335         clean: function( elems, context, fragment, scripts ) {
336                 context = context || document;
337
338                 // !context.createElement fails in IE with an error but returns typeof 'object'
339                 if ( typeof context.createElement === "undefined" ) {
340                         context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
341                 }
342
343                 var ret = [], div = context.createElement("div");
344
345                 jQuery.each(elems, function(i, elem){
346                         if ( typeof elem === "number" ) {
347                                 elem += '';
348                         }
349
350                         if ( !elem ) { return; }
351
352                         // Convert html string into DOM nodes
353                         if ( typeof elem === "string" ) {
354                                 // Fix "XHTML"-style tags in all browsers
355                                 elem = elem.replace(rxhtmlTag, fcloseTag);
356
357                                 // Trim whitespace, otherwise indexOf won't work as expected
358                                 var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
359                                         wrap = wrapMap[ tag ] || wrapMap._default,
360                                         depth = wrap[0];
361
362                                 // Go to html and back, then peel off extra wrappers
363                                 div.innerHTML = wrap[1] + elem + wrap[2];
364
365                                 // Move to the right depth
366                                 while ( depth-- ) {
367                                         div = div.lastChild;
368                                 }
369
370                                 // Remove IE's autoinserted <tbody> from table fragments
371                                 if ( !jQuery.support.tbody ) {
372
373                                         // String was a <table>, *may* have spurious <tbody>
374                                         var hasBody = rtbody.test(elem),
375                                                 tbody = tag === "table" && !hasBody ?
376                                                         div.firstChild && div.firstChild.childNodes :
377
378                                                         // String was a bare <thead> or <tfoot>
379                                                         wrap[1] == "<table>" && !hasBody ?
380                                                                 div.childNodes :
381                                                                 [];
382
383                                         for ( var j = tbody.length - 1; j >= 0 ; --j ) {
384                                                 if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
385                                                         tbody[ j ].parentNode.removeChild( tbody[ j ] );
386                                                 }
387                                         }
388
389                                 }
390
391                                 // IE completely kills leading whitespace when innerHTML is used
392                                 if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
393                                         div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
394                                 }
395
396                                 elem = jQuery.makeArray( div.childNodes );
397                         }
398
399                         if ( elem.nodeType ) {
400                                 ret.push( elem );
401                         } else {
402                                 ret = jQuery.merge( ret, elem );
403                         }
404
405                 });
406
407                 if ( fragment ) {
408                         for ( var i = 0; ret[i]; i++ ) {
409                                 if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
410                                         scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
411                                 } else {
412                                         if ( ret[i].nodeType === 1 ) {
413                                                 ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
414                                         }
415                                         fragment.appendChild( ret[i] );
416                                 }
417                         }
418                 }
419
420                 return ret;
421         }
422 });
423
424 function cleanData( elems ) {
425         for ( var i = 0, l = elems.length; i < l; i++ ) {
426                 var id = elems[i][expando];
427                 if ( id ) {
428                         delete jQuery.cache[ id ];
429                 }
430         }
431 }