Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Make your Backbone application dance

Make your Backbone application dance

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.

41000caad506975d45b2177ee511b673?s=128

Nicholas Valbusa

May 15, 2014
Tweet

Transcript

  1. ake your Backbone Application Dance May 15th, 2014 — Verona

    Nicholas Valbusa @squallstar
  2. I AM Nicholas Valbusa squallstar - squallstar.it AT Lead JS

    Developer cronycle.com
  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
  5. Always choose the right tool for the job

  6. None
  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” });
  10. for all the rest, there’s…

  11. Marionette.js marionettejs.com

  12. Very similar to Backbone, just goes a few more steps.

  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
  15. TAKES CARE OF THE RENDERING PROCESS

  16. Depends on: Backbone & Underscore Backbone.BabySitter & Backbone.Wreqr
 (both included)

    just 31kb !
  17. Preface The base structure I'm using was adopted from BackboneRails

  18. App skeleton & boot

  19. <html> <head> <title>JSDay2014 - Marionette</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <script src="libs/jquery.js"></script> <script src="libs/underscore.js"></script> <script src="libs/backbone.js"></script> <script src="libs/backbone.marionette.js"></script> <script src="boot.js"></script> </head> <body> <div id="header-region"></div> <div id="main-region"></div> <div id="footer-region"></div> <script> App.start({ environment: 'staging', foo: 'bar' }); </script> ! </body> </html> index.html
  20. App = new Backbone.Marionette.Application(); ! App.on("initialize:before", function (options) { //

    do something. come on! }); ! App.on("initialize:after", function (options) { if (Backbone.history){ Backbone.history.start(); } }); javascript / boot.js — that’s it!
  21. Regions

  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”
  23. header-region main-region footer-region

  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.
  25. header-region main-region footer-region MyView App.mainRegion.currentView

  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);
  27. Marionette Modules

  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");
  29. Split your sections/features into modules

  30. Always keep your app organised — while it grows

  31. Organise each module App.BlogApp (Marionette Module) BlogApp.Router (Marionette.AppRouter) BlogApp.Posts (Submodule)

    Posts.Controller Posts.View
  32. What about Backbone models/collections? App.Entities (Marionette module) Entities.Article (Backbone.Model) Entities.Articles

    (Backbone.Collection)
  33. yeah, about those models...

  34. The magic of Backbone models

  35. App.module("Entities", function (Entities, App, Backbone, Marionette, $, _) { !

    Entities.Post = Backbone.Model.extend(); ! Entities.Posts = Backbone.Collection.extend({ model: Entities.Post, url: “path/to/posts.json“ }); ! }); entities / posts.js
  36. App.module("BlogApp", function (BlogApp, App, Bk, Mr, $, _) { !

    BlogApp.Router = Backbone.Marionette.AppRouter.extend({ appRoutes: { "posts" : "showArticles", "posts/:id" : "showArticle" } }); ! var API = { showArticles: function () { BlogApp.Posts.Controller.Show(); }, showArticle: function (id) { BlogApp.Posts.Controller.Show(id); } }; ! App.addInitializer(function () { new BlogApp.Router({ controller: API }); }); ! }); apps / blog / app.js
  37. App.module("BlogApp.Posts", function (Posts, App, Bk, Mr, $, _) { !

    Posts.Controller = { Show: function () { ! var layout = new Posts.View({ collection: new App.Entities.Posts }); ! App.mainRegion.show(layout); } }; ! }); apps / blog / posts / controller.js
  38. App.module("BlogApp.Posts", function (Posts, App, Backbone, M, $, _) { !

    Posts.View = Backbone.Marionette.View.extend({ tagName: "section", className: "posts" }); ! }); apps / blog / posts / view.js
  39. Here comes the magic! Let’s have a look at Marionette

    Views
  40. Marionette.ItemView Renders a single item (Backbone.Model) Backbone.Model ItemView

  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
  43. CollectionView ItemView Backbone.Model Backbone.Collection ItemView Backbone.Model ItemView Backbone.Model CompositeView Template


    + Backbone.Collection + optional Backbone.Model Backbone.Model ItemView .some-selector Backbone.Model ItemView
  44. Before going further, choose your template engine

  45. Underscore templates works out of the box <script type="template" id="post-template">

    <h2> <%- title %> </h2> </script>
  46. you can also override Marionette Renderer: Backbone.Marionette.Renderer.render = function (template,

    data) { ! tpl = _.template($( "script.wtf-is-this-" + template ).html()); if (!tpl) throw("Template " + template + " not found!"); ! return tpl(data); ! }; <script type="text/template" class="wtf-is-this-post-template"> <h2> <%- title %> </h2> </script> config/marionette/renderer.js
  47. Using Rails? Go with Jade + JST gem 'tilt-jade' Compiles

    jade templates into js functions for use as clientside templates
  48. Jade is just amazing .post-content header(class='ng-wtf') h1= title span by

    #{author} ! if youAreUsingJade p You are amazing ! .body= description
  49. Backbone.Marionette.Renderer.render = (tpl, data) -> path = JST["apps/" + tpl]

    throw "Template #{tpl} not found!" unless path path data CoffeeScript...
  50. back to our app let's implement these views

  51. App.module("BlogApp.Posts", function (Posts, App, Bk, Mr, $, _) { !

    Posts.PostView = Backbone.Marionette.ItemView.extend({ tagName: "article", className: "post", template: “#post-template" }); ! Posts.View = Backbone.Marionette.CollectionView.extend({ tagName: "section", className: "posts", itemView: Posts.PostView, ! initialize: function (options) { options.collection.fetch(); } }); ! }); apps / blog / posts/ view.js
  52. None
  53. None
  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. None
  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()
  59. Template helpers <script id="my-template" type="template"> I think that <%= showMessage()

    %> </script> Posts.PostView = Backbone.Marionette.ItemView.extend({ ... ! templateHelpers: { showMessage: function () { return this.title + " rocks!"; } }, ! ... });
  60. Modal/Collection events Backbone.Marionette.CompositeView.extend({ ! modelEvents: { // eq to view.listenTo(view.model,

    "change:name", view.nameChanged, view) "change:name": "nameChanged" }, ! collectionEvents: { // eq to view.listenTo(view.collection, "add", view.itemAdded, view) "add": "itemAdded" }, ! // ... event handler methods nameChanged: function () { /* ... */ }, itemAdded: function () { /* ... */ } ! });
  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)
  64. left-sidebar-region with CompositeView

  65. Modal windows, just an overlay region

  66. Modal region App.module("Modals", function (Modals, App, Backbone, Marionette, $, _)

    { ! Modals.Region = Marionette.Region["extends"]({ el: "#modal-region", open: function(view) { $.magnificPopup.open(view); }, close: function() { $.magnificPopup.instance.close(); } }); ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! }); Modals.start = function () { App.addRegions({modalRegion: Modals.Region}); }; App.reqres.setHandler("alert", function (title) { view = new MyModalWindow({ title: title }); return App.modalRegion.show(view); }); // This is how you use it App.request("alert", "Roflcopter!");
  67. Fetching for new articles

  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...