Está en la página 1de 133

Marionette

Guides

Table of Contents
Introduction

Why Marionette?

Getting Started

Installing Marionette

2.1

Installing Marionette Inspector

2.2

Tutorial

2.3

Your first view

2.4

Adding new items

2.5

Laying out your page

2.6

Summary

2.7

Views

Lists of Data

3.1

Structuring your App

3.2

Events and Triggers

3.3

Built-in Triggers

3.3.1

Templates

Creating an Application

Integrating with Routing

5.1

Multiple Application Starters

5.2

Implementing Routing
Attaching Controllers
Sharing common view Behavior
Example Behaviors

6
6.1
7
7.1

Handling Data Latency

Dealing with Complex Persisted Data

Nesting Your Views

10

Passing Data with Events

11

FAQ

12

Cookbook

13

Appendix

14

Marionette Guides

Tutorial
Todo app
Routing
Full router example

14.1
14.1.1
14.2
14.2.1

Marionette Guides

Marionette Guides
Have you ever wanted to be a puppet master? Now, through the power of JavaScript, you
can pull the strings and make your applications dance! This book will take you through the
journey from Punch and Judy to Master of Puppets.
This book is a comprehensive guide to all aspects of the Backbone and Marionette
frameworks for JavaScript. From the basics of installing and setting up your first JavaScript
app to developing a complex full-featured application that can integrate with multiple web
services, navigate your user through your systems from any arbitrary entry point, and take
full advantage of the latest JavaScript and HTML5 features.

What is Marionette?
Marionette is a flexible web app framework that helps you build your apps quickly and
integrated into any of your web service backends easily.
Marionette is built on the popular Backbone.js framework - with a major upgrade of the View
layer of the MVC/MVVM application design pattern. In essence, Marionette gives you a set
of simple, yet powerful, components for building your web applications that handle the,
sometimes messy, business of rendering your data and handling user and server input on
your application and data.

Where Do I Start?
Firstly, check out our installation instructions and our getting started guide. If you have a
particular topic that you're interested in, our contents page should guide you in the right
direction.
For those just looking for a quick reference, the [Marionette reference][reference] should be
your next port of call.

Contribute
Do you see something that can be improved? Perhaps you know something about
Marionette that will be a great help. It might just be a typo but we welcome any and all
contributions to making this book the definitive source for working with Marionette.

Introduction

Marionette Guides

How Can I Help?


This book is maintained on Github and built using GitBook. Just pop over and open an issue
or just make and edit and submit a Pull Request.

Get Started with Marionette


There are a number of ways you can start with Marionette depending on how familiar you
are working with JavaScript. You can follow the tutorial, learn about different Marionette
topics, or jump into the reference documentation.

Summary
Introduction
Why Marionette?
Getting Started
Installing Marionette
Installing Marionette Inspector
Tutorial
Your first view
Adding new items
Laying out your page
Summary
Views
Lists of Data
Structuring your App
Events and Triggers
Built-in Triggers
Templates
Creating an Application
Integrating with Routing
Multiple Application Starters
Implementing Routing
Attaching Controllers
Sharing common view Behavior
Example Behaviors
Handling Data Latency

Introduction

Marionette Guides

Dealing with Complex Persisted Data


Nesting Your Views
Passing Data with Events
FAQ
Cookbook
Appendix
Tutorial
Todo app
Routing
Full router example

Introduction

Marionette Guides

Why Marionette?
Building large web applications using Backbone.js can be hard. Backbone is a great tool, but
it's designed to be minimalist and useful in a wide variety of situations. As a result, you get
less guidance and support from the tool as you scale up than you do from more opinionated
frameworks like Angular and Ember. When a Backbone application grows, maintaining it
requires adding structure, either through a custom set of conventions and components, or
based on somebody elses framework. There are a lot of different Backbone frameworks out
there, but we think you'll enjoy using Marionette.js.

Decisions, Decisions
Developing with Backbone is an exercise in decision making. Backbone provides you with a
minimalist set of Models and Collections that essentially serve as light wrappers around
JavaScript objects synced over Ajax. It provides you lightweight Views that associate an
object with a DOM node and some data. It provides a router that associates URLs with
function, and it provides helpers for managing events between all of these options. That
leaves Backbone developers with many questions to answer.
How do you render Views? - By default, Backbone's render method does nothing. To
use it, you need to fill in your own rendering function. That could use a templating
system like Underscore templates or Handlebars, jQuery manipulation of the DOM, or
simple string inserts with .innerHTML() . You could use the same method for every
View, or mix it up and use different methods for different Views.
How do you manage relationships between objects? - By default Backbone
provides a way to manage sets of Models as a Collection, but it doesn't have any builtin utilities for handling nested Models or Collections. And if you want to nest your Views
you're completely on your own. You can have a View manage it's child Views, have a
single object that manages all Views, or let each View manage itself.
How do your Views communicate between each other? - Views will often need to
communicate with each other. If for instance one View needs to change the contents of
another area of the page, it could do so directly through jQuery, could get a direct
reference to a View managing that area and call a function on it, change a Model that
another View listens to, adjust a URL that a router listens to, or fire an event that
another View could respond to. Apps can use some combination of all of these
methods.
How do you avoid repeating yourself? - If you're not careful, Backbone can involve a
lot of boilerplate. Taking the naive approach, you could end up writing rendering code,
View management code and event management code over and over again in every

Why Marionette?

Marionette Guides

View. If you try to get around that using inheritance, you can end up with brittle designs
that require you to make calls down to a Views prototype when you want View specific
code. Avoiding that type of repetition and the maintenance overhead it brings is a
challenge.
How do you manage a View's life-cycle?? - What code is responsible for rendering a
View? Does it render itself on creation? Or is it the responsibility of the object creating
it? Does it get attached to the DOM immediately on render? Or is that a separate step?
When the View is removed from the DOM or deleted, how do you handle any cleanup
that is needed?
How do you structure your application? - How do you get your app started? Do you
have a central object that starts everything, or is it more distributed? If you do centralize,
do you use the router to start things, or provide some other object for managing your
code?
How do you prevent memory leaks? - If your application is a Single Page Application
or it contains long lasting interactive sections, another issue that you may need to deal
with is memory leaks. It can be easy to create "zombie Views" in Backbone if you're not
attentive to the need to unregister events attached to a View after you're done with it.
That's just a small sample of the type of decision making that you have to make for a
Backbone project. Those questions signify flexibility, but they also represent mental
overhead. If you're like me, you see these common problems and think that you can get
better results relying on a shared solution that leverages the experience of the community.

What Does Marionette Give You?


Marionette is an attempt to provide this type of shared solution, capturing Backbone best
practices as a set of components and design patterns. So what value does it provide?
Marionette gives you:
A Standardized Rendering process - Marionette takes an opinionated stand on how
Views should be rendered. Without any additional configuration, it will take a template
that you specify with a View's template property, compile it with Underscore's templating
function and pass it a model or collection. If you need to pass it other data, or want to
use a different template library, Marionette provides hooks to customize that process in
a DRY way.
A consistent View lifecycle - Marionette defines a consistent View life cycle where
Views are initialized, rendered, shown, refreshed, and destroyed. Each of these events
has events and callbacks associated it, and any common boilerplate associated with
them is handled behind the scenes.
The ability to define and manage complex layouts - Marionette provides region
objects that define portions of the DOM that can display and swap out Views. Combined

Why Marionette?

Marionette Guides

with utilities to manage child views, you can easily create deeply nested View structures
with Marionette while minimizing complexity.
A central event bus with semantic events to simplify communication between
Views - Marionette includes Backbone.Wreqr or Backbone Radio as an event bus to
allow communication between Views without explicitly coupling them.
Helpers to help you write DRY Code - In addition to centralizing the rendering and
view management code, Marionette provides hooks to allow you to abstract away
details of the DOM and events in your View code, and a mechanism to pull common ui
operations out into separate reusable objects
Helpers to avoid "Zombie Views" and memory leaks - Marionette's lifecycle includes
an explicit destroy phase that cleans up many common sources of memory leaks, and
provides a hook for you to take care of the rest
A central Application object to initialize your application - Using Marionette, you're
able to specify a set of initializers that run any code that needs to be executed before
your application starts, providing a clear structure and starting point to your app.
That's not the complete feature set, but it is the essential sales pitch. The important thing to
understand is that Marionette provides a framework for building Backbone apps that builds
on established practices from the Backbone community. If you're building a Backbone
application and want to focus on the problems that are specific to your application,
Marionette is a great way to move past common issues and focus on what's unique to you.
This explanation is adapted from a blog post by a Marionette community member. You can
find the original post here.

Why Marionette?

Marionette Guides

Get Started with Marionette


There are a number of ways you can start with Marionette depending on how familiar you
are working with JavaScript. You can follow the tutorial, learn about different Marionette
topics, or jump into the reference documentation.

Install Marionette
Like all JavaScript libraries, there are a number of ways to install Marionette, depending on
your workflow. If you're new to JavaScript development, follow our simple guide for installing
Marionette.

Tutorial
Once you've installed Marionette and set up your environment, you'll want to build your first
app! Our tutorial will take you through all the major components in Backbone and Marionette
and build a fully functional application.

Getting Started

10

Marionette Guides

Installing Marionette
As with all JavaScript libraries, there are a number of ways to get started with a Marionette
application. In this section we'll cover the most common ways.

Using NPM and Webpack


Webpack is a build tool that makes it easy to pull your dependencies together into a single
bundle to be delivered to your browser's <script> tag. It works particularly well with
Marionette and jQuery.
To install Marionette using NPM and Webpack:
1. Install NPM following the advice from the NPM blog
2. Create a directory for your JavaScript application
3. Inside that directory, run npm init , giving your application names
4. Install Webpack: npm install --save webpack
5. Install Marionette's dependencies: npm install --save backbone backbone.marionette
underscore backbone.wreqr backbone.babysitter underscore-template-loader

Whether you bundle jQuery really depends on whether you have external dependencies on
jQuery. For example, if you're using Bootstrap's JavaScript as a <script> tag, you probably
don't want to bundle jQuery and you'll want to depend on the global window.$ variable.

Bundled jQuery
Bundling jQuery lets you require jquery inside your application and keeps all your
dependencies under Webpack's management. To bundle jQuery, simply npm install --save
jquery . We'll move on to configuring our Webpack application.

Configuring Webpack
Configuring Webpack with jQuery bundled in your application is relatively straightforward.
Let's assume that your application lives in a directory called app and the main entry point
(or driver) is called driver.js . In addition, you want to send the resulting file to a directory
called static/js .
Create a file called webpack.config.js with the following:

Installing Marionette

11

Marionette Guides

var webpack = require('webpack');


module.exports = {
entry: './app/driver.js',
module: {
loaders: [
{
test: /\.html$/,
loader: 'underscore-template-loader'
}
]
},
output: {
path: __dirname + '/static/js',
filename: 'bundle.js'
},
plugins: [
new webpack.ProvidePlugin({
_: 'underscore'
})
],
resolve: {
modulesDirectories: [__dirname + '/node_modules'],
root: __dirname + '/app'
},
resolveLoader: {
root: __dirname + '/node_modules'
}
};

Global jQuery
If you're referencing the window.$ variable created by including jQuery in a <script> tag,
download jQuery and place it in your static JS folder.

Configuring Webpack
Configuring Webpack for a global jQuery variable is only slightly more complicated than a
bundled jQuery. Let's assume that your application lives in a directory called app and the
main entry point (or driver) is called driver.js . In addition, you want to send the resulting
file to a directory called static/js .
Create a file called webpack.config.js with the following:

Installing Marionette

12

Marionette Guides

var webpack = require('webpack');


module.exports = {
entry: './app/driver.js',
externals: {
'jquery': '$'
},
module: {
loaders: [
{
test: /\.html$/,
loader: 'underscore-template-loader'
}
]
},
output: {
path: __dirname + '/static/js',
filename: 'bundle.js'
},
plugins: [
new webpack.ProvidePlugin({
_: 'underscore'
})
],
resolve: {
modulesDirectories: [__dirname + '/node_modules'],
root: __dirname + '/app'
},
resolveLoader: {
root: __dirname + '/node_modules'
}
};

Note the new externals key that tells Webpack to inject the global window.$ variable
whenever Backbone or Marionette reference require('jquery') in their imports.

Building your application


With Webpack configured, you can build your application simply by doing:
node_modules/.bin/webpack .

Serving your Application


We'll now create our index.html file to reference our new application and start it.

Installing Marionette

13

Marionette Guides

<script src="static/js/bundle.js"></script>

If you're using any other imported JavaScript (e.g. jQuery), make sure they're loaded before
our bundle.js file so anything that depends on them will be able to see the globals they
expose.

Using NPM and Brunch


Brunch is a builder. Not a generic task runner, but a specialized tool focusing on the
production of a small number of deployment-ready files from a large number of
heterogenous development files or trees.
To install Marionette using NPM and Brunch:
1. Install NPM following the advice from the NPM blog
2. Install Brunch: sudo npm install -g brunch
3. Run brunch new our_directory_name -s marionettejs . Brunch will create simple skeleton
and install all needed dependencies.
Simple skeleton is placed in our_directory_name . If we want to change our configuration file,
we should look at brunch-config.js inside our folder.
Here more information about how to configurate brunch .

Building your Application


When we want to compile our application, we'll run inside our folder:
brunch build --production builds minified project for production.

Serving your Application


When we want to run our application, we'll run inside our folder:
brunch watch --server . Brunch will watch the project with continuous rebuild.

Try opening http://localhost:3333/ we'll see our application working.

Using NPM and Browserify


Browserify is a build tool that makes it easy to bundle NPM modules into your application, so
you can require them as you would import dependencies in any other language.

Installing Marionette

14

Marionette Guides

