Backbone & Marionette Radio channels and events — how to loosely couple different components

This post is a part of Get Noticed! 2017 contest by Maciej Aniserowicz

Marionette uses Backbone Radio channels and events to create an easy way to communicate between components, when in the same time components does not know about others. In this post they will be used to display list of moves (veery simplified version) and to catch event in application class — for example, to send move to server.

Channels and events

Channels can be created either manually (moves-tab-view.js):

this.movesChannel = Radio.channel("chessboardMoves");

What is very important, channel needs to be written in lowercase, otherwise (with camel case, Channel) new channel is created — inside channel method you can find that if it was not present (by name) channel is created, if is present, existing instance is returned. Channel can be also provided in configuration (app.js):

channelName: "chessboardMoves",

To be frankly, I don’t know why providing channelName didn’t work for me for CollectionViews. When they are created manually, how to listen to them? With listenTo method:

this.listenTo(this.fieldClickedChannel, "field:clicked", this.onFieldClicked);

Of course, also this.fieldClickedChannel.on method can be used, but it set this to channel, and I wanted to have easy access to View’s methods and properties.

If they are provided in configuration, radioEvents settings can be used to declare callback for events:

radioEvents: {
"move": "onMove",
},

Events are triggered with trigger method:

this.fieldClickedChannel.trigger("field:clicked", {
clickedField: this._getClickedField(),
});

How they are used

Trigger an event on every click

First, when you click on chessboard field, field:clicked event is triggered (square-view.js):

this.fieldClickedChannel.trigger("field:clicked", {
clickedField: this._getClickedField(),
});

Gather information about moves

Secondly, chessboard view decides whether move occurred (two clicks mean that player moved from X to Y) [chessboard-view.js]:

this.listenTo(this.fieldClickedChannel, "field:clicked", this.onFieldClicked);
onFieldClicked: function ({clickedField}) {
if (!this.from) {
this.from = clickedField;
}
else {
this.to = clickedField;
}
if (this.from && this.to) {
this.chessboardMoveChannel.trigger("move", {
color: this.color,
from: this.from,
to: this.to,
});
this.from = undefined;
this.to = undefined;
this.color = this.color === "white"? "black" : "white";
}
},

And when move occurred, it emits another event with move object.
One important things from ES 6 is used here:

  1. Destructuring assignment syntax, which even gives us a possibility to set default arguments values.

Listen for moves to display them and to react on them in main Application class and in moves display

Thirdly, listen for moves in main class (app.js) and act on them:

const Application = Mn.Application.extend({
channelName: "chessboardMoves",
radioEvents: {
"move": "onMove",
},
onMove: function (move) {
alert(`Move: ${JSON.stringify(move)}`);
},
});

onMove handler may be used to, for example, send data to server.

Listener is also set in new moves-tab-view.js:

this.movesChannel = Radio.channel("chessboardMoves");
this.listenTo(this.movesChannel, "move", this.onMove);

Unfortunately, I was not able to display child views in regions as I used to (in onRender hook), due to very weird errors, so to not waste time I moved it to onDomRefresh hook:

onDomRefresh: function () {
if (!(this.getRegions().whiteMovesRegion)) {
this.addRegion("whiteMovesRegion", "#white-moves-region");
this.showChildView("whiteMovesRegion", new MovesView({
collection: this.whiteMoves,
}));
}
if (!(this.getRegions().blackMovesRegion)) {
this.addRegion("blackMovesRegion", "#black-moves-region");
this.showChildView("blackMovesRegion", new MovesView({
collection: this.blackMoves,
}));
}
},

When move occurred, it’s added to appropriate list:

onMove: function ({color, from, to}) {
if (color === "white") {
this.whiteMoves.push({
from,
to
});
}
if (color === "black") {
this.blackMoves.push({
from,
to,
});
}
}

Another nice feature from ES 6 is used here: shorthand property names. Instead of:

{
from: from,
to: to,
}

When variable names are the same as property names, it could be simplified to:

{
from,
to,
}

It looks like this:

List of moves

Conclusion

This is another simple, yes useful thing I learned about Backbone with Marionette, but I need time to be familiar with Channels/Events, cause all the time it tricked on me and I spend on them too much time.