Skip to content

Commit a5afa20

Browse files
author
Ben McCormick
committed
Add first class support for Backbone.Radio in Mn.Object
- Radio added as a dependency - Mn.Object now supports radioEvents, radioCommands, and radioRequests hashes in the form `{'channel message':'handler'}` - Tests added for this feature - Initial Documentation Note that this is only adding support to Mn.Object and the Classes that inherit from it, not trying to add the same support to Backbone.View
1 parent 05a5d88 commit a5afa20

File tree

10 files changed

+195
-3
lines changed

10 files changed

+195
-3
lines changed

SpecRunner.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
<script src="node_modules/jquery/dist/jquery.js"></script>
6060
<script src="node_modules/underscore/underscore.js"></script>
6161
<script src="node_modules/backbone/backbone.js"></script>
62-
<script src="node_modules/backbone.wreqr/lib/backbone.wreqr.js"></script>
6362
<script src="node_modules/backbone.babysitter/lib/backbone.babysitter.js"></script>
63+
<script src="node_modules/backbone.radio/build/backbone.radio.js"></script>
6464

6565

6666
<script>
@@ -69,6 +69,7 @@
6969
</script>
7070
<script src="src/trigger-method.js"></script>
7171
<script src="src/bind-entity-events.js"></script>
72+
<script src="src/radio-helpers.js"></script>
7273
<script src="src/dom-refresh.js"></script>
7374
<script src="src/helpers.js"></script>
7475
<script src="src/error.js"></script>
@@ -118,6 +119,7 @@
118119
<script src="test/unit/on-attach.spec.js"></script>
119120
<script src="test/unit/on-dom-refresh.spec.js"></script>
120121
<script src="test/unit/precompiled-template-rendering.spec.js"></script>
122+
<script src="test/unit/radio-helpers.spec.js"></script>
121123
<script src="test/unit/region-manager.spec.js"></script>
122124
<script src="test/unit/region.build-region.spec.js"></script>
123125
<script src="test/unit/region.spec.js"></script>

