Skip to content

Commit

Permalink
Refine disposal rule so that we can dispose immediately if we're not …
Browse files Browse the repository at this point in the history
…watching for any DOM node removal
  • Loading branch information
SteveSanderson committed Sep 26, 2013
1 parent 5c5664a commit 857cd38
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 9 deletions.
40 changes: 40 additions & 0 deletions spec/dependentObservableBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,46 @@ describe('Dependent Observable', function() {
expect(dependent.isActive()).toEqual(false);
});

it('Should dispose itself as soon as disposeWhen returns true, as long as it isn\'t waiting for a DOM node to be removed', function() {
var underlyingObservable = ko.observable(100),
dependent = ko.dependentObservable(
underlyingObservable,
null,
{ disposeWhen: function() { return true; } }
);

expect(underlyingObservable.getSubscriptionsCount()).toEqual(0);
expect(dependent.isActive()).toEqual(false);
});

it('Should delay disposal until after disposeWhen returns false if it is waiting for a DOM node to be removed', function() {
var underlyingObservable = ko.observable(100),
shouldDispose = true,
dependent = ko.dependentObservable(
underlyingObservable,
null,
{ disposeWhen: function() { return shouldDispose; }, disposeWhenNodeIsRemoved: true }
);

// Even though disposeWhen returns true, it doesn't dispose yet, because it's
// expecting an initial 'false' result to indicate the DOM node is still in the document
expect(underlyingObservable.getSubscriptionsCount()).toEqual(1);
expect(dependent.isActive()).toEqual(true);

// Trigger the false result. Of course it still doesn't dispose yet, because
// disposeWhen says false.
shouldDispose = false;
underlyingObservable(101);
expect(underlyingObservable.getSubscriptionsCount()).toEqual(1);
expect(dependent.isActive()).toEqual(true);

// Now trigger a true result. This time it will dispose.
shouldDispose = true;
underlyingObservable(102);
expect(underlyingObservable.getSubscriptionsCount()).toEqual(0);
expect(dependent.isActive()).toEqual(false);
});

it('Should describe itself as active if the evaluator has dependencies on its first run', function() {
var someObservable = ko.observable('initial'),
dependentObservable = new ko.dependentObservable(function () { return someObservable(); });
Expand Down
2 changes: 1 addition & 1 deletion src/binding/bindingAttributeSyntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
var self = this,
isFunc = typeof(dataItemOrAccessor) == "function",
nodes,
subscribable = ko.dependentObservable(updateContext, null, { disposeWhen: disposeWhen });
subscribable = ko.dependentObservable(updateContext, null, { disposeWhen: disposeWhen, disposeWhenNodeIsRemoved: true });

// At this point, the binding context has been initialized, and the "subscribable" computed observable is
// subscribed to any observables that were accessed in the process. If there is nothing to track, the
Expand Down
29 changes: 21 additions & 8 deletions src/subscribables/dependentObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
var _latestValue,
_hasBeenEvaluated = false,
_isBeingEvaluated = false,
_canDispose = false,
_suppressDisposalUntilDisposeWhenReturnsFalse = false,
readFunction = evaluatorFunctionOrOptions;

if (readFunction && typeof readFunction == "object") {
Expand Down Expand Up @@ -47,15 +47,16 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
return;
}

// Don't dispose until we've first gotten a false "disposeWhen" result. This supports binding to detached
// nodes that will later be added to the document.
if (disposeWhen && disposeWhen()) {
if (_canDispose) {
// See comment below about _suppressDisposalUntilDisposeWhenReturnsFalse
if (!_suppressDisposalUntilDisposeWhenReturnsFalse) {
dispose();
_hasBeenEvaluated = true;
return;
}
} else {
_canDispose = true;
// It just did return false, so we can stop suppressing now
_suppressDisposalUntilDisposeWhenReturnsFalse = false;
}

_isBeingEvaluated = true;
Expand Down Expand Up @@ -152,9 +153,21 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction

// Add a "disposeWhen" callback that, on each evaluation, disposes if the node was removed without using ko.removeNode.
if (disposeWhenNodeIsRemoved) {
disposeWhen = function () {
return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || (disposeWhenOption && disposeWhenOption());
};
// Since this computed is associated with a DOM node, and we don't want to dispose the computed
// until the DOM node is *removed* from the document (as opposed to never having been in the document),
// we'll prevent disposal until "disposeWhen" first returns false.
_suppressDisposalUntilDisposeWhenReturnsFalse = true;

// Only watch for the node's disposal if the value really is a node. It might not be,
// e.g., { disposeWhenNodeIsRemoved: true } can be used to opt into the "only dispose
// after first false result" behaviour even if there's no specific node to watch. This
// technique is intended for KO's internal use only and shouldn't be documented or used
// by application code, as it's likely to change in a future version of KO.
if (disposeWhenNodeIsRemoved.nodeType) {
disposeWhen = function () {
return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || (disposeWhenOption && disposeWhenOption());
};
}
}

// Evaluate, unless deferEvaluation is true
Expand Down

0 comments on commit 857cd38

Please sign in to comment.