To setup Browserify and Marionette, we must install a few dependencies and then install
Marionette itself.
1. Install NPM following the advice from the NPM blog
2. Create a directory for your JavaScript application
3. Inside that directory, run npm init , giving your application names
4. Install Browserify: sudo npm install -g browserify
5. Install Underscore: npm install --save underscore@1.7.0
6. Install Backbone: npm install --save backbone
7. Install Marionette: npm install --save backbone.marionette
8. Install the Underscore plugin for browserify: npm install --save node-underscorify
9. Install jquery npm install --save jquery
10. Download jQuery and place it in a static folder
Now we have our basics in place, we can setup a standard setup.js file that will ensure
Marionette can be accessed:
window._ = require('underscore'); // Backbone can't see it otherwise
var Backbone = require('backbone');
Backbone.$ = window.$; // Use the jQuery from the script tag
Backbone.Marionette = require('backbone.marionette');

When building our applications, we'll commonly use a driver.js file that will start by
importing this setup.js file: require('./setup.js') .

Building your Application


When we want to compile our application, we'll use Browserify:
browserify driver.js -t node-underscorify -o static_folder/app.js

where static_folder is the directory to the static directory that your web server provides.

Serving your Application


In the index.html file, or base template (if you're using Django, Rails, etc.) place the
following at the bottom of your body tag:
<script src="static_folder/jquery.min.js"></script>
<script src="static_folder/app.js"></script>

Installing Marionette

15

Marionette Guides

It's important to load jQuery first, so your setup.js file sees it.

Installing Marionette

16

Marionette Guides

Installing Marionette Inspector

Installing Marionette Inspector

17

Marionette Guides

Making your applications dance!


Welcome to the getting started guide for Backbone Marionette.

What will I get from this tutorial?


After reading this tutorial you will be able to write your own Marionette applications from
scratch. We will build a very simple todo list that we can add items to and remove items
from.
This tutorial will teach you how to render your data with views, structure your application
layout, route your user from the address bar, and integrate with data stored in the server.

Things to know
Before we start the tutorial, we'll set up a basic directory structure to keep everything in
order. We'll create a separate folder each for collections, models, routers, templates, and
views. The folder structure is:
|-- index.html
|-- app/
|-- driver.js
|-- collections/
|-- models/
|-- routers/
|-- templates/
|-- views/

Whenever we reference a file, we will typically refer to the filename and the type of file
corresponding to a directory in the above structure.
Our index.html file will be:

Tutorial

18

Marionette Guides

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="app-hook"></div>
<script src="static/js/app.js"></script>
</body>
</html>

If you haven't already, go through the Installing Marionette section for instructions in how to
setup and install a Marionette application.
You can optionally use a CSS framework such as Bootstrap to improve how your application
looks. This tutorial will stick to a simpler to understand structure and HTML.
This tutorial will regularly ask you to restructure code from previous chapters. We will detail
every change, so if you can't see a file, assume that it remains unchanged.

Experimentation
Throughout this tutorial you should feel free to experiment with the code samples to see if
you can understand what's happening. Try different options, templates, classes, or even
write some custom logic.

Tutorial

19

Marionette Guides

Hello, world
We're going to start by building a very simple view that simply displays some text to the
world.

Creating our view


Let's get started! Open up driver.js and enter the following:
var Marionette = require('backbone.marionette'); // 1

var HelloWorld = Marionette.LayoutView.extend({ // 2


el: '#app-hook', // 3
template: require('./templates/layout.html') // 4
});
var hello = new HelloWorld(); // 5
hello.render(); // 6

We then create a file in templates called layout.html and set it up as such:


<p>Hello, world!</p>

What does this all mean?


The template file itself is pretty straightforward, so let's focus on driver.js :
1. Import Marionette
2. Create a new type of view called HelloWorld that borrows from the standard Marionette
LayoutView. We'll go into more depth in that shortly.
3. We direct the view to the element we want to attach it to. This is a jQuery selector and
we can use any valid jQuery selector here.
4. We must set a template to display to our users.
5. We must create an instance of our HelloWorld class before we can do anything useful
with it.
6. Now the fun stuff begins and we call render() to display the template on the screen.

Running it all
Your first view

20

Marionette Guides

After compiling the file, navigate to index.html in your browser to see it render on your
screen. You've just built your first JavaScript application with Marionette!

Building a ToDo application


Now that we have a view, let's do something a little more interesting and render a predefined list of ToDo items. We'll need a to use a Backbone Model to store the list in a way
that our LayoutView can see it. Reopen our driver.js file:
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');

var TodoList = Marionette.LayoutView.extend({


el: '#app-hook',
template: require('./templates/layout.html')
});
var todo = new TodoList({
model: new Backbone.Model({
items: [
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]
})
});
todo.render();

The major point of note is that we're wrapping our object in a Backbone.Model instance.
Backbone models cleanly integrate with templates and make it easy to access their data.
We'll see in a few chapters how powerful models can be for our views.
Next we'll modify our templates/layout.html file to take advantage of this:
<ul>
<% _.each(items, function(item) { %>
<li><%- item.text %> &mdash; <%- item.assignee %></li>
<% }) %>
</ul>

We're using the built-in Underscore template engine to render our item list. You should have
a list of two items detailing simple todo items. We now have a list of items being rendered on
the page but it all feels a little clunky. What happens if we want to add or remove items in
this list?

Your first view

21

Marionette Guides

Collections and CollectionViews


Our list of items is really a collection, so we'll use the Backbone.Collection to model it. We'll
also use a view that specializes in rendering lists of data - the CollectionView . Back in our
driver.js file, we're going to use a couple of views:

var Backbone = require('backbone');


var Marionette = require('backbone.marionette');

var ToDo = Marionette.LayoutView.extend({


tagName: 'li',
template: require('./templates/todoitem.html')
});

var TodoList = Marionette.CollectionView.extend({


el: '#app-hook',
tagName: 'ul',
childView: ToDo
});
var todo = new TodoList({
collection: new Backbone.Collection([
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
])
});
todo.render();

The first thing we've done is add another view and attached it to our CollectionView using
the childView attribute. We also changed out this.model for this.collection in the
initialize method of our newly minted CollectionView and removed the wrapping items

key.
The CollectionView is a view that goes through this.collection and renders an instance
of its childView attribute for each item that it finds. We've removed the template attribute
as CollectionView has no template of its own.
In templates/todoitem.html we simply have:
<%- text %> &mdash; <%- assignee %>

Your first view

22

Marionette Guides

That's it! Marionette knows how to build up the surrounding ul and li tags and inserts
the iteration for us. Even better, if we wanted to add/remove items in the collection,
CollectionView sees that and automatically re-renders itself for us.

In the next chapter, we're going to add new items to this collection so we can keep track of
our job list.

Your first view

23

Marionette Guides

Storing user-entered data


We've just built a simple list of jobs that we need to complete but, as always, we want to add
more jobs to our list as they come in. In this chapter, we'll build that functionality using some
more of Marionette's functionality.

Building our form


First things first, we need a form to enter data. To make this work in our current application,
we can swap out CollectionView for CompositeView in our driver.js file like so:
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');

var ToDo = Marionette.LayoutView.extend({


tagName: 'li',
template: require('./templates/todoitem.html')
});

var TodoList = Marionette.CompositeView.extend({


el: '#app-hook',
template: require('./templates/todolist.html'),
childView: ToDo,
childViewContainer: 'ul'
});
var todo = new TodoList({
collection: new Backbone.Collection([
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
])
});
todo.render();

A CompositeView is a CollectionView with its own template. We then define where the
childView items are to be attached to the template using childViewContainer and pass in

the template. In templates/todolist.html we have:

Adding new items

24

Marionette Guides

<ul></ul>
<form>
<label for="id_text">Todo Text</label>
<input type="text" name="text" id="id_text" />
<label for="id_assignee">Assign to</label>
<input type="text" name="assignee" id="id_assignee" />
<button id="btn-add">Add Item</button>
</form>

We don't need to change the individual item template at all. Now when we refresh the page,
we'll have a form where we can enter data, click "Add Item" and... nothing happens.
Marionette doesn't know what elements to watch and what not to watch, so we have to tell it.

Binding to user input


We need to tell Marionette that it needs to listen to user input on the form and how it should
respond to that. We'll reopen driver.js and start building this in:

Adding new items

25

Marionette Guides

var Backbone = require('backbone');


var Marionette = require('backbone.marionette');

var ToDo = Marionette.LayoutView.extend({


tagName: 'li',
template: require('./templates/todoitem.html')
});

var TodoList = Marionette.CompositeView.extend({


el: '#app-hook',
template: require('./templates/todolist.html'),
childView: ToDo,
childViewContainer: 'ul',
ui: { // 1
assignee: '#id_assignee',
form: 'form',
text: '#id_text'
},
triggers: { // 2
'submit @ui.form': 'add:todo:item'
},
collectionEvents: { // 3
add: 'itemAdded'
},
onAddTodoItem: function() { // 4
this.collection.add({
assignee: this.ui.assignee.val(), // 5
text: this.ui.text.val()
});
},
itemAdded: function() { // 6
this.ui.assignee.val('');
this.ui.text.val('');
}
});
var todo = new TodoList({
collection: new Backbone.Collection([
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
])
});
todo.render();

Adding new items

26

Marionette Guides

We've added quite a bit of code here, so lets take a few minutes to break it down:
1. We've added a "ui hash" to our view. We can attach these to any view to create cached
jQuery selectors to elements in our view's template.
2. In the triggers hash, we can reference those ui keys and, when a jQuery event occurs,
we can listen for it and fire a trigger.
3. The collectionEvents hash allows us to listen to changes occurring on the attached
this.collection attribute. The value must exist as a method on this view.

4. This trigger is then converted to an onEventName method and called. This method need
not exist and is very powerful. We'll cover it in more depth later in the book.
5. We can also reference the ui hash inside our view and treat it just like a jQuery selector
object.
6. The method referenced in collectionEvents is called when the event is triggered.
Now, whenever we click on the "Add Item" button, a new job will be added to our todo list
and the form will be cleared. We've taken an opportunity to introduce model and collection
driven events as well. For a full list of events, see the Backbone documentation.

Validating input
A job shouldn't be added to the list unless it has some text and has been assigned to
someone. You'll notice that we don't really enforce that here but we really should. There are
a couple of ways we could go about this: we could use the ui hash and validate the jQuery
content, or we could use Backbone's Model validation to do it for us.
Apart from looking nicer, the advantage of doing data validation in the Model is that we can
share that model class between views and not have to rewrite all our validation logic every
time.
We'll create a new file called models/todo.js which contains:

Adding new items

27

Marionette Guides

var Backbone = require('backbone');

var ToDo = Backbone.Model.extend({


defaults: {
assignee: '',
text: ''
},
validate: function(attrs) {
var errors = {};
var hasError = false;
if (!attrs.assignee) {
errors.assignee = 'assignee must be set';
hasError = true;
}
if (!attrs.text) {
errors.text = 'text must be set';
hasError = true;
}
if (hasError) {
return errors;
}
}
});

module.exports = ToDo;

If we have no errors, our validate method must return nothing ( undefined ). When we do
have errors, we can return an object describing the errors. The Marionette view will then see
this validation error and let us handle it.
Back in our driver.js file we can do:
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var ToDoModel = require('./models/todo');

var ToDo = Marionette.LayoutView.extend({


tagName: 'li',
template: require('./templates/todoitem.html')
});

var TodoList = Marionette.CompositeView.extend({


el: '#app-hook',

Adding new items

28

Marionette Guides

template: require('./templates/todolist.html'),
childView: ToDo,
childViewContainer: 'ul',
ui: {
assignee: '#id_assignee',
form: 'form',
text: '#id_text'
},
triggers: {
'submit @ui.form': 'add:todo:item'
},
collectionEvents: {
add: 'itemAdded'
},
onAddTodoItem: function() {
this.model.set({
assignee: this.ui.assignee.val(),
text: this.ui.text.val()
}, {validate: true});
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
this.ui.assignee.val('');
this.ui.text.val('');
}
});
var todo = new TodoList({
collection: new Backbone.Collection([
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]),
model: new ToDoModel()
});
todo.render();

Adding new items

29

Marionette Guides

With these changes, we can now refuse to add an item unless it passes validation. We could
also display error messages if validation fails by binding the invalid event in modelEvents .
You can see how we've set our fields to blank fields on the model and also in the ui hash. If
we start having more and more fields, you can see how this would quickly become
unmanageable. Surely we can just clear the form when the model is cleared?

Rendering from models


Back in our first chapter, we were able to render data based on the model fields. We'll use
this to handle our form as well. Firstly we'll open up our todolist.html template:
<ul></ul>
<form>
<label for="id_text">Todo Text</label>
<input type="text" name="text" id="id_text" value="<%- text %>" />
<label for="id_assignee">Assign to</label>
<input type="text" name="assignee" id="id_assignee" value="<%- assignee %>"/>
<button id="btn-add">Add Item</button>
</form>

By adding in the model field values to our form, we'll be able to render the data directly from
our model fields. It also means that, when the model fields are blank, these will contain no
data. We just need to wire up our view to handle this:
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var ToDoModel = require('./models/todo');

var ToDo = Marionette.LayoutView.extend({


tagName: 'li',
template: require('./templates/todoitem.html')
});

var TodoList = Marionette.CompositeView.extend({


el: '#app-hook',
template: require('./templates/todolist.html'),
childView: ToDo,
childViewContainer: 'ul',
ui: {
assignee: '#id_assignee',

Adding new items

30

Marionette Guides

form: 'form',
text: '#id_text'
},
triggers: {
'submit @ui.form': 'add:todo:item'
},
collectionEvents: {
add: 'itemAdded'
},
modelEvents: {
change: 'render'
},
onAddTodoItem: function() {
this.model.set({
assignee: this.ui.assignee.val(),
text: this.ui.text.val()
}, {validate: true});
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
}
});
var todo = new TodoList({
collection: new Backbone.Collection([
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]),
model: new ToDoModel()
});
todo.render();

With these simple changes, the form will now re-render itself as an empty form whenever the
user clicks the "Add Item" button. However, there's one final thing to note - the render
method redraws the entire list as well. You can probably imagine this will start to get really
slow as the list grows in size. Ideally, we just want to re-render the form itself and handle the
list separately. We'll go into this in our next chapter.

Adding new items

31

Marionette Guides

Adding new items

32

Marionette Guides

Applications and regions


In the previous chapter we built our ToDo list application with the ability to add new jobs to
our list. So far, everything has been built into a single view. This led to an issue causing
everything to get re-rendered whenever we added an item to our list.
The CompositeView itself knows how to only add a single item to its list, but our change
hook was causing everything to be re-rendered anyway. In this chapter we're going to dig
into the LayoutView a little more and how we can use it to display multiple views side-byside and manage them independently.

Applications
Before we do that though, we'll take a slight diversion into the Application in Marionette.
The Application is an object that lets us manage our ToDo list and its interaction with the
surrounding page.

Creating an Application
We'll be using the layout described in the introduction, so let's move the bulk of our view
code into views/layout.js and rejig it a little:
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var ToDoModel = require('../models/todo');

var ToDo = Marionette.LayoutView.extend({


tagName: 'li',
template: require('../templates/todoitem.html')
});

var TodoList = Marionette.CompositeView.extend({


el: '#app-hook',
template: require('../templates/todolist.html'),
childView: ToDo,
childViewContainer: 'ul',
ui: {
assignee: '#id_assignee',

Laying out your page

33

Marionette Guides

form: 'form',
text: '#id_text'
},
triggers: {
'submit @ui.form': 'add:todo:item'
},
collectionEvents: {
add: 'itemAdded'
},
modelEvents: {
change: 'render'
},
onAddTodoItem: function() {
this.model.set({
assignee: this.ui.assignee.val(),
text: this.ui.text.val()
}, {validate: true});
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
}
});

module.exports = TodoList;

We now need a way to render this view when our application loads. Marionette gives us the
Application class for just this case. An Application sits between your pre-rendered page

and your application. Commons tasks for an Application are:


1. Take pre-defined data from your page and feed it into your application
2. Render your initial views
3. Start the Backbone.history and initialize your application's Router (more on this later)
Let's put this knowledge into practice by rewriting our driver.js file to look like:

Laying out your page

34

Marionette Guides

var Marionette = require('backbone.marionette');


var TodoView = require('./views/layout');
var initialData = [
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
];
var app = new Marionette.Application({
onStart: function(options) {
var todo = new TodoView({
collection: new Backbone.Collection(options.initialData),
model: new ToDoModel()
});
todo.render();
todo.triggerMethod('show');
}
});
app.start({initialData: initialData});

With an Application object, we now have an obvious starting point for our application. We've
passed our initial data in from the driver.js file instead of the individual view file itself.
Now, if our index.html file gets generated by a web server e.g. Django, Rails, PHP; we can
generate a different list for each user, attach it to an object inside index.html and reference
it from our JavaScript application.

Layouts
With that little diversion out the way, we can now start breaking up our application's layout so
we have different views for different purposes. The first thing we need to do is add an extra
layout. First, we're going to break up our existing views/layout.js into views/list.js and
views/form.js which look like:

Laying out your page

35

Marionette Guides

// views/list.js
var Marionette = require('backbone.marionette');
var ToDo = Marionette.LayoutView.extend({
tagName: 'li',
template: require('../templates/todoitem.html')
});

var TodoList = Marionette.CollectionView.extend({


tagName: 'ul',
childView: ToDo
});

module.exports = TodoList;

// views/form.js
var Marionette = require('backbone.marionette');
var FormView = Marionette.LayoutView.extend({
tagName: 'form',
template: require('../templates/form.html'),
triggers: {
submit: 'add:todo:item'
},
modelEvents: {
change: 'render'
},
ui: {
assignee: '#id_assignee',
text: '#id_text'
}
});

module.exports = FormView;

You can see straight away how much simpler these views are. They only deal with their own
data management and are completely unaware of each other. We've also turned the list
back into a CollectionView because we no longer need to attach a model or a form
template. We can safely remove the todolist.html template. The todoitem.html template
is unchanged but we have a new form.html template:

Laying out your page

36

Marionette Guides

<label for="id_text">Todo Text</label>


<input type="text" name="text" id="id_text" value="<%- text %>" />
<label for="id_assignee">Assign to</label>
<input type="text" name="assignee" id="id_assignee" value="<%- assignee %>"/>
<button id="btn-add">Add Item</button>

We no longer need the wrapping form because the LayoutView will generate that for us.
Our views/layout.js file now handles the management of the two separate views:

Laying out your page

37

Marionette Guides

var Marionette = require('backbone.marionette');


var FormView = require('./form');
var ListView = require('./list');

var Layout = Marionette.LayoutView.extend({


el: '#app-hook',
template: require('../templates/layout.html'),
regions: {
form: '.form',
list: '.list'
},
collectionEvents: {
add: 'itemAdded'
},
onShow: function() {
var formView = new FormView({model: this.model});
var listView = new ListView({collection: this.collection});
this.showChildView('form', formView);
this.showChildView('list', listView);
},
onChildviewAddTodoItem: function(child) {
this.model.set({
assignee: child.ui.assignee.val(),
text: child.ui.text.val()
}, {validate: true});
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
}
});
module.exports = Layout;

The major changes here are that some of the form rendering logic is pushed into the form
view itself, while logic that links the form to the list is kept in this LayoutView . We also have
an onShow handler that renders the views into the jQuery selectors referenced by the
Laying out your page

38

Marionette Guides

regions hash. Finally, a LayoutView can see events occurring on its children by

prepending its event handler with Childview as in onChildviewAddTodoItem . The Childview


triggers always send a copy of their view as the first argument, allowing us to see the ui
object.
The individual views don't directly interact with each other, instead interacting with the model
and letting the view event handlers recognize when they need to do something.
Finally, this layout solves the issue identified before. Now only the form itself will be rerendered when data changes. The list is able to manage the individual items being attached
and render only what needs to be rendered.
For completeness, the layout.html template is detailed below:
<div class="list"></div>
<div class="form"></div>

As a template, the layout has been relegated to just an overarching frame that delegates
most of its rendering responsibilities to its subordinate views.

Laying out your page

39

Marionette Guides

Wrapping up
After reading this tutorial, you should now be able to start building your own Marionette
apps.

Full code sample


If you're interested, you can view a reference for the full code sample in the todo app in the
appendix.

Summary

40

Marionette Guides

Views and Templates


In the web, the user interacts with your application using their web browser. The web
browser displays your page by rendering the HTML your server sends it. It can also,
crucially, render HTML that you generate inside the browser itself using JavaScript. This is
what allows us to build dynamic applications (like Google Docs, Trello, and Slack) inside the
web browser.
Early web browsers weren't very good at executing JavaScript and, as a result, early web
developers didn't write much JavaScript, preferring to do all the processing and HTML
generation on the server. Over time, with the development of complex client-side
applications like Gmail, and the improvement of JavaScript engines in Chrome and Firefox,
more and more processing and data rendering could be moved into the user's browser.
You've probably heard of jQuery - the first popular library for manipulating the HTML in your
browser based on user input. Even today, jQuery is used by many sites to handle simple,
and complex, HTML manipulation. Marionette itself uses jQuery to handle the low-level
details of rendering your data.
Developers used the simpler syntax offered by jQuery and began building extremely
complex web applications that would listen to user events, interact with web servers, and
render the page based on the outcome. However, over time this code would become more
and more difficult to manage. Developers would embed application state inside the page
itself which, as the system grew more complex, would become harder to reason about. If
multiple sources were acting on a part of a page, what would be the outcome of the new
function we're adding? What if we didn't expect certain data to exist in the HTML elements
we're acting on?
Marionette aims to solve this problem by taking lessons from elsewhere in the application
development world - desktop and mobile apps. By splitting data storage, rendering, and
handling user-input, it becomes easier to reason about the expected states of the application
and to extend it.

Marionette Views
To structure the data on your page, Marionette requires you to split up the logical structure
into different views. Each view takes a template which it can render (transform into HTML)
and display. Your view will then watch this section of your application for user input,
providing the hooks you need to react to user actions.

Views

41

Marionette Guides

Let's start by building a simple view with the following template which we'll call
mytemplate.html :

<div class="mytext">Some text to render</div>


<input class="myinput" />
<button class="mybutton" type="button">Click Me</button>

Now that we have the template, we'll create a view to draw it:
var Marionette = require('backbone.marionette');
var MyView = Marionette.LayoutView.extend({
template: require('mytemplate.html')
});
view = new MyView();
view.render();

This is among the simplest views we could build - it simply renders the HTML displayed and
(with some extra code) will attach it to our page. We have a button on display, let's do
something when it gets clicked:
var Marionette = require('backbone.marionette');
var MyView = Marionette.LayoutView.extend({
template: require('mytemplate.html'),
events: {
'click .mybutton': 'alertBox'
},
alertBox: function() {
alert('Button Clicked');
}
});
view = new MyView();
view.render();

Now, whenever we click our button, we'll get an alert box. This is handled through the
events object. Put simply, the events object maps a combination of a DOM event (e.g.

click, keyup) with a jQuery selector ( .mtbutton ) to a method to call on the view ( alertBox ).
We can do something a little more complex like so:

Views

42

Marionette Guides

var Marionette = require('backbone.marionette');


var MyView = Marionette.LayoutView.extend({
template: require('mytemplate.html'),
events: {
'keyup .myinput': 'changeDiv'
},
changeDiv: function() {
var text = this.$el.find('.myinput').val();
this.$el.find('.mytext').text(text);
}
});
view = new MyView();
view.render();

Now, whenever we modify the input, the contents of the div tag will change to reflect it.
You might find yourself asking why we'll go to all these lengths to do something we could do
in 2 lines of jQuery. We're just using jQuery anyway, aren't we?

Declaring your UI
Before we move on, we'll have a quick look at how to make our jQuery references a little
cleaner. Marionette views can contain an object called ui that lets us name jQuery
selectors. Let's look at a short example of how this works:

Views

43

Marionette Guides

var Marionette = require('backbone.marionette');


var MyView = Marionette.LayoutView.extend({
template: require('mytemplate.html'),
ui: {
content: '.mytext',
input: '.myinput',
save: '.mybutton'
},
events: {
'click @ui.save': 'changeDiv'
},
changeDiv: function() {
var text = this.ui.input.val();
this.ui.content.text(text);
}
});
view = new MyView();
view.render();

By using the ui object we can make the code a little easier to read and a lot less brittle changing the underlying template only requires us to update the ui object with the new
selectors. Another advantage to using ui references over raw jQuery selectors is that they
get stored as references - we can look up the same references over and over and jQuery will
only search the DOM once.

Using Models to share data


What happens if the data we're entering and how it needs to be handled are on completely
different parts of the application? There's no reason for them to be aware of each other in
the system. We can use a Backbone Model to store data changes and share them between
different views in a structured way. Assuming we have two views that share a model
instance, actions on one view can affect another.
We'll start with the view being affected, with the template output.html :
<div class="mytext"><%- mytext %></div>

Views

44

Marionette Guides

var Marionette = require('backbone.marionette');


var Output = Marionette.LayoutView.extend({
template: require('./output.html'),
modelEvents: {
'change:mytext': 'render'
}
});
module.exports = Output;

Because this view is so simple, we'll just completely redraw the template whenever the
underlying data changes.
The view where our user enters data with the template input.html :
<input class="myinput" />
<button class="mybutton">Click Me</button>

var Marionette = require('backbone.marionette');


var Input = Marionette.LayoutView.extend({
template: require('./input.html'),
ui: {
input: '.myinput',
button: '.mybutton'
},
events: {
'click @ui.button': 'updateModel'
},
updateModel: function() {
var text = this.ui.input.val();
this.model.update({
mytext: text
});
}
});
module.exports = Input;

At the top-level, all we need to do is attach the same model to both views and they can then
both change and listen to it.

Views

45

Marionette Guides

Binding to Models
The above is an example of binding views to models. This is a key aspect of building
Marionette applications, especially those with dynamic data.
To bind a view to a model, simply pass it in when you create a new instance of the view:
var MyView = require('./myview');
var view = new MyView({
model: new Backbone.Model()
});

Once we have a model bound to our view, we can access it from this.model and listen to
events on the model. The official Backbone documentation contains the full list of events,
and what they apply to.

Listening to Model events


If we want our view to listen to events on its attached model, simply bind it in the
modelEvents object like so:

var MyView = Marionette.LayoutView.extend({


template: require('./mytemplate.html'),
modelEvents: {
'change': 'changeAnything',
'change:myfield': 'changeSpecificField'
},
changeAnything: function(model, options) {
alert('Triggered on any field change');
},
changeSpecificField: function(model, value, options) {
alert('Triggered because myfield changed - ' + value);
}
});

Listening to custom events


If the built-in model events aren't sufficient, it's also possible to set and trigger custom
events. For example, Backbone.Model only defines a sync event but no special event to tell
us what triggered the sync e.g. fetch() or save() . Let's imagine we want to execute some
custom code after a save such as updating our collection:
Views

46

Marionette Guides

var MyView = Marionette.LayoutView.extend({


template: require('mytemplate.html'),
modelEvents: {
save: 'afterSave'
},
afterSave: function(model, options) {
alert('Model was saved');
},
onButtonClicked: function() {
var model = this.model;
model.save({
success: function() {
model.trigger('save', model, {});
}
});
}
});

Now, whenever that event is fired by the model, we can listen to the save event and act on it
from whichever views are bound to the model. While this works, this ad-hoc method for
triggering model events only works for one-off sections of your application. If you want to
standardize this behavior across your app's models then we would recommend you provide
a custom method or extend the model's save() method (and/or any others).

Views

47

Marionette Guides

Lists of Data
A key component of views is being able to handle lists (or collections) of data. The
CollectionView and CompositeView are specifically designed to deal with these lists by

binding to the Backbone.Collection .


When you bind a collection to your collection view, it will iterate over each item in the list,
rendering a new view for each one. Let's start with an example CollectionView with our
items using a item.html template called 'list.js':
<a href="<%- url %>"><%- text %></a>

var Item = Marionette.LayoutView.extend({


tagName: 'li',
template: require('./item.html')
});
var List = Marionette.CollectionView.extend({
tagName: 'ul',
childView: Item
});
module.exports = List;

Now let's create an instance of this with our collection:


var List = require('./list');
var collection = new Backbone.Collection([
{text: 'Some Text', url: '/items/1'},
{text: 'Some other text', url: '/items/4'}
]);
var view = new List({
collection: collection
});
view.render();

The output from this will look like:

Lists of Data

48

Marionette Guides

<ul>
<li><a href="/items/1">Some Text</a></li>
<li><a href="/items/2">Some other text</a></li>
</ul>

If we want to add an item to this list, we simply do:


collection.add({
text: 'New Item',
url: '/items/8'
});

The CollectionView will recognize the item has been added and inject the item into the
HTML template at the right location.

Events on Collections
Just like we can bind modelEvents we can also bind collectionEvents to our views. The full
list of events can be found in the Backbone documentation. This includes events for add
and remove (which is what Marionette listens to internally). Let's see an example:
var List = Marionette.CollectionView.extend({
tagName: 'ul',
childView: Item,
collectionEvents: {
add: 'itemAdded'
},
itemAdded: function(collection) {
alert('New item added');
}
});

Now, whenever an item gets added, no matter how, an alert box will be displayed for each
one.

Rendering Tables
You'll notice the CollectionView doesn't define its own template. This makes it unsuitable
for more complex listed layouts such as tables. To solve this problem, we have the
CompositeView - a view that lets us assign a template to be rendered.

Lists of Data

49

Marionette Guides

Let's take two templates making up a table: row.html and table.html


<thead>
<tr>
<th>Name</th>
<th>Nationality</th>
<th>Gender</th>
</tr>
</thead>
<tbody></tbody>

<td><%- name %></td>


<td><%- nationality %></td>
<td><%- gender %></td>

We'll now use the CompositeView to build this:


var Row = Marionette.LayoutView.extend({
tagName: 'tr',
template: require('row.html')
});
var Table = Marionette.CompositeView.extend({
tagName: 'table',
template: require('table.html'),
childView: Row,
childViewContainer: 'tbody'
});
module.exports = Table;

The CompositeView takes two extra required attributes: the familiar template and
childViewContainer - a jQuery selector to the element in our template to attach the
childView elements. Using the CompositeView is identical to the CollectionView :

var Table = require('./table');


var collection = new Backbone.Collection([
{name: 'John Smith', gender: 'male', nationality: 'UK', url: '/items/1'},
{name: 'Jane Doe', gender: 'female', nationality: 'USA', url: '/items/4'}
]);
var view = new Table({
collection: collection
});
view.render();

Lists of Data

50

Marionette Guides

Adding and removing items works exactly as you'd expect - the new views are injected at
the correct locations in the template.

Binding Models
The CompositeView has another advantage over CollectionView - it can take and additional
model argument and render based on the contents of the model. Let's say we wanted to

know how many people were in the final list above, we'll modify our table.html :
<thead>
<tr>
<th>Name</th>
<th>Nationality</th>
<th>Gender</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<th>Total</th>
<td colspan="2"><%- total %></td>
</tr>
</tfoot>

This total needs to come from a model which we'll pass when creating the table:
var Table = require('./table');
var collection = new Backbone.Collection([
{name: 'John Smith', gender: 'male', nationality: 'UK', url: '/items/1'},
{name: 'Jane Doe', gender: 'female', nationality: 'USA', url: '/items/4'}
]);
var model = new Backbone.Model({
total: 30
});
var view = new Table({
collection: collection,
model: model
});
view.render();

And that's it, our table now has access to the total field in the model!

Events
Lists of Data

51

Marionette Guides

Our CompositeView can now listen to both collectionEvents and modelEvents at the same
time. One good use for this is a report table with a summary that is calculated and fetched
separately:
var Table = Marionette.CompositeView.extend({
tagName: 'table',
template: require('table.html'),
childView: Row,
childViewContainer: 'tbody',
modelEvents: {
sync: 'render'
},
collectionEvents: {
update: 'checkStatus'
},
checkStatus: function(collection) {
collection.each(function(model) {
// Do something
});
}
});

With this example, when our model is fetched from the server, we'll re-render the table.
When our collection is modified, we run another handler that, in this case, does some form
of checking/modification for the collection.

Lists of Data

52

Marionette Guides

Structuring your App


A key component of your Marionette toolkit is the LayoutView . This view allows you to break
your application's screen into regions that are implemented separately and pieced together
through a parent LayoutView .

The Layout view


Let's demonstrate a simple layout - we'll construct a simple report with a summary region.
We'll start with our layout.html template:
<div class="summary"></div>
<div class="report"></div>

We've created two empty elements with hooks that we want to attach our regions to. Next,
we'll define our view:
var Summary = require('./summary');
var Table = require('./table');

var MyLayout = Marionette.LayoutView.extend({


template: require('./layout.html'),
regions: {
summary: '.summary',
report: '.report'
}
});
module.exports = MyLayout;

The LayoutView takes a regions object that maps our keys to jQuery selector strings in the
template. The next step is to tell Marionette what views to display when it is itself displayed:

Structuring your App

53

Marionette Guides

var Summary = require('./summary');


var Table = require('./table');

var MyLayout = Marionette.LayoutView.extend({


template: require('./layout.html'),
regions: {
summary: '.summary',
report: '.report'
},
onShow: function() {
var summary = new Summary({model: new Backbone.Model});
var table = new Table({collection: new Backbone.Collection});
this.showChildView('summary', summary);
this.showChildView('table', table);
}
});
module.exports = MyLayout;

We've added our onShow handler to our layout. When we show the MyLayout view in a
parent region, the show trigger is fired, upon which it will then create and show our
summary and table. Each of these views will be rendered and their show triggers will be
fired. If these contain LayoutView s, we could keep listening to this trigger and show more
views down the tree.

Layouts as mediators
The LayoutView is a great medium for transferring messages between one view and
another, particularly if they're on the same level. For example, we could contain a form
which, whenever the user saves it, adds a new record to a collection elsewhere in the
application. To make this easier, we'll use triggers on the form. Triggers are covered more indepth in another chapter so we'll stick to the parts that are relevant for our layouts.
We'll gloss over the templates and stick to just the views, keeping everything in a single file
for simplicity:

Structuring your App

54

Marionette Guides

var FormView = Marionette.LayoutView.extend({


tagName: 'form',
template: require('./form.html'),
ui: {
save: '.save-button'
},
triggers: {
'click @ui.save': 'save:model'
}
});
var Item = Marionette.LayoutView.extend({
tagName: 'li',
template: require('./item.html')
});
var ListView = Marionette.CollectionView.extend({
tagName: 'ul',
childView: Item
});
var Layout = Marionette.LayoutView.extend({
template: require('./layout.html'),
regions: {
list: '.list',
form: '.form'
},
onShow: function() {
this.showChildView(
'form',
new FormView({model: new Backbone.Model()})
);
this.showChildView(
'list',
new ListView({collection: new Backbone.Collection})
);
}
});
module.exports = Layout;

Now that we have our skeleton, we need to figure out how to get the 'save:model' trigger
fired on the FormView to be recognized from its parent Layout . Luckily, Marionette gives us
just a tool for that - the childview: trigger prefix. Every time a trigger is fired on a view, any
parent views can see it as well. Taking just our Layout , we can do the following:

Structuring your App

55

Marionette Guides

var Layout = Marionette.LayoutView.extend({


template: require('./layout.html'),
regions: {
list: '.list',
form: '.form'
},
onShow: function() {
this.showChildView(
'form',
new FormView({model: new Backbone.Model()})
);
this.showChildView(
'list',
new ListView({collection: new Backbone.Collection})
);
},
onChildviewSaveModel: function(child, model) {
var list = this.getChildView('list');
var newModel = model.clone();
list.collection.add(newModel);
model.clear();
}
});

The layout view knows about all its children, as it should, but the individual views don't know
about each other. This means each view can continue operating independently and manage
its own state - when that state needs to be shared, we need only start looking up the view
hierarchy to see when, and how, that state gets shared between views.

Structuring your App

56

Marionette Guides

Events and Triggers


Communicating data within and between views is one of the major challenges for any
application framework. The method chosen by Backbone was to provide a framework for
triggering and handling events fired by objects. The modelEvents object we've already seen
is an example of this from Backbone. Marionette takes this a step further by providing a
more powerful trigger framework on top of Backbone's event handlers. With triggers, we are
able to decouple the firing of an event from its handler. Many views fire triggers by default,
leaving it to the developer to choose how (or if) they handle them.

Using Events
Marionette provides an events object that allows us to listen to activity in our view's
template and respond to it with a specific method. For example, we can listen to data entry
or a button press and determine the method to call. For example:
var MyView = Marionette.LayoutView.extend({
template: require('./events.html'),
ui: {
title: '.title',
save: '.save'
},
events: {
'keyup @ui.title': 'setTitle',
'blur @ui.title': 'setTitle',
'click @ui.save': 'saveForm'
},
setTitle: function(domEvent) {
this.model.set({
title: this.ui.title.val()
});
},
saveForm: function(domEvent) {
this.model.save();
}
});

Events and Triggers

57

Marionette Guides

This is a simple example of setting a model field and saving the data to the server. It should
be clear what methods are being called when we perform actions on the page. The events
object must be bound at initialization and will cause an error if any of the referenced
methods don't exist on the view.

Triggers
What happens if we want to provide more generic behavior? We could want to build a base
view for our application that gets extended by other views, providing events that can be
listened to when we want to extend the behavior of the view. Forcing us to define all the
methods when creating the base class would be overkill, especially in a language like
JavaScript that aims for a certain amount of brevity and clarity.
Luckily Marionette gives us the triggers framework. To use a trigger, we simply define the
name of the trigger to be fired and either provide listeners or, for simplicity, a special method
name that will be called. We'll rewrite our above example to demonstrate:
var MyView = Marionette.LayoutView.extend({
template: require('./events.html'),
ui: {
title: '.title',
save: '.save'
},
triggers: {
'keyup @ui.title': 'set:title',
'blur @ui.title': 'set:title',
'click @ui.save': 'save:form'
},
onSetTitle: function(domEvent) {
this.model.set({
title: this.ui.title.val()
});
},
onSaveForm: function(domEvent) {
this.model.save();
}
});

As you can see, the changes were minor - we changes the events object to triggers and
specified some trigger names. Triggers are typically named using the : as word
separators. This special syntax is recognized by Marionette to work out which methods to

Events and Triggers

58

Marionette Guides

call. As you can see, set:title will call onSetTitle when triggered and save:form will
call onSaveForm when triggered.

Manually Firing Triggers


We can also manually fire triggers. A common pattern is to use a before: trigger to alert
listeners that an action is about to be performed. Take our save example:
var MyView = Marionette.LayoutView.extend({
template: require('./events.html'),
ui: {
title: '.title',
save: '.save'
},
triggers: {
'keyup @ui.title': 'set:title',
'blur @ui.title': 'set:title',
'click @ui.save': 'save:form'
},
onSetTitle: function(domEvent) {
this.model.set({
title: this.ui.title.val()
});
},
onSaveForm: function(domEvent) {
this.triggerMethod('before:model:save');
var view = this;
this.model.save({
success: function() {
view.triggerMethod('model:save');
}
});
}
});

This style is used throughout Marionette to allow developers to bind hooks before and after
common actions occur. Some of the most common examples are the render and show
hooks: before:render , render , before:show , show .

Built-in Triggers
We can listen to these standard triggers just like any other:

Events and Triggers

59

Marionette Guides

var MyView = Marionette.LayoutView.extend({


template: require('./events.html'),
onBeforeRender: function() {
alert('This view is about to be rendered');
},
onRender: function() {
alert('This view was rendered');
}
});

The next section will cover the built in triggers in more detail.

Events and Triggers

60

Marionette Guides

Built-in Triggers
Marionette views include a set of built-in triggers that are fired throughout the View creations
and destruction lifecycle. These triggers are designed to give you more fine-grained control
over your view, for example being able to modify your template before it gets attached to
your page, or even before the template gets rendered.
It's also possible to write your own custom triggers and handlers to provide hooks for
specific actions that aren't provided by Marionette.

Listening for triggers


To listen to a trigger, we can attach a handler to our view:
// Listen to the 'render' trigger
view = new Marionette.LayoutView();
view.listenTo('render', function() {
console.log('View was rendered');
});
// Listen to the 'before:attach' trigger
view.listenTo('before:attach', function() {
console.log('View is about to be attached to the page');
});

Most triggers have before:action and action hooks that fire before and after action has
occurred. A common example could be before:render and render which fire before and
after the template is rendered.

Short-hand
All triggers that get fired can also be handled by defining methods in a specific style on our
views: the onTriggerFired format e.g. onBeforeRender . To know what name our handler
method requires, we simply take the trigger name ( before:render in this case), prefix on
and uppercase the first letter and every first letter following a : in the name. So render
becomes onRender , before:render becomes onBeforeRender , attach becomes
onAttach , etc.

An example should help explain:

Built-in Triggers

61

Marionette Guides

var MyLayout = Marionette.LayoutView.extend({


onBeforeRender: function() {
console.log('before:render was fired');
},
onRender: function() {
console.log('render was fired');
}
});

For simplicity, this reference will use the trigger:fired format, which you should now be
able to convert to the method name using the formula described above.

List of View Triggers


This reference lists all the triggers that can be fired in alphabetical order, with the before:
<trigger> as part of the <trigger> section. We'll list the view types that can fire each

trigger (or all), and what each trigger means to you. Where appropriate, we'll provide an
example of common use of the trigger.
If you just want to know what triggers a view can fire, skip to the bottom of this reference to
find the list of views and the triggers they can each fire.

add:child and before:add:child


This trigger fires whenever a child view gets rendered and attached to the CollectionView .
We will get an argument referencing the child that has just been added. This can be useful if
you need to do some extra work on the child view where you need information from the
parent.

Example
On Views
CollectionView
CompositeView

attach and before:attach

Built-in Triggers

62

Marionette Guides

This trigger fires once our view gets attached to the actual DOM i.e. once the view is
completely rendered and viewable in the browser window. This is usually used to perform
actions that require the view to be completely rendered and visible - for instance displaying a
modal or datepicker widget. We also use this as a safe point where we know that the view is
finished and active for the user.

Example
This example uses Bootstrap's Modal JavaScript which requires the HTML to be attached to
the DOM to work.
var ModalLayout = Marionette.Layout.extend({
ui: {
wrapper: '.modal-wrapper'
},
/** Use Bootstrap's modal JavaScript. This assumes that the JS files are
* loaded and will work.
*/
onAttach: function() {
this.ui.wrapper.modal('show');
}
});

On Views
View
ItemView
LayoutView
CollectionView
CompositeView

destroy and before:destroy


Fired by the region manager to perform any extra clean up on the view after it has been
destroyed. This can be things like executing extra JS on plugins like modals and
datepickers.

Example
In this example, we'll use the before:destroy trigger to close a modal before it gets
removed from the DOM. We're using something similar to Bootstrap's modal JavaScript.

Built-in Triggers

63

Marionette Guides

var ModalView = Marionette.LayoutView.extend({


ui: {
wrapper: '.modal-wrapper'
},
onBeforeDestroy: function() {
this.ui.wrapper.modal('hide');
}
});

As an aside, this wouldn't work in Bootstrap as the modal method returns immediately,
causing the view to be destroyed before the modal can be hidden.

On Views
View
ItemView
LayoutView
CollectionView
CompositeView

dom:refresh
Only fired when a view is re-rendered. This does not get fired on the first render, only on
subsequent rendering.

Example
This example demonstrates when the dom:refresh trigger is fired.

Built-in Triggers

64

Marionette Guides

var MyLayout = Marionette.LayoutView.extend({


onDomRefresh: function() {
console.log('dom:refresh');
},
onRender: function() {
console.log('render');
},
onShow: function() {
console.log('show');
}
});
var view = new MyLayout();
regionManager.get('layout').show(view);
// 'render'
// 'show'
view.render();
// 'render'
// 'dom:refresh'

On Views
View
ItemView
LayoutView
CollectionView
CompositeView

render and before:render


The render event fires when the view's HTML template has been rendered (the string has
been built) but before it gets attached to the browser DOM. We commonly use this hook to
operate on the HTML but before the user can see it.

