Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…t#970, knockout#375, knockout#964 from version 2.3.0 branch to version 3.0.0 branch

Conflicts:
	src/binding/defaultBindings/options.js
  • Loading branch information
mbest committed Jun 3, 2013
2 parents f3b0932 + 81b6e04 commit 05241a0
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 15 deletions.
2 changes: 1 addition & 1 deletion build/fragments/amd-post.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
});
}));
2 changes: 1 addition & 1 deletion build/fragments/amd-pre.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
!function(factory) {
(function(factory) {
// Support three module loading scenarios
if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
// [1] CommonJS/Node.js
Expand Down
20 changes: 20 additions & 0 deletions spec/defaultBindings/optionsBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,24 @@ describe('Binding: Options', function() {
expect(testNode.childNodes[0].selectedIndex).toEqual(2);
expect(testNode.childNodes[0]).toHaveTexts(["-", "Annie", "Bob"]);
});

it('Should call an optionsAfterRender callback function and not cause updates if an observable accessed in the callback is changed', function () {
testNode.innerHTML = "<select data-bind=\"options: someItems, optionsText: 'childprop', optionsAfterRender: callback\"></select>";
var callbackObservable = ko.observable(1),
someItems = ko.observableArray([{ childprop: 'first child' }]),
callbacks = 0;
ko.applyBindings({ someItems: someItems, callback: function() { callbackObservable(); callbacks++; } }, testNode);
expect(callbacks).toEqual(1);

// Change the array, but don't update the observableArray so that the options binding isn't updated
someItems().push({ childprop: 'hidden child'});
expect(testNode.childNodes[0]).toContainText('first child');
// Update callback observable and check that the binding wasn't updated
callbackObservable(2);
expect(testNode.childNodes[0]).toContainText('first child');
// Update the observableArray and verify that the binding is now updated
someItems.valueHasMutated();
expect(testNode.childNodes[0]).toContainText('first childhidden child');
expect(callbacks).toEqual(2);
});
});
9 changes: 9 additions & 0 deletions spec/defaultBindings/valueBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ describe('Binding: Value', function() {
expect(testNode.childNodes[0].value).toEqual("456");
});

it('For observable values, should update on change if new value is \'strictly\' different from previous value', function() {
var myobservable = new ko.observable("+123");
testNode.innerHTML = "<input data-bind='value:someProp' />";
ko.applyBindings({ someProp: myobservable }, testNode);
expect(testNode.childNodes[0].value).toEqual("+123");
myobservable(123);
expect(testNode.childNodes[0].value).toEqual("123");
});

it('For writeable observable values, should catch the node\'s onchange and write values back to the observable', function () {
var myobservable = new ko.observable(123);
testNode.innerHTML = "<input data-bind='value:someProp' />";
Expand Down
29 changes: 29 additions & 0 deletions spec/dependentObservableBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,33 @@ describe('Dependent Observable', function() {
expect(computedInner.getDependenciesCount()).toEqual(1);
expect(computedOuter.getDependenciesCount()).toEqual(1);
});

it('Should be able to re-evaluate a computed that previously threw an exception', function() {
var observable = ko.observable(true),
computed = ko.computed(function() {
if (!observable()) {
throw Error("Some dummy error");
} else {
return observable();
}
});

// Initially the computed value is true (executed sucessfully -> same value as observable)
expect(computed()).toEqual(true);

var didThrow = false;
try {
// Update observable to cause computed to throw an exception
observable(false);
} catch(e) {
didThrow = true;
}
expect(didThrow).toEqual(true);
// The value of the computed is now undefined, although currently it keeps the previous value
expect(computed()).toEqual(true);

// Update observable to cause computed to re-evaluate
observable(1);
expect(computed()).toEqual(1);
});
})
6 changes: 6 additions & 0 deletions spec/templatingBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ describe('Templating', function() {
expect(testNode.childNodes[0].value).toEqual("hello");
});

it('Should handle data-bind attributes with spaces around equals sign from inside templates and reference variables', function () {
ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "<input data-bind = 'value:message' />" }));
ko.renderTemplate("someTemplate", null, { templateRenderingVariablesInScope: { message: "hello"} }, testNode);
expect(testNode.childNodes[0].value).toEqual("hello");
});

