Skip to content

Commit

Permalink
Preserve CHECKED status similarly to VALUE.
Browse files Browse the repository at this point in the history
Fixes #510.
  • Loading branch information
glasser committed Dec 4, 2012
1 parent 5f94b43 commit 8296ecd
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 11 deletions.
43 changes: 32 additions & 11 deletions packages/spark/patch.js
Expand Up @@ -361,15 +361,25 @@ Spark._Patcher._copyAttributes = function(tgt, src) {
tgt.style.cssText = '';

var isRadio = false;
var finalChecked = null;
if (tgt.nodeName === "INPUT") {
// Record for later whether this is a radio button.
isRadio = (tgt.type === 'radio');
// Clearing the attributes of a checkbox won't necessarily
// uncheck it, eg in FF12, so we uncheck explicitly
// (if necessary; we don't want to generate spurious
// propertychange events in old IE).
if (tgt.checked === true && src.checked === false) {
tgt.checked = false;

// Figure out whether this should be checked or not. If the re-rendering
// changed its idea of checkedness, go with that; otherwsie go with whatever
// the control's current setting is.
if (isRadio || tgt.type === 'checkbox') {
var tgtOriginalChecked = !!tgt._sparkOriginalRenderedChecked &&
tgt._sparkOriginalRenderedChecked[0];
var srcOriginalChecked = !!src._sparkOriginalRenderedChecked &&
src._sparkOriginalRenderedChecked[0];
if (tgtOriginalChecked === srcOriginalChecked) {
finalChecked = !!tgt.checked;
} else {
finalChecked = !!srcOriginalChecked;
tgt._sparkOriginalRenderedChecked = [finalChecked];
}
}
}

Expand Down Expand Up @@ -446,9 +456,6 @@ Spark._Patcher._copyAttributes = function(tgt, src) {
if (srcExpando)
src._sparkOriginalRenderedValue = srcExpando;

if (typeof tgt.checked !== "undefined" && src.checked)
tgt.checked = src.checked;

if (src.name)
tgt.name = src.name;

Expand All @@ -463,8 +470,7 @@ Spark._Patcher._copyAttributes = function(tgt, src) {
if (name === "type") {
// can't change type of INPUT in IE; don't support it
} else if (name === "checked") {
tgt.checked = tgt.defaultChecked = (value && value !== "false");
tgt.setAttribute("checked", "checked");
// handled specially below
} else if (name === "style") {
tgt.style.cssText = src.style.cssText;
} else if (name === "class") {
Expand Down Expand Up @@ -543,4 +549,19 @@ Spark._Patcher._copyAttributes = function(tgt, src) {
// around we'll be comparing to this rendered value instead of the old one.
tgt._sparkOriginalRenderedValue = [srcOriginalRenderedValue];
}

// Deal with checkboxes and radios.
if (finalChecked !== null) {
// Don't do a no-op write to 'checked', since in some browsers that triggers
// events.
if (tgt.checked !== finalChecked)
tgt.checked = finalChecked;

// Set various other fields related to checkedness.
tgt.defaultChecked = finalChecked;
if (finalChecked)
tgt.setAttribute("checked", "checked");
else
tgt.removeAttribute("checked");
}
};
1 change: 1 addition & 0 deletions packages/spark/patch_tests.js
Expand Up @@ -138,6 +138,7 @@ Tinytest.add("spark - patch - copyAttributes", function(test) {
var nodeHtml = buf.join('');
var frag = DomUtils.htmlToFragment(nodeHtml);
var n = frag.firstChild;
n._sparkOriginalRenderedChecked = [n.checked];
if (! node) {
node = n;
} else {
Expand Down
6 changes: 6 additions & 0 deletions packages/spark/spark.js
Expand Up @@ -311,9 +311,15 @@ _.extend(Spark._Renderer.prototype, {
// We save it in a one-element array expando. We use the array because IE8
// gets confused by expando properties with scalar values and exposes them
// as HTML attributes.
//
// We also save the values of CHECKED for radio and checkboxes.
_.each(DomUtils.findAll(ret, '[value], textarea, select'), function (node) {
node._sparkOriginalRenderedValue = [DomUtils.getElementValue(node)];
});
_.each(DomUtils.findAll(ret, 'input[type=checkbox], input[type=radio]'),
function (node) {
node._sparkOriginalRenderedChecked = [node.checked];
});

return ret;
}
Expand Down
68 changes: 68 additions & 0 deletions packages/spark/spark_tests.js
Expand Up @@ -2712,6 +2712,74 @@ Tinytest.add("spark - controls - radio", function(test) {
div.kill();
});

Tinytest.add("spark - controls - checkbox", function(test) {
var labels = ["Foo", "Bar", "Baz"];
var Rs = {};
_.each(labels, function (label) {
Rs[label] = ReactiveVar(false);
});
var changeBuf = [];
var div = OnscreenDiv(renderWithPreservation(function() {
var buf = [];
_.each(labels, function (label) {
var checked = Rs[label].get() ? 'checked="checked"' : '';
buf.push('<input type="checkbox" name="checky" '+
'value="'+label+'" '+checked+'/>');
});
return buf.join('');
}));

Meteor.flush();

// get the three boxes; they should be considered 'labeled' by the patcher and
// not change identities!
var boxes = nodesToArray(div.node().getElementsByTagName("INPUT"));

test.equal(_.pluck(boxes, 'checked'), [false, false, false]);

// Re-render with first one checked.
Rs.Foo.set(true);
Meteor.flush();
test.equal(_.pluck(boxes, 'checked'), [true, false, false]);

// Re-render with first one unchecked again.
Rs.Foo.set(false);
Meteor.flush();
test.equal(_.pluck(boxes, 'checked'), [false, false, false]);

// User clicks the second one.
clickElement(boxes[1]);
test.equal(_.pluck(boxes, 'checked'), [false, true, false]);
Meteor.flush();
test.equal(_.pluck(boxes, 'checked'), [false, true, false]);

// Re-render with third one checked. Second one should stay checked because
// it's a user update!
Rs.Baz.set(true);
Meteor.flush();
test.equal(_.pluck(boxes, 'checked'), [false, true, true]);

// User turns second and third off.
clickElement(boxes[1]);
clickElement(boxes[2]);
test.equal(_.pluck(boxes, 'checked'), [false, false, false]);
Meteor.flush();
test.equal(_.pluck(boxes, 'checked'), [false, false, false]);

// Re-render with first one checked. Third should stay off because it's a user
// update!
Rs.Foo.set(true);
Meteor.flush();
test.equal(_.pluck(boxes, 'checked'), [true, false, false]);

// Re-render with first one unchecked. Third should still stay off.
Rs.Foo.set(false);
Meteor.flush();
test.equal(_.pluck(boxes, 'checked'), [false, false, false]);

div.kill();
});

_.each(['textarea', 'text', 'password', 'submit', 'button',
'reset', 'select', 'hidden'], function (type) {
Tinytest.add("spark - controls - " + type, function(test) {
Expand Down

0 comments on commit 8296ecd

Please sign in to comment.