Skip to content

Commit 84b9172

Browse files
committed
listenTo equivalents for Commands and Requests.
1 parent c200394 commit 84b9172

File tree

4 files changed

+402
-0
lines changed

4 files changed

+402
-0
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,20 @@ for an example.
271271

272272
Returns the instance of Commands.
273273

274+
#### `complyFor( other, commandName, callback )`
275+
276+
An analogous method to `Events.listenTo`. Registers a callback on `other`. The advantage to using this form is that it allows the object
277+
to remove its own handlers at a later date. This is particularly useful for registering events on a channel.
278+
279+
#### `complyForOnce( other, commandName, callback )`
280+
281+
An analogous method to `Events.listenToOnce`. Just like `complyFor`, but the callback will only be executed once before being removed.
282+
283+
#### `stopComplyingFor( [other] [, commandName] [, callback] )
284+
285+
An analogous method to `Events.stopListening`. Removes callbacks registered on `other`. Pass no arguments to remove all callbacks,
286+
or pass any combination of the arguments for fine-grained control over what is removed.
287+
274288
### Requests
275289

276290
#### `request( requestName [, args...] )`
@@ -333,6 +347,20 @@ You may also pass a hash of replies or space-separated replies to remove many at
333347

334348
Returns the instance of Requests.
335349

350+
#### `replyFor( other, requestName, callback )`
351+
352+
An analogous method to `Events.listenTo`. Registers a callback on `other`. The advantage to using this form is that it allows the object
353+
to remove its own handlers at a later date. This is particularly useful for registering requests on a channel.
354+
355+
#### `replyForOnce( other, requestName, callback )`
356+
357+
An analogous method to `Events.listenToOnce`. Just like `replyFor`, but the callback will only be executed once before being removed.
358+
359+
#### `stopReplyingFor( [other] [, requestName] [, callback] )
360+
361+
An analogous method to `Events.stopListening`. Removes replies registered on `other`. Pass no arguments to remove all callbacks,
362+
or pass any combination of the arguments for fine-grained control over what is removed.
363+
336364
### Channel
337365

338366
#### `channelName`

src/backbone.radio.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,43 @@ Radio.Commands = {
228228
debugLog('Attempted to remove the unregistered command', name, this.channelName);
229229
}
230230

231+
return this;
232+
},
233+
234+
stopComplyingFor: function(obj, name, callback) {
235+
var complyingFor = this._complyingFor;
236+
if (!complyingFor) { return this; }
237+
var remove = !name && !callback;
238+
if (!callback && typeof name === 'object') { callback = this; }
239+
if (obj) { (complyingFor = {})[obj._commandId] = obj; }
240+
for (var id in complyingFor) {
241+
obj = complyingFor[id];
242+
obj.stopComplying(name, callback, this);
243+
if (remove || _.isEmpty(obj._commands)) { delete this._complyingFor[id]; }
244+
}
231245
return this;
232246
}
233247
};
234248

249+
// listenTo equivalent for Commands
250+
var listenMethods = {complyFor: 'comply', complyForOnce: 'complyOnce'};
251+
_.each(listenMethods, function(implementation, method) {
252+
Radio.Commands[method] = function(obj, name, callback) {
253+
var complyingFor = this._complyingFor || (this._complyingFor = {});
254+
var id = obj._commandId || (obj._commandId = _.uniqueId('c'));
255+
complyingFor[id] = obj;
256+
if (!callback && typeof name === 'object') { callback = this; }
257+
if (implementation === 'once') {
258+
callback = _.compose(function(result) {
259+
this.stopListening(_.rest(arguments));
260+
return result;
261+
}, callback);
262+
}
263+
obj[implementation](name, callback, this);
264+
return this;
265+
};
266+
});
267+
235268
//
236269
// Backbone.Radio.Requests
237270
// A messaging system for requesting data.
@@ -317,10 +350,43 @@ Radio.Requests = {
317350
debugLog('Attempted to remove the unregistered request', name, this.channelName);
318351
}
319352

353+
return this;
354+
},
355+
356+
stopReplyingFor: function(obj, name, callback) {
357+
var replyingFor = this._replyingFor;
358+
if (!replyingFor) { return this; }
359+
var remove = !name && !callback;
360+
if (!callback && typeof name === 'object') { callback = this; }
361+
if (obj) { (replyingFor = {})[obj._requestId] = obj; }
362+
for (var id in replyingFor) {
363+
obj = replyingFor[id];
364+
obj.stopReplying(name, callback, this);
365+
if (remove || _.isEmpty(obj._requests)) { delete this._replyingFor[id]; }
366+
}
320367
return this;
321368
}
322369
};
323370