Example
A common example is to check for data and add/remove classes on the view's element
every time it gets rendered. This example checks to see if a table row was selected for a
form element and attaches the appropriate class.

Built-in Triggers

65

Marionette Guides

var RowView = Marionette.LayoutView.extend({


tagName: 'tr',
onRender: function() {
if (this.model.get('selected')) {
this.$el.addClass('selected');
}
else {
this.$el.removeClass('selected');
}
}
});

var TableView = Marionette.CompositeView.extend({


tagName: 'table',
childView: RowView,
childViewContainer: 'tbody'
});

On Views
ItemView
LayoutView
CollectionView
CompositeView

render:collection and
before:render:collection
Fired after the collection has been rendered and attached to the view. This will only fire if the
view's collection is not empty. In other words, if the view attached to emptyView is displayed,
this trigger won't be fired.

render:empty and before:render:empty


Fired after the emptyView has been rendered.

On Views
CollectionView
CompositeView

Built-in Triggers

66

Marionette Guides

render:template and before:render:template


The render:template trigger is fired when the wrapper template for a CompositeView is
rendered but before the collection items have started rendering - childViewContainer will be
empty while this trigger is first fired.

Example
On Views
CompositeView

reorder and before:reorder


Gets fired whenever we reorder the underlying collection attached to a view.

