Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

#8500 and #8060 - Appending disconnected radio or checkbox inputs and keeping checked setting #332

Closed
wants to merge 5 commits into from

3 participants

Timmy Willison John Resig Corey Frang
Timmy Willison
Collaborator

IE6/7 can't handle holding on to the checked property when attaching to the DOM, this was apparent mostly when attaching newly-made checkboxes to the DOM or doing dom manipulation that involved reattaching radios or checkboxes.

  • Thanks to gnarf for helping out with this one. Any review is appreciated.

  • I didn't see a way to use an existing input in support.js and needed to create another one that would actually be attached to our mock body.

  • Also, many of the lines are simple formatting changes as I was working on this. The important lines are 512-522 and 644-655

src/manipulation.js
((89 lines not shown))
  632
+			// Resets defaultChecked for any radios and checkboxes about to be appended to the DOM in IE 6/7 (#8060)
  633
+			if ( !jQuery.support.appendChecked ) {
  634
+				fixChecked = function( elem ) {
  635
+					if ( jQuery.nodeName( elem, "input" ) && (elem.type === "checkbox" || elem.type === "radio") ) {
  636
+						elem.defaultChecked = elem.checked;
  637
+					}
  638
+				};
  639
+			
  640
+				if ( elem[0] && typeof elem.length === "number" ) {
  641
+					for ( i = 0, len = elem.length; i < len; i++ ) {
  642
+						if ( elem[i].getElementsByTagName ) {
  643
+							fixChecked( elem[i] );
  644
+							jQuery.grep( elem[i].getElementsByTagName("input"), fixChecked );
  645
+						}
  646
+					}
  647
+				} else {
1
Corey Frang Owner
gnarf added a note

not a huge deal, but couldn't just just be an else if ( elem.getElementsByTagName )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/manipulation.js
((92 lines not shown))
  635
+					if ( jQuery.nodeName( elem, "input" ) && (elem.type === "checkbox" || elem.type === "radio") ) {
  636
+						elem.defaultChecked = elem.checked;
  637
+					}
  638
+				};
  639
+			
  640
+				if ( elem[0] && typeof elem.length === "number" ) {
  641
+					for ( i = 0, len = elem.length; i < len; i++ ) {
  642
+						if ( elem[i].getElementsByTagName ) {
  643
+							fixChecked( elem[i] );
  644
+							jQuery.grep( elem[i].getElementsByTagName("input"), fixChecked );
  645
+						}
  646
+					}
  647
+				} else {
  648
+					if ( elem.getElementsByTagName ) {
  649
+						fixChecked( elem );
  650
+						jQuery.grep( elem.getElementsByTagName("input"), fixChecked );
1
Corey Frang Owner
gnarf added a note

Since both if statements already call fixChecked, perhaps it would be better to do the jQuery.grep in fixChecked() if the element passed in isn't an input...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Timmy Willison
Collaborator

Thanks gnarf! Changes added.

John Resig jeresig commented on the diff
src/manipulation.js
((87 lines not shown))
627 643
 
628  
-				elem = div.childNodes;
  644
+			// Resets defaultChecked for any radios and checkboxes
  645
+			// about to be appended to the DOM in IE 6/7 (#8060)
  646
+			var len;
  647
+			if ( !jQuery.support.appendChecked ) {
  648
+				if ( elem[0] && typeof (len = elem.length) === "number" ) {
2
John Resig
jeresig added a note

This is a bit too cute - can this be broken out and declared inside the for loop?

Timmy Willison Collaborator
timmywil added a note

declaring here saves accessing length twice, and declaring in the if only defines len if elem[0] passes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/manipulation.js
((87 lines not shown))
627 643
 
628  
-				elem = div.childNodes;
  644
+			// Resets defaultChecked for any radios and checkboxes
  645
+			// about to be appended to the DOM in IE 6/7 (#8060)
  646
+			var len;
  647
+			if ( !jQuery.support.appendChecked ) {
  648
+				if ( elem[0] && typeof (len = elem.length) === "number" ) {
  649
+					for ( i = 0; i < len; i++ ) {
  650
+						fixChecked( elem[i] );
  651
+					}
  652
+				} else {
2
John Resig
jeresig added a note

When is this branch arrived at? Does it need to exist?

Timmy Willison Collaborator
timmywil added a note

Apparently clean can receive a nodelist or a single element, so this would be reached in the latter case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/manipulation.js
... ...
@@ -508,6 +509,18 @@ function getAll( elem ) {
508 509
 	}
509 510
 }
510 511
 
  512
+// Used in clean, fixes the defaultChecked property
  513
+// on all inputs and checkboxes within html recursively
  514
+function fixChecked( elem ) {
  515
+	if ( jQuery.nodeName( elem, "input" ) ) {
5
John Resig
jeresig added a note

It seems like this might be overkill, given that we're doing getElementsByTagName("input"). Perhaps this check can be moved to the for loop that wraps this?

Timmy Willison Collaborator
timmywil added a note

Looking into this.

Timmy Willison Collaborator
timmywil added a note

Unfortunately I don't think it can be moved. I don't only want to pass inputs to fixChecked since I need to do get any inputs within any element passed to clean. Putting this if statement in clean would require duplicating most of this function within clean twice.

Corey Frang Owner
gnarf added a note

Its basically turning it into two functions... fixChecked() can be passed any element (which may contain inputs, hence the later grep) or it can be passed an input element, which if a checkbox gets the checked property fixed.

Maybe it could be 2 functions to avoid calling nodeName on every found "input" tag...

// elem is assumed to be an input
 function fixDefaultChecked( elem ) {
   if ( elem.type == 'radio' || elem.type == 'checkbox') {
     elem.defaultChecked = elem.checked;
   }
 }

 function findInputs( elem ) {
    if (jQuery.nodeName( elem, "input" )) {
      fixDefaultChecked(elem);
    } else if (elem.getElementByTagName) { 
      jQuery.grep( elem.getElementsByTagName( "input "), fixDefaultChecked );
    }
 }
Timmy Willison Collaborator
timmywil added a note

Added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
John Resig

Landed in commit d274b7b.

John Resig jeresig closed this
David Peñuela davidpenuelab referenced this pull request from a commit
Landing pull request 332. Appending disconnected radio or checkbox in…
…puts and keeping checked setting Fixes #8060, #8500.

More Details:
 - #332
 - http://bugs.jquery.com/ticket/8060
 - http://bugs.jquery.com/ticket/8500
d274b7b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 5 unique commits by 1 author.

Apr 17, 2011
#8500 and #8060 - Checked attribute did not carry over once a discon…
…nected radio or checkbox is appended to the DOM

- Cleaned up some code in $.fn.clean (badum-psh)
0bcbbde
Drying up the fixChecked calls ac39d0c
Move fixChecked function definition out of clean and out of the for loop 54b3fb8
Separate defaultChecked fix from finding inputs to avoid calling node…
…Name unnecessarily
098625d
Apr 20, 2011
Add comment for support addition 67c4b1a
This page is out of date. Refresh to see the latest.
2  src/core.js
@@ -731,7 +731,7 @@ jQuery.extend({
731 731
 				}
732 732
 			}
733 733
 
734  
-		// Go thorugh every key on the object,
  734
+		// Go through every key on the object,
735 735
 		} else {
736 736
 			for ( key in elems ) {
737 737
 				value = callback( elems[ key ], key, arg );
113  src/manipulation.js
@@ -70,7 +70,7 @@ jQuery.fn.extend({
70 70
 				}
71 71
 
72 72
 				return elem;
73  
-			}).append(this);
  73
+			}).append( this );
74 74
 		}
75 75
 
76 76
 		return this;
@@ -379,13 +379,13 @@ function cloneCopyEvent( src, dest ) {
379 379
 }
380 380
 
381 381
 function cloneFixAttributes( src, dest ) {
  382
+	var nodeName;
  383
+
382 384
 	// We do not need to do anything for non-Elements
383 385
 	if ( dest.nodeType !== 1 ) {
384 386
 		return;
385 387
 	}
386 388
 
387  
-	var nodeName = dest.nodeName.toLowerCase();
388  
-
389 389
 	// clearAttributes removes the attributes, which we don't want,
390 390
 	// but also removes the attachEvent events, which we *do* want
391 391
 	if ( dest.clearAttributes ) {
@@ -398,6 +398,8 @@ function cloneFixAttributes( src, dest ) {
398 398
 		dest.mergeAttributes( src );
399 399
 	}
400 400
 
  401
+	nodeName = dest.nodeName.toLowerCase();
  402
+
401 403
 	// IE6-8 fail to clone children inside object elements that use
402 404
 	// the proprietary classid attribute value (rather than the type
403 405
 	// attribute) to identify the type of content to display
@@ -446,11 +448,10 @@ jQuery.buildFragment = function( args, nodes, scripts ) {
446 448
 		args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
447 449
 
448 450
 		cacheable = true;
  451
+
449 452
 		cacheresults = jQuery.fragments[ args[0] ];
450  
-		if ( cacheresults ) {
451  
-			if ( cacheresults !== 1 ) {
452  
-				fragment = cacheresults;
453  
-			}
  453
+		if ( cacheresults && cacheresults !== 1 ) {
  454
+			fragment = cacheresults;
454 455
 		}
455 456
 	}
456 457
 
@@ -508,6 +509,21 @@ function getAll( elem ) {
508 509
 	}
509 510
 }
510 511
 
  512
+// Used in clean, fixes the defaultChecked property
  513
+function fixDefaultChecked( elem ) {
  514
+	if ( elem.type === "checkbox" || elem.type === "radio" ) {
  515
+		elem.defaultChecked = elem.checked;
  516
+	}
  517
+}
  518
+// Finds all inputs and passes them to fixDefaultChecked
  519
+function findInputs( elem ) {
  520
+	if ( jQuery.nodeName( elem, "input" ) ) {
  521
+		fixDefaultChecked( elem );
  522
+	} else if ( elem.getElementsByTagName ) {
  523
+		jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
  524
+	}
  525
+}
  526
+
511 527
 jQuery.extend({
512 528
 	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
513 529
 		var clone = elem.cloneNode(true),
@@ -578,54 +594,67 @@ jQuery.extend({
578 594
 			}
579 595
 
580 596
 			// Convert html string into DOM nodes
581  
-			if ( typeof elem === "string" && !rhtml.test( elem ) ) {
582  
-				elem = context.createTextNode( elem );
583  
-
584  
-			} else if ( typeof elem === "string" ) {
585  
-				// Fix "XHTML"-style tags in all browsers
586  
-				elem = elem.replace(rxhtmlTag, "<$1></$2>");
  597
+			if ( typeof elem === "string" ) {
  598
+				if ( !rhtml.test( elem ) ) {
  599
+					elem = context.createTextNode( elem );
  600
+				} else {
  601
+					// Fix "XHTML"-style tags in all browsers
  602
+					elem = elem.replace(rxhtmlTag, "<$1></$2>");
587 603
 
588  
-				// Trim whitespace, otherwise indexOf won't work as expected
589  
-				var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
590  
-					wrap = wrapMap[ tag ] || wrapMap._default,
591  
-					depth = wrap[0],
592  
-					div = context.createElement("div");
  604
+					// Trim whitespace, otherwise indexOf won't work as expected
  605
+					var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
  606
+						wrap = wrapMap[ tag ] || wrapMap._default,
  607
+						depth = wrap[0],
  608
+						div = context.createElement("div");
593 609
 
594  
-				// Go to html and back, then peel off extra wrappers
595  
-				div.innerHTML = wrap[1] + elem + wrap[2];
  610
+					// Go to html and back, then peel off extra wrappers
  611
+					div.innerHTML = wrap[1] + elem + wrap[2];
596 612
 
597  
-				// Move to the right depth
598  
-				while ( depth-- ) {
599  
-					div = div.lastChild;
600  
-				}
  613
+					// Move to the right depth
  614
+					while ( depth-- ) {
  615
+						div = div.lastChild;
  616
+					}
601 617
 
602  
-				// Remove IE's autoinserted <tbody> from table fragments
603  
-				if ( !jQuery.support.tbody ) {
  618
+					// Remove IE's autoinserted <tbody> from table fragments
  619
+					if ( !jQuery.support.tbody ) {
604 620
 
605  
-					// String was a <table>, *may* have spurious <tbody>
606  
-					var hasBody = rtbody.test(elem),
607  
-						tbody = tag === "table" && !hasBody ?
608  
-							div.firstChild && div.firstChild.childNodes :
  621
+						// String was a <table>, *may* have spurious <tbody>
  622
+						var hasBody = rtbody.test(elem),
  623
+							tbody = tag === "table" && !hasBody ?
  624
+								div.firstChild && div.firstChild.childNodes :
609 625
 
610  
-							// String was a bare <thead> or <tfoot>
611  
-							wrap[1] === "<table>" && !hasBody ?
612  
-								div.childNodes :
613  
-								[];
  626
+								// String was a bare <thead> or <tfoot>
  627
+								wrap[1] === "<table>" && !hasBody ?
  628
+									div.childNodes :
  629
+									[];
614 630
 
615  
-					for ( var j = tbody.length - 1; j >= 0 ; --j ) {
616  
-						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
617  
-							tbody[ j ].parentNode.removeChild( tbody[ j ] );
  631
+						for ( var j = tbody.length - 1; j >= 0 ; --j ) {
  632
+							if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
  633
+								tbody[ j ].parentNode.removeChild( tbody[ j ] );
  634
+							}
618 635
 						}
619 636
 					}
620 637
 
621  
-				}
  638
+					// IE completely kills leading whitespace when innerHTML is used
  639
+					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
  640
+						div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
  641
+					}
622 642
 
623  
-				// IE completely kills leading whitespace when innerHTML is used
624  
-				if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
625  
-					div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
  643
+					elem = div.childNodes;
626 644
 				}
  645
+			}
627 646
 
628  
-				elem = div.childNodes;
  647
+			// Resets defaultChecked for any radios and checkboxes
  648
+			// about to be appended to the DOM in IE 6/7 (#8060)
  649
+			var len;
  650
+			if ( !jQuery.support.appendChecked ) {
  651
+				if ( elem[0] && typeof (len = elem.length) === "number" ) {
  652
+					for ( i = 0; i < len; i++ ) {
  653
+						findInputs( elem[i] );
  654
+					}
  655
+				} else {
  656
+					findInputs( elem );
  657
+				}
629 658
 			}
630 659
 
631 660
 			if ( elem.nodeType ) {
8  src/support.js
@@ -185,6 +185,14 @@ jQuery.support = (function() {
185 185
 	support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
186 186
 	div.innerHTML = "";
187 187
 
  188
+	// Check if a disconnected checkbox will retain its checked
  189
+	// value of true after appended to the DOM
  190
+	input = document.createElement("input");
  191
+	input.setAttribute("type", "checkbox");
  192
+	input.checked = true;
  193
+	div.appendChild( input );
  194
+	support.appendChecked = input.checked;
  195
+
188 196
 	// Check if div with explicit width and no margin-right incorrectly
189 197
 	// gets computed margin-right based on width of container. For more
190 198
 	// info see bug #3333
16  test/unit/manipulation.js
@@ -227,7 +227,7 @@ test("unwrap()", function() {
227 227
 });
228 228
 
229 229
 var testAppend = function(valueObj) {
230  
-	expect(37);
  230
+	expect(40);
231 231
 	var defaultText = "Try them out:"
232 232
 	var result = jQuery("#first").append(valueObj("<b>buga</b>"));
233 233
 	equals( result.text(), defaultText + "buga", "Check if text appending works" );
@@ -330,6 +330,20 @@ var testAppend = function(valueObj) {
330 330
 	d.contents().appendTo("#nonnodes");
331 331
 	d.remove();
332 332
 	ok( jQuery("#nonnodes").contents().length >= 2, "Check node,textnode,comment append cleanup worked" );
  333
+
  334
+	QUnit.reset();
  335
+	var $input = jQuery("<input />").attr({ "type": "checkbox", "checked": true }).appendTo('#testForm');
  336
+	equals( $input[0].checked, true, "A checked checkbox that is appended stays checked" );
  337
+
  338
+	QUnit.reset();
  339
+	var $radios = jQuery("input:radio[name='R1']"),
  340
+		$radioNot = jQuery("<input type='radio' name='R1' checked='checked'/>").insertAfter( $radios ),
  341
+		$radio = $radios.eq(1).click();
  342
+	$radioNot[0].checked = false;
  343
+	$radios.parent().wrap("<div></div>");
  344
+	equals( $radio[0].checked, true, "Reappending radios uphold which radio is checked" );
  345
+	equals( $radioNot[0].checked, false, "Reappending radios uphold not being checked" );
  346
+	QUnit.reset();
333 347
 }
334 348
 
335 349
 test("append(String|Element|Array&lt;Element&gt;|jQuery)", function() {
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.