Skip to content

Commit

Permalink
Merge branch 'master' into 660-handle-malformed-template-markup
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSanderson committed Oct 10, 2012
2 parents 5242fe3 + 5e12b9e commit c420788
Show file tree
Hide file tree
Showing 12 changed files with 972 additions and 808 deletions.
2 changes: 1 addition & 1 deletion build/fragments/version.txt
@@ -1 +1 @@
2.1.0
2.2.0rc
1,523 changes: 822 additions & 701 deletions build/output/knockout-latest.debug.js

Large diffs are not rendered by default.

165 changes: 82 additions & 83 deletions build/output/knockout-latest.js

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions spec/defaultBindings/attrBehaviors.js
Expand Up @@ -14,12 +14,18 @@ describe('Binding: Attr', {
testNode.innerHTML = "<input data-bind='attr: { name: myValue }' />";
ko.applyBindings({ myValue: myValue }, testNode);
value_of(testNode.childNodes[0].name).should_be("myName");
value_of(testNode.childNodes[0].outerHTML).should_match('name="?myName"?');
if (testNode.childNodes[0].outerHTML) { // Old Firefox doesn't support outerHTML
value_of(testNode.childNodes[0].outerHTML).should_match('name="?myName"?');
}
value_of(testNode.childNodes[0].getAttribute("name")).should_be("myName");

// Also check we can remove it (which, for a name attribute, means setting it to an empty string)
myValue(false);
value_of(testNode.childNodes[0].name).should_be("");
value_of(testNode.childNodes[0].outerHTML).should_not_match('name="?([^">]+)');
if (testNode.childNodes[0].outerHTML) { // Old Firefox doesn't support outerHTML
value_of(testNode.childNodes[0].outerHTML).should_not_match('name="?([^">]+)');
}
value_of(testNode.childNodes[0].getAttribute("name")).should_be("");
},

'Should respond to changes in an observable value': function() {
Expand Down
23 changes: 17 additions & 6 deletions spec/defaultBindings/valueBehaviors.js
Expand Up @@ -323,17 +323,24 @@ describe('Binding: Value', {
value_of(dropdown.selectedIndex).should_be(2);
},

'On IE, should respond exactly once to "propertychange" followed by "blur" or "change" or both': function() {
var isIE = navigator.userAgent.indexOf("MSIE") >= 0;
'On IE < 10, should handle autofill selection by treating "propertychange" followed by "blur" as a change event': function() {
// This spec describes the awkward choreography of events needed to detect changes to text boxes on IE < 10,
// because it doesn't fire regular "change" events when the user selects an autofill entry. It isn't applicable
// on IE 10+ or other browsers, because they don't have that problem with autofill.
var isOldIE = JSSpec.Browser.IEVersion && JSSpec.Browser.IEVersion < 10;

if (isIE) {
if (isOldIE) {
var myobservable = new ko.observable(123).extend({ notify: 'always' });
var numUpdates = 0;
myobservable.subscribe(function() { numUpdates++ });
testNode.innerHTML = "<input data-bind='value:someProp' />";
ko.applyBindings({ someProp: myobservable }, testNode);

// First try change then blur
// Simulate:
// 1. Select from autofill
// 2. Modify the textbox further
// 3. Tab out of the textbox
// --- should be treated as a single change
testNode.childNodes[0].value = "some user-entered value";
ko.utils.triggerEvent(testNode.childNodes[0], "propertychange");
ko.utils.triggerEvent(testNode.childNodes[0], "change");
Expand All @@ -342,14 +349,18 @@ describe('Binding: Value', {
ko.utils.triggerEvent(testNode.childNodes[0], "blur");
value_of(numUpdates).should_be(1);

// Now try blur then change
// Simulate:
// 1. Select from autofill
// 2. Tab out of the textbox
// 3. Reselect, edit, then tab out of the textbox
// --- should be treated as two changes (one after step 2, one after step 3)
testNode.childNodes[0].value = "different user-entered value";
ko.utils.triggerEvent(testNode.childNodes[0], "propertychange");
ko.utils.triggerEvent(testNode.childNodes[0], "blur");
value_of(myobservable()).should_be("different user-entered value");
value_of(numUpdates).should_be(2);
ko.utils.triggerEvent(testNode.childNodes[0], "change");
value_of(numUpdates).should_be(2);
value_of(numUpdates).should_be(3);
}
}
});
2 changes: 1 addition & 1 deletion spec/domNodeDisposalBehaviors.js
Expand Up @@ -63,7 +63,7 @@ describe('DOM node disposal', {
ko.utils.domNodeDisposal.addDisposeCallback(originalNode, function() { });

// Clone it, then dispose it. Then check it's still safe to associate DOM data with the clone.
var cloneNode = originalNode.cloneNode();
var cloneNode = originalNode.cloneNode(true);
ko.cleanNode(originalNode);
ko.utils.domNodeDisposal.addDisposeCallback(cloneNode, function() { });
}
Expand Down
17 changes: 9 additions & 8 deletions spec/editDetectionBehaviors.js
Expand Up @@ -63,19 +63,20 @@ describe('Compare Arrays', {
var oldArray = ["A", "B", "C", "D", "E"];
var newArray = ["F", "G", "H", "I", "J"];
var compareResult = ko.utils.compareArrays(oldArray, newArray);
// the order of added and deleted doesn't really matter
compareResult.sort(function(a, b) { return a.status.localeCompare(b.status) });
// The order of added and deleted doesn't really matter. We sort by a property that
// contains unique values to ensure the results are in a known order for verification.
compareResult.sort(function(a, b) { return a.value.localeCompare(b.value) });
value_of(compareResult).should_be([
{ status: "added", value: "F", index: 0},
{ status: "added", value: "G", index: 1},
{ status: "added", value: "H", index: 2},
{ status: "added", value: "I", index: 3},
{ status: "added", value: "J", index: 4},
{ status: "deleted", value: "A", index: 0},
{ status: "deleted", value: "B", index: 1},
{ status: "deleted", value: "C", index: 2},
{ status: "deleted", value: "D", index: 3},
{ status: "deleted", value: "E", index: 4}
{ status: "deleted", value: "E", index: 4},
{ status: "added", value: "F", index: 0},
{ status: "added", value: "G", index: 1},
{ status: "added", value: "H", index: 2},
{ status: "added", value: "I", index: 3},
{ status: "added", value: "J", index: 4}
]);
}
});
Expand Down
16 changes: 15 additions & 1 deletion spec/lib/JSSpec.extensions.js
Expand Up @@ -62,4 +62,18 @@ JSSpec.prepareTestNode = function() {
testNode = document.createElement("div");
testNode.id = "testNode";
document.body.appendChild(testNode);
};
};

// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
// If there is a future need to detect specific versions of IE10+, we will amend this.
JSSpec.Browser.IEVersion = (function() {
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');

// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
while (
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
iElems[0]
);
return version > 4 ? version : undefined;
}());
5 changes: 4 additions & 1 deletion spec/mappingHelperBehaviors.js
Expand Up @@ -109,6 +109,9 @@ describe('Mapping helpers', {
value_of(ko.toJSON(data, myReplacer)).should_be("\"my replacement\"");

// With spacer
value_of(ko.toJSON(data, undefined, " ")).should_be("{\n \"a\": 1\n}");
value_of(ko.toJSON(data, undefined, " ")).should_be_one_of([
"{\n \"a\":1\n}", // Firefox 3.6, for some reason, omits the space after the colon. Doesn't really matter to us.
"{\n \"a\": 1\n}" // All other browsers produce this format
]);
}
})
4 changes: 2 additions & 2 deletions src/binding/defaultBindings/value.js
Expand Up @@ -3,6 +3,7 @@ ko.bindingHandlers['value'] = {
// Always catch "change" event; possibly other events too if asked
var eventsToCatch = ["change"];
var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
var propertyChangedFired = false;
if (requestedEventsToCatch) {
if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
requestedEventsToCatch = [requestedEventsToCatch];
Expand All @@ -11,6 +12,7 @@ ko.bindingHandlers['value'] = {
}

var valueUpdateHandler = function() {
propertyChangedFired = false;
var modelValue = valueAccessor();
var elementValue = ko.selectExtensions.readValue(element);
ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue);
Expand All @@ -21,11 +23,9 @@ ko.bindingHandlers['value'] = {
var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
&& element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
var propertyChangedFired = false;
ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
ko.utils.registerEventHandler(element, "blur", function() {
if (propertyChangedFired) {
propertyChangedFired = false;
valueUpdateHandler();
}
});
Expand Down
10 changes: 8 additions & 2 deletions src/binding/editDetection/arrayToDomNodeChildren.js
Expand Up @@ -155,6 +155,9 @@
// Call beforeMove first before any changes have been made to the DOM
callCallback(options['beforeMove'], itemsForMoveCallbacks);

// Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);

// Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
// Get nodes for newly added items
Expand All @@ -174,8 +177,11 @@
}
}

// Next remove nodes for deleted items; or call beforeRemove, which will remove them
ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
// If there's a beforeRemove callback, call it after reordering.
// Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
// some sort of animation, which is why we first reorder the nodes that will be removed. If the
// callback instead removes the nodes right away, it would be more efficient to skip reordering them.
// Perhaps we'll make that change in the future if this scenario becomes more common.
callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);

// Finally call afterMove and afterAdd callbacks
Expand Down
3 changes: 3 additions & 0 deletions src/utils.js
Expand Up @@ -16,6 +16,9 @@ ko.utils = new (function () {
var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406

// Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
// If there is a future need to detect specific versions of IE10+, we will amend this.
var ieVersion = (function() {
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');

Expand Down

0 comments on commit c420788

Please sign in to comment.