docs/marionette.object.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,35 @@ john.on('announce', function(message) {
5353
john.graduate();
5454
```
5555

56+
57+
### Radio Events
58+
`Marionette.Object` integrates with `Backbone.Radio` to provide powerful messaging capabilities. Objects can respond to any of Radio's three message types; `Events`, `Commands` and `Requests`. The syntax is similar to the `events` syntax from Backbone Views, and looks like this:
59+
60+
```js
61+
radioEvents: {
62+
'app start': 'onAppStart',
63+
'books finish': 'onBooksFinish',
64+
},
65+
66+
radioCommands: {
67+
'app doFoo': 'executeFoo',
68+
},
69+
70+
radioRequests: {
71+
'resources bar': 'getBar',
72+
},
73+
```
74+
75+
where each hash value is in the form `'channel eventName' : 'handler'`. So
76+
77+
```js
78+
radioCommands: {
79+
'app doFoo': 'executeFoo',
80+
},
81+
```
82+
83+
means that the object will listen for the `doFoo` command on the `app` channel, and run the 'executeFoo' method. When using Radio Commands and Requests with Objects, the same rules and restrictions that normal Radio use implies also apply here: a single handler can be associated with a command or request, either through manual use of the comply or reply functions, or through the Object API.
84+
5685
### getOption
5786
Retrieve an object's attribute either directly from the object, or from the object's this.options, with this.options taking precedence.
5887

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"github": "https://github.com/marionettejs/backbone.marionette",
4242
"dependencies": {
4343
"backbone.babysitter": "^0.1.0",
44+
"backbone.radio": "^0.9.0",
4445
"backbone": "1.0.0 - 1.1.2",
4546
"underscore": "1.4.4 - 1.6.0"
4647
},

src/build/bundled.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
// @include ../trigger-method.js
3737
// @include ../dom-refresh.js
3838
// @include ../bind-entity-events.js
39+
// @include ../radio-helpers.js
3940

4041
// @include ../error.js
4142
// @include ../object.js

src/build/core.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
(function(root, factory) {
22

33
if (typeof define === 'function' && define.amd) {
4-
define(['backbone', 'underscore', 'backbone.babysitter'], function(Backbone, _) {
4+
define(['backbone', 'underscore','backbone.radio', 'backbone.babysitter'],
5+
function(Backbone, _) {
56
return (root.Marionette = root.Mn = factory(root, Backbone, _));
67
});
78
} else if (typeof exports !== 'undefined') {
89
var Backbone = require('backbone');
910
var _ = require('underscore');
1011
var BabySitter = require('backbone.babysitter');
12+
var Radio = require('backbone.radio');
1113
module.exports = factory(root, Backbone, _);
1214
} else {
1315
root.Marionette = root.Mn = factory(root, root.Backbone, root._);
@@ -32,6 +34,7 @@
3234
// @include ../trigger-method.js
3335
// @include ../dom-refresh.js
3436
// @include ../bind-entity-events.js
37+
// @include ../radio-helpers.js
3538

3639
// @include ../error.js
3740
// @include ../object.js

src/object.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Object borrows many conventions and utilities from Backbone.
66
Marionette.Object = function(options) {
77
this.options = _.extend({}, _.result(this, 'options'), options);
8-
8+
Marionette.proxyRadioHandlers.apply(this);
99
this.initialize.apply(this, arguments);
1010
};
1111

@@ -23,6 +23,7 @@ _.extend(Marionette.Object.prototype, Backbone.Events, {
2323
destroy: function() {
2424
this.triggerMethod('before:destroy');
2525
this.triggerMethod('destroy');
26+
Marionette.unproxyRadioHandlers.apply(this);
2627
this.stopListening();
2728
},
2829

@@ -38,4 +39,5 @@ _.extend(Marionette.Object.prototype, Backbone.Events, {
3839

3940
// Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
4041
unbindEntityEvents: Marionette.proxyUnbindEntityEvents
42+
4143
});

src/radio-helpers.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
(function(Marionette, Radio) {
2+
3+
//Proxy Radio message handling to enable declarative interactions with radio channels
4+
var radioAPI = {
5+
'radioEvents' : {
6+
startMethod: 'on',
7+
stopMethod: 'off'
8+
},
9+
'radioCommands' : {
10+
startMethod: 'comply',
11+
stopMethod: 'stopComplying'
12+
},
13+
'radioRequests' : {
14+
startMethod: 'reply',
15+
stopMethod: 'stopReplying'
16+
}
17+
};
18+
19+
function proxyRadioHandlers() {
20+
unproxyRadioHandlers.apply(this);
21+
_.each(radioAPI, function(commands, radioType) {
22+
var hash = _.result(this, radioType);
23+
if (!hash) {
24+
return;
25+
}
26+
_.each(hash, function(handler, radioMessage) {
27+
handler = normalizeHandler.call(this, handler);
28+
if (!handler) {
29+
return;
30+
}
31+
var messageComponents = radioMessage.split(' '),
32+
channel = messageComponents[0],
33+
messageName = messageComponents[1];
34+
proxyRadioHandler.call(this,channel, radioType, messageName, handler);
35+
}, this);
36+
}, this);
37+
}
38+
39+
function proxyRadioHandler(channel, radioType, messageName, handler) {
40+
var method = radioAPI[radioType].startMethod;
41+
this._radioChannels = this._radioChannels || [];
42+
if(!_.contains(this._radioChannels, channel)) {
43+
this._radioChannels.push(channel);
44+
}
45+
46+
Radio[method](channel, messageName, handler, this);
47+
}
48+
49+
function unproxyRadioHandlers() {
50+
_.each(this._radioChannels, function(channel) {
51+
_.each(radioAPI,function(commands) {
52+
Radio[commands.stopMethod](channel, null, null, this);
53+
}, this);
54+
}, this);
55+
}
56+
57+
function normalizeHandler(handler) {
58+
if (!_.isFunction(handler)) {
59+
handler = this[handler];
60+
}
61+
return handler;
62+
}
63+
64+
Marionette.proxyRadioHandlers = proxyRadioHandlers;
65+
Marionette.unproxyRadioHandlers = unproxyRadioHandlers;
66+
67+
})(Marionette,Backbone.Radio);

test/unit/object.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('marionette object', function() {
4343
it('should maintain a reference to the options', function() {
4444
expect(this.object.options).to.deep.equal(this.options);
4545
});
46+
4647
});
4748

4849
describe('when destroying a object', function() {

test/unit/radio-helpers.spec.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
describe('Marionette radio helpers', function() {
2+
3+
describe('when creating a Marionette.Object', function() {
4+
5+
beforeEach(function() {
6+
this.clickStub1 = this.sinon.stub();
7+
this.clickStub2 = this.sinon.stub();
8+
this.clickStub3 = this.sinon.stub();
9+
this.Object = Marionette.Object.extend({
10+
radioEvents: {
11+
'foo bar' : this.clickStub1
12+
},
13+
radioCommands: {
14+
'bar foo' : this.clickStub2
15+
},
16+
radioRequests: {
17+
'foo bar' : 'baz'
18+
},
19+
20+
baz: this.clickStub3,
21+
22+
});
23+
this.object = new this.Object();
24+
Backbone.Radio.channel('foo').trigger('bar');
25+
Backbone.Radio.channel('foo').request('bar');
26+
Backbone.Radio.channel('bar').command('foo');
27+
28+
});
29+
30+
it('should support listening to radio events declaratively', function() {
31+
expect(this.clickStub1).to.have.been.calledOnce;
32+
});
33+
34+
it('should support complying to radio commands declaratively', function() {
35+
expect(this.clickStub2).to.have.been.calledOnce;
36+
});
37+
38+
it('should support replying to radio requests declaratively', function() {
39+
expect(this.clickStub3).to.have.been.calledOnce;
40+
});
41+
42+
43+
it('should unsubscribe events when the object is destroyed', function() {
44+
this.object.destroy();
45+
Backbone.Radio.channel('foo').trigger('bar');
46+
expect(this.clickStub1).to.have.been.calledOnce;
47+
});
48+
49+
it('should unsubscribe commands when the object is destroyed', function() {
50+
this.object.destroy();
51+
Backbone.Radio.channel('bar').command('foo');
52+
expect(this.clickStub2).to.have.been.calledOnce;
53+
});
54+
55+
it('should unsubscribe requests when the object is destroyed', function() {
56+
this.object.destroy();
57+
Backbone.Radio.channel('foo').request('bar');
58+
expect(this.clickStub3).to.have.been.calledOnce;
59+
});
60+
61+
it('shouldn\'t overunsubscribe events when the object is destroyed', function() {
62+
this.object2 = new this.Object();
63+
this.object.destroy();
64+
Backbone.Radio.channel('foo').trigger('bar');
65+
expect(this.clickStub1).to.have.been.calledTwice;
66+
});
67+
68+
it('shouldn\'t overunsubscribe commands when the object is destroyed', function() {
69+
this.object2 = new this.Object();
70+
this.object.destroy();
71+
Backbone.Radio.channel('bar').command('foo');
72+
expect(this.clickStub2).to.have.been.calledTwice;
73+
});
74+
75+
it('shouldn\'t overunsubscribe requests when the object is destroyed', function() {
76+
this.object2 = new this.Object();
77+
this.object.destroy();
78+
Backbone.Radio.channel('foo').request('bar');
79+
expect(this.clickStub3).to.have.been.calledTwice;
80+
});
81+
82+
});
83+
84+
});

test/unit/setup/node.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ global.Backbone = require('backbone');
3434
global.Backbone.$ = global.$;
3535
global.Marionette = Backbone.Marionette = {};
3636
require('backbone.babysitter');
37+
require('backbone.radio');
3738
global.slice = Array.prototype.slice;
3839
requireHelper('bind-entity-events');
40+
requireHelper('radio-helpers');
3941
requireHelper('trigger-method');
4042
requireHelper('helpers');
4143
requireHelper('dom-refresh');

0 commit comments

Comments
 (0)