Skip to content

Commit

Permalink
Adds onFirstListener and onNoListener callbacks
Browse files Browse the repository at this point in the history
These callbacks allow anything extending `Backbones.Events` to be
notified when something starts listening to it, and when nothing
is left listening to it.

Because of `internalOn` this is hard to implement in user land.

The use case for this is if you had a Model presenting the output of
an expensive continuous calculation (say real time Stock Prices or
a timer).

The `onFirstListener` function can be used to start your expensive
calculation, as you only care about it while the something is listening
to changes in it.

When that item stops listening to changes in it, `onNoListeners` will
trigger, allowing you to clean up your code (unsubscribe the stock price,
or stop the timer or even stop listening to another Event).
  • Loading branch information
DomBlack committed Jun 8, 2016
1 parent 8ec8860 commit 1ad8931
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# EditorConfig: http://EditorConfig.org
root = true

[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 2
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
raw
*.sw?
.DS_Store
.idea
node_modules
bower_components
20 changes: 20 additions & 0 deletions backbone.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@
return events;
};

// An overridable callback to allow event objects to know when something starts
// listening to it when previously it had no listeners. Useful for knowing this
// object needs to start listening to something else.
// (which might be expensive to run all the time)
Events.onFirstListener = function() {};

// An overridable callback to allow event objects to know when the last listener
// stops listening to it. Useful to know this object can stop listening to it's
// sources.
Events.onNoListeners = function() {};

// Bind an event to a `callback` function. Passing `"all"` will bind
// the callback to all events fired.
Events.on = function(name, callback, context) {
Expand All @@ -165,6 +176,10 @@

// Guard the `listening` argument from the public API.
var internalOn = function(obj, name, callback, context, listening) {
if (!obj._events || _.isEmpty(obj._events)) {
obj.onFirstListener();
}

obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
context: context,
ctx: obj,
Expand Down Expand Up @@ -222,6 +237,11 @@
context: context,
listeners: this._listeners
});

if (!this._events || _.isEmpty(this._events)) {
this.onNoListeners();
}

return this;
};

Expand Down
40 changes: 40 additions & 0 deletions test/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -703,4 +703,44 @@
two.trigger('y', 2);
});

QUnit.test('onFirstListener & onNoListeners are triggered', function(assert) {
assert.expect(6);
var first = 1;
var none = 1;

var expectedFirsts = 0;
var expectedNones = 0;

var obj = _.extend({}, Backbone.Events, {
onFirstListener: function() { assert.ok(first++ === expectedFirsts); },
onNoListeners: function() { assert.ok(none++ === expectedNones); }
});
var obj2 = _.extend({}, Backbone.Events, {
onFirstListener: function() { assert.ok(false, 'Should not have been called'); },
onNoListeners: function() { assert.ok(false, 'Should not have been called'); }
});
var fn = function() {};

expectedFirsts = 1;
obj.on('a', fn); // expects a call to onFirstListener
obj.on('b', fn);

obj.off('a', fn); // no call
expectedNones = 1;
obj.off('b', fn); // expects a call to onNoListeners

expectedFirsts = 2;
obj2.listenTo(obj, 'a', fn);
obj2.listenTo(obj, 'b', fn);

obj2.stopListening(obj, 'a', fn);
expectedNones = 2;
obj2.stopListening(obj, 'b', fn);

expectedFirsts = 3;
obj2.listenToOnce(obj, 'a', fn);
expectedNones = 3;
obj.trigger('a');
})

})();

0 comments on commit 1ad8931

Please sign in to comment.