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