Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Closed
wants to merge 5 commits into from

3 participants

@timmywil
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))
+ // Resets defaultChecked for any radios and checkboxes about to be appended to the DOM in IE 6/7 (#8060)
+ if ( !jQuery.support.appendChecked ) {
+ fixChecked = function( elem ) {
+ if ( jQuery.nodeName( elem, "input" ) && (elem.type === "checkbox" || elem.type === "radio") ) {
+ elem.defaultChecked = elem.checked;
+ }
+ };
+
+ if ( elem[0] && typeof elem.length === "number" ) {
+ for ( i = 0, len = elem.length; i < len; i++ ) {
+ if ( elem[i].getElementsByTagName ) {
+ fixChecked( elem[i] );
+ jQuery.grep( elem[i].getElementsByTagName("input"), fixChecked );
+ }
+ }
+ } else {
@gnarf 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))
+ if ( jQuery.nodeName( elem, "input" ) && (elem.type === "checkbox" || elem.type === "radio") ) {
+ elem.defaultChecked = elem.checked;
+ }
+ };
+
+ if ( elem[0] && typeof elem.length === "number" ) {
+ for ( i = 0, len = elem.length; i < len; i++ ) {
+ if ( elem[i].getElementsByTagName ) {
+ fixChecked( elem[i] );
+ jQuery.grep( elem[i].getElementsByTagName("input"), fixChecked );
+ }
+ }
+ } else {
+ if ( elem.getElementsByTagName ) {
+ fixChecked( elem );
+ jQuery.grep( elem.getElementsByTagName("input"), fixChecked );
@gnarf 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
@timmywil
Collaborator

Thanks gnarf! Changes added.

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

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

@timmywil Collaborator

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))
- elem = div.childNodes;
+ // Resets defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ var len;
+ if ( !jQuery.support.appendChecked ) {
+ if ( elem[0] && typeof (len = elem.length) === "number" ) {
+ for ( i = 0; i < len; i++ ) {
+ fixChecked( elem[i] );
+ }
+ } else {
@jeresig
jeresig added a note

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

@timmywil Collaborator

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 ) {
}
}
+// Used in clean, fixes the defaultChecked property
+// on all inputs and checkboxes within html recursively
+function fixChecked( elem ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
@jeresig
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?

@timmywil Collaborator

Looking into this.

@timmywil Collaborator

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.

@gnarf 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 );
    }
 }
@timmywil Collaborator

Added.

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

Landed in commit d274b7b.

@jeresig jeresig closed this
@davidpenuelab davidpenuelab referenced this pull request from a commit
timmywil 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
@mescoda mescoda referenced this pull request from a commit in mescoda/jquery
timmywil Landing pull request 332. Appending disconnected radio or checkbox in…
…puts and keeping checked setting Fixes #8060, #8500.

More Details:
 - jquery#332
 - http://bugs.jquery.com/ticket/8060
 - http://bugs.jquery.com/ticket/8500
d7d56d9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 17, 2011
  1. #8500 and #8060 - Checked attribute did not carry over once a discon…

    timmywil authored
    …nected radio or checkbox is appended to the DOM
    
    - Cleaned up some code in $.fn.clean (badum-psh)
  2. Drying up the fixChecked calls

    timmywil authored
  3. Separate defaultChecked fix from finding inputs to avoid calling node…

    timmywil authored
    …Name unnecessarily
Commits on Apr 20, 2011
  1. Add comment for support addition

    timmywil authored