Example
On Views
CollectionView
CompositeView

remove:child and before:remove:child


The remove:child trigger gets fired when a collection item is removed, typically when
this.collection.remove(id) is called. Unlike the collection event, this fires after the view

has been removed from the DOM.

Example
On Views
CollectionView
CompositeView

show and before:show

Built-in Triggers

67

Marionette Guides

The show event fires when region.show(view) - or layoutView.showChildView(view) - is


complete. We'll commonly use this trigger to build up our nested layout hierarchy and show
a layout's sub-views.

Example
Our example is a Marionette pattern that is commonly used to build up our view hierarchy by
chaining show handlers.
var MyLayout = Marionette.LayoutView.extend({
regions: {
todoList: '.todo-hook'
},
onShow: function() {
console.log('regionManager has show this view');
this.showChildView('todoList', new CompositeView({
collection: this.collection
}); // This will trigger CompositeView's 'before:show' and 'show' handlers
}
})
// Assuming regionManager has been defined with a region 'layout'
regionManager.get('layout').show(new MyLayout({collection: todoList}));

On views
View
ItemView
LayoutView
CollectionView
CompositeView

List of Views and their Triggers


The Marionette view lifecycle involves firing a number of triggers at each stage of its
creation. The exact lifecycle depends on how the view is displayed and what type of view it
is.

Built-in Triggers

68

Marionette Guides

Built-in Triggers

69

Marionette Guides

This diagram displays the full lifecycle. Our before:add:child and add:child only get
called on CollectionView and CompositeView before the view lifecycle of each child is
executed.
Our before:show and show triggers get fired whenever the view is shown as part of a usual
lifecycle - from region.show(view) , view.showChildView(view) , or when a new child is added
to a CollectionView with view.collection.add(model) .

Views and their Triggers


Included below is a reference to all the in-built view triggers and the views that can trigger
them:

View

ItemView

LayoutView

add:child

CollectionView

CompositeView

attach

destroy

dom:refresh

render

render:collection

render:template
reorder

remove:child

show

Built-in Triggers

70

Marionette Guides

Working with Templates


After we've constructed views from models and collections, we need to show this to our
users. This section covers how to render dynamic data and the different approaches we can
take using Marionette to to this.

A simple template
With a LayoutView we can render a template quite easily:
var MyLayout = Marionette.LayoutView.extend({
template: require('mylayouttemplate.html')
});

With our template mylayouttemplate.html as something like:


<h1>Hello, world</h1>
<p>We have something to talk about</p>

When we show or render our view, we can see the template that we've just created. This
isn't very interesting, however, so let's take it a step further using some model data:
var mymodel = new Backbone.Model({
name: 'Scott'
});
var layout = new MyLayout({model: mymodel});
layout.render();

Let's change our template before compiling this:


<h1>Hello, <%- name %></h1>
<p>I now know your name</p>

Our page is now a little more interesting - we can change it based on the model data we
pass into our view. With just this knowledge we can, and will, build plenty of complex web
applications.

Templates

71

Marionette Guides

Template syntax
This syntax comes from Underscore's template engine; an extremely simple template engine
that does most of what we need. We'll quickly go through its syntax here.

Output data
There are two basic ways to output data in an underscore template: escaped and
unescaped.

Escaped data
The default syntax we'll use is the familiar <%- %> syntax from above. This will cause
Underscore to escape HTML strings to make them safe. This should be your default goto,
especially for user-entered data:
<h1><%- heading %></h1>
<p><%- user_data %></p>

Let's say our user has the following model:


{
heading: 'Some text',
user_data: '<script>alert("test")</script>'
}

Once rendered, our HTML would be:


<h1>Some text</h1>
<p>&lt;script&gt;alert("test")&lt;/script&gt;</p>

The escaping has protected us from potential cross-site scripting (XSS) attacks.

Unescaped data
On occasion, we need to output data exactly as it is entered into our model. For example,
we may have pre-escaped our output, or we explicitly want to output HTML that the browser
will render (for example, rendered Markdown).
In this case, we can tell Underscore to output our data as-is with <%= %> .

Templates

72

Marionette Guides

<h1><%- heading %></h1>


<%= user_data %>

With the same model as above, the raw output will be:
<h1>Some text</h1>
<script>alert("test")</script>

Causing the alert code to execute.

When to escape and when not to


By default, we should prefer <%- %> escaping over <%= %> except in rare, well-defined
cases. The cases are:
When the output comes from a trusted source
When the output has been processed by a template helper
In each of these cases, the output to be printed must contain HTML markup that you want to
render - otherwise we should just use <%- %> and get the same effect.

Accessing undefined fields


Some template engines (such as Django templates) allow us to attempt to display variables
that haven't been defined - rendering an empty string. Underscore templates don't have this
property; attempting to access an undefined variable causes an error.
For example, take the following model:
{
heading: 'Scott'
}

The following template can't render and will raise an error instead:
<h1><%- heading %></h1>
<%- user_data %>

Introducing JavaScript logic


Underscore templates allow us to execute JavaScript inline with the template using <% %> .
With this syntax we can conditionally display data or iterate through an array.

Templates

73

Marionette Guides

Let's take the following models:


{
heading: 'Some header',
data: [
'string 1',
'string 2'
]
}

and an empty model:


{
heading: null,
data: []
}

<% if (heading === null) { %>


<h1>New Item</h1>
<% } else { %>
<h1><%- heading %></h1>
<% } %>

For our populated model, the following is output:


<h1>Some header</h1>

and our empty model outputs:


<h1>New Item</h1>

We get access to the _ namespace in our templates too, making it easier to iterate:
<ul>
<% _.each(data, function(item) { %>
<li><%- item %></li>
<% }) %>
</ul>

Again, with our populated model, the output is:

Templates

74

Marionette Guides

<ul>
<li>string 1</li>
<li>string 2</li>
</ul>

while our empty model simply looks like:


<ul>
</ul>

Template Helpers
As we can see, Underscore's template language is pretty basic and gives us very little to
work with. For example, setting variables or custom logic isn't well supported, and
attempting to access an undefined variable causes the entire template to not render. In
addition, if we want to start introducing some complex logic, the template itself will quickly
become unwieldy. We can mitigate some of this complexity using views and layouts but,
when we simply want to validate data or format it, we turn to the templateHelpers attribute
on our view.
The templateHelpers attribute lets us assign an object - or function returning and object whose keys will be available in the template. Let's build a simple template helper that
outputs some information about a basket.
Our desired template is:
<p>
Your basket total is <%- toCurrency(total) %> and contains
<%- count(items) %> items.
</p>

and our model is:


{
items: [
{description: "An item", cost: "30.00"},
{description: "Another item", cost: "5.00"}
],
total: "35.00"
}

The output we'd want to get would be:

Templates

75

Marionette Guides

<p>
Your basket total is 35.00 and contains 2 items.
</p>

Let's define a simple template helper on our view:


var BasketView = Marionette.LayoutView.extend({
template: require('./basket.html'),
templateHelpers: {
count: function(items) {
return items.length;
},
toCurrency: function(total) {
var totalFloat = parseFloat(total);
return '' + totalFloat.toFixed(2);
}
}
});

Now, our template can see the keys count and toCurrency and can call these functions
directly. Another key advantage of this method is reusability - it's simple to just import a
function call and attach it to our templateHelpers as such:
var currencyFormatter = require('./helpers/currency');
var arrayHelpers = require('./helpers/array');
var BasketView = Marionette.LayoutView.extend({
template: require('./basket.html'),
templateHelpers: {
count: arrayHelpers.count,
toCurrency: currencyFormatter
}
});

Returning a function
The templateHelpers attribute can also take a function and call it for you. This is especially
useful in the case where we have potentially undefined variables on our model:

Templates

76

Marionette Guides

var currencyFormatter = require('./helpers/currency');


var BasketView = Marionette.LayoutView.extend({
template: require('./basket.html'),
templateHelpers: function() {
var items = this.model.get('items');
var total = this.model.get('total');
var count = _.isUndefined(items) ? 0 : items.length;
var cleanTotal = _.isUndefined(total) ? '0.00' : total;
return {
count: count,
totalAsCurrency: currencyFormatter(cleanTotal)
};
}
});

We can modify our template slightly to look like:


<p>
Your basket total is <%- totalAsCurrency %> and contains
<%- count %> items.
</p>

as our view has pre-processed the values for us.

Templates

77

Marionette Guides

Creating an Application
The Marionette.Application base class is used as the root of your application. The
Application is commonly used to:

1. Transfer any data from the surrounding page into your application.
2. Initialize your regions and views.
3. Start your router.
This section explores the different use cases and how to combine them effectively.

Starting your Application


The most common application pattern is to construct your view hierarchy and populate your
initial data from your surrounding screen in the onStart handler.
The application lives in your driver.js or main.js file and looks like:
var Marionette = require('backbone.marionette');
var Layout = require('./views/layout');

var app = new Marionette.Application({


onStart: function(options) {
var layout = new Layout(options);
layout.render();
}
});
app.start();

This simple pattern will work for many types of application. It also works well for index pages
with data injected from a server-side template.

Initial Server data


Sometimes we want to pre-load data from the server. We'd usually do this to minimize server
round-trips and load our data faster. Let's look at the following index.html template that our
server generated for us:

Creating an Application

78

Marionette Guides

<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="body-hook"></div>
<script>
/** This section was generated by a server template engine e.g. Django
or Ruby on Rails
*/
window.initialData = [
{
id: 1,
url: '/item/1',
strapline: 'Make your applications dance',
title: 'Marionette'
},
{
id: 3,
url: '/item/3',
strapline: '',
title: 'Backbone.js'
}
];
/** End server generated section */
</script>
<script src="/static/js/app.js"></script>
</body>
</html>

Now we'll modify our driver.js file to look for the initial data from the server:
var Marionette = require('backbone.marionette');
var Layout = require('./views/layout');

var app = new Marionette.Application({


onStart: function(options) {
var layout = new Layout(options);
layout.render();
}
});
app.start({initialData: window.initialData});

All we've done here is pass window.initialData as an argument to app.start . Maintaining


a dependency on the page in our driver file is perfectly acceptable - that is its purpose after
all! When we want to test our app, we can simply inject the options directly into Layout in

Creating an Application

79

Marionette Guides

our test runner. For this reason, it's preferable to keep our driver.js file as simple as
possible - we want to minimize the amount of setup code we need to duplicate.

Creating an Application

80

Marionette Guides

Integrating with Routing


One of the biggest uses of the Application is to start your router, which in turn starts
rendering your initial views. This is normally quite straightforward from our driver.js file:
var Marionette = require('backbone.marionette');
var Router = require('./router');

var app = new Marionette.Application({


onStart: function(options) {
var router = new Router(options);
}
});
app.start();

This is an ideal way to pass page data into router, as well as the rest of our application.

Loading Initial Data for Push State


We can use the Router in combination with the initial data pattern to allow our server to
respond to HTML5 Push State URLs by sending initial data to the application to be rendered
from the Router. Simply modify our driver file like so:
var app = new Marionette.Application({
onStart: function(options) {
var router = new Router({
pushState: true,
initialData: options.initialData
});
}
});
app.start({initialData: window.initialData});

Now, when our Router responds to the URL from HTML Push State, we'll also send in any
data specific to that page to be immediately rendered.

Examples

Integrating with Routing

81

Marionette Guides

We'll just take a few minutes to look at potential example pages that a server could generate
for given URLs. Assuming the rest of the page remains the same, we'll just be looking at the
<script> tag that generates the initialData key.

/
window.initialData = {
guides: [
{
name: 'Marionette',
url: '/guides/marionette'
},
{
name: 'Ember',
url: '/guides/ember'
}
],
authors: [
{
name: 'Scott Walton',
email: 'scott@example.com'
},
{
name: 'Joanne Daudier',
email: 'joanne@example.com'
}
]
}

The Router will trigger the handler bound to the '' route and inject two lists of guides and
authors into the application to be rendered (or not).

/guides/marionette

Integrating with Routing

82

Marionette Guides

window.initialData = [
{
name: 'The Marionette Guide',
authors: [
{
name: 'Scott Walton',
email: 'scott@example.com'
},
{
name: 'Joanne Daudier',
email: 'joanne@example.com'
}
],
published: '2015'
},
{
name: 'Marionette: A Gentle Introduction',
authors: [
{
name: 'David Sulc',
email: 'david@example.com'
}
]
}
];

This data will trigger the handler bound to the 'guides/:guide' route and inject a list of
Marionette guides and their authors to be rendered. This is obviously useful when we have a
short list that's easy for the server to include with the page being loaded. When the
application has loaded, we'll only need to request the data as and when we need it. On the
initial application load, no matter what page we start from, we'll always get some data that
we can render immediately.
This pattern lets us make the best use of HTML5's Push State to get the best of both worlds.
Even better, this pattern only requires us to change the template that our server generates our JavaScript files are safely cached and we don't need to keep duplicating the same basic
functionality!

Integrating with Routing

83

Marionette Guides

Multiple Application Starters


Another pattern for web applications is to use multiple applications and starters. This is
useful when integrating with an existing web service that mixes JavaScript with regular web
pages and forms.

Dynamic Initializer
The key to achieving this is by attaching initializers on the application. To do this, we can use
the addInitializer method like so:
var Marionette = require('backbone.marionette');
var initializerFunction = require(window.initializer);
var application = new Marionette.Application();
application.addInitializer(initializerFunction);
app.start({initialData: window.initialData});

We can then reference the initializer function in our application template:


<script>
window.initializer = 'initializers/root';
window.initialData = {};
</script>
<script src="/static/js/app.js"></script>

The value of initializer is then set by the template created by the server.

Browserify Integration
If you use Browserify, you'll notice that this doesn't work at all. The reason for this is
because Browserify tracks the strings in your require() calls to determine the modules to
import and doesn't support dynamic imports.
To get round this, we can use a includes.js file to map all the locations to keys:

Multiple Application Starters

84

Marionette Guides

module.exports = {
root: require('./initializers/root'),
guides: require('./initializers/guides')
};

Now, driver.js looks like:


var Marionette = require('backbone.marionette');
var includes = require('./includes');
var initializerFunction = includes[window.initializer];
var application = new Marionette.Application();
application.addInitializer(initializerFunction);
app.start({initialData: window.initialData});

Finally, we modify our template to output:


<script>
window.initializer = 'root';
window.initialData = {};
</script>
<script src="/static/js/app.js"></script>

Now Browserify can follow the imports from includes.js and will automatically import all
the modules.

Purpose
Why go to all this trouble? There are plenty of valid reasons an application may use multiple
starters. It can reduce the amount of JS code transferred to the client in one go - the server
will implement some of the logic itself. It also allows us to use Marionette for certain screens;
a major benefit in particularly large legacy applications that would take a long time to
completely port over to a JS app which may not be worth the cost.

Multiple Application Starters

85

Marionette Guides

Implementing Routing
In this chapter, we'll take a brief look at the history of JavaScript web apps so we can
understand the need for routing. We'll then examine how Marionette aims to solve the issues
we uncover.

History
One of the main benefits to building a JavaScript application is showing/hiding views based
on user input without lengthy calls to the server. For example, when choosing an item from a
list, we can just render that item based on information stored in a Collection without
making any lengthy calls to the server to fetch data we have in our browser already. Over
time this method has been successfully used in many applications, reducing server load and
increasing the responsiveness of our web applications.
However, a major downside of this method has historically been the inability to get back to
that page. You'll undoubtedly remember the frustration of accidentally clicking back,
refreshing a page, or closing your browser only to get sent to the home page and have to
find your way back to the screen you were working on, or that interesting article you were
reading.

Solutions
Over the years different methods were tried, including having the server try to remember
your position in an application, mostly with poor results, bugs, complex code, inability to
bookmark or share a post, and many other issues you have likely encountered. What we
really wanted was to just use the trusted browser URL in our applications.
Twitter popularized the use of the fragment - the portion of the URL after # - to store
information about the current location. This part of the URL isn't read by the server but is
understood by web browsers to jump to certain sections of a page. It is also the part of the
window.location object that we can change without causing the browser to change the

whole page from underneath us. When we display some information we may want to retrieve
later, we could simply do: window.location.hashCode = 'newLocation'; and the fragment
would be updated. When we want to retrieve the data on page load, we can read the value
of window.location.hashCode and map it to the code we want to execute to retrieve and
render the information referenced by the fragment.

Implementing Routing

86

Marionette Guides

Routing in Marionette
The Marionette AppRouter is an attempt to abstract over this behavior and provide a URL
routing engine that mirrors those found in the more mature server-side web frameworks
such as those provided by Django, Flask, and Ruby on Rails.
There are two aspects to routing in Marionette:
1. Set the fragment for state to restore later.
2. Render the view matching the fragment on page load.

Setting the fragment


To remember a state that we want to come back to later, we simply call:
Backbone.history.navigate('route/to/restore'); to update the fragment. The key thing we

need to remember is that this is all navigate does! We must not call navigate if we want
to run code from our router, we'll see how to do this in the Router and Controller section
below.

Router and Controller


Routing in Marionette is split into two connected parts: a router and a controller. The router
takes a map of URL fragments to listen for and maps them to method names on an attached
controller to call. The controller is a simple object with matching methods to be called by the
router.
The Marionette class used is called the AppRouter . To use it we simply attach an
appRoutes object to map the routes to methods:

var Router = Marionette.AppRouter.extend({


appRoutes: {
'blog/': 'blogList',
'blog/:entry': 'blogEntry',
'blog/:entry/comments/:comment': 'blogComment'
}
});
module.exports = Router;

The methods referenced in appRoutes must exist on an attached controller. We can attach
this controller in a number of ways which we'll explore shortly.

Implementing Routing

87

Marionette Guides

What happens now?


The router watches the fragment when the page loads, and calls the mapped mathod for
that route. For example, http://example.com#blog/ will, once the router is initialized, call
blogList for us.

The other two routes have variables in their URLs ( entry and comment ) which we pass
into the mapped methods. Some examples:
http://example.com#blog/3 will call blogEntry('3')
http://example.com#blog/5/comments/32 will call blogComment('5', '32')
http://example.com#blog/my-title/comments/2015-02-11 will call blogComment('my-title',
'2015-02-11')

