Building Backbone applications quickly became a de-facto standard, though we can’t really define it as framework but rather just as foundation to create other frameworks.
If you like Backbone, you’ll fall in love with Marionette, the best application library to create large scale Javascript applications.
Nicholas will explain the framework, show examples and talk about some projects like cronycle.com that have been delivered to production using Marionette.
3. Our challenges:
- Expand your timeline tweets into stories
- Organize and filter your stories into collections
"Cronycle is a content curation tool based on
Twitter tweets and RSS feeds"
- Make it real-time, responsive, awesome
- Live search
4. 10ppl in London
- Rails+Marionette Webclient
Paid service,
at the end of this speech (Yipee!!!)
- Rails API, Elasticsearch, Redis queue
- iOS Native app
7. • Solid foundation/core classes
• Flexible and simple
• Great pub/sub events architecture
• Awesome Model/Collection implementation
for REST resources
• Good MV*-style code separation (p.s. it’s a MVP)
• It is not like Angular.JS
THE GOOD PARTS
8. • It's incredibly easy to end up in a bad place
• No Application main class
(some people use its router)
• Doesn’t include automatic and good ways to bind models to their views
• No “native” way to organise the pieces of your webapp into modules
• No "native" way to organize layouts (header/footer/sidebar/content/etc..)
• It is not sponsored/maintained by Google
THE BAD PARTS
9. let me read that again,
• “Awesome Model/Collection implementation
for REST resources”
var Library = Backbone.Collection.extend({
model: Book,
url: “v1/books”
});
13. “A composite application library for Backbone that
aims to simplify the construction of large scale
JavaScript applications”
!
— that sits on top of Backbone
An event-driven collection of common design
and implementation patterns.
Basically… Marionette brings an
application architecture to Backbone
14. Key features
• Organised as hell
Applications are built in modules, and with event-driven architecture
• No zombies
Built-in memory management and zombie-killing in views, regions and
layouts
• Flexible
Just code the way you prefer, and picks only the features you need.
• Takes care of the rendering process
22. App.addRegions({
headerRegion: "#header-region",
mainRegion: "#main-region"
footerRegion: "#footer-region"
});
MyCustomHeaderRegion = Marionette.Region.extend({
el: "#header-region"
});
!
MyApp.addRegions({
headerRegion: MyCustomHeaderRegion
});
You can also define custom classes for your regions:
“Regions provide consistent methods to manage, show
and close views in your applications and layouts”
24. Showing a view in a region
var myView = new MyView();
!
// renders and displays the view
App.mainRegion.show(myView);
!
// closes the current view
App.mainRegion.close();
— simple as that.
26. If you replace the current view with a new view by calling
show, it will automatically close the previous view
// Show the first view.
var myView = new MyView();
MyApp.mainRegion.show(myView);
no more zombies!
// Replace the view with another. The
// `close` method is called for you
var anotherView = new AnotherView();
MyApp.mainRegion.show(anotherView);
28. AMD/Require vs Marionette Modules
Take advantage of Marionette's built in module-loader
App.module("MyModule", function (MyModule, App, Backbone, Marionette, $, _) {
!
// do stuff here ...
!
var myData = "this is private data";
!
MyModule.someData = "public data";
!
});
!
var theSameModule = MyApp.module("MyModule");
41. Marionette.CollectionView
Renders the items of a Backbone.Collection
Doesn’t need a template
CollectionView
ItemView Backbone.Model
Backbone.Collection
ItemView Backbone.Model
42. Marionette.CompositeView
Renders the items of a Backbone.Collection
within a wrapper
Extends from Marionette.CollectionView
!
Also: Represents both a branch and a tree structure
Therefore: can also render a model if needed
54. let’s make it better
<script type="text/template" id="post-template">
<a href="#"><%- title %></a>
</script>
!
!
<script type="text/template" id="posts-template">
<h1>My nice blog</h1>
<ul></ul>
</script>
55. Posts.View = Backbone.Marionette.CompositeView.extend({
tagName: "section",
className: "posts",
template: “#posts-template",
itemView: Posts.PostView,
itemViewContainer: "ul",
!
initialize: function (options) {
options.collection.fetch();
}
});
just a few changes to the CollectionView
56. Posts.PostView = Backbone.Marionette.ItemView.extend({
tagName: "li",
className: "post",
template: “#post-template",
events: {
"click a" : "showSinglePost"
},
showSinglePost: function (event) {
event.preventDefault();
Backbone.history.navigate("posts/" + this.model.get('id'));
}
});
and some more to the ItemView
57.
58. Serializing the data
Marionette calls model.toJSON() by default
Posts.PostView = Backbone.Marionette.ItemView.extend({
...
!
// overrides the default behaviour
serializeData: function () {
return _.extend(this.model.toJSON(), {
"foo" : "bar"
});
}
});
can be overridden by defining serializeData()
61. App global requests
// define your request
App.reqres.setHandler("show:post", function (id) {
Backbone.history.navigate("posts/" + id, true);
});
AKA let your modules talk with each other
// use it
App.request("show:post", 3);
62. Marionette in the real world
— 5 minutes of Marionette applied to Cronycle —
63. header-region with ItemView (User, Backbone.Model)
main-region with CollectionView
(Backbone.Collection)
CompositeView (Model +
Collection)
ItemView (Model)
ItemView (Model)
68. 1. define a comparator on your collection
Fetching for new articles
var Entities.Posts = Backbone.Collection.extends({
model: Entities.Post,
url: "/posts",
!
comparator: function (model) {
return -parseInt(model.get('published_at'), 10);
}
});
69. 2. define a custom fetch method
Fetching for new articles
var Entities.Posts = Backbone.Collection.extends({
!
fetchNewPosts: function (callback) {
this.fetch({
url: "posts/?min_ts=#{@first().get('published_at')}",
update: true,
add: true,
remove: false
}
!
});
70. Fetching for new articles
3. override the appendHtml method on your CompositeView
var YourItemView = Backbone.Marionette.CompositeView.extends({
!
ui: {
linksContainer: ".posts-container"
},
!
appendHtml: function (collectionView, itemView, index) {
if (index == 0){
this.ui.linksContainer.prepend(itemView.$el);
} else {
childAtIndex = this.ui.linksContainer.find("> article:eq(" + index + ")");
!
if (childAtIndex.length) {
childAtIndex.before(itemView.$el);
} else {
this.ui.linksContainer.append(itemView.$el);
}
}
}
!
});
71. put a test on it
https://github.com/bradphelan/jasminerice
+ jasmine-rice for Rails users
If you like your goat...
72. describe("Navigating to the posts route", function () {
!
it("should display some articles", function () {
!
Backbone.history.navigate("/posts", true);
!
expect(App.mainRegion.$el.find('article.post').length).toBeGreaterThan(0);
!
expect(App.mainRegion.currentView.collection.at(0).get('title')).toBe('foo');
!
});
});