This page is out of date. Refresh to see the latest.
View
2  src/core.js
@@ -731,7 +731,7 @@ jQuery.extend({
}
}
- // Go thorugh every key on the object,
+ // Go through every key on the object,
} else {
for ( key in elems ) {
value = callback( elems[ key ], key, arg );
View
113 src/manipulation.js
@@ -70,7 +70,7 @@ jQuery.fn.extend({
}
return elem;
- }).append(this);
+ }).append( this );
}
return this;
@@ -379,13 +379,13 @@ function cloneCopyEvent( src, dest ) {
}
function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
// We do not need to do anything for non-Elements
if ( dest.nodeType !== 1 ) {
return;
}
- var nodeName = dest.nodeName.toLowerCase();
-
// clearAttributes removes the attributes, which we don't want,
// but also removes the attachEvent events, which we *do* want
if ( dest.clearAttributes ) {
@@ -398,6 +398,8 @@ function cloneFixAttributes( src, dest ) {
dest.mergeAttributes( src );
}
+ nodeName = dest.nodeName.toLowerCase();
+
// IE6-8 fail to clone children inside object elements that use
// the proprietary classid attribute value (rather than the type
// attribute) to identify the type of content to display
@@ -446,11 +448,10 @@ jQuery.buildFragment = function( args, nodes, scripts ) {
args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
cacheable = true;
+
cacheresults = jQuery.fragments[ args[0] ];
- if ( cacheresults ) {
- if ( cacheresults !== 1 ) {
- fragment = cacheresults;
- }
+ if ( cacheresults && cacheresults !== 1 ) {
+ fragment = cacheresults;
}
}
@@ -508,6 +509,21 @@ function getAll( elem ) {
}
}
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( elem.type === "checkbox" || elem.type === "radio" ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ fixDefaultChecked( elem );
+ } else if ( elem.getElementsByTagName ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+}
+
jQuery.extend({
clone: function( elem, dataAndEvents, deepDataAndEvents ) {
var clone = elem.cloneNode(true),
@@ -578,54 +594,67 @@ jQuery.extend({
}
// Convert html string into DOM nodes
- if ( typeof elem === "string" && !rhtml.test( elem ) ) {
- elem = context.createTextNode( elem );
-
- } else if ( typeof elem === "string" ) {
- // Fix "XHTML"-style tags in all browsers
- elem = elem.replace(rxhtmlTag, "<$1></$2>");
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
- // Trim whitespace, otherwise indexOf won't work as expected
- var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
- wrap = wrapMap[ tag ] || wrapMap._default,
- depth = wrap[0],
- div = context.createElement("div");
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
+ wrap = wrapMap[ tag ] || wrapMap._default,
+ depth = wrap[0],
+ div = context.createElement("div");
- // Go to html and back, then peel off extra wrappers
- div.innerHTML = wrap[1] + elem + wrap[2];
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
- // Move to the right depth
- while ( depth-- ) {
- div = div.lastChild;
- }
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
- // Remove IE's autoinserted <tbody> from table fragments
- if ( !jQuery.support.tbody ) {
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
- // String was a <table>, *may* have spurious <tbody>
- var hasBody = rtbody.test(elem),
- tbody = tag === "table" && !hasBody ?
- div.firstChild && div.firstChild.childNodes :
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = rtbody.test(elem),
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
- // String was a bare <thead> or <tfoot>
- wrap[1] === "<table>" && !hasBody ?
- div.childNodes :
- [];
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
- for ( var j = tbody.length - 1; j >= 0 ; --j ) {
- if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
- tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ for ( var j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
}
}
- }
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
- // IE completely kills leading whitespace when innerHTML is used
- if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
- div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ elem = div.childNodes;
}
+ }
- elem = div.childNodes;
+ // Resets defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ var len;
+ if ( !jQuery.support.appendChecked ) {
+ if ( elem[0] && typeof (len = elem.length) === "number" ) {
@jeresig
jeresig added a note

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

@timmywil Collaborator

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
+ for ( i = 0; i < len; i++ ) {
+ findInputs( elem[i] );
+ }
+ } else {
+ findInputs( elem );
+ }
}
if ( elem.nodeType ) {
View
8 src/support.js
@@ -185,6 +185,14 @@ jQuery.support = (function() {
support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
div.innerHTML = "";
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM
+ input = document.createElement("input");
+ input.setAttribute("type", "checkbox");
+ input.checked = true;
+ div.appendChild( input );
+ support.appendChecked = input.checked;
+
// Check if div with explicit width and no margin-right incorrectly
// gets computed margin-right based on width of container. For more
// info see bug #3333
View
16 test/unit/manipulation.js
@@ -227,7 +227,7 @@ test("unwrap()", function() {
});
var testAppend = function(valueObj) {
- expect(37);
+ expect(40);
var defaultText = "Try them out:"
var result = jQuery("#first").append(valueObj("<b>buga</b>"));
equals( result.text(), defaultText + "buga", "Check if text appending works" );
@@ -330,6 +330,20 @@ var testAppend = function(valueObj) {
d.contents().appendTo("#nonnodes");
d.remove();
ok( jQuery("#nonnodes").contents().length >= 2, "Check node,textnode,comment append cleanup worked" );
+
+ QUnit.reset();
+ var $input = jQuery("<input />").attr({ "type": "checkbox", "checked": true }).appendTo('#testForm');
+ equals( $input[0].checked, true, "A checked checkbox that is appended stays checked" );
+
+ QUnit.reset();
+ var $radios = jQuery("input:radio[name='R1']"),
+ $radioNot = jQuery("<input type='radio' name='R1' checked='checked'/>").insertAfter( $radios ),
+ $radio = $radios.eq(1).click();
+ $radioNot[0].checked = false;
+ $radios.parent().wrap("<div></div>");
+ equals( $radio[0].checked, true, "Reappending radios uphold which radio is checked" );
+ equals( $radioNot[0].checked, false, "Reappending radios uphold not being checked" );
+ QUnit.reset();
}
test("append(String|Element|Array&lt;Element&gt;|jQuery)", function() {
Something went wrong with that request. Please try again.