As we learned above, the controller's methods will not be called when


Backbone.history.navigate is called. For example,
Backbone.history.navigate('blog/5/comments/32') will set the fragment to
#blog/5/comments/32 and that's it.

Attaching a Controller
Once you've created your AppRouter you'll need to attach a controller to it before any
routes will be activated. To attach a controller, simply extend Marionette.Object with the
method names matching the values of appRoutes and attach an instance to our
AppRouter.controller attribute:

var Controller = Marionette.Object.extend({


blogList: function() {
// ...
},
blogEntry: function(entry) {
// ...
},
blogComment: function(entry, comment) {
}
});
var Router = Marionette.AppRouter.extend({
controller: new Controller(),
appRoutes: {
// ...
}
});

Implementing Routing

88

Marionette Guides

A simple app
Now we have all the building blocks in place, we can look at a simple app that uses a single
region and manages which view is being rendered using routes. As is common in many
applications, the Controller is going to be the core of our app, initializing our layouts and
handling rendering.
We'll just show the Router and View here. For the full app, visit the appendix.

Our Router
Our router and controller will be stored in router.js and looks like:
var LayoutView = require('./blog');

var Controller = Marionette.Object.extend({


initialize: function() {
var layout = new LayoutView();
layout.render();
this.options.layout = layout;
},
blogList: function() {
var layout = this.getOption('layout');
layout.triggerMethod('show:blog:list');
},
blogEntry: function(entry) {
var layout = this.getOption('layout');
layout.triggerMethod('show:blog:entry', entry);
}
})
var Router = Marionette.AppRouter.extend({
appRoutes: {
'blog/': 'blogList',
'blog/:entry': 'blogEntry'
},
controller: new Controller
});
module.exports = Router;

