3.3.0
[jquery.git] / src / manipulation.js
blob142e296ad0e196d580f5f0d1b34b2d2d4b6ae43f
1 define( [
2         "./core",
3         "./var/concat",
4         "./var/isFunction",
5         "./var/push",
6         "./core/access",
7         "./manipulation/var/rcheckableType",
8         "./manipulation/var/rtagName",
9         "./manipulation/var/rscriptType",
10         "./manipulation/wrapMap",
11         "./manipulation/getAll",
12         "./manipulation/setGlobalEval",
13         "./manipulation/buildFragment",
14         "./manipulation/support",
16         "./data/var/dataPriv",
17         "./data/var/dataUser",
18         "./data/var/acceptData",
19         "./core/DOMEval",
20         "./core/nodeName",
22         "./core/init",
23         "./traversing",
24         "./selector",
25         "./event"
26 ], function( jQuery, concat, isFunction, push, access,
27         rcheckableType, rtagName, rscriptType,
28         wrapMap, getAll, setGlobalEval, buildFragment, support,
29         dataPriv, dataUser, acceptData, DOMEval, nodeName ) {
31 "use strict";
33 var
35         /* eslint-disable max-len */
37         // See https://github.com/eslint/eslint/issues/3229
38         rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,
40         /* eslint-enable */
42         // Support: IE <=10 - 11, Edge 12 - 13 only
43         // In IE/Edge using regex groups here causes severe slowdowns.
44         // See https://connect.microsoft.com/IE/feedback/details/1736512/
45         rnoInnerhtml = /<script|<style|<link/i,
47         // checked="checked" or checked
48         rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
49         rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
51 // Prefer a tbody over its parent table for containing new rows
52 function manipulationTarget( elem, content ) {
53         if ( nodeName( elem, "table" ) &&
54                 nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
56                 return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
57         }
59         return elem;
62 // Replace/restore the type attribute of script elements for safe DOM manipulation
63 function disableScript( elem ) {
64         elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
65         return elem;
67 function restoreScript( elem ) {
68         if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
69                 elem.type = elem.type.slice( 5 );
70         } else {
71                 elem.removeAttribute( "type" );
72         }
74         return elem;
77 function cloneCopyEvent( src, dest ) {
78         var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
80         if ( dest.nodeType !== 1 ) {
81                 return;
82         }
84         // 1. Copy private data: events, handlers, etc.
85         if ( dataPriv.hasData( src ) ) {
86                 pdataOld = dataPriv.access( src );
87                 pdataCur = dataPriv.set( dest, pdataOld );
88                 events = pdataOld.events;
90                 if ( events ) {
91                         delete pdataCur.handle;
92                         pdataCur.events = {};
94                         for ( type in events ) {
95                                 for ( i = 0, l = events[ type ].length; i < l; i++ ) {
96                                         jQuery.event.add( dest, type, events[ type ][ i ] );
97                                 }
98                         }
99                 }
100         }
102         // 2. Copy user data
103         if ( dataUser.hasData( src ) ) {
104                 udataOld = dataUser.access( src );
105                 udataCur = jQuery.extend( {}, udataOld );
107                 dataUser.set( dest, udataCur );
108         }
111 // Fix IE bugs, see support tests
112 function fixInput( src, dest ) {
113         var nodeName = dest.nodeName.toLowerCase();
115         // Fails to persist the checked state of a cloned checkbox or radio button.
116         if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
117                 dest.checked = src.checked;
119         // Fails to return the selected option to the default selected state when cloning options
120         } else if ( nodeName === "input" || nodeName === "textarea" ) {
121                 dest.defaultValue = src.defaultValue;
122         }
125 function domManip( collection, args, callback, ignored ) {
127         // Flatten any nested arrays
128         args = concat.apply( [], args );
130         var fragment, first, scripts, hasScripts, node, doc,
131                 i = 0,
132                 l = collection.length,
133                 iNoClone = l - 1,
134                 value = args[ 0 ],
135                 valueIsFunction = isFunction( value );
137         // We can't cloneNode fragments that contain checked, in WebKit
138         if ( valueIsFunction ||
139                         ( l > 1 && typeof value === "string" &&
140                                 !support.checkClone && rchecked.test( value ) ) ) {
141                 return collection.each( function( index ) {
142                         var self = collection.eq( index );
143                         if ( valueIsFunction ) {
144                                 args[ 0 ] = value.call( this, index, self.html() );
145                         }
146                         domManip( self, args, callback, ignored );
147                 } );
148         }
150         if ( l ) {
151                 fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
152                 first = fragment.firstChild;
154                 if ( fragment.childNodes.length === 1 ) {
155                         fragment = first;
156                 }
158                 // Require either new content or an interest in ignored elements to invoke the callback
159                 if ( first || ignored ) {
160                         scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
161                         hasScripts = scripts.length;
163                         // Use the original fragment for the last item
164                         // instead of the first because it can end up
165                         // being emptied incorrectly in certain situations (#8070).
166                         for ( ; i < l; i++ ) {
167                                 node = fragment;
169                                 if ( i !== iNoClone ) {
170                                         node = jQuery.clone( node, true, true );
172                                         // Keep references to cloned scripts for later restoration
173                                         if ( hasScripts ) {
175                                                 // Support: Android <=4.0 only, PhantomJS 1 only
176                                                 // push.apply(_, arraylike) throws on ancient WebKit
177                                                 jQuery.merge( scripts, getAll( node, "script" ) );
178                                         }
179                                 }
181                                 callback.call( collection[ i ], node, i );
182                         }
184                         if ( hasScripts ) {
185                                 doc = scripts[ scripts.length - 1 ].ownerDocument;
187                                 // Reenable scripts
188                                 jQuery.map( scripts, restoreScript );
190                                 // Evaluate executable scripts on first document insertion
191                                 for ( i = 0; i < hasScripts; i++ ) {
192                                         node = scripts[ i ];
193                                         if ( rscriptType.test( node.type || "" ) &&
194                                                 !dataPriv.access( node, "globalEval" ) &&
195                                                 jQuery.contains( doc, node ) ) {
197                                                 if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {
199                                                         // Optional AJAX dependency, but won't run scripts if not present
200                                                         if ( jQuery._evalUrl ) {
201                                                                 jQuery._evalUrl( node.src );
202                                                         }
203                                                 } else {
204                                                         DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node );
205                                                 }
206                                         }
207                                 }
208                         }
209                 }
210         }
212         return collection;
215 function remove( elem, selector, keepData ) {
216         var node,
217                 nodes = selector ? jQuery.filter( selector, elem ) : elem,
218                 i = 0;
220         for ( ; ( node = nodes[ i ] ) != null; i++ ) {
221                 if ( !keepData && node.nodeType === 1 ) {
222                         jQuery.cleanData( getAll( node ) );
223                 }
225                 if ( node.parentNode ) {
226                         if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
227                                 setGlobalEval( getAll( node, "script" ) );
228                         }
229                         node.parentNode.removeChild( node );
230                 }
231         }
233         return elem;
236 jQuery.extend( {
237         htmlPrefilter: function( html ) {
238                 return html.replace( rxhtmlTag, "<$1></$2>" );
239         },
241         clone: function( elem, dataAndEvents, deepDataAndEvents ) {
242                 var i, l, srcElements, destElements,
243                         clone = elem.cloneNode( true ),
244                         inPage = jQuery.contains( elem.ownerDocument, elem );
246                 // Fix IE cloning issues
247                 if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
248                                 !jQuery.isXMLDoc( elem ) ) {
250                         // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
251                         destElements = getAll( clone );
252                         srcElements = getAll( elem );
254                         for ( i = 0, l = srcElements.length; i < l; i++ ) {
255                                 fixInput( srcElements[ i ], destElements[ i ] );
256                         }
257                 }
259                 // Copy the events from the original to the clone
260                 if ( dataAndEvents ) {
261                         if ( deepDataAndEvents ) {
262                                 srcElements = srcElements || getAll( elem );
263                                 destElements = destElements || getAll( clone );
265                                 for ( i = 0, l = srcElements.length; i < l; i++ ) {
266                                         cloneCopyEvent( srcElements[ i ], destElements[ i ] );
267                                 }
268                         } else {
269                                 cloneCopyEvent( elem, clone );
270                         }
271                 }
273                 // Preserve script evaluation history
274                 destElements = getAll( clone, "script" );
275                 if ( destElements.length > 0 ) {
276                         setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
277                 }
279                 // Return the cloned set
280                 return clone;
281         },
283         cleanData: function( elems ) {
284                 var data, elem, type,
285                         special = jQuery.event.special,
286                         i = 0;
288                 for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
289                         if ( acceptData( elem ) ) {
290                                 if ( ( data = elem[ dataPriv.expando ] ) ) {
291                                         if ( data.events ) {
292                                                 for ( type in data.events ) {
293                                                         if ( special[ type ] ) {
294                                                                 jQuery.event.remove( elem, type );
296                                                         // This is a shortcut to avoid jQuery.event.remove's overhead
297                                                         } else {
298                                                                 jQuery.removeEvent( elem, type, data.handle );
299                                                         }
300                                                 }
301                                         }
303                                         // Support: Chrome <=35 - 45+
304                                         // Assign undefined instead of using delete, see Data#remove
305                                         elem[ dataPriv.expando ] = undefined;
306                                 }
307                                 if ( elem[ dataUser.expando ] ) {
309                                         // Support: Chrome <=35 - 45+
310                                         // Assign undefined instead of using delete, see Data#remove
311                                         elem[ dataUser.expando ] = undefined;
312                                 }
313                         }
314                 }
315         }
316 } );
318 jQuery.fn.extend( {
319         detach: function( selector ) {
320                 return remove( this, selector, true );
321         },
323         remove: function( selector ) {
324                 return remove( this, selector );
325         },
327         text: function( value ) {
328                 return access( this, function( value ) {
329                         return value === undefined ?
330                                 jQuery.text( this ) :
331                                 this.empty().each( function() {
332                                         if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
333                                                 this.textContent = value;
334                                         }
335                                 } );
336                 }, null, value, arguments.length );
337         },
339         append: function() {
340                 return domManip( this, arguments, function( elem ) {
341                         if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
342                                 var target = manipulationTarget( this, elem );
343                                 target.appendChild( elem );
344                         }
345                 } );
346         },
348         prepend: function() {
349                 return domManip( this, arguments, function( elem ) {
350                         if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
351                                 var target = manipulationTarget( this, elem );
352                                 target.insertBefore( elem, target.firstChild );
353                         }
354                 } );
355         },
357         before: function() {
358                 return domManip( this, arguments, function( elem ) {
359                         if ( this.parentNode ) {
360                                 this.parentNode.insertBefore( elem, this );
361                         }
362                 } );
363         },
365         after: function() {
366                 return domManip( this, arguments, function( elem ) {
367                         if ( this.parentNode ) {
368                                 this.parentNode.insertBefore( elem, this.nextSibling );
369                         }
370                 } );
371         },
373         empty: function() {
374                 var elem,
375                         i = 0;
377                 for ( ; ( elem = this[ i ] ) != null; i++ ) {
378                         if ( elem.nodeType === 1 ) {
380                                 // Prevent memory leaks
381                                 jQuery.cleanData( getAll( elem, false ) );
383                                 // Remove any remaining nodes
384                                 elem.textContent = "";
385                         }
386                 }
388                 return this;
389         },
391         clone: function( dataAndEvents, deepDataAndEvents ) {
392                 dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
393                 deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
395                 return this.map( function() {
396                         return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
397                 } );
398         },
400         html: function( value ) {
401                 return access( this, function( value ) {
402                         var elem = this[ 0 ] || {},
403                                 i = 0,
404                                 l = this.length;
406                         if ( value === undefined && elem.nodeType === 1 ) {
407                                 return elem.innerHTML;
408                         }
410                         // See if we can take a shortcut and just use innerHTML
411                         if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
412                                 !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
414                                 value = jQuery.htmlPrefilter( value );
416                                 try {
417                                         for ( ; i < l; i++ ) {
418                                                 elem = this[ i ] || {};
420                                                 // Remove element nodes and prevent memory leaks
421                                                 if ( elem.nodeType === 1 ) {
422                                                         jQuery.cleanData( getAll( elem, false ) );
423                                                         elem.innerHTML = value;
424                                                 }
425                                         }
427                                         elem = 0;
429                                 // If using innerHTML throws an exception, use the fallback method
430                                 } catch ( e ) {}
431                         }
433                         if ( elem ) {
434                                 this.empty().append( value );
435                         }
436                 }, null, value, arguments.length );
437         },
439         replaceWith: function() {
440                 var ignored = [];
442                 // Make the changes, replacing each non-ignored context element with the new content
443                 return domManip( this, arguments, function( elem ) {
444                         var parent = this.parentNode;
446                         if ( jQuery.inArray( this, ignored ) < 0 ) {
447                                 jQuery.cleanData( getAll( this ) );
448                                 if ( parent ) {
449                                         parent.replaceChild( elem, this );
450                                 }
451                         }
453                 // Force callback invocation
454                 }, ignored );
455         }
456 } );
458 jQuery.each( {
459         appendTo: "append",
460         prependTo: "prepend",
461         insertBefore: "before",
462         insertAfter: "after",
463         replaceAll: "replaceWith"
464 }, function( name, original ) {
465         jQuery.fn[ name ] = function( selector ) {
466                 var elems,
467                         ret = [],
468                         insert = jQuery( selector ),
469                         last = insert.length - 1,
470                         i = 0;
472                 for ( ; i <= last; i++ ) {
473                         elems = i === last ? this : this.clone( true );
474                         jQuery( insert[ i ] )[ original ]( elems );
476                         // Support: Android <=4.0 only, PhantomJS 1 only
477                         // .get() because push.apply(_, arraylike) throws on ancient WebKit
478                         push.apply( ret, elems.get() );
479                 }
481                 return this.pushStack( ret );
482         };
483 } );
485 return jQuery;
486 } );