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