Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion SpecRunner.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
<script src="node_modules/jquery/dist/jquery.js"></script>
<script src="node_modules/underscore/underscore.js"></script>
<script src="node_modules/backbone/backbone.js"></script>
<script src="node_modules/backbone.wreqr/lib/backbone.wreqr.js"></script>
<script src="node_modules/backbone.babysitter/lib/backbone.babysitter.js"></script>
<script src="node_modules/backbone.radio/build/backbone.radio.js"></script>


<script>
Expand All @@ -69,6 +69,7 @@
</script>
<script src="src/trigger-method.js"></script>
<script src="src/bind-entity-events.js"></script>
<script src="src/radio-helpers.js"></script>
<script src="src/dom-refresh.js"></script>
<script src="src/helpers.js"></script>
<script src="src/error.js"></script>
Expand Down Expand Up @@ -118,6 +119,7 @@
<script src="test/unit/on-attach.spec.js"></script>
<script src="test/unit/on-dom-refresh.spec.js"></script>
<script src="test/unit/precompiled-template-rendering.spec.js"></script>
<script src="test/unit/radio-helpers.spec.js"></script>
<script src="test/unit/region-manager.spec.js"></script>
<script src="test/unit/region.build-region.spec.js"></script>
<script src="test/unit/region.spec.js"></script>
Expand Down
29 changes: 29 additions & 0 deletions docs/marionette.object.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,35 @@ john.on('announce', function(message) {
john.graduate();
```


### Radio Events
`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:

```js
radioEvents: {
'app start': 'onAppStart',
'books finish': 'onBooksFinish',
},

radioCommands: {
'app doFoo': 'executeFoo',
},

radioRequests: {
'resources bar': 'getBar',
},
```

where each hash value is in the form `'channel eventName' : 'handler'`. So

```js
radioCommands: {
'app doFoo': 'executeFoo',
},
```

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.

### getOption
Retrieve an object's attribute either directly from the object, or from the object's this.options, with this.options taking precedence.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"github": "https://github.com/marionettejs/backbone.marionette",
"dependencies": {
"backbone.babysitter": "^0.1.0",
"backbone.radio": "^0.9.0",
"backbone": "1.0.0 - 1.1.2",
"underscore": "1.4.4 - 1.6.0"
},
Expand Down
1 change: 1 addition & 0 deletions src/build/bundled.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
// @include ../trigger-method.js
// @include ../dom-refresh.js
// @include ../bind-entity-events.js
// @include ../radio-helpers.js

// @include ../error.js
// @include ../object.js
Expand Down
5 changes: 4 additions & 1 deletion src/build/core.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
(function(root, factory) {

if (typeof define === 'function' && define.amd) {
define(['backbone', 'underscore', 'backbone.babysitter'], function(Backbone, _) {
define(['backbone', 'underscore','backbone.radio', 'backbone.babysitter'],
function(Backbone, _) {
return (root.Marionette = root.Mn = factory(root, Backbone, _));
});
} else if (typeof exports !== 'undefined') {
var Backbone = require('backbone');
var _ = require('underscore');
var BabySitter = require('backbone.babysitter');
var Radio = require('backbone.radio');
module.exports = factory(root, Backbone, _);
} else {
root.Marionette = root.Mn = factory(root, root.Backbone, root._);
Expand All @@ -32,6 +34,7 @@
// @include ../trigger-method.js
// @include ../dom-refresh.js
// @include ../bind-entity-events.js
// @include ../radio-helpers.js

// @include ../error.js
// @include ../object.js
Expand Down
4 changes: 3 additions & 1 deletion src/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Object borrows many conventions and utilities from Backbone.
Marionette.Object = function(options) {
this.options = _.extend({}, _.result(this, 'options'), options);

Marionette.proxyRadioHandlers.apply(this);
this.initialize.apply(this, arguments);
};

Expand All @@ -23,6 +23,7 @@ _.extend(Marionette.Object.prototype, Backbone.Events, {
destroy: function() {
this.triggerMethod('before:destroy');
this.triggerMethod('destroy');
Marionette.unproxyRadioHandlers.apply(this);
this.stopListening();
},

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

// Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
unbindEntityEvents: Marionette.proxyUnbindEntityEvents

});
67 changes: 67 additions & 0 deletions src/radio-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
(function(Marionette, Radio) {

//Proxy Radio message handling to enable declarative interactions with radio channels
var radioAPI = {
'radioEvents' : {
startMethod: 'on',
stopMethod: 'off'
},
'radioCommands' : {
startMethod: 'comply',
stopMethod: 'stopComplying'
},
'radioRequests' : {
startMethod: 'reply',
stopMethod: 'stopReplying'
}
};

function proxyRadioHandlers() {
unproxyRadioHandlers.apply(this);
_.each(radioAPI, function(commands, radioType) {
var hash = _.result(this, radioType);
if (!hash) {
return;
}
_.each(hash, function(handler, radioMessage) {
handler = normalizeHandler.call(this, handler);
if (!handler) {
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

Could we use a normalizeX method?

var messageComponents = radioMessage.split(' '),
channel = messageComponents[0],
messageName = messageComponents[1];
proxyRadioHandler.call(this,channel, radioType, messageName, handler);
}, this);
}, this);
}

function proxyRadioHandler(channel, radioType, messageName, handler) {
var method = radioAPI[radioType].startMethod;
this._radioChannels = this._radioChannels || [];
if(!_.contains(this._radioChannels, channel)) {
this._radioChannels.push(channel);
}

Radio[method](channel, messageName, handler, this);
}

function unproxyRadioHandlers() {
_.each(this._radioChannels, function(channel) {
_.each(radioAPI,function(commands) {
Radio[commands.stopMethod](channel, null, null, this);
}, this);
}, this);
}

function normalizeHandler(handler) {
if (!_.isFunction(handler)) {
handler = this[handler];
}
return handler;
}

Marionette.proxyRadioHandlers = proxyRadioHandlers;
Marionette.unproxyRadioHandlers = unproxyRadioHandlers;

})(Marionette,Backbone.Radio);
1 change: 1 addition & 0 deletions test/unit/object.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('marionette object', function() {
it('should maintain a reference to the options', function() {
expect(this.object.options).to.deep.equal(this.options);
});

});

describe('when destroying a object', function() {
Expand Down
84 changes: 84 additions & 0 deletions test/unit/radio-helpers.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
describe('Marionette radio helpers', function() {

describe('when creating a Marionette.Object', function() {

beforeEach(function() {
this.clickStub1 = this.sinon.stub();
this.clickStub2 = this.sinon.stub();
this.clickStub3 = this.sinon.stub();
this.Object = Marionette.Object.extend({
radioEvents: {
'foo bar' : this.clickStub1
Copy link
Member

Choose a reason for hiding this comment

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

This is weird because you run into the object2 problem w/ prototypes.

The easier thing is to add the stub to the object directly.

O = extend({
  radioEvents: {'foo bar': 'barHandler'}

  barHandler: function() {}
});

o = new O();
o.barHandler = this.barHandler;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, I can switch that up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jasonLaster So after looking at this again, I don't see the problem here. I pass 2 functions directly and one by reference, to make sure that we handle both of those cases. Yes object2 shares the stub on the prototype, but I think the testing is still pretty unambiguous. I just wrote it out the other way, and it makes the code a lot more verbose and (in my mind) less clear. So I'm going to leave it unless you strongly object.

Copy link
Member

Choose a reason for hiding this comment

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

i'm cool with this

},
radioCommands: {
'bar foo' : this.clickStub2
},
radioRequests: {
'foo bar' : 'baz'
},

baz: this.clickStub3,

});
this.object = new this.Object();
Backbone.Radio.channel('foo').trigger('bar');
Backbone.Radio.channel('foo').request('bar');
Backbone.Radio.channel('bar').command('foo');

});

it('should support listening to radio events declaratively', function() {
expect(this.clickStub1).to.have.been.calledOnce;
});

it('should support complying to radio commands declaratively', function() {
expect(this.clickStub2).to.have.been.calledOnce;
});

it('should support replying to radio requests declaratively', function() {
expect(this.clickStub3).to.have.been.calledOnce;
});


it('should unsubscribe events when the object is destroyed', function() {
this.object.destroy();
Backbone.Radio.channel('foo').trigger('bar');
expect(this.clickStub1).to.have.been.calledOnce;
});

it('should unsubscribe commands when the object is destroyed', function() {
this.object.destroy();
Backbone.Radio.channel('bar').command('foo');
expect(this.clickStub2).to.have.been.calledOnce;
});

it('should unsubscribe requests when the object is destroyed', function() {
this.object.destroy();
Backbone.Radio.channel('foo').request('bar');
expect(this.clickStub3).to.have.been.calledOnce;
});

it('shouldn\'t overunsubscribe events when the object is destroyed', function() {
this.object2 = new this.Object();
this.object.destroy();
Backbone.Radio.channel('foo').trigger('bar');
expect(this.clickStub1).to.have.been.calledTwice;
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. why do we need these?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mostly that was to assuage the concerns from my original version. I'm ok with not having the tests, but there was a concern raised that my code might over-unsubscribe and we should wait for Radio to add listenTo equivalents.

});

it('shouldn\'t overunsubscribe commands when the object is destroyed', function() {
this.object2 = new this.Object();
this.object.destroy();
Backbone.Radio.channel('bar').command('foo');
expect(this.clickStub2).to.have.been.calledTwice;
});

it('shouldn\'t overunsubscribe requests when the object is destroyed', function() {
this.object2 = new this.Object();
this.object.destroy();
Backbone.Radio.channel('foo').request('bar');
expect(this.clickStub3).to.have.been.calledTwice;
});

});

});
2 changes: 2 additions & 0 deletions test/unit/setup/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ global.Backbone = require('backbone');
global.Backbone.$ = global.$;
global.Marionette = Backbone.Marionette = {};
require('backbone.babysitter');
require('backbone.radio');
global.slice = Array.prototype.slice;
requireHelper('bind-entity-events');
requireHelper('radio-helpers');
requireHelper('trigger-method');
requireHelper('helpers');
requireHelper('dom-refresh');
Expand Down