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.

Nicholas Valbusa

May 15, 2014
Tweet

Other Decks in Programming

Transcript

  1. ake your Backbone
    Application Dance
    May 15th, 2014 — Verona
    Nicholas Valbusa
    @squallstar

    View Slide

  2. I AM
    Nicholas Valbusa
    squallstar - squallstar.it
    AT
    Lead JS Developer
    cronycle.com

    View Slide

  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

    View Slide

  4. 10ppl in London
    - Rails+Marionette Webclient
    Paid service,
    at the end of this speech (Yipee!!!)
    - Rails API, Elasticsearch, Redis queue
    - iOS Native app

    View Slide

  5. Always choose the
    right tool for the job

    View Slide

  6. View Slide

  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

    View Slide

  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

    View Slide

  9. let me read that again,
    • “Awesome Model/Collection implementation

    for REST resources”
    var Library = Backbone.Collection.extend({
    model: Book,
    url: “v1/books”
    });

    View Slide

  10. for all the rest, there’s…

    View Slide

  11. Marionette.js
    marionettejs.com

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  15. TAKES CARE OF
    THE RENDERING
    PROCESS

    View Slide

  16. Depends on:
    Backbone & Underscore
    Backbone.BabySitter & Backbone.Wreqr

    (both included)
    just 31kb !

    View Slide

  17. Preface
    The base structure I'm using was adopted from BackboneRails

    View Slide

  18. App skeleton
    & boot

    View Slide



  19. JSDay2014 - Marionette











    <br/>App.start({<br/>environment: 'staging',<br/>foo: 'bar'<br/>});<br/>
    !


    index.html

    View Slide

  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!

    View Slide

  21. Regions

    View Slide

  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”

    View Slide

  23. header-region
    main-region
    footer-region

    View Slide

  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.

    View Slide

  25. header-region
    main-region
    footer-region
    MyView
    App.mainRegion.currentView

    View Slide

  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);

    View Slide

  27. Marionette Modules

    View Slide

  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");

    View Slide

  29. Split your sections/features into modules

    View Slide

  30. Always keep your
    app organised —
    while it grows

    View Slide

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

    View Slide

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

    View Slide

  33. yeah, about those models...

    View Slide

  34. The magic of Backbone models

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  39. Here comes the magic!
    Let’s have a look at Marionette Views

    View Slide

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

    View Slide

  41. Marionette.CollectionView
    Renders the items of a Backbone.Collection
    Doesn’t need a template
    CollectionView
    ItemView Backbone.Model
    Backbone.Collection
    ItemView Backbone.Model

    View Slide

  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

    View Slide

  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

    View Slide

  44. Before going further, choose your
    template engine

    View Slide

  45. Underscore templates works out of the box
    <br/><h2><br/><%- title %><br/></h2><br/>

    View Slide

  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);
    !
    };
    <br/><h2><br/><%- title %><br/></h2><br/>
    config/marionette/renderer.js

    View Slide

  47. Using Rails? Go with Jade + JST
    gem 'tilt-jade'
    Compiles jade templates into js functions
    for use as clientside templates

    View Slide

  48. Jade is just amazing
    .post-content
    header(class='ng-wtf')
    h1= title
    span by #{author}
    !
    if youAreUsingJade
    p You are amazing
    !
    .body= description

    View Slide

  49. Backbone.Marionette.Renderer.render = (tpl, data) ->
    path = JST["apps/" + tpl]
    throw "Template #{tpl} not found!" unless path
    path data
    CoffeeScript...

    View Slide

  50. back to our app
    let's implement these views

    View Slide

  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

    View Slide

  52. View Slide

  53. View Slide

  54. let’s make it better
    <br/><a href="#"><%- title %></a><br/>
    !
    !
    <br/><h1>My nice blog</h1><br/><ul></ul><br/>

    View Slide

  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

    View Slide

  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

    View Slide

  57. View Slide

  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()

    View Slide

  59. Template helpers
    <br/>I think that <%= showMessage() %><br/>
    Posts.PostView = Backbone.Marionette.ItemView.extend({
    ...
    !
    templateHelpers: {
    showMessage: function () {
    return this.title + " rocks!";
    }
    },
    !
    ...
    });

    View Slide

  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 () { /* ... */ }
    !
    });

    View Slide

  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);

    View Slide

  62. Marionette in the real world
    — 5 minutes of Marionette applied to Cronycle —

    View Slide

  63. header-region with ItemView (User, Backbone.Model)
    main-region with CollectionView
    (Backbone.Collection)
    CompositeView (Model +
    Collection)
    ItemView (Model)
    ItemView (Model)

    View Slide

  64. left-sidebar-region
    with CompositeView

    View Slide

  65. Modal windows, just an overlay region

    View Slide

  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!");

    View Slide

  67. Fetching for new articles

    View Slide

  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);
    }
    });

    View Slide

  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
    }
    !
    });

    View Slide

  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);
    }
    }
    }
    !
    });

    View Slide

  71. put a test on it
    https://github.com/bradphelan/jasminerice
    + jasmine-rice for Rails users
    If you like your goat...

    View Slide