it('Data binding syntax should be able to use $element in binding value', function() {
ko.setTemplateEngine(new dummyTemplateEngine({ someTemplate: "<div data-bind='text: $element.tagName'></div>" }));
ko.renderTemplate("someTemplate", null, null, testNode);
Expand Down
18 changes: 15 additions & 3 deletions src/binding/defaultBindings/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ ko.bindingHandlers['options'] = {
while (element.length > 0) {
element.remove(0);
}

// Ensures that the binding processor doesn't try to bind the options
return { 'controlsDescendantBindings': true };
},
'update': function (element, valueAccessor, allBindings) {
var previousScrollTop = element.scrollTop;
var selectWasPreviouslyEmpty = element.length == 0;
var previousScrollTop = (!selectWasPreviouslyEmpty && element.multiple) ? element.scrollTop : null;

var unwrappedArray = ko.utils.unwrapObservable(valueAccessor());
var includeDestroyed = allBindings.get('optionsIncludeDestroyed');
Expand Down Expand Up @@ -90,7 +94,15 @@ ko.bindingHandlers['options'] = {
}
}

ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, null, setSelectionCallback);
var callback = setSelectionCallback;
if (allBindings['has']('optionsAfterRender')) {
callback = function(arrayEntry, newOptions) {
setSelectionCallback(arrayEntry, newOptions);
ko.dependencyDetection.ignore(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== caption ? arrayEntry : undefined]);
}
}

ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, null, callback);

// Clear previousSelectedValues so that future updates to individual objects don't get stale data
previousSelectedValues = null;
Expand All @@ -104,7 +116,7 @@ ko.bindingHandlers['options'] = {
// Workaround for IE bug
ko.utils.ensureSelectElementIsRenderedCorrectly(element);

if (Math.abs(previousScrollTop - element.scrollTop) > 20)
if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20)
element.scrollTop = previousScrollTop;
}
};
Expand Down
7 changes: 1 addition & 6 deletions src/binding/defaultBindings/value.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,7 @@ ko.bindingHandlers['value'] = {
var valueIsSelectOption = ko.utils.tagNameLower(element) === "select";
var newValue = ko.utils.unwrapObservable(valueAccessor());
var elementValue = ko.selectExtensions.readValue(element);
var valueHasChanged = (newValue != elementValue);

// JavaScript's 0 == "" behavious is unfortunate here as it prevents writing 0 to an empty text box (loose equality suggests the values are the same).
// We don't want to do a strict equality comparison as that is more confusing for developers in certain cases, so we specifically special case 0 != "" here.
if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
valueHasChanged = true;
var valueHasChanged = (newValue !== elementValue);

if (valueHasChanged) {
var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
Expand Down
6 changes: 4 additions & 2 deletions src/subscribables/dependentObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,16 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
_hasBeenEvaluated = true;

dependentObservable["notifySubscribers"](_latestValue, "beforeChange");

_latestValue = newValue;
if (DEBUG) dependentObservable._latestValue = _latestValue;
dependentObservable["notifySubscribers"](_latestValue);

} finally {
ko.dependencyDetection.end();
_isBeingEvaluated = false;
}

dependentObservable["notifySubscribers"](_latestValue);
_isBeingEvaluated = false;
if (!_subscriptionsToDependencies.length)
dispose();
}
Expand Down
2 changes: 1 addition & 1 deletion src/templating/templateRewriting.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

ko.templateRewriting = (function () {
var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind=)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\3/gi;
var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi;
var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;

function validateDataBindValuesForRewriting(keyValueArray) {
Expand Down
2 changes: 1 addition & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ ko.utils = (function () {
while (
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
iElems[0]
);
) {}
return version > 4 ? version : undefined;
}());
var isIe6 = ieVersion === 6,
Expand Down

0 comments on commit 05241a0

Please sign in to comment.