Here we setup our top-level view in the controller, which simply renders by triggering events
on our view. The view, described below, then renders the correct view based on the layout.

Implementing Routing

89

Marionette Guides

Our View
In our blog.js file, we'll just outline the top-level layout. If you want to see the full
application, check out the appendix:
var LayoutView = Marionette.LayoutView.extend({
template: require('./templates/layout'),
regions: {
main: '.app-hook'
},
onShowBlogList: function() {
this.showChildView('main', new BlogListView());
},
onShowBlogEntry: function(entry) {
var model = this.collection.get(entry);
this.showChildView('main', new BlogEntryView({model: model}));
},
/** Called when `BlogEntryView` triggers `show:blog:list` */
onChildviewShowBlogList: function() {
this.triggerMethod('show:blog:list');
Backbone.history.navigate('blog/');
},
/** Called when `BlogListView` triggers `show:blog:entry` */
onChildviewShowBlogEntry: function(entry) {
this.triggerMethod('show:blog:entry', entry);
Backbone.history.navigate('blog/' + entry);
}
});
module.exports = LayoutView;

The key is that the layout can listen to its children, renders its main region and calls
Backbone.history.navigate to let us know that a URL change occurred. Our router then, at

page load, attempts to match a fragment and triggers the main layout to render the correct
view.

Starting the Router


When you load the page, you'll notice the page hasn't responded to the fragment. There's
one last thing we need to do - start the routing framework. Luckily for us, this is quite simple.
We just need to call the following method after initializing our application:

Implementing Routing

90

Marionette Guides

Backbone.history.start();

Browser History API


More recently, browser vendors recognized the benefits of this pattern and began working
on the Browser History and Push State APIs. These combined the benefits of using the
fragment with more natural looking URLs that could be recognized by the server too.
Backbone and Marionette support this API by setting {pushState: true} in the options
passed to start like so: Backbone.history.start({pushState: true}) . The History API is a
great way to support natural-looking URLs while still using a JavaScript-powered SPA.
However, it only works if your web server backend has been designed to support it.

Implementing Routing

91

Marionette Guides

Attaching controllers
There are a number of ways to attach a controller to your AppRouter , depending on how
you use it in your application. This chapter covers the different strategies and why you might
want to use each one.

Simple objects
The easiest way to attach a controller is to simple attach an object to the controller key
when extending AppRouter as such:
var Router = Marionette.AppRouter.extend({
appRoutes: {
'blog/': 'blogList',
'blog/:entry': 'blogEntry',
'blog/:entry/comments/:comment': 'blogComment'
},
controller: {
blogList: function() {
// ...
},
blogEntry: function(entry) {
// ...
},
blogComment: function(entry, comment) {
// ...
}
}
});
module.exports = Router;

This method works for simpler controllers that don't have much internal state. We just treat
this like any other JavaScript object.

Marionette Object class


If we want a more complex controller, we can use Marionette.Object - a custom object that
contains many of the event handling and initializer logic shared with other Marionette
components:

Attaching Controllers

92

Marionette Guides

var Controller = Marionette.Object.extend({


blogList: function() {
// ...
},
blogEntry: function(entry) {
// ...
},
blogComment: function(entry, comment) {
// ...
}
});

var Router = Marionette.AppRouter.extend({


appRoutes: {
'blog/': 'blogList',
'blog/:entry': 'blogEntry',
'blog/:entry/comments/:comment': 'blogComment'
},
controller: new Controller()
});
module.exports = Router;

We can now have the Object listen to events that occur in the application - for example, we
might handle major layout changes like route-changing logic in our controller by listening to
events on views. This will reduce the amount of code needed to change route inside the
application versus setting up the views on page load.

During initialization
We can also attach a controller during initialize to pass options through the router:

Attaching Controllers

93

Marionette Guides

var Controller = Marionette.Object.extend({


blogList: function() {
// ...
},
blogEntry: function(entry) {
// ...
},
blogComment: function(entry, comment) {
// ...
}
});

var Router = Marionette.AppRouter.extend({


appRoutes: {
'blog/': 'blogList',
'blog/:entry': 'blogEntry',
'blog/:entry/comments/:comment': 'blogComment'
},
initialize: function() {
this.controller = new Controller(this.options);
}
});
module.exports = Router;

This pattern lets us initialize a router inside our application and pass any options directly
through to the controller without having to expose it to the rest of the application.

No Controller
If you're familiar with Backbone, you'll see that the AppRouter is very similar to Backbone's
built-in Router class. As you can probably guess, the AppRouter is simply an extension of
Backbone.Router so, intuitively, supports the standard Backbone Router behavior.

What this means for our simpler applications is that we can merge the router and controller
into a single AppRouter class by setting URLs on the routes key instead of appRoutes .
Any methods declared on routes must exist on our router, just as in standard Backbone:

Attaching Controllers

94

Marionette Guides

