backbone.marionette.js

Backbone.Marionette v0.9.10

Copyright (C)2012 Derick Bailey, Muted Solutions, LLC Distributed Under MIT License

Documentation and Full License Available at: http://github.com/derickbailey/backbone.marionette

Backbone.Marionette = (function(Backbone, _, $){
  var Marionette = {};

EventBinder

The event binder facilitates the binding and unbinding of events from objects that extend Backbone.Events. It makes unbinding events, even with anonymous callback functions, easy.

Inspired by Johnny Oshika

Marionette.EventBinder = function(){
  this._eventBindings = [];
};

_.extend(Marionette.EventBinder.prototype, {

Store the event binding in array so it can be unbound easily, at a later point in time.

  bindTo: function (obj, eventName, callback, context) {
    context = context || this;
    obj.on(eventName, callback, context);

    var binding = { 
      obj: obj, 
      eventName: eventName, 
      callback: callback, 
      context: context 
    };

    this._eventBindings.push(binding);

    return binding;
  },

Unbind from a single binding object. Binding objects are returned from the bindTo method call.

  unbindFrom: function(binding){
    binding.obj.off(binding.eventName, binding.callback, binding.context);
    this._eventBindings = _.reject(this._eventBindings, function(bind){return bind === binding;});
  },

Unbind all of the events that we have stored.

  unbindAll: function () {
    var that = this;

The unbindFrom call removes elements from the array while it is being iterated, so clone it first.

    var bindings = _.map(this._eventBindings, _.identity);
    _.each(bindings, function (binding, index) {
      that.unbindFrom(binding);
    });
  }
});

Copy the extend function used by Backbone's classes

Marionette.EventBinder.extend = Backbone.View.extend;

Marionette.View

The core view type that other Marionette views extend from.

Marionette.View = Backbone.View.extend({
  constructor: function(){
    var eventBinder = new Marionette.EventBinder();
    _.extend(this, eventBinder);

    Backbone.View.prototype.constructor.apply(this, arguments);

    this.bindTo(this, "show", this.onShowCalled, this);
  },

Get the template for this view instance. You can set a template attribute in the view definition or pass a template: "whatever" parameter in to the constructor options.

  getTemplate: function(){
    var template;

Get the template from this.options.template or this.template. The options takes precedence.

    if (this.options && this.options.template){
      template = this.options.template;
    } else {
      template = this.template;
    }

    return template;
  },

Serialize the model or collection for the view. If a model is found, .toJSON() is called. If a collection is found, .toJSON() is also called, but is used to populate an items array in the resulting data. If both are found, defaults to the model. You can override the serializeData method in your own view definition, to provide custom serialization for your view's data.

  serializeData: function(){
    var data;

    if (this.model) { 
      data = this.model.toJSON(); 
    }
    else if (this.collection) {
      data = { items: this.collection.toJSON() };
    }

    data = this.mixinTemplateHelpers(data);

    return data;
  },

Mix in template helper methods. Looks for a templateHelpers attribute, which can either be an object literal, or a function that returns an object literal. All methods and attributes from this object are copies to the object passed in.

  mixinTemplateHelpers: function(target){
    target = target || {};
    var templateHelpers = this.templateHelpers;
    if (_.isFunction(templateHelpers)){
      templateHelpers = templateHelpers.call(this);
    }
    return _.extend(target, templateHelpers);
  },

Configure triggers to forward DOM events to view events. triggers: {"click .foo": "do:foo"}

  configureTriggers: function(){
    if (!this.triggers) { return; }

    var triggers = this.triggers;
    var that = this;
    var triggerEvents = {};

Allow triggers to be configured as a function

    if (_.isFunction(triggers)){ triggers = triggers.call(this); }

Configure the triggers, prevent default action and stop propagation of DOM events

    _.each(triggers, function(value, key){

      triggerEvents[key] = function(e){
        if (e && e.preventDefault){ e.preventDefault(); }
        if (e && e.stopPropagation){ e.stopPropagation(); }
        that.trigger(value);
      };

    });

    return triggerEvents;
  },

Overriding Backbone.View's delegateEvents specifically to handle the triggers configuration

  delegateEvents: function(events){
    events = events || this.events;
    if (_.isFunction(events)){ events = events.call(this); }

    var combinedEvents = {};
    var triggers = this.configureTriggers();
    _.extend(combinedEvents, events, triggers);

    Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
  },

Internal method, handles the show event.

  onShowCalled: function(){},

Default close implementation, for removing a view from the DOM and unbinding it. Regions will call this method for you. You can specify an onClose method in your view to add custom code that is called after the view is closed.

  close: function(){
    if (this.beforeClose) { this.beforeClose(); }

    this.remove();

    if (this.onClose) { this.onClose(); }
    this.trigger('close');
    this.unbindAll();
    this.unbind();
  },

This method binds the elements specified in the "ui" hash inside the view's code with the associated jQuery selectors.

  bindUIElements: function(){
    if (!this.ui) { return; }

    var that = this;

    if (!this.uiBindings) {

We want to store the ui hash in uiBindings, since afterwards the values in the ui hash will be overridden with jQuery selectors.

      this.uiBindings = this.ui;
    }

refreshing the associated selectors since they should point to the newly rendered elements.

    this.ui = {};
    _.each(_.keys(this.uiBindings), function(key) {
      var selector = that.uiBindings[key];
      that.ui[key] = that.$(selector);
    });
  }

});

Item View

A single item view implementation that contains code for rendering with underscore.js templates, serializing the view's model or collection, and calling several methods on extended views, such as onRender.

Marionette.ItemView =  Marionette.View.extend({
  constructor: function(){
    Marionette.View.prototype.constructor.apply(this, arguments);

    if (this.initialEvents){
      this.initialEvents();
    }
  },

Render the view, defaulting to underscore.js templates. You can override this in your view definition to provide a very specific rendering for your view. In general, though, you should override the Marionette.Renderer object to change how Marionette renders views.

  render: function(){
    if (this.beforeRender){ this.beforeRender(); }
    this.trigger("before:render", this);
    this.trigger("item:before:render", this);

    var data = this.serializeData();
    var template = this.getTemplate();
    var html = Marionette.Renderer.render(template, data);
    this.$el.html(html);
    this.bindUIElements();

    if (this.onRender){ this.onRender(); }
    this.trigger("render", this);
    this.trigger("item:rendered", this);
    return this;
  },

Override the default close event to add a few more events that are triggered.

  close: function(){
    this.trigger('item:before:close');
    Marionette.View.prototype.close.apply(this, arguments);
    this.trigger('item:closed');
  }
});

Collection View

A view that iterates over a Backbone.Collection and renders an individual ItemView for each model.

Marionette.CollectionView = Marionette.View.extend({
  constructor: function(){
    Marionette.View.prototype.constructor.apply(this, arguments);
    this.initChildViewStorage();
    this.initialEvents();
    this.onShowCallbacks = new Marionette.Callbacks();
  },

Configured the initial events that the collection view binds to. Override this method to prevent the initial events, or to add your own initial events.

  initialEvents: function(){
    if (this.collection){
      this.bindTo(this.collection, "add", this.addChildView, this);
      this.bindTo(this.collection, "remove", this.removeItemView, this);
      this.bindTo(this.collection, "reset", this.render, this);
    }
  },

Handle a child item added to the collection

  addChildView: function(item, collection, options){
    this.closeEmptyView();
    var ItemView = this.getItemView();
    return this.addItemView(item, ItemView, options.index);
  },

Override from Marionette.View to guarantee the onShow method of child views is called.

  onShowCalled: function(){
    this.onShowCallbacks.run();
  },

Internal method to trigger the before render callbacks and events

  triggerBeforeRender: function(){
    if (this.beforeRender) { this.beforeRender(); }
    this.trigger("before:render", this);
    this.trigger("collection:before:render", this);
  },

Internal method to trigger the rendered callbacks and events

  triggerRendered: function(){
    if (this.onRender) { this.onRender(); }
    this.trigger("render", this);
    this.trigger("collection:rendered", this);
  },

Render the collection of items. Override this method to provide your own implementation of a render function for the collection view.

  render: function(){
    this.triggerBeforeRender();
    this.closeEmptyView();
    this.closeChildren();

    if (this.collection && this.collection.length > 0) {
      this.showCollection();
    } else {
      this.showEmptyView();
    }

    this.triggerRendered();
    return this;
  },

Internal method to loop through each item in the collection view and show it

  showCollection: function(){
    var that = this;
    var ItemView = this.getItemView();
    this.collection.each(function(item, index){
      that.addItemView(item, ItemView, index);
    });
  },

Internal method to show an empty view in place of a collection of item views, when the collection is empty

  showEmptyView: function(){
    var EmptyView = this.options.emptyView || this.emptyView;
    if (EmptyView && !this._showingEmptyView){
      this._showingEmptyView = true;
      var model = new Backbone.Model();
      this.addItemView(model, EmptyView, 0);
    }
  },

Internal method to close an existing emptyView instance if one exists. Called when a collection view has been rendered empty, and then an item is added to the collection.

  closeEmptyView: function(){
    if (this._showingEmptyView){
      this.closeChildren();
      delete this._showingEmptyView;
    }
  },

Retrieve the itemView type, either from this.options.itemView or from the itemView in the object definition. The "options" takes precedence.

  getItemView: function(){
    var itemView = this.options.itemView || this.itemView;

    if (!itemView){
      var err = new Error("An `itemView` must be specified");
      err.name = "NoItemViewError";
      throw err;
    }

    return itemView;
  },

Render the child item's view and add it to the HTML for the collection view.

  addItemView: function(item, ItemView, index){
    var that = this;

    var view = this.buildItemView(item, ItemView);

Store the child view itself so we can properly remove and/or close it later

    this.storeChild(view);
    if (this.onItemAdded){ this.onItemAdded(view); }
    this.trigger("item:added", view);

Render it and show it

    var renderResult = this.renderItemView(view, index);

call onShow for child item views

    if (view.onShow){
      this.onShowCallbacks.add(view.onShow, view);
    }

Forward all child item view events through the parent, prepending "itemview:" to the event name

    var childBinding = this.bindTo(view, "all", function(){
      var args = slice.call(arguments);
      args[0] = "itemview:" + args[0];
      args.splice(1, 0, view);

      that.trigger.apply(that, args);
    });

Store all child event bindings so we can unbind them when removing / closing the child view

    this.childBindings = this.childBindings || {};
    this.childBindings[view.cid] = childBinding;
    
    return renderResult;
  },
  

render the item view

  renderItemView: function(view, index) {
    view.render();
    this.appendHtml(this, view, index);
  },

Build an itemView for every model in the collection.

  buildItemView: function(item, ItemView){
    var itemViewOptions = _.result(this, "itemViewOptions");
    var options = _.extend({model: item}, itemViewOptions);
    var view = new ItemView(options);
    return view;
  },

Remove the child view and close it

  removeItemView: function(item){
    var view = this.children[item.cid];
    if (view){
      var childBinding = this.childBindings[view.cid];
      if (childBinding) {
        this.unbindFrom(childBinding);
        delete this.childBindings[view.cid];
      }
      view.close();
      delete this.children[item.cid];
    }

    if (!this.collection || this.collection.length === 0){
      this.showEmptyView();
    }

    this.trigger("item:removed", view);
  },

Append the HTML to the collection's el. Override this method to do something other then .append.

  appendHtml: function(collectionView, itemView, index){
    collectionView.$el.append(itemView.el);
  },

Store references to all of the child itemView instances so they can be managed and cleaned up, later.

  storeChild: function(view){
    this.children[view.model.cid] = view;
  },

Internal method to set up the children object for storing all of the child views

  initChildViewStorage: function(){
    this.children = {};
  },

Handle cleanup and other closing needs for the collection of views.

  close: function(){
    this.trigger("collection:before:close");
    this.closeChildren();
    Marionette.View.prototype.close.apply(this, arguments);
    this.trigger("collection:closed");
  },

Close the child views that this collection view is holding on to, if any

  closeChildren: function(){
    var that = this;
    if (this.children){
      _.each(_.clone(this.children), function(childView){
        that.removeItemView(childView.model);
      });
    }
  }
});

Composite View

Used for rendering a branch-leaf, hierarchical structure. Extends directly from CollectionView and also renders an an item view as modelView, for the top leaf

Marionette.CompositeView = Marionette.CollectionView.extend({
  constructor: function(options){
    Marionette.CollectionView.apply(this, arguments);
    this.itemView = this.getItemView();
  },

Configured the initial events that the composite view binds to. Override this method to prevent the initial events, or to add your own initial events.

  initialEvents: function(){
    if (this.collection){
      this.bindTo(this.collection, "add", this.addChildView, this);
      this.bindTo(this.collection, "remove", this.removeItemView, this);
      this.bindTo(this.collection, "reset", this.renderCollection, this);
    }
  },

Retrieve the itemView to be used when rendering each of the items in the collection. The default is to return this.itemView or Marionette.CompositeView if no itemView has been defined

  getItemView: function(){
    return this.itemView || this.constructor;
  },

Renders the model once, and the collection once. Calling this again will tell the model's view to re-render itself but the collection will not re-render.

  render: function(){
    var that = this;

    this.resetItemViewContainer();

    var html = this.renderModel();
    this.$el.html(html);

the ui bindings is done here and not at the end of render since they should be available before the collection is rendered.

    this.bindUIElements();
    this.trigger("composite:model:rendered");
    this.trigger("render");

    this.renderCollection();
    this.trigger("composite:rendered");
    return this;
  },

Render the collection for the composite view

  renderCollection: function(){
    Marionette.CollectionView.prototype.render.apply(this, arguments);
    this.trigger("composite:collection:rendered");
  },

Render an individual model, if we have one, as part of a composite view (branch / leaf). For example: a treeview.

  renderModel: function(){
    var data = {};
    data = this.serializeData();

    var template = this.getTemplate();
    return Marionette.Renderer.render(template, data);
  },

Appends the el of itemView instances to the specified itemViewContainer (a jQuery selector). Override this method to provide custom logic of how the child item view instances have their HTML appended to the composite view instance.

  appendHtml: function(cv, iv){
    var $container = this.getItemViewContainer(cv);
    $container.append(iv.el);
  },

Internal method to ensure an $itemViewContainer exists, for the appendHtml method to use.

  getItemViewContainer: function(containerView){
    var container;
    if ("$itemViewContainer" in containerView){
      container = containerView.$itemViewContainer;
    } else {
      if (containerView.itemViewContainer){
        container = containerView.$(_.result(containerView, "itemViewContainer"));

        if (container.length <= 0) {
          var err = new Error("Missing `itemViewContainer`");
          err.name = "ItemViewContainerMissingError";
          throw err;
        }
      } else {
        container = containerView.$el;
      }
      containerView.$itemViewContainer = container;
    }
    return container;
  },

Internal method to reset the $itemViewContainer on render

  resetItemViewContainer: function(){
    if (this.$itemViewContainer){
      delete this.$itemViewContainer;
    }
  }
});

Region

Manage the visual regions of your composite application. See http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/

Marionette.Region = function(options){
  this.options = options || {};

  var eventBinder = new Marionette.EventBinder();
  _.extend(this, eventBinder, options);

  if (!this.el){
    var err = new Error("An 'el' must be specified");
    err.name = "NoElError";
    throw err;
  }

  if (this.initialize){
    this.initialize.apply(this, arguments);
  }
};

_.extend(Marionette.Region.prototype, Backbone.Events, {

Displays a backbone view instance inside of the region. Handles calling the render method for you. Reads content directly from the el attribute. Also calls an optional onShow and close method on your view, just after showing or just before closing the view, respectively.

  show: function(view){

    this.ensureEl();
    this.close();

    view.render();
    this.open(view);

    if (view.onShow) { view.onShow(); }
    view.trigger("show");

    if (this.onShow) { this.onShow(view); }
    this.trigger("view:show", view);

    this.currentView = view;
  },

  ensureEl: function(){
    if (!this.$el || this.$el.length === 0){
      this.$el = this.getEl(this.el);
    }
  },

Override this method to change how the region finds the DOM element that it manages. Return a jQuery selector object.

  getEl: function(selector){
    return $(selector);
  },

Override this method to change how the new view is appended to the $el that the region is managing

  open: function(view){
    this.$el.html(view.el);
  },

Close the current view, if there is one. If there is no current view, it does nothing and returns immediately.

  close: function(){
    var view = this.currentView;
    if (!view){ return; }

    if (view.close) { view.close(); }
    this.trigger("view:closed", view);

    delete this.currentView;
  },

Attach an existing view to the region. This will not call render or onShow for the new view, and will not replace the current HTML for the el of the region.

  attachView: function(view){
    this.currentView = view;
  },

Reset the region by closing any existing view and clearing out the cached $el. The next time a view is shown via this region, the region will re-query the DOM for the region's el.

  reset: function(){
    this.close();
    delete this.$el;
  }
});

Copy the extend function used by Backbone's classes

Marionette.Region.extend = Backbone.View.extend;

Layout

Used for managing application layouts, nested layouts and multiple regions within an application or sub-application.

A specialized view type that renders an area of HTML and then attaches Region instances to the specified regions. Used for composite view management and sub-application areas.

Marionette.Layout = Marionette.ItemView.extend({
  regionType: Marionette.Region,

  constructor: function () {
    Backbone.Marionette.ItemView.apply(this, arguments);
    this.initializeRegions();
  },

Layout's render will use the existing region objects the first time it is called. Subsequent calls will close the views that the regions are showing and then reset the el for the regions to the newly rendered DOM elements.

  render: function(){
    var result = Marionette.ItemView.prototype.render.apply(this, arguments);

Rewrite this function to handle re-rendering and re-initializing the el for each region

    this.render = function(){
      this.closeRegions();
      this.reInitializeRegions();

      var result = Marionette.ItemView.prototype.render.apply(this, arguments);
      return result;
    };

    return result;
  },

Handle closing regions, and then close the view itself.

  close: function () {
    this.closeRegions();
    this.destroyRegions();
    Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
  },

Initialize the regions that have been defined in a regions attribute on this layout. The key of the hash becomes an attribute on the layout object directly. For example: regions: { menu: ".menu-container" } will product a layout.menu object which is a region that controls the .menu-container DOM element.

  initializeRegions: function () {
    if (!this.regionManagers){
      this.regionManagers = {};
    }

    var that = this;
    _.each(this.regions, function (region, name) {
      if (    typeof region != 'string' 
           && typeof region.selector != 'string' ) {
        throw new Exception('Region must be specified as a selector ' +
                            'string or an object with selector property');
      }

      selector = typeof region === 'string' ? region : region.selector;
      var regionType = typeof region.regionType === 'undefined' 
        ? that.regionType : region.regionType;
      
      var regionManager = new regionType({
        el: selector,
          getEl: function(selector){
            return that.$(selector);
          }
      });

      that.regionManagers[name] = regionManager;
      that[name] = regionManager;
    });

  },

Re-initialize all of the regions by updating the el that they point to

  reInitializeRegions: function(){
    if (this.regionManagers && _.size(this.regionManagers)===0){
      this.initializeRegions();
    } else {
      _.each(this.regionManagers, function(region){
        region.reset();
      });
    }
  },

Close all of the regions that have been opened by this layout. This method is called when the layout itself is closed.

  closeRegions: function () {
    var that = this;
    _.each(this.regionManagers, function (manager, name) {
      manager.close();
    });
  },

Destroys all of the regions by removing references from the Layout

  destroyRegions: function(){
    var that = this;
    _.each(this.regionManagers, function (manager, name) {
      delete that[name];
    });
    this.regionManagers = {};
  }
});

Application

Contain and manage the composite application as a whole. Stores and starts up Region objects, includes an event aggregator as app.vent

Marionette.Application = function(options){
  this.initCallbacks = new Marionette.Callbacks();
  this.vent = new Marionette.EventAggregator();
  this.submodules = {};

  var eventBinder = new Marionette.EventBinder();
  _.extend(this, eventBinder, options);
};

_.extend(Marionette.Application.prototype, Backbone.Events, {

Add an initializer that is either run at when the start method is called, or run immediately if added after start has already been called.

  addInitializer: function(initializer){
    this.initCallbacks.add(initializer);
  },

kick off all of the application's processes. initializes all of the regions that have been added to the app, and runs all of the initializer functions

  start: function(options){
    this.trigger("initialize:before", options);
    this.initCallbacks.run(options, this);
    this.trigger("initialize:after", options);

    this.trigger("start", options);
  },

Add regions to your app. Accepts a hash of named strings or Region objects addRegions({something: "#someRegion"}) addRegions{{something: Region.extend({el: "#someRegion"}) });

  addRegions: function(regions){
    var regionValue, regionObj, region;

    for(region in regions){
      if (regions.hasOwnProperty(region)){
        regionValue = regions[region];

        if (typeof regionValue === "string"){
          regionObj = new Marionette.Region({
            el: regionValue
          });
        } else {
          regionObj = new regionValue();
        }

        this[region] = regionObj;
      }
    }
  },

Removes a region from your app. Accepts the regions name removeRegion('myRegion')

  removeRegion: function(region) {
    this[region].close();
    delete this[region];
  },

Create a module, attached to the application

  module: function(moduleNames, moduleDefinition){

slice the args, and add this application object as the first argument of the array

    var args = slice.call(arguments);
    args.unshift(this);

see the Marionette.Module object for more information

    return Marionette.Module.create.apply(Marionette.Module, args);
  }
});

Copy the extend function used by Backbone's classes

Marionette.Application.extend = Backbone.View.extend;

AppRouter

Reduce the boilerplate code of handling route events and then calling a single method on another object. Have your routers configured to call the method on your object, directly.

Configure an AppRouter with appRoutes.

App routers can only take one controller object. It is recommended that you divide your controller objects in to smaller peices of related functionality and have multiple routers / controllers, instead of just one giant router and controller.

You can also add standard routes to an AppRouter.

Marionette.AppRouter = Backbone.Router.extend({

  constructor: function(options){
    Backbone.Router.prototype.constructor.call(this, options);

    if (this.appRoutes){
      var controller = this.controller;
      if (options && options.controller) {
        controller = options.controller;
      }
      this.processAppRoutes(controller, this.appRoutes);
    }
  },

Internal method to process the appRoutes for the router, and turn them in to routes that trigger the specified method on the specified controller.

  processAppRoutes: function(controller, appRoutes){
    var method, methodName;
    var route, routesLength, i;
    var routes = [];
    var router = this;

    for(route in appRoutes){
      if (appRoutes.hasOwnProperty(route)){
        routes.unshift([route, appRoutes[route]]);
      }
    }

    routesLength = routes.length;
    for (i = 0; i < routesLength; i++){
      route = routes[i][0];
      methodName = routes[i][1];
      method = controller[methodName];

      if (!method){
        var msg = "Method '" + methodName + "' was not found on the controller";
        var err = new Error(msg);
        err.name = "NoMethodError";
        throw err;
      }

      method = _.bind(method, controller);
      router.route(route, methodName, method);
    }
  }
});

Module

A simple module system, used to create privacy and encapsulation in Marionette applications

Marionette.Module = function(moduleName, app, customArgs){
  this.moduleName = moduleName;

store sub-modules

  this.submodules = {};

  this._setupInitializersAndFinalizers();

store the configuration for this module

  this._config = {};
  this._config.app = app;
  this._config.customArgs = customArgs;
  this._config.definitions = [];

extend this module with an event binder

  var eventBinder = new Marionette.EventBinder();
  _.extend(this, eventBinder);
};

Extend the Module prototype with events / bindTo, so that the module can be used as an event aggregator or pub/sub.

_.extend(Marionette.Module.prototype, Backbone.Events, {

Initializer for a specific module. Initializers are run when the module's start method is called.

  addInitializer: function(callback){
    this._initializerCallbacks.add(callback);
  },

Finalizers are run when a module is stopped. They are used to teardown and finalize any variables, references, events and other code that the module had set up.

  addFinalizer: function(callback){
    this._finalizerCallbacks.add(callback);
  },

Start the module, and run all of it's initializers

  start: function(options){

Prevent re-start the module

    if (this._isInitialized){ return; }

    this._runModuleDefinition();
    this._initializerCallbacks.run(options, this);
    this._isInitialized = true;

start the sub-modules

    if (this.submodules){
      _.each(this.submodules, function(mod){
        mod.start(options);
      });
    }
  },

Stop this module by running its finalizers and then stop all of the sub-modules for this module

  stop: function(){

if we are not initialized, don't bother finalizing

    if (!this._isInitialized){ return; }
    this._isInitialized = false;

run the finalizers

    this._finalizerCallbacks.run();

then reset the initializers and finalizers

    this._setupInitializersAndFinalizers();

stop the sub-modules

    _.each(this.submodules, function(mod){ mod.stop(); });
  },

Configure the module with a definition function and any custom args that are to be passed in to the definition function

  addDefinition: function(moduleDefinition){
    this._config.definitions.push(moduleDefinition);
  },

Internal method: run the module definition function with the correct arguments

  _runModuleDefinition: function(){
    if (this._config.definitions.length === 0) { return; }

build the correct list of arguments for the module definition

    var args = _.flatten([
      this, 
      this._config.app, 
      Backbone, 
      Marionette, 
      $, _, 
      this._config.customArgs
    ]);

run the module definition function with the correct args

    var definitionCount = this._config.definitions.length-1;
    for(var i=0; i <= definitionCount; i++){

      var definition = this._config.definitions[i];
      definition.apply(this, args);

    }
  },

Internal method: set up new copies of initializers and finalizers. Calling this method will wipe out all existing initializers and finalizers.

  _setupInitializersAndFinalizers: function(){
    this._initializerCallbacks = new Marionette.Callbacks();
    this._finalizerCallbacks = new Marionette.Callbacks();
  }
});

Function level methods to create modules

_.extend(Marionette.Module, {

Create a module, hanging off the app parameter as the parent object.

  create: function(app, moduleNames, moduleDefinition){
    var that = this;
    var parentModule = app;
    moduleNames = moduleNames.split(".");

get the custom args passed in after the module definition and get rid of the module name and definition function

    var customArgs = slice.apply(arguments);
    customArgs.splice(0, 3);

Loop through all the parts of the module definition

    var length = moduleNames.length;
    _.each(moduleNames, function(moduleName, i){
      var isLastModuleInChain = (i === length-1);

Get an existing module of this name if we have one

      var module = parentModule[moduleName];
      if (!module){ 

Create a new module if we don't have one

        module = new Marionette.Module(moduleName, app, customArgs);
        parentModule[moduleName] = module;

store the module on the parent

        parentModule.submodules[moduleName] = module;
      }

Only add a module definition and initializer when this is the last module in a "parent.child.grandchild" hierarchy of module names

      if (isLastModuleInChain ){
        that._createModuleDefinition(module, moduleDefinition, app);
      }

Reset the parent module so that the next child in the list will be added to the correct parent

      parentModule = module;
    });

Return the last module in the definition chain

    return parentModule;
  },

  _createModuleDefinition: function(module, moduleDefinition, app){
    var moduleOptions = this._getModuleDefinitionOptions(moduleDefinition);
    

add the module definition

    if (moduleOptions.definition){
      module.addDefinition(moduleOptions.definition);
    }

    if (moduleOptions.startWithApp){

start the module when the app starts

      app.addInitializer(function(options){
        module.start(options);
      });
    }
  },

  _getModuleDefinitionOptions: function(moduleDefinition){

default to starting the module with the app

    var options = { startWithApp: true };

short circuit if we don't have a module definition

    if (!moduleDefinition){ return options; }

    if (_.isFunction(moduleDefinition)){

if the definition is a function, assign it directly and use the defaults

      options.definition = moduleDefinition;

    } else {

the definition is an object. grab the "define" attribute and the "startWithApp" attribute, as set the options appropriately

      options.definition = moduleDefinition.define;
      if (moduleDefinition.hasOwnProperty("startWithApp")){
        options.startWithApp = moduleDefinition.startWithApp;
      }
    }

    return options;
  }
});

Template Cache

Manage templates stored in <script> blocks, caching them for faster access.

Marionette.TemplateCache = function(templateId){
  this.templateId = templateId;
};

TemplateCache object-level methods. Manage the template caches from these method calls instead of creating your own TemplateCache instances

_.extend(Marionette.TemplateCache, {
  templateCaches: {},

Get the specified template by id. Either retrieves the cached version, or loads it from the DOM.

  get: function(templateId){
    var that = this;
    var cachedTemplate = this.templateCaches[templateId];

    if (!cachedTemplate){
      cachedTemplate = new Marionette.TemplateCache(templateId);
      this.templateCaches[templateId] = cachedTemplate;
    }

    return cachedTemplate.load();
  },

Clear templates from the cache. If no arguments are specified, clears all templates: clear()

If arguments are specified, clears each of the specified templates from the cache: clear("#t1", "#t2", "...")

  clear: function(){
    var i;
    var length = arguments.length;

    if (length > 0){
      for(i=0; i<length; i++){
        delete this.templateCaches[arguments[i]];
      }
    } else {
      this.templateCaches = {};
    }
  }
});

TemplateCache instance methods, allowing each template cache object to manage it's own state and know whether or not it has been loaded

_.extend(Marionette.TemplateCache.prototype, {

Internal method to load the template asynchronously.

  load: function(){
    var that = this;

Guard clause to prevent loading this template more than once

    if (this.compiledTemplate){
      return this.compiledTemplate;
    }

Load the template and compile it

    var template = this.loadTemplate(this.templateId);
    this.compiledTemplate = this.compileTemplate(template);

    return this.compiledTemplate;
  },

Load a template from the DOM, by default. Override this method to provide your own template retrieval, such as asynchronous loading from a server.

  loadTemplate: function(templateId){
    var template = $(templateId).html();

    if (!template || template.length === 0){
      var msg = "Could not find template: '" + templateId + "'";
      var err = new Error(msg);
      err.name = "NoTemplateError";
      throw err;
    }

    return template;
  },

Pre-compile the template before caching it. Override this method if you do not need to pre-compile a template (JST / RequireJS for example) or if you want to change the template engine used (Handebars, etc).

  compileTemplate: function(rawTemplate){
    return _.template(rawTemplate);
  }
});

Renderer

Render a template with data by passing in the template selector and the data to render.

Marionette.Renderer = {

Render a template with data. The template parameter is passed to the TemplateCache object to retrieve the template function. Override this method to provide your own custom rendering and template handling for all of Marionette.

  render: function(template, data){
    var templateFunc = typeof template === 'function' ? template : Marionette.TemplateCache.get(template);
    var html = templateFunc(data);
    return html;
  }
};

Callbacks

A simple way of managing a collection of callbacks and executing them at a later point in time, using jQuery's Deferred object.

Marionette.Callbacks = function(){
  this.deferred = $.Deferred();
  this.promise = this.deferred.promise();
};

_.extend(Marionette.Callbacks.prototype, {

Add a callback to be executed. Callbacks added here are guaranteed to execute, even if they are added after the run method is called.

  add: function(callback, contextOverride){
    this.promise.done(function(context, options){
      if (contextOverride){ context = contextOverride; }
      callback.call(context, options);
    });
  },

Run all registered callbacks with the context specified. Additional callbacks can be added after this has been run and they will still be executed.

  run: function(options, context){
    this.deferred.resolve(context, options);
  }
});

Event Aggregator

A pub-sub object that can be used to decouple various parts of an application through event-driven architecture.

Marionette.EventAggregator = Marionette.EventBinder.extend({
  

Extend any provided options directly on to the event binder

  constructor: function(options){
    Marionette.EventBinder.apply(this, arguments);
    _.extend(this, options);
  },

Override the bindTo method to ensure that the event aggregator is used as the event binding storage

  bindTo: function(eventName, callback, context){
    return Marionette.EventBinder.prototype.bindTo.call(this, this, eventName, callback, context);
  }
});

Copy the basic Backbone.Events on to the event aggregator

_.extend(Marionette.EventAggregator.prototype, Backbone.Events);

Copy the extend function used by Backbone's classes

Marionette.EventAggregator.extend = Backbone.View.extend;

Helpers

For slicing arguments in functions

var slice = Array.prototype.slice;


  return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);