Skip to content

Commit

Permalink
Add ko.when
Browse files Browse the repository at this point in the history
  • Loading branch information
mbest committed Oct 4, 2017
1 parent 716eb28 commit 4540c88
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 0 deletions.
1 change: 1 addition & 0 deletions build/fragments/source-references.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ knockoutDebugCallback([
'src/subscribables/observableArray.changeTracking.js',
'src/subscribables/dependentObservable.js',
'src/subscribables/mappingHelpers.js',
'src/subscribables/observableUtils.js',
'src/binding/selectExtensions.js',
'src/binding/expressionRewriting.js',
'src/virtualElements.js',
Expand Down
52 changes: 52 additions & 0 deletions spec/asyncBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1238,4 +1238,56 @@ describe('Deferred', function() {
expect(notifySpy).not.toHaveBeenCalled();
});
});

describe('ko.when', function() {
it('Runs callback in a sepearate task when predicate function becomes true, but only once', function() {
this.restoreAfter(ko.options, 'deferUpdates');
ko.options.deferUpdates = true;

var x = ko.observable(3),
called = 0;

ko.when(function () { return x() === 4; }, function () { called++; });

x(5);
expect(called).toBe(0);
expect(x.getSubscriptionsCount()).toBe(1);

x(4);
expect(called).toBe(0);

jasmine.Clock.tick(1);
expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);

x(3);
x(4);
jasmine.Clock.tick(1);
expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);
});

it('Runs callback in a sepearate task if predicate function is already true', function() {
this.restoreAfter(ko.options, 'deferUpdates');
ko.options.deferUpdates = true;

var x = ko.observable(4),
called = 0;

ko.when(function () { return x() === 4; }, function () { called++; });

expect(called).toBe(0);
expect(x.getSubscriptionsCount()).toBe(1);

jasmine.Clock.tick(1);
expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);

x(3);
x(4);
jasmine.Clock.tick(1);
expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);
});
});
});
91 changes: 91 additions & 0 deletions spec/observableUtilsBehaviors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
describe('ko.when', function() {
it('Runs callback when predicate function becomes true, but only once', function() {
var x = ko.observable(3),
called = 0;

ko.when(function () { return x() === 4; }, function () { called++; });

x(5);
expect(called).toBe(0);
expect(x.getSubscriptionsCount()).toBe(1);

x(4);
expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);

x(3);
x(4);
expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);
});

it('Runs callback if predicate function is already true', function() {
var x = ko.observable(4),
called = 0;

ko.when(function () { return x() === 4; }, function () { called++; });

expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);

x(3);
x(4);
expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);
});

it('Accepts an observable as the predicate', function() {
var x = ko.observable(false),
called = 0;

ko.when(x, function () { called++; });

expect(called).toBe(0);
expect(x.getSubscriptionsCount()).toBe(1);

x(true);
expect(called).toBe(1);
expect(x.getSubscriptionsCount()).toBe(0);
});

it('Returns an object with a dispose function that cancels the notification', function() {
var x = ko.observable(false),
called = 0;

var handle = ko.when(x, function () { called++; });

expect(called).toBe(0);
expect(x.getSubscriptionsCount()).toBe(1);

handle.dispose();
expect(x.getSubscriptionsCount()).toBe(0);

x(true);
expect(called).toBe(0);
});

it('Will call callback function only once even if value is updated during callback', function() {
var x = ko.observable(false),
called = 0;

ko.when(x, function () {
called++;
x(false);
x(true);
});

expect(called).toBe(0);
expect(x.getSubscriptionsCount()).toBe(1);

x(true);
expect(called).toBe(1);
});

it('Should be able to specify a \'this\' pointer for the callback', function () {
var model = {
someProperty: 123,
myCallback: function () { expect(this.someProperty).toEqual(123); }
};
ko.when(ko.observable(true), model.myCallback, model);
});
});
1 change: 1 addition & 0 deletions spec/runner.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<script type="text/javascript" src="dependentObservableDomBehaviors.js"></script>
<script type="text/javascript" src="pureComputedBehaviors.js"></script>
<script type="text/javascript" src="extenderBehaviors.js"></script>
<script type="text/javascript" src="observableUtilsBehaviors.js"></script>
<script type="text/javascript" src="domNodeDisposalBehaviors.js"></script>
<script type="text/javascript" src="mappingHelperBehaviors.js"></script>
<script type="text/javascript" src="expressionRewritingBehaviors.js"></script>
Expand Down
1 change: 1 addition & 0 deletions spec/runner.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ require('./mappingHelperBehaviors');
require('./observableArrayBehaviors');
require('./observableArrayChangeTrackingBehaviors');
require('./observableBehaviors');
require('./observableUtilsBehaviors');
require('./subscribableBehaviors');
require('./taskBehaviors');
require('./utilsBehaviors');
Expand Down
15 changes: 15 additions & 0 deletions src/subscribables/observableUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ko.when = function(predicate, callback, context) {
var observable = ko.pureComputed(predicate).extend({notify:'always'});
var subscription = observable.subscribe(function(value) {
if (value) {
subscription.dispose();
callback.call(context);
}
});
// In case the initial value is true, process it right away
observable['notifySubscribers'](observable.peek());

return subscription;
};

ko.exportSymbol('when', ko.when);

3 comments on commit 4540c88

@miellaby
Copy link
Contributor

@miellaby miellaby commented on 4540c88 Dec 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome.

And why not getting the predicate returned value as a callback 1st parameter? Consider ...

ko.when(function() {
    return collection().find(function(element) {
        return element.id === 'waited';
    })[0];
}, function(waited) {
   console.log(waited);
});

naïve question: why is it not necessary to dispose the observable built in ko.when?

@mbest
Copy link
Member Author

@mbest mbest commented on 4540c88 Dec 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not getting the predicate returned value as a callback 1st parameter?

I'm open to that. Would you like to submit a pull request?

@mbest
Copy link
Member Author

@mbest mbest commented on 4540c88 Dec 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it not necessary to dispose the observable built in ko.when?

Once the subscription is disposed, the pureComputed will go to sleep.

Please sign in to comment.