var Router = Marionette.AppRouter.extend({


routes: {
'blog/': 'blogList',
'blog/:entry': 'blogEntry',
'blog/:entry/comments/:comment': 'blogComment'
},
blogList: function() {
// ...
},
blogEntry: function(entry) {
// ...
},
blogComment: function(entry, comment) {
// ...
}
});
module.exports = Router;

With the AppRouter , we can mix the two styles in a single routing class.

Attaching Controllers

95

Marionette Guides

Sharing common view Behavior


While building your views, you'll find that you're repeating some common operations such as
displaying modals, handling form validation, or custom form fields with complex behavior.
We can try to share this using inheritance:
var BaseFormView = Marionette.LayoutView.extend({
ui: {
save: '.save-button'
},
triggers: {
'click @ui.save': 'save:form'
},
onSaveForm: function() {
this.model.save();
}
});
var PersonForm = BaseFormView.extend({
template: require('./templates/form.html')
});

This will work until you need to override anything that the base view provides i.e. the ui or
triggers hashes. If the form is also a modal, we then have to choose the View to extend,

forsaking the other. Clearly we need a better way.

What is a Behavior?
At its most basic level, a Behavior is an object attached to a view that is able to listen, and
respond, to events/triggers on the view. It has access to the view object, allowing it to
perform modifications if it needs to. Let's take a look at a simple form Behavior:

Sharing common view Behavior

96

Marionette Guides

var FormBehavior = Marionette.Behavior.extend({


ui: {
save: '.save-button'
},
events: {
'click @ui.save': 'saveForm'
},
saveForm: function() {
this.view.model.save({})
}
});

var PersonForm = Marionette.LayoutView.extend({


behaviors: {
form: {
behaviorClass: FormBehavior
}
},
template: require('./templates/form.html')
});

Now, with a slightly different configuration, we've achieved the same effect in a way that can
be shared across all views that contain a .save-button element. The behaviors object is
used to map Behaviors to Views with extra options. The object key is completely arbitrary,
here we've decided to call it form to reflect its use. We then tell the view which class to use
with the behaviorClass key. Next we'll examine ways of adding extra information to our
behavior.

Listening to View events


Behaviors are able to listen to events and triggers fired on views, models, and collections.
Let's take an example of a behavior that highlights newly created items in collections:

Sharing common view Behavior

97

Marionette Guides

var SuccessBehavior = Marionette.Behavior.extend({


collectionEvents: {
add: 'highlightModel'
},
highlightModel: function(model, collection, options) {
var unhighlight = collection.where({newlyAdded: true});
_.each(unhighlight, function(item) {
item.set('newlyAdded', false);
});
model.set('newlyAdded', true);
}
});

/** Using Bootstrap CSS */


var RowView = Marionette.LayoutView.extend({
tagName: 'tr',
template: require('./templates/row.html'),
modelEvents: {
'change:newlyAdded': 'render'
},
onRender: function() {
if (this.model.get('newlyAdded')) {
this.$el.addClass('success');
}
else {
this.$el.removeClass('success');
}
}
});
var TableView = Marionette.LayoutView.extend({
behaviors: {
successHighlight: {
behaviorClass: SuccessBehavior
}
},
tagName: 'table',
className: 'table',
template: require('./templates/table.html')
});

This behavior can now, when a new item is added to our collection, highlight the table row of
the newly added model, like so:

Sharing common view Behavior

98

Marionette Guides

var table = new TableView({


collection: new Collection([
{name: 'Scott', language: 'English', handle: 'scott-w'}
])
});
table.add({name: 'Joanne', language: 'English', handle: 'jdaudier'});

Attaching Multiple Behaviors


Attaching multiple behaviors is easy, just add another key in the behaviors hash:
var ModalBehavior = Marionette.Behavior.extend({
// ... Handle rendering and tearing down a modal
});
var FormBehavior = Marionette.Behavior.extend({
// ... Handle form and save logic
});
var ModalForm = Marionette.LayoutView.extend({
behaviors: {
modal: {
behaviorClass: ModalBehavior
},
form: {
behaviorClass: FormBehavior
}
}
});

Now we have a form that can be a modal! Another common case is to create a basic View
that does what you need, then add a modal version of it like so:
var NoteView = Marionette.LayoutView.extend({
template: require('./template/note.html')
});
var ModalNoteView = NoteView.extend({
// May be needed depending on the modal implementation
template: require('./template/modalnote.html'),
behaviors: {
modal: {
behaviorClass: ModalBehavior
}
}
});

Sharing common view Behavior

99

Marionette Guides

Now we're able to both render a Note and display it in a Modal with minimal added effort.

Adding options to our Behavior


Behaviors can also be customized for each view, taking options to slightly alter their
behavior. This could be as simple as defining custom elements to look for, or even specific
functions to call.
To add options, we just set them next to our behaviorClass and we can get them using the
getOption method on Behavior :

var HighlightBehavior = Marionette.Behavior.extend({


ui: {
item: '.item-handle'
},
events: {
'click @ui.item': 'highlight'
},
// This could be called via an event
highlight: function() {
this.ui.item.addClass(
this.getOption('highlightClass'));
}
});

var HighlightRedView = Marionette.LayoutView.extend({


behaviors: {
highlight: {
behaviorClass: HighlightBehavior,
highlightClass: 'highlight-red'
}
}
});

Setting default options


In general, we'll have a default value in mind for each option so let's set it using the
defaults hash:

Sharing common view Behavior

100

Marionette Guides

var HighlightBehavior = Marionette.Behavior.extend({


defaults: {
highlightClass: 'highlight-green'
},
ui: {
item: '.item-handle'
},
events: {
'click @ui.item': 'highlight'
},
// This could be called via an event
highlight: function() {
this.ui.item.addClass(
this.getOption('highlightClass'));
}
});

var HighlightGreenView = Marionette.LayoutView.extend({


behaviors: {
highlight: {
behaviorClass: HighlightBehavior
}
}
});

var HighlightRedView = Marionette.LayoutView.extend({


behaviors: {
highlight: {
behaviorClass: HighlightBehavior,
highlightClass: 'highlight-red'
}
}
});

Our HighlightGreenView doesn't need to set a highlightClass as the HighlightBehavior


has highlight-green as a default value.

Listening to Model/Collection events


Like views, Behaviors are able to listen to the modelEvents and collectionEvents hashes
with their own custom behavior. We could use this to, for example, highlight a saved form

Sharing common view Behavior

101

Marionette Guides

var FormSaveBehavior = Marionette.Behavior.extend({


ui: {
highlight: '.highlight-field'
},
modelEvents: {
error: 'highlightError',
sync: 'highlightSaved'
},
collectionEvents: {
sync: 'clearHighlights'
},
clearHighlights: function() {
this.ui.highlight.removeClass('highlight-green highlight-red')
},
highlightError: function() {
this.ui.highlight.addClass('highlight-red');
this.ui.highlight.removeClass('highlight-green');
},
highlightSaved: function() {
this.ui.highlight.addClass('highlight-green');
this.ui.highlight.removeClass('highlight-red');
}
})

Sharing common view Behavior

102

Marionette Guides

Common Behavior Patterns


There are a number of design patterns that can be made simpler with behaviors. This
section will outline some of the more common ones.

Data Manipulation
Forms
The form behavior is one of the most common behaviors for interactive applications. The
form here will be a simple behavior that you can build on and customize for your specific
case:
var FormBehavior = Marionette.Behavior.extend({
defaults: {
formSelector: 'form',
submitSelector: '.submit-form'
},
ui: {
return {
form: this.getOption('formSelector'),
submit: this.getOption('submitSelector')
};
},
events: {
'submit @ui.form': 'saveForm',
'click @ui.submit': 'saveForm'
},
saveForm: function() {
this.view.triggerMethod('before:form:save', this.view.model);
this.view.model.save();
this.view.triggerMethod('form:save', this.view.model);
}
})

We've provided before:form:save and form:save handlers that the view can listen for to
provide extra hooks. Some possible ways to extend this behavior could be to add field
selectors that could be used to automatically bind the field data to the model before saving it.

Example Behaviors

103

Marionette Guides

Bootstrap
A number of Bootstrap's JavaScript widgets involve repetitive rendering tasks that can be
handled using behaviors.

Modal
The Bootstrap CSS framework provides a modal dialog that we can use for confirmation
boxes, extra information, or even pop-up forms. This behavior wraps a view's region in a
modal that can be rendered anywhere, as well as providing the handlers to tear it back down
again.
var ModalBehavior = Marionette.Behavior.extend({
defaults: {
modalClasses: '',
modalOptions: null
},
ui: {
close: '.close-modal'
},
events: {
'hidden.bs.modal': 'triggerFinish',
},
triggers: {
'click @ui.close': 'close:modal'
},
onRender: function() {
this.view.$el.addClass('modal ' + this.getOption('modalClasses'));
},
onAttach: function() {
this.view.$el.modal(this.getOption('modalOptions') || {});
},
onCloseModal: function() {
this.view.$el.modal('hide');
},
triggerFinish: function() {
this.view.triggerMethod('destroy:modal');
}
});

Example Behaviors

104

Marionette Guides

We provide a destroy:modal handler that a parent view can listen to so it knows when it's
safe to empty the region. For example:
var ModalView = Marionette.LayoutView.extend({
behaviors: {
modal: {
behaviorClass: ModalBehavior
}
},
template: require('./templates/view_with_modal.html')
});

var Parent = Marionette.LayoutView.extend({


regions: {
modalRegion: '.modal-hook'
},
onShow: function() {
this.showChildView('modalRegion', new ModalView());
},
onChildviewDestroyModal: function() {
this.getRegion('modalRegion').empty();
}
})

Tooltip
The tooltip behavior is a lot easier than the modal, as we'll see:
var TooltipBehavior = Marionette.Behavior.extend({
defaults: {
tooltipSelector: '.has-tooltip'
},
ui: function() {
return {
tooltip: this.getOption('tooltipSelector');
}
},
onRender: function() {
this.ui.tooltip.tooltip();
}
})

Example Behaviors

105

Marionette Guides

Example Behaviors

106

Marionette Guides

Handling Data Latency

Handling Data Latency

107

Marionette Guides

Handling persisted data


To do any interesting work, a web application usually needs to interact with a web server.
Historically, this would have been managed by using jQuery's $.ajax function, and storing
the result in a JavaScript object. As our applications grow in size, this starts to become more
and more difficult to manage unless we introduce a structure.
Another drawback of this method is that updating our UI in response to data changes gets
very complex very quickly. Take an application like Evernote - there are usually two versions
of a note: the one in the list and the one you're currently editing. How do you easily keep
both versions in sync? If we want to get an updated version of the note from our server, how
can we easily make sure both versions are updated simultaneously?
Backbone gives us the Model and Collection classes for just this purpose. In short, the
Model represents a single object/resource/record with attributes that can be synchronized
with a server. A Collection is simply a list of Models, with extra helper methods, that can also
be synchronized with our server. With these two classes, it becomes very easy to simply list
a Collection, change the individual Model instances, and have those changes
simultaneously propagated across different sections of your application.
Sound too easy? Let's look at how to make it all happen.

Models
We'll start by looking at the Backbone.Model and how to use it. Let's create a simple note
with a timestamp, content, and title.
var Note = Backbone.Model.extend({
defaults: {
timestamp: '',
content: '',
title: ''
}
});
var note = new Note({
timestamp: '2015-09-02 11:00:00',
content: "I'm writing a book!",
title: 'Doing something'
});

Now we have our note, we can read its fields:


Dealing with Complex Persisted Data

108

Marionette Guides

console.log(note.get('content'));
// Doing something

If we have some new data to put in this note, we can update it like so:
note.set('content', 'New content');

Or, if we wanted to update multiple fields at a time:


note.set({
content: 'New content',
title: 'Updated title too'
});

Besides brevity, there's a very good reason we'd want to update multiple fields at once, as
we'll get to later.
There's no reason we have to stick to the fields defined by defaults (we don't even need to
define them), so we can add some ad-hoc data:
note.set('reminder', '2015-09-04 11:01:00');

Server synchronization
This is all well and good but it still takes us no closer to pushing and pulling data to a server.
First, we'll need to define a pretend server. Let's give it the URL http://example.com/note/1
which, when we GET it, returns:
{
"id": 1,
"title": "My note",
"content": "Some content saved online",
"timestamp": "2015-09-02 11:01:02",
"reminder": null
}

First, we'll define a new type of Note:

Dealing with Complex Persisted Data

109

Marionette Guides

var Note = Backbone.Model.extend({


urlRoot: 'http://example.com/note/'
});
var note = new Note({
id: 1
});
note.fetch();

The Model.fetch method knows how to construct a URL from its urlRoot and id
properties - namely appending id to urlRoot . Like most web calls in JavaScript, fetch is
asynchronous - execution will continue before the web request completes.
If we wanted to perform an action on the data once the fetch method returns, we can attach
a success callback, as in jQuery:
note.fetch({
success: function(response, model) {
// Do something
}
});

When we're done modifying our data and want to save it, we'll call Model.save() and
Backbone will save the data back to the server:
note.set('title', 'New title');
note.save();

We could also modify data and save in one call:


note.save({
title: 'New title'
});