371+
// listenTo equivalent for Requests
372+
var listenMethods = {replyFor: 'reply', replyForOnce: 'replyOnce'};
373+
_.each(listenMethods, function(implementation, method) {
374+
Radio.Requests[method] = function(obj, name, callback) {
375+
var replyingFor = this._replyingFor || (this._replyingFor = {});
376+
var id = obj._requestId || (obj._requestId = _.uniqueId('r'));
377+
replyingFor[id] = obj;
378+
if (!callback && typeof name === 'object') { callback = this; }
379+
if (implementation === 'once') {
380+
callback = _.compose(function(result) {
381+
this.stopListening(_.rest(arguments));
382+
return result;
383+
}, callback);
384+
}
385+
obj[implementation](name, callback, this);
386+
return this;
387+
};
388+
});
389+
324390
//
325391
// Backbone.Radio.channel
326392
// Get a reference to a channel by name.

test/spec/commands.js

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,4 +493,224 @@ describe('Commands:', function() {
493493
.and.calledWith('commandTwo');
494494
});
495495
});
496+
497+
describe('when registering a command with `complyFor`, then executing it', function() {
498+
beforeEach(function() {
499+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
500+
this.callback = stub();
501+
this.Commands.complyFor(this.CommandsTwo, 'myCommand', this.callback);
502+
this.CommandsTwo.command('myCommand');
503+
});
504+
505+
it('should execute the callback with the correct context', function() {
506+
expect(this.callback)
507+
.to.have.been.calledOnce
508+
.and.to.have.always.been.calledOn(this.Commands);
509+
});
510+
});
511+
512+
describe('when registering a command with `complyFor` and an event map, and then executing one of the commands', function() {
513+
beforeEach(function() {
514+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
515+
this.callbackOne = stub();
516+
this.callbackTwo = stub();
517+
this.Commands.complyFor(this.CommandsTwo, {
518+
commandOne: this.callbackOne,
519+
commandTwo: this.callbackTwo
520+
});
521+
this.CommandsTwo.command('commandOne');
522+
});
523+
524+
it('should execute the callback with the correct context', function() {
525+
expect(this.callbackOne)
526+
.to.have.been.calledOnce
527+
.and.to.have.always.been.calledOn(this.Commands);
528+
});
529+
530+
it('should not execute the callbacks not specified', function() {
531+
expect(this.callbackTwo).to.not.have.been.called;
532+
});
533+
});
534+
535+
describe('`complyForOnce`', function() {
536+
beforeEach(function() {
537+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
538+
this.callback = stub();
539+
this.Commands.complyForOnce(this.CommandsTwo, 'myCommand', this.callback);
540+
this.CommandsTwo.command('myCommand');
541+
});
542+
543+
it('should execute the callback with the correct context', function() {
544+
expect(this.callback)
545+
.to.have.been.calledOnce
546+
.and.to.have.always.been.calledOn(this.Commands);
547+
});
548+
549+
it('should remove the reference from the original object', function() {
550+
expect(this.CommandsTwo._commands).to.deep.equal({});
551+
});
552+
});
553+
554+
describe('`complyForOnce` with an event map', function() {
555+
beforeEach(function() {
556+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
557+
this.callbackOne = stub();
558+
this.callbackTwo = stub();
559+
this.Commands.complyForOnce(this.CommandsTwo, {
560+
commandOne: this.callbackOne,
561+
commandTwo: this.callbackTwo
562+
});
563+
this.CommandsTwo.command('commandOne');
564+
});
565+
566+
it('should execute the callback with the correct context', function() {
567+
expect(this.callbackOne)
568+
.to.have.been.calledOnce
569+
.and.to.have.always.been.calledOn(this.Commands);
570+
});
571+
572+
it('should not execute the callbacks not specified', function() {
573+
expect(this.callbackTwo).to.not.have.been.called;
574+
});
575+
576+
it('should remove the key for a', function() {
577+
expect(this.CommandsTwo._commands).to.not.have.key('commandOne');
578+
});
579+
580+
it('should keep the key for b', function() {
581+
expect(this.CommandsTwo._commands).to.have.key('commandTwo');
582+
});
583+
});
584+
585+
describe('`stopComplyingFor` with the first argument', function() {
586+
beforeEach(function() {
587+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
588+
this.CommandsThree = _.clone(Backbone.Radio.Commands);
589+
this.callbackOne = stub();
590+
this.callbackTwo = stub();
591+
this.callbackThree = stub();
592+
this.Commands.complyFor(this.CommandsTwo, {
593+
commandOne: this.callbackOne,
594+
commandTwo: this.callbackTwo
595+
});
596+
this.Commands.complyFor(this.CommandsThree, 'commandThree', this.callbackThree);
597+
this.Commands.stopComplyingFor(this.CommandsTwo);
598+
});
599+
600+
it('should only remove the callbacks on CommandsTwo', function() {
601+
expect(this.CommandsTwo._commands).to.deep.equal({});
602+
});
603+
604+
it('should leave the callback on CommandThree', function() {
605+
expect(this.CommandsThree._commands).to.have.key('commandThree');
606+
});
607+
});
608+
609+
describe('`stopComplyingFor` with the first two arguments', function() {
610+
beforeEach(function() {
611+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
612+
this.CommandsThree = _.clone(Backbone.Radio.Commands);
613+
this.callbackOne = stub();
614+
this.callbackTwo = stub();
615+
this.callbackThree = stub();
616+
this.Commands.complyFor(this.CommandsTwo, {
617+
commandOne: this.callbackOne,
618+
commandTwo: this.callbackTwo
619+
});
620+
this.Commands.complyFor(this.CommandsThree, 'commandThree', this.callbackThree);
621+
this.Commands.stopComplyingFor(this.CommandsTwo, 'commandOne');
622+
});
623+
624+
it('should only remove the proper callback on CommandsTwo', function() {
625+
expect(this.CommandsTwo._commands).to.not.have.key('commandOne');
626+
});
627+
628+
it('should not touch the other callback on CommandsTwo', function() {
629+
expect(this.CommandsTwo._commands).to.have.key('commandTwo');
630+
});
631+
632+
it('should leave the callback on CommandThree', function() {
633+
expect(this.CommandsThree._commands).to.have.key('commandThree');
634+
});
635+
});
636+
637+
describe('`stopComplyingFor` with the all three arguments', function() {
638+
beforeEach(function() {
639+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
640+
this.CommandsThree = _.clone(Backbone.Radio.Commands);
641+
this.callbackOne = stub();
642+
this.callbackTwo = stub();
643+
this.callbackThree = stub();
644+
this.Commands.complyFor(this.CommandsTwo, {
645+
commandOne: this.callbackOne,
646+
commandTwo: this.callbackTwo
647+
});
648+
this.Commands.complyFor(this.CommandsThree, 'commandThree', this.callbackThree);
649+
this.Commands.stopComplyingFor(this.CommandsTwo, 'commandOne', this.callbackOne);
650+
});
651+
652+
it('should only remove the proper callback on CommandsTwo', function() {
653+
expect(this.CommandsTwo._commands).to.not.have.key('commandOne');
654+
});
655+
656+
it('should not touch the other callback on CommandsTwo', function() {
657+
expect(this.CommandsTwo._commands).to.have.key('commandTwo');
658+
});
659+
660+
it('should leave the callback on CommandThree', function() {
661+
expect(this.CommandsThree._commands).to.have.key('commandThree');
662+
});
663+
});
664+
665+
describe('`stopComplyingFor` with just the second argument', function() {
666+
beforeEach(function() {
667+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
668+
this.CommandsThree = _.clone(Backbone.Radio.Commands);
669+
this.callbackOne = stub();
670+
this.callbackTwo = stub();
671+
this.callbackThree = stub();
672+
this.Commands.complyFor(this.CommandsTwo, {
673+
commandOne: this.callbackOne,
674+
commandTwo: this.callbackOne
675+
});
676+
this.Commands.complyFor(this.CommandsThree, {
677+
commandTwo: this.callbackThree,
678+
commandThree: this.callbackThree
679+
});
680+
this.Commands.stopComplyingFor(undefined, 'commandTwo');
681+
});
682+
683+
it('should only remove the proper callback on CommandsTwo', function() {
684+
expect(this.CommandsTwo._commands).to.not.have.key('commandTwo');
685+
expect(this.CommandsTwo._commands).to.have.key('commandOne');
686+
});
687+
688+
it('should not touch the other callback on CommandsTwo', function() {
689+
expect(this.CommandsThree._commands).to.not.have.key('commandTwo');
690+
expect(this.CommandsThree._commands).to.have.key('commandThree');
691+
});
692+
});
693+
694+
describe('`stopComplyingFor` with just the third argument', function() {
695+
beforeEach(function() {
696+
this.CommandsTwo = _.clone(Backbone.Radio.Commands);
697+
this.CommandsThree = _.clone(Backbone.Radio.Commands);
698+
this.callbackOne = stub();
699+
this.callbackTwo = stub();
700+
this.Commands.complyFor(this.CommandsTwo, {
701+
commandOne: this.callbackOne,
702+
commandTwo: this.callbackOne
703+
});
704+
this.Commands.complyFor(this.CommandsThree, 'commandThree', this.callbackTwo);
705+
this.Commands.stopComplyingFor(undefined, undefined, this.callbackOne);
706+
});
707+
708+
it('should only remove the proper callback on CommandsTwo', function() {
709+
expect(this.CommandsTwo._commands).to.deep.equal({});
710+
});
711+
712+
it('should not touch the other callback on CommandsTwo', function() {
713+
expect(this.CommandsThree._commands).to.have.key('commandThree');
714+
});
715+
});
496716
});

0 commit comments

Comments
 (0)