Backbone, Marionette and RequireJS: marriage of convenience with happy end in future (I guess)?

Radek Anuszewski
Get Noticed! 2017 [Radek Anuszewski]
5 min readMar 25, 2017
This post is a part of Get Noticed! 2017 contest by Maciej Aniserowicz

I planned to write a competition project in React with Typescript, but in my 9–5 I got a new project. It requires from me to quickly learn new technologies — where the most important are Backbone.js, Marionette.js and RequireJS. So, I contacted the competition organizer and if he agrees, new posts will be about mentioned technologies.

So today I made my first, simple login component. It will evolve as I learn more about Backbone/Marionette and as I add some back-end. Additionally, I want to try some ES6 features, the will be added as soon as I get to know them better. For now, when you specify login and password, if password is the same as login it will redirect you to home page and if they are different, it will display an alert. Let’s talk about how it is done — despite the fact that it’s only prototype, I think there is few things which may interest you.

RequireJS

Asynchronous Module Definition

RequireJS uses AMD to define a module. Example may look like this (home-view.js):

define(['jquery', 'underscore', 'backbone.marionette'], function ($, _, Mn) {
return Mn.View.extend({
el: "#main",
template: _.template($("#home-view").html()),
});
});

And example usage in main.js:

define("jquery", "underscore", "backbone", "../home/home-view", function($, _, Backbone, HomeView){ 
const element = $("#element");
const isError = _.isError(new Error());
const Model = Backbone.Model.extend({
defaults: {
id: undefined,
name: "John",
},
});
const homeView = new HomeView();
});

Libraries are often installed by package manager like bower, links to them may change, so to be able to always write "jquery" instead of "somepath/jquery" you should predefined them in RequireJS config:

require.config({
paths: {
"jquery": "../libs/jquery/dist/jquery"
"underscore": "../libs/underscore-amd/underscore",
"backbone": "../libs/backbone-amd/backbone",
},
});

I have an Angular background, so for me it looks like widely used Dependency Injection:

function ConfirmDeleteModalController($uibModalInstance) {
// Do something with injected service
}

In case of Angular, framework instantiates element, with AMD you can made a decision to instantiate or to return constructor function.

Simplified CommonJS wrapping

But in most common cases, you can find module injection using Simplified CommonJS wrapping:

define(function(require){
const $ = require("jquery");
const _ = require("underscore");
const Backbone = require("backbone");
const HomeView = require("../home/home-view");
const element = $("#element");
const isError = _.isError(new Error());
const Model = Backbone.Model.extend({
defaults: {
id: undefined,
name: "John",
},
});
const homeView = new HomeView();
});

For me it’s nicer than previous solution, although it’s harder to see module dependencies.

Backbone.js

Backbone Model

Simple model for storing login and password may look like this:

define(function (require) {
const Backbone = require("backbone");

return Backbone.Model.extend({
defaults: {
login: undefined,
password: undefined,
},
});
});

Backbone model has many useful features, like parsing response from server, and I will discuss them in future posts where they will be widely used.

Marionette.js

AppRouter

AppRouter takes routes as one of its options, where key is URL and value is function which will be run on this route. At this moment routing for my application is very simple (dependencies omitted for brevity):

return Mn.AppRouter.extend({
routes: {
"": "login",
"home": "home"
},
login: function () {
const loginView = new LoginView({
model: new LoginModel(),
router: this,
});
loginView.render();
},
home: function () {
const homeView = new HomeView({

});
homeView.render();
}
});

First render of views is started manually, but there are other solutions. One of them is to re-render views when model changes — view may have initialize method when it is declared:

initialize: function ({id}) {
this.intialized = false;
this.listenTo(this.model, 'change', this.render);
this.model.fetch({
url: "http://pokeapi.co/api/v2/pokemon/" + id,
}).then(_enableChangeEvents.bind(this));

function _enableChangeEvents () {
this.initialized = true;
}
},

By the way, this example comes from my Backbone Playground repository. Which is, to be frankly, more complicated than this project (at this moment) :)

Marionette View

Now they are all in index.html file. But soon I’ll change it. Photo credits: Meme generator

At this moment, all View’s templates are declared in index.html file:

<script type="marionette-template" id="home-view">
You are logged
</script>

And are compiled with underscore template function, from string get with html method from jquery. But it will change. When View is instantiated, you can provide parameters like model which is bound automatically but also custom parameters:

// Instantiation, from router's method
const loginView = new LoginView({
model: new LoginModel(),
router: this,
});
//Initialization:
initialize: function ({router}) {
this.router = router;
},

If you look close, you can find {router}. This is object destructuring. I found it useful, cause it gives an ability to quickly emulate named parameters with default values, in Javascript 6:

function displayFullName ({name="Nick", surname}) {
alert(name + " " + surname);
}
displayName({name: "John", surname: "Smith"});

One of things I found very useful in Marionette, even with as simple project as this, was defining ui:

ui: {
login: "#login",
password: "#password",
form: "#login-form",
},

(example comes from login-view.js file)

Elements declared as ui can be used in event handling:

events: {
"change @ui.login": "onLoginChange",
"change @ui.password": "onPasswordChange",
"submit @ui.form": "onSubmit",
},

And to get value of this elements without using DOM operation to retrieve value (like $("login").val()) cause they are accessible through ui object:

onLoginChange: function (event) {
this.model.set({login: this.ui.login.val()});
},
onPasswordChange: function (event) {
this.model.set({password: this.ui.password.val()});
},

Which is more verbose than DOM operations, even made with jQuery. Moreover, now you can change login ID and you need to update it only in one place, in ui declaration.

Conclusion

I have an Angular background, so using Backbone and Marionette for me look like things, which Angular do under the hood. They are more customizable, where Angular often provides a right way to do things. Backbone and Marionette work on much lower level of abstraction (at least for me), which not necessarily have to be a drawback — as Artem Sapegin wrote in article 2017 is the year that front-end developers should go back and master the basics:

When every week we have a new JavaScript framework that’s better than any older framework, it’s easy to spend most of your time learning frameworks rather than the language itself.

With this few hours with Backbone and Marionette I liked them, so it sounds like a lot of fun.

P.S.

If you are interested in Angular content, I wrote a new post — AngularJS component binding types — my experiences.

--

--

Radek Anuszewski
Get Noticed! 2017 [Radek Anuszewski]

Software developer, frontend developer in AltConnect.pl, mostly playing with ReactJS, AngularJS and, recently, BackboneJS / MarionetteJS.