title: How to create a reusable backbone/backbone.marionette component such a datagrid?
body: <p>I am pretty new to Backbone and Backbone.Marionette. I succeed to create a simple page with a sort of datagrid that allows me paging (first, previous, next, last pages), quick searching (triggered at each time a key is pressed), choosing the number of items shown on page (5, 10, all, ...)</p>
  
  <p>Now that I have something working, I tried to improve that and to make these features as a sort of reusable component but I do not know exactly the way to follow. I do not know how to start to refine the work already done.</p>
  
  <p>For example, I want to be able to change the collection/model manage by the datagrid without rewriting everything. This is where I am not confident how to do that and it is probably due to a lack of knowledge. So your inputs and advice to go further will be really appreciated and welcomed.</p>
  
  <pre><code>// JST and HAML Assets is used for the templating pre-compilation
  Backbone.Marionette.Renderer.render = function(template, data) {
    if (!JST[template]) {
      throw "Template '" + template + "' not found!";
    }
    return JST[template](data);
  };
  
  window.MyApp = new Backbone.Marionette.Application();
  
  MyApp.addRegions({
    content: ".content-box"
  });
  
  MyApp.Datagrid = (function() {
    var Datagrid, ItemPerPageView, Layout, PagerView, QuickSearchView, Theme, ThemeView, Themes, ThemesView;
  
    Datagrid = {};
  
    Layout = Backbone.Marionette.Layout.extend({
      template: "layouts/grid",
      regions: {
        grid: "#grid",
        quickSearch: "#quickSearch",
        itemPerPage: "#itemPerPage",
        pager: ".pager"
      }
    });
  
    Theme = Backbone.Model.extend();
  
    Themes = Backbone.ExtendedCollection.paginatedCollection.extend({
      url: "/themes",
      model: Theme,
  
      initialize: function() {
        var _this = this;
  
        MyApp.vent.on("quickSearch:term", function(term) {
          _this.quickSearch(term);
        });
  
        MyApp.vent.on("itemPerPage:count", function(count) {
          _this.perPage(count);
        });
  
        MyApp.vent.on("pager:previous", function() {
          _this.previous();
        });
  
        MyApp.vent.on("pager:next", function() {
          _this.next();
        });
  
        MyApp.vent.on("pager:first", function() {
          _this.first();
        });
  
        MyApp.vent.on("pager:last", function() {
          _this.last();
        });
      }
    });
  
    ThemeView = Backbone.Marionette.ItemView.extend({
      tagName: "tr",
      template: "theme",
      model: Theme,
  
      events: {
        "click span": "edit",
        "blur input": "save"
      },
  
      edit: function(event) {
        var id, span;
        id = this.model.get("id");
        span = $("span", this.el).hide();
        $("input", this.el).show().focus().val(span.text());
      },
  
      save: function(event) {
        var id, input, span;
        id = this.model.get("id");
        span = $("span", this.el).show();
        input = $("input", this.el).hide();
        if (this.model.get("name") !== input.val()) {
          this.model.set("name", input.val());
          this.model.save();
        }
        span.text(this.model.get("name"));
      }
    });
  
    ThemesView = Backbone.Marionette.CompositeView.extend({
      template: "index",
      model: Theme,
      itemView: ThemeView,
      collection: Themes,
      itemViewContainer: "#themes",
  
      serializeData: function() {
        return this.data;
      }
    });
  
    QuickSearchView = Backbone.Marionette.View.extend({
      el: "#quickSearch",
  
      events: {
        "keyup input": "search"
      },
  
      search: function(event) {
        var searchTerm;
        searchTerm = this.$("input").val().trim();
        MyApp.vent.trigger("quickSearch:term", searchTerm);
      }
    });
  
    ItemPerPageView = Backbone.Marionette.View.extend({
      el: "#itemPerPage",
  
      events: {
        "change select": "count"
      },
  
      count: function(event) {
        var count;
        count = this.$("select").val();
        MyApp.vent.trigger("itemPerPage:count", count);
      }
    });
  
    PagerView = Backbone.Marionette.View.extend({
      el: ".pager",
  
      events: {
        "click #next": "next",
        "click #previous": "previous",
        "click #first": "first",
        "click #last": "last"
      },
  
      first: function(event) {
        MyApp.vent.trigger("pager:first");
      },
  
      last: function(event) {
        MyApp.vent.trigger("pager:last");
      },
  
      next: function(event) {
        MyApp.vent.trigger("pager:next");
      },
  
      previous: function(event) {
        MyApp.vent.trigger("pager:previous");
      }
    });
  
    Datagrid.initializeLayout = function() {
      var collection;
  
      Datagrid.layout = new Layout();
  
      Datagrid.layout.on("show", function() {
        MyApp.vent.trigger("layout:rendered");
      });
  
      MyApp.content.show(Datagrid.layout);
  
      collection = new Themes();
      collection.fetch();
  
      collection.on("reset", function() {
        return Datagrid.layout.grid.show(new ThemesView({
          collection: collection
        }));
      });
    };
  
    MyApp.vent.on("layout:rendered", function() {
      var itemPerPageView, pagerView, quickSearchView;
  
      quickSearchView = new QuickSearchView();
      Datagrid.layout.quickSearch.attachView(quickSearchView);
  
      itemPerPageView = new ItemPerPageView();
      Datagrid.layout.itemPerPage.attachView(itemPerPageView);
  
      pagerView = new PagerView();
      Datagrid.layout.pager.attachView(pagerView);
    });
  
    return Datagrid;
  })();
  
  MyApp.addInitializer(function() {
    MyApp.Datagrid.initializeLayout();
  });
  
  $(document).ready(function() {
    return MyApp.start();
  });
  </code></pre>
  
  <p><strong>Edit 1:</strong></p>
  
  <p>Based on the answer given and my own ideas, I wrote a first draft of a solution. I did not succeed to write a real reusable component but I have a solution that consolidate my code. Some part need to be refactored and improved. There are also some pitfals that I want to solve in a later refactoring.</p>
  
  <p>To add some context, the application is written with Rails as the backend. So there is my javascript folder structure</p>
  
  <pre><code>assets
  |--javascripts
     |--application.js
     |--admin
        |--admin.js
        |--admin.layout.js
        |--subthemes
           |--admin.subtheme.controller.js
           |--admin.subtheme.view.js
        |--themes
           |--admin.theme.controller.js
           |--admin.theme.view.js
  |--templates
     |--admin
        |--subthemes
           |--index.hamlc
           |--subtheme.hamlc
        |--themes
           |--index.hamlc
           |--theme.hamlc
     |--layouts
        |--grid.hamlc
  </code></pre>
  
  <p>First, the application.js start. The assets pipelines from Rails 3.2 will prepare the dependencies as expected:</p>
  
  <pre><code>//= require underscore
  //= require backbone
  //= require backbone.marionette
  //= require_tree ./lib/backbone
  //= require hamlcoffee
  //= require i18n
  //= require i18n/translations
  //= require_tree ../templates/
  //= require_tree ./admin
  //= require_tree ./admin/theme
  //= require_tree ./admin/subtheme
  
  I18n.defaultLocale = "en";
  
  
  Backbone.Marionette.Renderer.render = function(template, data) {
    if (!JST[template]) {
      throw "Template '" + template + "' not found!";
    }
    return JST[template](data);
  };
  
  $(document).ready(function() {
    return MyApp.start();
  });
  </code></pre>
  
  <p>Now, we can prepare the admin part to start:</p>
  
  <pre><code>var AdminRouter, TempView;
  
  // Create the application for admin part
  MyApp.Admin = new Backbone.Marionette.Application();
  
  // Define a router to handle the grid collection type change
  AdminRouter = Backbone.Marionette.AppRouter.extend({
    initialize: function() {
      var _this = this;
  
      // Route quite generic to easily change the data in the grid
      this.route(/^admin\/(.*?)$/, "changeCollection");
  
      // Manage event to handle the navigation on client side
      MyApp.Admin.vent.on("admin:navigate", function(link) {
        _this.navigate(link, {
          trigger: true
        });
      });
    },
  
    // Trigger an event to change the collection if one exist for the URL
    changeCollection: function(collectionName) {
      MyApp.Admin.vent.trigger("grid:collection:change", collectionName);
    }
  });
  
  // Side menu that allows changing the collection in the data grid
  SideMenuView = Backbone.Marionette.View.extend({
    el: ".side-menu",
  
    events: {
      "click a": "handleClick"
    },
  
    // Prevent the normal behavior on the link click
    handleClick: function(event) {
      event.preventDefault();
      MyApp.Admin.vent.trigger("admin:navigate", $(event.target).attr("href"));
    }
  });
  
  // Add the initializer to the main application to prepare the admin part (grid)
  MyApp.addInitializer(function() {
    new SideMenuView();
    new AdminRouter();
    Backbone.history.start({
      pushState: true
    });
    MyApp.Admin.start();
  });
  </code></pre>
  
  <p>Then we can define the datagrid part:</p>
  
  <pre><code>// This the grid layout module in the admin namespace
  MyApp.Admin.module("GridLayout", function(GridLayout, Admin, Backbone, Marionette, $, _) {
    var ItemPageSelectorView, Layout, PagerView, QuickSearchView;
  
    // The quick search view handle the related fields to do the quick search
    QuickSearchView = Backbone.Marionette.View.extend({
      el: ".gridQuickSearch",
  
      events: {
        "keyup input": "search"
      },
  
      // Get the field content and trigger an event with it
      search: function(event) {
        var searchTerm;
        searchTerm = $(event.target).val().trim();
        $("input", this.$el).val(searchTerm);
        Admin.vent.trigger("grid:quickSearch:term", searchTerm);
      }
    });
  
    // The item page selecto handle the choice of how many rows should be displayed per page
    ItemPageSelectorView = Backbone.Marionette.View.extend({
      el: ".gridItemPageSelector",
  
      events: {
        "change select": "count"
      },
  
      // Get the number of items per page that should be displayed
      count: function(event) {
        var count;
        count = $(event.target).val();
        $("select", this.$el).val(count);
        Admin.vent.trigger("grid:itemPageSelector:count", count);
      }
    });
  
    // The pager view manage the view components to change the page shown in the data grid
    PagerView = Backbone.Marionette.View.extend({
      el: ".gridPager",
  
      events: {
        "click #next": "next",
        "click #previous": "previous",
        "click #first": "first",
        "click #last": "last",
        "click #page": "page"
      },
  
      //
      // The following functions triggers events to go to the right pages
      //
      first: function(event) {
        Admin.vent.trigger("grid:pager:first");
      },
  
      previous: function(event) {
        Admin.vent.trigger("grid:pager:previous");
      },
  
      page: function(event) {
        Admin.vent.trigger("grid:pager:page");
      },
  
      next: function(event) {
        Admin.vent.trigger("grid:pager:next");
      },
  
      last: function(event) {
        Admin.vent.trigger("grid:pager:last");
      }
    });
  
    // The grid layout with the regions to display the different part of the data grid
    Layout = Backbone.Marionette.Layout.extend({
      template: "layouts/grid",
  
      regions: {
        gridTable: "#gridTable",
        gridQuickSearch: ".gridQuickSearch",
        gridItemPageSelector: ".gridItemPageSelector",
        gridPager: ".gridPager"
      }
    });
  
    // Once the layout is rendered, the different views are attached to the right regions
    Admin.vent.on("grid:layout:rendered", function() {
      var itemPageSelectorView, pagerView, quickSearchView;
  
      quickSearchView = new QuickSearchView();
      Admin.gridLayout.gridQuickSearch.attachView(quickSearchView);
  
      itemPageSelectorView = new ItemPageSelectorView();
      Admin.gridLayout.gridItemPageSelector.attachView(itemPageSelectorView);
  
      pagerView = new PagerView();
      Admin.gridLayout.gridPager.attachView(pagerView);
    });
  
    // Initializer to do at the application start
    GridLayout.addInitializer(function() {
      Admin.addRegions({
        content: ".content-box"
      });
  
      Admin.gridLayout = new Layout();
  
      // Trigger the rendered event when the grid layout is shown
      Admin.gridLayout.on("show", function() {
        Admin.vent.trigger("grid:layout:rendered");
      });
  
      // Manage the collection data change
      Admin.vent.on("grid:collection:change", function(collectionName) {
        // Close the previous view in the grid table region
        Admin.gridLayout.gridTable.close();
  
        // Trigger an event to fetch the collection
        Admin.vent.trigger("" + collectionName + ":collection:fetch");
  
        // Show the grid layout if not already done
        if (!this.shown) {
          this.shown = true;
          Admin.content.show(Admin.gridLayout);
        }
      });
    });
  
    return GridLayout;
  });
  </code></pre>
  
  <p>We are done on the structural code. Now we can go to one of the controller. For example, the ThemeController:</p>
  
  <pre><code>MyApp.Admin.module("ThemeController", function(ThemeController, Admin, Backbone, Marionette, $, _) {
    // Define the model to use in the collection
    ThemeController.Theme = Backbone.Model.extend();
  
    // Define the collection with the related url on the server. The collection extends a paginated collection that has the methods to manage the quick search and the pagination
    ThemeController.Themes = Backbone.ExtendedCollection.paginatedCollection.extend({
      url: "/admin/themes",
  
      model: ThemeController.Theme,
  
      initialize: function() {
        var _this = this;
  
        //
        // The following functions handle the events for the quick search and pagination
        //
  
        Admin.vent.on("grid:quickSearch:term", function(term) {
          _this.quickSearch(term);
        });
  
        Admin.vent.on("grid:itemPageSelector:count", function(count) {
          _this.perPage(count);
        });
  
        Admin.vent.on("grid:pager:previous", function() {
          _this.previous();
        });
  
        Admin.vent.on("grid:pager:next", function() {
          _this.next();
        });
  
        Admin.vent.on("grid:pager:first", function() {
          _this.first();
        });
  
        return MyApp.Admin.vent.on("grid:collection:fetched", function() {
          Admin.gridLayout.gridTable.show(new Admin.ThemeView.Table({
            collection: _this
          }));
        });
      }
    });
  
    // At the application initilization, we need to be sure this controller can 
    // handle the event to fetch the data from the server
    Admin.addInitializer(function() {
      Admin.vent.on("themes:collection:fetch", function() {
        ThemeController.themes = new ThemeController.Themes();
  
        // Once the data are fetched from the server, trigger an event to display them
        ThemeController.themes.fetch({
          success: function() {
            Admin.vent.trigger("grid:collection:fetched");
          }
        });
      });
    });
  });
  </code></pre>
  
  <p>And finally the views for the previous controller:</p>
  
  <pre><code>MyApp.Admin.module("ThemeView", function(ThemeView, Admin, Backbone, Marionette, $, _) {
    // The view to show one item in a row of the data grid
    ThemeView.Item = Backbone.Marionette.ItemView.extend({
      tagName: "tr",
      template: "admin/themes/theme",
      model: Admin.ThemeController.Theme
    });
  
    // The view to show the collection of item
    ThemeView.Table = Backbone.Marionette.CompositeView.extend({
      template: "admin/themes/index",
      model: Admin.ThemeController.Theme,
      itemView: ThemeView.Item,
      collection: Admin.ThemeController.Themes,
      itemViewContainer: "#themes",
  
      // ! I was force to add this to have data in the original format that is used by my templates !
      serializeData: function() {
        return this.data;
      }
    });
  });
  </code></pre>
  
  <p><strong>Remark:</strong> The subtheme controller and view files contains exactly the same kind of codes. Only templates and kind of stuffs differ.</p>
  
  <p>The grid layout in HAML compiled through Rails assets pipeline looks like:</p>
  
  <pre><code>.gridPager
    %button#first= "&lt;&lt;"
    %button#previous= "&lt;"
    %button#next= "&gt;"
    %button#last= "&gt;&gt;"
  
  %span.gridItemPageSelector= "Item per page"
    %select
      %option= 5
      %option= 10
      %option{"value" =&gt; -1}= "All"
  
  %span.gridQuickSearch= "Quick search:"
    %input#gridSearchTerm{"type" =&gt; "text"}
  
  #gridTable
  
  %span.gridItemPageSelector= "Item per page"
    %select
      %option= 5
      %option= 10
      %option{"value" =&gt; -1}= "All"
  
  %span.gridQuickSearch= "Quick search:"
    %input#gridSearchTerm{"type" =&gt; "text"}
  
  .gridPager
    %button#first= "&lt;&lt;"
    %button#previous= "&lt;"
    %button#next= "&gt;"
    %button#last= "&gt;&gt;"
  </code></pre>
  
  <p>As you can see, there is quite a lot of repetition. I wanted to have quick search and pagination on top and bottom of my grid. At the moment, the simplest way to do that is to duplicate the code. I will change that later when I will find how to do that.</p>
  
  <p>The template for table that shows the themes:</p>
  
  <pre><code>%table.table.table-striped
    %thead
      %tr
        %th= "Id"
        %th= "Name"
    %tbody#themes
  </code></pre>
  
  <p>Quite simple and nothing special to say. At this time, the headers are hardcoded !</p>
  
  <p>And finally, the item view template to show a theme:</p>
  
  <pre><code>%td= this.id
  %td= this.name
  </code></pre>
  
  <p>This template is realy simple.</p>
  
  <p>I am in a situation were that is pretty well working. For example, when I click on other links to change the collection shown, the quick search fields and kind stuff like that are not reinitialized. For that, I wanted to add a sort of state management to keep trace of the collection state and when come back to an already shown collection, I want to show it as it was previously.</p>
  
  <p>I am sure that my solution is not perfect and could be refactored a lot. I also probably did a lot of "newbie" mistakes. So feel free to challenge my proposition. I try to learn and improve my solution and hope it will help someone to do something like that.</p>