Preserve CHECKED status similarly to VALUE.

Fixes #510.
This commit is contained in:
David Glasser
2012-11-28 10:34:17 -08:00
parent 723154e472
commit 442b86eebd
4 changed files with 107 additions and 11 deletions

View File

@@ -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];
}
}
}
@@ -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;
@@ -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") {
@@ -544,4 +550,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");
}
};

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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) {