Again, like fetch, we can use the success callback to execute based on the result of the call:
note.save(
{
title: 'New title'
},
{
success: function(response, model) {
// Do something
}
);

Dealing with Complex Persisted Data

110

Marionette Guides

This dependency on the success and error callbacks doesn't help us when our model is
attached to multiple views - what happens if one view updates the model but another one
needs to be changed? Do both views need to know about each other? Let's find out.

Events
Models use events to signal that something has happened that another object may be
interested in. For example, a model can signal that fields have changed, it has successfully
saved its data (or failed), or that it has fetched a new set of data from the server. These
events can then be listened to by views, or any other object that knows about the model.
Using this, our models can affect multiple parts of an application without needing to be
explicitly told about them. Let's look at some examples:
note.set('title', 'New title');
// Fires the 'change' and 'change:title' events
note.set('content', 'New content');
// Fires another 'change' event and 'change:content' event
note.set({
content: 'Newer content',
title: 'Newer title'
});
// Fires only one 'change' event, 'change:content', and 'change:title'
note.save();
// Fires the 'request' event, then either 'sync' or 'error' depending on the
// server response
note.fetch();
// Like save, fires `request`, then `sync` or `error` depending on the response

A full, up-to-date, list of events can always be found on the Backbone documentation, with a
description of when each event fires. As we can see in the example, change is fired every
time we successfully set a field - if we want to only fire a single change event, we must
pass an object into set with all the fields we want to update.

Listening to Events
For these events to be useful, we need to attach listeners that act when the event is fired.
When the title is updated, let's listen for it like so:

Dealing with Complex Persisted Data

111

Marionette Guides

var titleUpdated = function(model, value) {


console.log('title is now ' + value);
};
note.on('change:title', titleUpdated);
note.set('title', 'Changed Title');
// Outputs 'title is now Changed Title'

We can also listen to events from our views by using the modelEvents attribute. When
defining our view:
var NoteView = Marionette.LayoutView.extend({
modelEvents: {
'change:title': 'updateTitle'
},
updateTitle: function(model, value, options) {
console.log('title for NoteView is now ' + value);
}
});

This will trigger the updateTitle method on our NoteView whenever title changes.

Custom Events
When you start building your apps, you'll notice that Backbone doesn't always give you the
events you need. As a common example, there's no way to distinguish between a
successful save and a successful data pull - sync covers both cases.
Luckily, we can fire custom events on models, and Marionette views are capable of binding
to them. Let's see an example of this now:

Dealing with Complex Persisted Data

112

Marionette Guides

var MyView = Marionette.LayoutView.extend({


modelEvents: {
saved: 'saveComplete'
},
triggers: {
'click .save-button': 'save:note'
},
onSaveNote: function() {
this.model.save(
{
content: 'New content',
title: 'New title'
},
{
success: function() {
note.trigger('save', note);
}
}
);
},
saveComplete: function(model) {
console.log('Note saved');
}
});

Now, when save succeeds, our saveComplete method gets called and Note saved makes
it to the log. Whilst useful, this example has a flaw in that only NoteView will trigger the
saved event, and so it's not much better than just executing the code in success directly.

This could be acceptable if our save is only called in this view - other views can still happily
listen to the saved event, even though they don't fire it.

Collections
Managing a single model is good, and we can do a lot of interesting things with just this
knowledge. When it comes to building applications, we will normally operate on collections
of data to render lists, draw charts, and otherwise aggregate data.
The Backbone.Collection class is used to model and act on multiple models at the same
time. Let's take our note example and see how we could build up a list of notes that we'd like
to draw later:

Dealing with Complex Persisted Data

113

Marionette Guides

var NoteCollection = Backbone.Collection.extend({


});
var noteList = new NoteCollection([
new Note({title: 'Note1', content: 'Content1'}),
new Note({title: 'Note2', content: 'Content2'})
]);

This will store the list of notes. After creating our collection, we can add new notes using the
add method, like so:

noteList.add(new Note({title: 'Note3', content: 'Content3'}));

Since we only have a single type in our list, let's set this constraint in the definition:
var NoteCollection = Backbone.Collection.extend({
model: Note
});
var noteList = new NoteCollection([
{title: 'Note1', content: 'Content1'},
{title: 'Note2', content: 'Content2'}
]);
noteList.add({title: 'Note3', content: 'Content3'});

Now Backbone will convert our raw JavaScript objects into Note objects, both on creation
and when adding new objects. It will also enforce this:
var NotANote = Backbone.Model.extend();
noteList.add(new NotANote({something: 'Some Value'}));
// ERROR - This is not a Note object

Synchronizing data
Collections are used to pull lists of data from our server and build an abstraction we can
operate on. Assuming that our note-taking app has a server endpoint /note/ that returns a
JSON list in the form:

Dealing with Complex Persisted Data

114

Marionette Guides

[
{
"content": "Note Content",
"title": "Note title",
"reminder": null,
"timestamp": "2015-09-01 12:01:00",
"id": 1
},
{
"content": "Another note with some content",
"title": "Note title2",
"reminder": "2015-09-02 09:00:00",
"timestamp": "2015-09-01 12:51:00",
"id": 2
},
{
"content": "Yet another note",
"title": "Note title 3",
"reminder": null,
"timestamp": "2015-02-01 12:01:00",
"id": 3
}
]

Now let's define our collection that references the URL endpoint:
var NoteCollection = Backbone.Collection.extend({
url: '/note/',
model: Note
});
var noteList = new NoteCollection();
noteList.fetch();

Like in Model , we use the fetch method to pull our data from the server and insert it into
our collection.

Dealing with Complex Persisted Data

115

Marionette Guides

Nesting Your Views

Nesting Your Views

116

Marionette Guides

Passing Data with Events

Passing Data with Events

117

Marionette Guides

FAQ

FAQ

118

Marionette Guides

Cookbook
It'd be great if we can add all the cookbook recipes with each recipe as a subchapter.

Cookbook

119

Marionette Guides

Appendix

Appendix

120

Marionette Guides

Todo App Code


The code for the Todo app tutorial can be found before as a comparison with your own
application. This code is for demonstration purposes - it is not necessarily the best way to
build such applications and it certainly isn't the only way.

Driver file
driver.js :

var Marionette = require('backbone.marionette');


var TodoView = require('./views/layout');
var initialData = {
items: [
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]
};

var App = new Marionette.Application({


onStart: function(options) {
var todo = new TodoView({
collection: new Backbone.Collection(options.initialData.items),
model: new ToDoModel()
});
todo.render();
todo.triggerMethod('show');
}
});
App.start({initialData: initialData});

Views
Everything in this section is contained in the views/ directory.
layout.js :

Todo app

121

Marionette Guides

var Backbone = require('backbone');


var Marionette = require('backbone.marionette');
var ToDoModel = require('../models/todo');
var FormView = require('./form');
var ListView = require('./list');

var Layout = Marionette.LayoutView.extend({


el: '#app-hook',
template: require('../templates/layout.html'),
regions: {
form: '.form',
list: '.list'
},
collectionEvents: {
add: 'itemAdded'
},
onShow: function() {
var formView = new FormView({model: this.model});
var listView = new ListView({collection: this.collection});
this.showChildView('form', formView);
this.showChildView('list', listView);
},
onChildviewAddTodoItem: function(child) {
this.model.set({
assignee: child.ui.assignee.val(),
text: child.ui.text.val()
}, {validate: true});
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
}
});
module.exports = Layout;

form.js :

Todo app

122

Marionette Guides

// views/form.js
var Marionette = require('backbone.marionette');

var FormView = Marionette.LayoutView.extend({


tagName: 'form',
template: require('../templates/form.html'),
triggers: {
submit: 'add:todo:item'
},
modelEvents: {
change: 'render'
},
ui: {
assignee: '#id_assignee',
text: '#id_text'
}
});

module.exports = FormView;

list.js :

// views/list.js
var Marionette = require('backbone.marionette');
var ToDo = Marionette.LayoutView.extend({
tagName: 'li',
template: require('../templates/todoitem.html')
});

var TodoList = Marionette.CollectionView.extend({


tagName: 'ul',
childView: ToDo
});

module.exports = TodoList;

Templates
Everything here is stored in the templates directory.

Todo app

123

Marionette Guides

layout.html :

<div class="list"></div>
<div class="form"></div>

form.html :

<label for="id_text">Todo Text</label>


<input type="text" name="text" id="id_text" value="<%- text %>" />
<label for="id_assignee">Assign to</label>
<input type="text" name="assignee" id="id_assignee" value="<%- assignee %>"/>
<button id="btn-add">Add Item</button>

todoitem.html :

<%- item.text %> &mdash; <%- item.assignee %>

Models
Everything here is under the models directory:
todo.js

Todo app

124

Marionette Guides

var Backbone = require('backbone');

var ToDo = Backbone.Model.extend({


defaults: {
assignee: '',
text: ''
},
validate: function(attrs) {
var errors = {};
var hasError = false;
if (!attrs.assignee) {
errors.assignee = 'assignee must be set';
hasError = true;
}
if (!attrs.text) {
errors.text = 'text must be set';
hasError = true;
}
if (hasError) {
return errors;
}
}
});

module.exports = ToDo;

Todo app

125

Marionette Guides

App Router Code


The code for the App Router tutorial can be used as a reference for how to build and
integrate a simple application that uses the AppRouter for its navigation. Each application
will have different requirements, so feel free to adapt this layout for your own application
design.

Index file
index.html :

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="blog-hook"></div>
<script src="static/js/app.js"></script>
</body>
</html>

As with all the appendix items here, this is a simplistic HTML file. Feel free to utilize styling
libraries such as Bootstrap to jazz it up.

Driver file
driver.js :

Full router example

126

Marionette Guides

var Marionette = require('backbone.marionette');


var Router = require('./router');

var initialData = {
posts: [
{
author: 'Scott',
title: 'Why Marionette is amazing',
content: '...',
id: 42,
comments: [
{
author: 'Steve',
content: '...',
id: 56
}
]
},
{
author: 'Andrew',
title: 'How to use Routers',
content: '...',
id: 17
}
]
};

var App = new Marionette.Application({


onStart: function(options) {
var router = new Router(options);
/** Starts the URL handling framework */
Backbone.history.start();
}
});
App.start({initialData: initialData});

Router and Controller


router.js :

var LayoutView = require('./views/layout');


var BlogList = require('./collections/blog');

var Controller = Marionette.Object.extend({

Full router example

127

Marionette Guides

initialize: function() {
/** The region manager gives us a consistent UI and event triggers across
our different layouts.
*/
this.options.regionManager = new Marionette.RegionManager({
regions: {
main: '#blog-hook'
}
});
var initialData = this.getOption('initialData');
var layout = new LayoutView({
collection: new BlogList(initialData.posts)
});
this.getOption('regionManager').get('main').show(layout);
/** We want easy access to our root view later */
this.options.layout = layout;
},
/** List all blog entrys with a summary */
blogList: function() {
var layout = this.getOption('layout');
layout.triggerMethod('show:blog:list');
},
/** List a named entry with its comments underneath */
blogEntry: function(entry) {
var layout = this.getOption('layout');
layout.triggerMethod('show:blog:entry', entry);
}
})
var Router = Marionette.AppRouter.extend({
appRoutes: {
'blog/': 'blogList',
'blog/:entry': 'blogEntry'
},
/** Initialize our controller with the options passed into the application,
such as the initial posts list.
*/
initialize: function() {
this.controller = new Controller({
initialData: this.getOption('initialData');
});
}
});
module.exports = Router;

Full router example

128

Marionette Guides

Collections and Models


models/blog.js :

module.exports = Backbone.Model.extend({
/** Let us inject 0 comments in from the data set
*/
defaults: function() {
return {
comments: []
}
}
});

collections/blog.js :

var Blog = require('../models/blog');


module.exports = Backbone.Collection.extend({
model: Blog
});

models/comment.js :

module.exports = Backbone.Model.extend();

collections/comment.js :

var Comment = require('../models/comment');

module.exports = Backbone.Collection.extend({
model: Comment
});

Views
views/layout.js :

Full router example

129

Marionette Guides

var List = require('./list');


var Blog = require('./blog');

var LayoutView = Marionette.LayoutView.extend({


template: require('../templates/blog/layout.html'),
regions: {
layout: '.layout-hook'
},
onShowBlogList: function() {
var list = new List({collection: this.collection});
this.showChildView('layout', list);
/* Remember - this only sets the fragment, so we can safely call this as
often as we like with no negative side-effects.
*/
Backbone.history.navigate('blog/');
},
onShowBlogEntry: function(entry) {
var model = this.collection.get(entry);
this.showBlog(model);
},
onChildviewSelectEntry: function(child, model) {
this.showBlog(model);
},
/** Child-initiated alias to onShowBlogList */
onChildviewShowBlogList: function() {
this.triggerMethod('show:blog:list');
},
/** Share some simple logic from our subviews */
showBlog: function(blogModel) {
var blog = new Blog({model: blogModel});
this.showChildView('layout', blog);
/* Remember - this only sets the fragment, so we can safely call this as
often as we like with no negative side-effects.
*/
Backbone.history.navigate('blog/' + blog.id);
}
});
module.exports = LayoutView;

views/list.js :

Full router example

130

Marionette Guides

var Entry = Marionette.LayoutView.extend({


template: require('../templates/blog/item.html'),
tagName: 'li',
triggers: {
click: 'select:entry'
}
});

var BlogList = Marionette.CollectionView.extend({


childView: Entry,
tagName: 'ul',
onChildviewSelectEntry: function(child, options) {
this.triggerMethod('select:entry', child.model);
}
});
module.exports = BlogList;

views/blog.js :

Full router example

131

Marionette Guides

var Comment = Marionette.LayoutView.extend({


tagName: 'li',
template: require('../templates/blog/comment.html')
});
var CommentListView = Marionette.CollectionView.extend({
tagName: 'ol',
childView: Comment
});
var Blog = Marionette.LayoutView.extend({
template: require('../templates/blog/blog.html'),
regions: {
comments: '.comment-hook'
},
ui: {
back: '.back'
},
triggers: {
'click @ui.back': 'show:blog:list'
},
onShow: function() {
var comments = new CommentList(this.model.get('comments'));
var commentView = new CommentListView({collection: comments});
this.showChildView('comments', commentView);
}
});
module.exports = Blog;

Templates
templates/blog/layout.html :

<h1>Marionette Blog</h1>
<div class="layout-hook"></div>

templates/blog/item.html :

Full router example

132

Marionette Guides

<a href="/blog/<%- id %>"> <!-- This won't actually link anything -->
<div class="title"><%- title %></div>
by
<span class="author"><%- author %></span>
</a>

templates/blog/blog.html :

<a href="/blog/" class="back">To Blog List</a>


<h2><%- title %></h2>
<div class="author">by <%- author %></div>
<%- content %>
<div class="comment-hook"></div>

templates/blog/comment.html :

<%- content %>


<div class="author"><%- author %></div>

Full router example

133

También podría gustarte