add backbone.marionette's missing version from v0.9.1 to v2.0.0

This commit is contained in:
Peter Dave Hello
2014-08-24 01:05:39 +08:00
parent 084e016b65
commit e27e8edbc2
225 changed files with 112486 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,797 @@
// Backbone.Marionette v0.7.0
//
// Copyright (C)2011 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 = {};
Marionette.version = "0.7.0";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
return template;
},
// 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(){
this.beforeClose && this.beforeClose();
this.unbindAll();
this.remove();
this.onClose && this.onClose();
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// 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() };
}
return data;
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var template = this.getTemplateSelector();
var deferredData = this.serializeData();
this.beforeRender && this.beforeRender();
this.trigger("item:before:render", that);
$.when(deferredData).then(function(data) {
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(function(html){
that.$el.html(html);
that.onRender && that.onRender();
that.trigger("item:rendered", that);
that.trigger("render", that);
deferredRender.resolve();
});
});
return deferredRender.promise();
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
this.beforeRender && this.beforeRender();
this.trigger("collection:before:render", this);
this.closeChildren();
this.collection && this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
deferredRender.done(function(){
this.onRender && this.onRender();
this.trigger("collection:rendered", this);
});
$.when(promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// Render an individual model, if we have one, as
// part of a composite view (branch / leaf). For example:
// a treeview.
renderModel: function(){
var data = {};
if (this.model){
data = this.model.toJSON();
}
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, options);
if (!this.el){
var err = new Error("An 'el' must be specified");
err.name = "NoElError";
throw err;
}
};
_.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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
view.onShow && view.onShow();
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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; }
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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
},
initializeRegions: function () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
closeRegions: function () {
var that = this;
_.each(this.regionManagers, function (manager, name) {
manager.close();
delete that[name];
});
this.regionManagers = {};
}
});
// 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 reocmmended 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);
}
},
processAppRoutes: function(controller, appRoutes){
var method, methodName;
var route, routesLength;
var routes = [];
var router = this;
for(route in appRoutes){
routes.unshift([route, appRoutes[route]]);
}
routesLength = routes.length;
for (var i = 0; i < routesLength; i++){
route = routes[i][0];
methodName = routes[i][1];
method = _.bind(controller[methodName], controller);
router.route(route, methodName, method);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
for(var 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;
}
}
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) this.bindings = [];
this.bindings.push({
obj: obj,
eventName: eventName,
callback: callback,
context: context
});
},
// Unbind all of the events that we have stored.
unbindAll: function () {
_.each(this.bindings, function (binding) {
binding.obj.off(binding.eventName, binding.callback);
});
this.bindings = [];
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
callback.call(this, template);
},
// 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 length = arguments.length;
if (length > 0){
for(var i=0; i<length; i++){
delete this.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
if (!template || template.length === 0){
var msg = "A template must be specified";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
var html = _.template(template, data);
return html;
}
}
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
;
File diff suppressed because one or more lines are too long
@@ -0,0 +1,822 @@
// Backbone.Marionette v0.7.1
//
// Copyright (C)2011 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 = {};
Marionette.version = "0.7.1";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
return template;
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// 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() };
}
return data;
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var deferredData = this.serializeData();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("item:before:render", that);
$.when(deferredData).then(function(data) {
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(function(html){
that.$el.html(html);
if (that.onRender) { that.onRender(); }
that.trigger("item:rendered", that);
that.trigger("render", that);
deferredRender.resolve();
});
});
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// Render an individual model, if we have one, as
// part of a composite view (branch / leaf). For example:
// a treeview.
renderModel: function(){
var data = {};
if (this.model){
data = this.model.toJSON();
}
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
},
initializeRegions: function () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
closeRegions: function () {
var that = this;
_.each(this.regionManagers, function (manager, name) {
manager.close();
delete that[name];
});
this.regionManagers = {};
}
});
// 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 reocmmended 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);
}
},
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 = _.bind(controller[methodName], controller);
router.route(route, methodName, method);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
this.bindings.push({
obj: obj,
eventName: eventName,
callback: callback,
context: context
});
},
// Unbind all of the events that we have stored.
unbindAll: function () {
_.each(this.bindings, function (binding) {
binding.obj.off(binding.eventName, binding.callback);
});
this.bindings = [];
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = _.template(template);
callback.call(this, template);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,855 @@
// Backbone.Marionette v0.7.2
//
// Copyright (C)2011 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 = {};
Marionette.version = "0.7.2";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
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() };
}
return data;
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var deferredData = this.serializeData();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("item:before:render", that);
$.when(deferredData).then(function(data) {
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(function(html){
that.$el.html(html);
var onRenderPromise = {};
if (that.onRender) { onRenderPromise = that.onRender(); }
$.when(onRenderPromise).then(function() {
that.trigger("item:rendered", that);
that.trigger("render", that);
deferredRender.resolve();
});
});
});
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// 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.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
if (that.onShow) { that.onShow(view); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
},
initializeRegions: function () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
closeRegions: function () {
var that = this;
_.each(this.regionManagers, function (manager, name) {
manager.close();
delete that[name];
});
this.regionManagers = {};
}
});
// 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 reocmmended 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);
}
},
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 = _.bind(controller[methodName], controller);
router.route(route, methodName, method);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
var binding = {
obj: obj,
eventName: eventName,
callback: callback,
context: context
}
this.bindings.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);
var index = _.indexOf(this.bindings, binding);
Array.remove(this.bindings, index);
},
// Unbind all of the events that we have stored.
unbindAll: function () {
var that = this;
_.each(this.bindings, function (binding, index) {
that.unbindFrom(binding);
});
this.bindings = [];
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = this.compileTemplate(template);
callback.call(this, 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);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
// Array Remove - By John Resig (MIT Licensed)
if (!Array.remove){
Array.remove = function(array, from, to) {
var rest = array.slice((to || from) + 1 || array.length);
array.length = from < 0 ? array.length + from : from;
return array.push.apply(array, rest);
};
}
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,855 @@
// Backbone.Marionette v0.7.2
//
// Copyright (C)2011 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 = {};
Marionette.version = "0.7.2";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
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() };
}
return data;
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var deferredData = this.serializeData();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("item:before:render", that);
$.when(deferredData).then(function(data) {
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(function(html){
that.$el.html(html);
var onRenderPromise = {};
if (that.onRender) { onRenderPromise = that.onRender(); }
$.when(onRenderPromise).then(function() {
that.trigger("item:rendered", that);
that.trigger("render", that);
deferredRender.resolve();
});
});
});
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// 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.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
if (that.onShow) { that.onShow(view); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
},
initializeRegions: function () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
closeRegions: function () {
var that = this;
_.each(this.regionManagers, function (manager, name) {
manager.close();
delete that[name];
});
this.regionManagers = {};
}
});
// 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 reocmmended 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);
}
},
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 = _.bind(controller[methodName], controller);
router.route(route, methodName, method);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
var binding = {
obj: obj,
eventName: eventName,
callback: callback,
context: context
}
this.bindings.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);
var index = _.indexOf(this.bindings, binding);
Array.remove(this.bindings, index);
},
// Unbind all of the events that we have stored.
unbindAll: function () {
var that = this;
_.each(this.bindings, function (binding, index) {
that.unbindFrom(binding);
});
this.bindings = [];
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = this.compileTemplate(template);
callback.call(this, 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);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
// Array Remove - By John Resig (MIT Licensed)
if (!Array.remove){
Array.remove = function(array, from, to) {
var rest = array.slice((to || from) + 1 || array.length);
array.length = from < 0 ? array.length + from : from;
return array.push.apply(array, rest);
};
}
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,873 @@
// Backbone.Marionette v0.7.4
//
// Copyright (C)2011 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 = {};
Marionette.version = "0.7.4";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
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);
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var deferredData = this.serializeData();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("item:before:render", that);
$.when(deferredData).then(function(data) {
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(function(html){
that.$el.html(html);
var onRenderPromise = {};
if (that.onRender) { onRenderPromise = that.onRender(); }
$.when(onRenderPromise).then(function() {
that.trigger("item:rendered", that);
that.trigger("render", that);
deferredRender.resolve();
});
});
});
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// 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.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
if (that.onShow) { that.onShow(view); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
},
initializeRegions: function () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
closeRegions: function () {
var that = this;
_.each(this.regionManagers, function (manager, name) {
manager.close();
delete that[name];
});
this.regionManagers = {};
}
});
// 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 reocmmended 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);
}
},
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);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
var binding = {
obj: obj,
eventName: eventName,
callback: callback,
context: context
}
this.bindings.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);
this.bindings = _.reject(this.bindings, 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.bindings, _.identity);
_.each(bindings, function (binding, index) {
that.unbindFrom(binding);
});
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = this.compileTemplate(template);
callback.call(this, 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);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,900 @@
// Backbone.Marionette v0.7.5
//
// Copyright (C)2011 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 = {};
Marionette.version = "0.7.5";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
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);
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// 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.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
if (that.onShow) { that.onShow(view); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
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 () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
// 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();
delete that[name];
});
this.regionManagers = {};
}
});
// 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);
}
},
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);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
var binding = {
obj: obj,
eventName: eventName,
callback: callback,
context: context
}
this.bindings.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);
this.bindings = _.reject(this.bindings, 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.bindings, _.identity);
_.each(bindings, function (binding, index) {
that.unbindFrom(binding);
});
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = this.compileTemplate(template);
callback.call(this, 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);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(callback);
}
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,912 @@
// Backbone.Marionette v0.7.6
//
// Copyright (C)2011 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 = {};
Marionette.version = "0.7.6";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
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);
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.bindTo(view, "all", function(){
// get the args, prepend the event name
// with "itemview:" and insert the child view
// as the first event arg (after the event name)
var args = slice.call(arguments);
args[0] = "itemview:" + args[0];
args.splice(1, 0, view);
that.trigger.apply(that, args);
});
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// 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.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
if (that.onShow) { that.onShow(view); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
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 () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
// 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();
delete that[name];
});
this.regionManagers = {};
}
});
// 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);
}
},
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);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
var binding = {
obj: obj,
eventName: eventName,
callback: callback,
context: context
}
this.bindings.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);
this.bindings = _.reject(this.bindings, 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.bindings, _.identity);
_.each(bindings, function (binding, index) {
that.unbindFrom(binding);
});
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = this.compileTemplate(template);
callback.call(this, 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);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(callback);
}
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,955 @@
// Backbone.Marionette v0.8.0
//
// 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 = {};
Marionette.version = "0.8.0";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
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);
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.bindTo(view, "all", function(){
// get the args, prepend the event name
// with "itemview:" and insert the child view
// as the first event arg (after the event name)
var args = slice.call(arguments);
args[0] = "itemview:" + args[0];
args.splice(1, 0, view);
that.trigger.apply(that, args);
});
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// 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.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
if (that.onShow) { that.onShow(view); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
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 () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
// 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();
delete that[name];
});
this.regionManagers = {};
}
});
// 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);
}
},
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);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
},
// Add modules to the application, providing direct
// access to your applicaiton object, Backbone,
// Marionette, jQuery and Underscore as parameters
// to a callback function.
module: function(moduleNames, moduleDefinition){
var moduleName, module, moduleOverride;
var parentModule = this;
var moduleNames = moduleNames.split(".");
// Loop through all the parts of the module definition
var length = moduleNames.length;
for(var i = 0; i < length; i++){
// Get the module name, and check if it exists on
// the current parent already
moduleName = moduleNames[i];
module = parentModule[moduleName];
if (!module){
// Create the module
module = new Marionette.Application();
// And build the module definition
if (moduleDefinition){
moduleOverride = moduleDefinition(module, this, Backbone, Marionette, jQuery, _);
// Override it if necessary
if (moduleOverride){
module = moduleOverride;
}
}
// And attach it to the parent module
parentModule[moduleName] = module;
}
// Reset the parent module so that the next child
// in the list will be added to the correct parent
parentModule = module;
}
return module;
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
var binding = {
obj: obj,
eventName: eventName,
callback: callback,
context: context
}
this.bindings.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);
this.bindings = _.reject(this.bindings, 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.bindings, _.identity);
_.each(bindings, function (binding, index) {
that.unbindFrom(binding);
});
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = this.compileTemplate(template);
callback.call(this, 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);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(callback);
}
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,961 @@
// Backbone.Marionette v0.8.1
//
// 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 = {};
Marionette.version = "0.8.1";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
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);
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.bindTo(view, "all", function(){
// get the args, prepend the event name
// with "itemview:" and insert the child view
// as the first event arg (after the event name)
var args = slice.call(arguments);
args[0] = "itemview:" + args[0];
args.splice(1, 0, view);
that.trigger.apply(that, args);
});
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// 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.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
if (that.onShow) { that.onShow(view); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
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 () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
// 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();
delete that[name];
});
this.regionManagers = {};
}
});
// 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);
}
},
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);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
},
// Add modules to the application, providing direct
// access to your applicaiton object, Backbone,
// Marionette, jQuery and Underscore as parameters
// to a callback function.
module: function(moduleNames, moduleDefinition){
var moduleName, module, moduleOverride;
var parentModule = this;
var moduleNames = moduleNames.split(".");
// Loop through all the parts of the module definition
var length = moduleNames.length;
for(var i = 0; i < length; i++){
// only run the module definition if this is the
// last module in the chaing: "Foo.Bar.Baz" only
// runs the module definition for the "Baz" module
var defineModule = (i === length-1);
// Get the module name, and check if it exists on
// the current parent already
moduleName = moduleNames[i];
module = parentModule[moduleName];
if (!module){
// Create the module
module = new Marionette.Application();
// Build the module definition if this is the last
// module specified in the . chain
if (defineModule && moduleDefinition){
moduleOverride = moduleDefinition(module, this, Backbone, Marionette, jQuery, _);
// Override it if necessary
if (moduleOverride){
module = moduleOverride;
}
}
// And attach it to the parent module
parentModule[moduleName] = module;
}
// Reset the parent module so that the next child
// in the list will be added to the correct parent
parentModule = module;
}
return module;
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
var binding = {
obj: obj,
eventName: eventName,
callback: callback,
context: context
}
this.bindings.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);
this.bindings = _.reject(this.bindings, 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.bindings, _.identity);
_.each(bindings, function (binding, index) {
that.unbindFrom(binding);
});
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = this.compileTemplate(template);
callback.call(this, 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);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(callback);
}
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,999 @@
// Backbone.Marionette v0.8.2
//
// 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 = {};
Marionette.version = "0.8.2";
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette.View = Backbone.View.extend({
// Get the template or template id/selector 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. The `template` can also be
// a function that returns a selector string.
getTemplateSelector: 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;
}
// check if it's a function and execute it, if it is
if (_.isFunction(template)){
template = template.call(this);
}
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;
},
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);
},
// 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.unbindAll();
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbind();
}
});
// 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(){
var args = slice.call(arguments);
Marionette.View.prototype.constructor.apply(this, args);
_.bindAll(this, "render");
this.initialEvents();
},
// Configured the initial events that the item 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, "reset", this.render, this);
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var asyncRender = that.renderHtml(data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
},
// Render the data for this item view in to some HTML.
// Override this method to replace the specific way in
// which an item view has it's data rendered in to html.
renderHtml: function(data) {
var template = this.getTemplateSelector();
return Marionette.Renderer.render(template, data);
},
// 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);
_.bindAll(this, "addItemView", "render");
this.initialEvents();
},
// 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){
var ItemView = this.getItemView();
return this.addItemView(item, ItemView);
},
// Loop through all of the items and render
// each of them with the specified `itemView`.
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
this.collection.each(function(item){
var promise = that.addItemView(item, ItemView);
promises.push(promise);
});
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// 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){
var that = this;
var view = this.buildItemView(item, ItemView);
this.bindTo(view, "all", function(){
// get the args, prepend the event name
// with "itemview:" and insert the child view
// as the first event arg (after the event name)
var args = slice.call(arguments);
args[0] = "itemview:" + args[0];
args.splice(1, 0, view);
that.trigger.apply(that, args);
});
this.storeChild(view);
this.trigger("item:added", view);
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view);
});
return viewRendered;
},
// Build an `itemView` for every model in the collection.
buildItemView: function(item, ItemView){
var view = new ItemView({
model: item
});
return view;
},
// Remove the child view and close it
removeItemView: function(item){
var view = this.children[item.cid];
if (view){
view.close();
delete this.children[item.cid];
}
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){
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){
if (!this.children){
this.children = {};
}
this.children[view.model.cid] = view;
},
// 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");
},
closeChildren: function(){
if (this.children){
_.each(this.children, function(childView){
childView.close();
});
}
}
});
// 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();
},
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
// 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.getTemplateSelector();
return Marionette.Renderer.render(template, data);
}
});
// 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 || {};
_.extend(this, 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, appendMethod){
this.ensureEl();
this.close();
this.open(view, appendMethod);
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);
},
// Internal method to render and display a view. Not meant
// to be called from any external code.
open: function(view, appendMethod){
var that = this;
appendMethod = appendMethod || "html";
$.when(view.render()).then(function () {
that.$el[appendMethod](view.el);
if (view.onShow) { view.onShow(); }
if (that.onShow) { that.onShow(view); }
view.trigger("show");
that.trigger("view:show", view);
});
},
// 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;
}
});
// Layout
// ------
// Formerly known as Composite Region.
//
// 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({
constructor: function () {
this.vent = new Backbone.Marionette.EventAggregator();
Backbone.Marionette.ItemView.apply(this, arguments);
this.regionManagers = {};
},
render: function () {
this.initializeRegions();
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
},
close: function () {
this.closeRegions();
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 () {
var that = this;
_.each(this.regions, function (selector, name) {
var regionManager = new Backbone.Marionette.Region({
el: selector,
getEl: function(selector){
return that.$(selector);
}
});
that.regionManagers[name] = regionManager;
that[name] = regionManager;
});
},
// 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();
delete that[name];
});
this.regionManagers = {};
}
});
// 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);
}
},
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);
}
}
});
// Composite 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();
_.extend(this, 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(this, options);
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;
}
}
},
// Add modules to the application, providing direct
// access to your applicaiton object, Backbone,
// Marionette, jQuery and Underscore as parameters
// to a callback function.
module: function(moduleNames, moduleDefinition){
var moduleName, module, moduleOverride;
var parentModule = this;
var moduleNames = moduleNames.split(".");
// Loop through all the parts of the module definition
var length = moduleNames.length;
for(var i = 0; i < length; i++){
// only run the module definition if this is the
// last module in the chaing: "Foo.Bar.Baz" only
// runs the module definition for the "Baz" module
var defineModule = (i === length-1);
// Get the module name, and check if it exists on
// the current parent already
moduleName = moduleNames[i];
module = parentModule[moduleName];
if (!module){
// Create the module
module = new Marionette.Application();
// Build the module definition if this is the last
// module specified in the . chain
if (defineModule && moduleDefinition){
moduleOverride = moduleDefinition(module, this, Backbone, Marionette, jQuery, _);
// Override it if necessary
if (moduleOverride){
module = moduleOverride;
}
}
// And attach it to the parent module
parentModule[moduleName] = module;
}
// Reset the parent module so that the next child
// in the list will be added to the correct parent
parentModule = module;
}
return module;
}
});
// BindTo: Event Binding
// ---------------------
// BindTo facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Thanks to Johnny Oshika for this code.
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
Marionette.BindTo = {
// 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);
if (!this.bindings) { this.bindings = []; }
var binding = {
obj: obj,
eventName: eventName,
callback: callback,
context: context
}
this.bindings.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);
this.bindings = _.reject(this.bindings, 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.bindings, _.identity);
_.each(bindings, function (binding, index) {
that.unbindFrom(binding);
});
}
};
// 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){
this.promise.done(function(context, options){
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(context, options){
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 = function(options){
_.extend(this, options);
};
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
// Assumes the event aggregator itself is the
// object being bound to.
bindTo: function(eventName, callback, context){
return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette.TemplateCache = {
templates: {},
loaders: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function(templateId){
var that = this;
var templateRetrieval = $.Deferred();
var cachedTemplate = this.templates[templateId];
if (cachedTemplate){
templateRetrieval.resolve(cachedTemplate);
} else {
var loader = this.loaders[templateId];
if(loader) {
templateRetrieval = loader;
} else {
this.loaders[templateId] = templateRetrieval;
this.loadTemplate(templateId, function(template){
delete that.loaders[templateId];
that.templates[templateId] = template;
templateRetrieval.resolve(template);
});
}
}
return templateRetrieval.promise();
},
// 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, callback){
var template = $(templateId).html();
// Make sure we have a template before trying to compile it
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
template = this.compileTemplate(template);
callback.call(this, 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);
},
// 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.templates[arguments[i]];
}
} else {
this.templates = {};
}
}
};
// 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
// actual template. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function(template, data){
var that = this;
var asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(template){
var html = that.renderTemplate(template, data);
asyncRender.resolve(html);
});
return asyncRender.promise();
},
// Default implementation uses underscore.js templates. Override
// this method to use your own templating engine.
renderTemplate: function(template, data){
var html = template(data);
return html;
}
};
// Helpers
// -------
// For slicing `arguments` in functions
var slice = Array.prototype.slice;
// Copy the `extend` function used by Backbone's classes
var extend = Marionette.View.extend;
Marionette.Region.extend = extend;
Marionette.Application.extend = extend;
// Copy the features of `BindTo` on to these objects
_.extend(Marionette.View.prototype, Marionette.BindTo);
_.extend(Marionette.Application.prototype, Marionette.BindTo);
_.extend(Marionette.Region.prototype, Marionette.BindTo);
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(callback);
}
return Marionette;
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,339 @@
// Backbone.Marionette v0.9.0
//
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT License
//
// Documentation and Full License Available at:
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises = [];
var ItemView = this.getItemView();
var EmptyView = this.options.emptyView || this.emptyView;
if (this.beforeRender) { this.beforeRender(); }
this.trigger("collection:before:render", this);
this.closeChildren();
if (this.collection) {
if (this.collection.length === 0 && EmptyView) {
var promise = this.addItemView(new Backbone.Model(), EmptyView, 0);
promises.push(promise);
} else {
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
}
}
deferredRender.done(function(){
if (this.onRender) { this.onRender(); }
this.trigger("collection:rendered", this);
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
});
this.currentView = view;
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,9 @@
// Backbone.Marionette v0.9.0
//
// 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.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var b=this,c=d.Deferred(),e=[],f=this.getItemView(),g=this.options.emptyView||this.emptyView;this.beforeRender&&this.beforeRender(),this.trigger("collection:before:render",this),this.closeChildren();if(this.collection)if(this.collection.length===0&&g){var h=this.addItemView(new a.Model,g,0);e.push(h)}else this.collection.each(function(a,c){var d=b.addItemView(a,f,c);e.push(d)});return c.done(function(){this.onRender&&this.onRender(),this.trigger("collection:rendered",this)}),d.when.apply(this,e).then(function(){c.resolveWith(b)}),c.promise()},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred(),c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this;this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a)}),this.currentView=a}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,359 @@
// Backbone.Marionette v0.9.1
//
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT License
//
// Documentation and Full License Available at:
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView){
this.showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
},
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
});
this.currentView = view;
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,9 @@
// Backbone.Marionette v0.9.1
//
// 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.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d){this.showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred(),c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this;this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a)}),this.currentView=a}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,360 @@
// Backbone.Marionette, v0.9.10
// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeEmptyView();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView && !this._showingEmptyView){
this._showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
this.resetItemViewContainer();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
}
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
var asyncShow = $.Deferred();
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
asyncShow.resolve();
});
this.currentView = view;
return asyncShow.promise();
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,5 @@
// Backbone.Marionette, v0.9.10
// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
// http://github.com/derickbailey/backbone.marionette
Backbone.Marionette.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeEmptyView(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d&&!this._showingEmptyView){this._showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred();this.resetItemViewContainer();var c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this,c=d.Deferred();return this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a),c.resolve()}),this.currentView=a,c.promise()}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender);
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,360 @@
// Backbone.Marionette, v0.9.11
// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeEmptyView();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView && !this._showingEmptyView){
this._showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
this.resetItemViewContainer();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
}
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
var asyncShow = $.Deferred();
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
asyncShow.resolve();
});
this.currentView = view;
return asyncShow.promise();
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,5 @@
// Backbone.Marionette, v0.9.11
// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
// http://github.com/derickbailey/backbone.marionette
Backbone.Marionette.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeEmptyView(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d&&!this._showingEmptyView){this._showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred();this.resetItemViewContainer();var c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this,c=d.Deferred();return this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a),c.resolve()}),this.currentView=a,c.promise()}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender);
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,363 @@
// Backbone.Marionette v0.9.2
//
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT License
//
// Documentation and Full License Available at:
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView){
this.showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
this.resetItemViewContainer();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
}
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
var asyncShow = $.Deferred();
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
asyncShow.resolve();
});
this.currentView = view;
return asyncShow.promise();
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,9 @@
// Backbone.Marionette v0.9.2
//
// 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.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d){this.showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred();this.resetItemViewContainer();var c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this,c=d.Deferred();return this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a),c.resolve()}),this.currentView=a,c.promise()}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,363 @@
// Backbone.Marionette v0.9.3
//
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT License
//
// Documentation and Full License Available at:
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView){
this.showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
this.resetItemViewContainer();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
}
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
var asyncShow = $.Deferred();
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
asyncShow.resolve();
});
this.currentView = view;
return asyncShow.promise();
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,9 @@
// Backbone.Marionette v0.9.3
//
// 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.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d){this.showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred();this.resetItemViewContainer();var c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this,c=d.Deferred();return this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a),c.resolve()}),this.currentView=a,c.promise()}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,364 @@
// Backbone.Marionette v0.9.4
//
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT License
//
// Documentation and Full License Available at:
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeEmptyView();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView && !this._showingEmptyView){
this._showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
this.resetItemViewContainer();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
}
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
var asyncShow = $.Deferred();
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
asyncShow.resolve();
});
this.currentView = view;
return asyncShow.promise();
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,9 @@
// Backbone.Marionette v0.9.4
//
// 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.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeEmptyView(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d&&!this._showingEmptyView){this._showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred();this.resetItemViewContainer();var c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this,c=d.Deferred();return this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a),c.resolve()}),this.currentView=a,c.promise()}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,364 @@
// Backbone.Marionette v0.9.5
//
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT License
//
// Documentation and Full License Available at:
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeEmptyView();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView && !this._showingEmptyView){
this._showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
this.resetItemViewContainer();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
}
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
var asyncShow = $.Deferred();
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
asyncShow.resolve();
});
this.currentView = view;
return asyncShow.promise();
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,9 @@
// Backbone.Marionette v0.9.5
//
// 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.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeEmptyView(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d&&!this._showingEmptyView){this._showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred();this.resetItemViewContainer();var c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this,c=d.Deferred();return this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a),c.resolve()}),this.currentView=a,c.promise()}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,364 @@
// Backbone.Marionette v0.9.6
//
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT License
//
// Documentation and Full License Available at:
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeEmptyView();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView && !this._showingEmptyView){
this._showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
this.resetItemViewContainer();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
}
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
var asyncShow = $.Deferred();
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
asyncShow.resolve();
});
this.currentView = view;
return asyncShow.promise();
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,9 @@
// Backbone.Marionette v0.9.6
//
// 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.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeEmptyView(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d&&!this._showingEmptyView){this._showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred();this.resetItemViewContainer();var c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this,c=d.Deferred();return this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a),c.resolve()}),this.currentView=a,c.promise()}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,364 @@
// Backbone.Marionette v0.9.7
//
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT License
//
// Documentation and Full License Available at:
// http://github.com/derickbailey/backbone.marionette
// Marionette.Async
// ----------------
// Provides asynchronous rendering implementations
// for the various view types in Marionette
Backbone.Marionette.Async = (function(Backbone, Marionette, _, $){
// Configure Marionette to use the async rendering for all view types
var Async = {
init: function(){
Marionette.TemplateCache = Async.TemplateCache;
Marionette.Renderer = Async.Renderer;
_.extend(Marionette.ItemView.prototype, Async.ItemView);
_.extend(Marionette.CollectionView.prototype, Async.CollectionView);
_.extend(Marionette.CompositeView.prototype, Async.CompositeView);
_.extend(Marionette.Region.prototype, Async.Region);
}
};
// Async ItemView
// --------------
// An asynchronous rendering method for the `ItemView`. This method
// assumes template loading, data serialization, `beforRender`, and
// `onRender` functions are all asynchronous, using `jQuery.Deferred()`
// and `jQuery.when(...).then(...)` to manage async calls.
Async.ItemView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var beforeRenderDone = function() {
that.trigger("before:render", that);
that.trigger("item:before:render", that);
var deferredData = that.serializeData();
$.when(deferredData).then(dataSerialized);
}
var dataSerialized = function(data){
var template = that.getTemplate();
var asyncRender = Marionette.Renderer.render(template, data);
$.when(asyncRender).then(templateRendered);
}
var templateRendered = function(html){
that.$el.html(html);
callDeferredMethod(that.onRender, onRenderDone, that);
}
var onRenderDone = function(){
that.trigger("render", that);
that.trigger("item:rendered", that);
deferredRender.resolve();
}
callDeferredMethod(this.beforeRender, beforeRenderDone, this);
return deferredRender.promise();
}
};
// Async CollectionView
// --------------------
// provides async rendering for collection views
Async.CollectionView = {
render: function(){
var that = this;
var deferredRender = $.Deferred();
var promises;
this.triggerBeforeRender();
this.closeEmptyView();
this.closeChildren();
if (this.collection && this.collection.length > 0) {
promises = this.showCollection();
} else {
var promise = this.showEmptyView();
promises = [promise];
}
deferredRender.done(function(){
that.triggerRendered();
});
$.when.apply(this, promises).then(function(){
deferredRender.resolveWith(that);
});
return deferredRender.promise();
},
// Internal method to loop through each item in the
// collection view and show it
showCollection: function(){
var that = this;
var promises = [];
var ItemView = this.getItemView();
this.collection.each(function(item, index){
var promise = that.addItemView(item, ItemView, index);
promises.push(promise);
});
return promises;
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView: function(promises){
var promise;
var EmptyView = this.options.emptyView || this.emptyView;
if (EmptyView && !this._showingEmptyView){
this._showingEmptyView = true;
var model = new Backbone.Model();
promise = this.addItemView(model, EmptyView, 0);
}
return promise;
},
renderItemView: function(view, index) {
var that = this;
var viewRendered = view.render();
$.when(viewRendered).then(function(){
that.appendHtml(that, view, index);
});
return viewRendered;
}
}
// Async Composite View
// --------------------
Async.CompositeView = {
// 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;
var compositeRendered = $.Deferred();
this.resetItemViewContainer();
var modelIsRendered = this.renderModel();
$.when(modelIsRendered).then(function(html){
that.$el.html(html);
that.trigger("composite:model:rendered");
that.trigger("render");
var collectionIsRendered = that.renderCollection();
$.when(collectionIsRendered).then(function(){
compositeRendered.resolve();
});
});
compositeRendered.done(function(){
that.trigger("composite:rendered");
});
return compositeRendered.promise();
},
// Render the collection for the composite view
renderCollection: function(){
var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
collectionDeferred.done(function(){
this.trigger("composite:collection:rendered");
});
return collectionDeferred.promise();
}
}
// Async Region
// ------------
// Show a view that is rendered asynchronously, waiting for the view
// to be rendered before swaping it in.
Async.Region = {
show: function(view){
var that = this;
var asyncShow = $.Deferred();
this.ensureEl();
this.close();
// Wait for the view to finish rendering
$.when(view.render()).then(function () {
that.open(view);
if (view.onShow) { view.onShow(); }
view.trigger("show");
if (that.onShow) { that.onShow(view); }
that.trigger("view:show", view);
asyncShow.resolve();
});
this.currentView = view;
return asyncShow.promise();
}
};
// Async Renderer
// --------------
// Render a template with data by passing in the template
// selector and the data to render. Do it all asynchronously.
Async.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 asyncRender = $.Deferred();
var templateRetrieval = Marionette.TemplateCache.get(template);
$.when(templateRetrieval).then(function(templateFunc){
var html = templateFunc(data);
asyncRender.resolve(html);
});
return asyncRender.promise();
}
};
// Async Template Cache
// --------------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Async.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(Async.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(Async.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.deferred){
return this.deferred.promise();
}
// Load the template asynchronously
this.deferred = $.Deferred();
this.loadTemplate(this.templateId, function(template){
that.template = template;
that.deferred.resolve(template);
});
return this.deferred.promise();
},
// 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, callback){
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;
}
template = this.compileTemplate(template);
callback.call(this, 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);
}
});
// Async Helpers
// -------------
// A simple wrapper method for deferring a callback until
// after another method has been called, passing the
// results of the first method to the second. Uses jQuery's
// deferred / promise objects, and $.when/then to make it
// work.
var callDeferredMethod = function(fn, callback, context){
var promise;
if (fn) { promise = fn.call(context); }
$.when(promise).then(_.bind(callback, context));
}
// Initialize the async-modules
Async.init();
return Async;
})(Backbone, Backbone.Marionette, _, window.jQuery || window.Zepto || window.ender);
@@ -0,0 +1,9 @@
// Backbone.Marionette v0.9.7
//
// 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.Async=function(a,b,c,d){var e={init:function(){b.TemplateCache=e.TemplateCache,b.Renderer=e.Renderer,c.extend(b.ItemView.prototype,e.ItemView),c.extend(b.CollectionView.prototype,e.CollectionView),c.extend(b.CompositeView.prototype,e.CompositeView),c.extend(b.Region.prototype,e.Region)}};e.ItemView={render:function(){var a=this,c=d.Deferred(),e=function(){a.trigger("before:render",a),a.trigger("item:before:render",a);var b=a.serializeData();d.when(b).then(g)},g=function(c){var e=a.getTemplate(),f=b.Renderer.render(e,c);d.when(f).then(h)},h=function(b){a.$el.html(b),f(a.onRender,i,a)},i=function(){a.trigger("render",a),a.trigger("item:rendered",a),c.resolve()};return f(this.beforeRender,e,this),c.promise()}},e.CollectionView={render:function(){var a=this,b=d.Deferred(),c;this.triggerBeforeRender(),this.closeEmptyView(),this.closeChildren();if(this.collection&&this.collection.length>0)c=this.showCollection();else{var e=this.showEmptyView();c=[e]}return b.done(function(){a.triggerRendered()}),d.when.apply(this,c).then(function(){b.resolveWith(a)}),b.promise()},showCollection:function(){var a=this,b=[],c=this.getItemView();return this.collection.each(function(d,e){var f=a.addItemView(d,c,e);b.push(f)}),b},showEmptyView:function(b){var c,d=this.options.emptyView||this.emptyView;if(d&&!this._showingEmptyView){this._showingEmptyView=!0;var e=new a.Model;c=this.addItemView(e,d,0)}return c},renderItemView:function(a,b){var c=this,e=a.render();return d.when(e).then(function(){c.appendHtml(c,a,b)}),e}},e.CompositeView={render:function(){var a=this,b=d.Deferred();this.resetItemViewContainer();var c=this.renderModel();return d.when(c).then(function(c){a.$el.html(c),a.trigger("composite:model:rendered"),a.trigger("render");var e=a.renderCollection();d.when(e).then(function(){b.resolve()})}),b.done(function(){a.trigger("composite:rendered")}),b.promise()},renderCollection:function(){var a=b.CollectionView.prototype.render.apply(this,arguments);return a.done(function(){this.trigger("composite:collection:rendered")}),a.promise()}},e.Region={show:function(a){var b=this,c=d.Deferred();return this.ensureEl(),this.close(),d.when(a.render()).then(function(){b.open(a),a.onShow&&a.onShow(),a.trigger("show"),b.onShow&&b.onShow(a),b.trigger("view:show",a),c.resolve()}),this.currentView=a,c.promise()}},e.Renderer={render:function(a,c){var e=d.Deferred(),f=b.TemplateCache.get(a);return d.when(f).then(function(a){var b=a(c);e.resolve(b)}),e.promise()}},e.TemplateCache=function(a){this.templateId=a},c.extend(e.TemplateCache,{templateCaches:{},get:function(a){var c=this,d=this.templateCaches[a];return d||(d=new b.TemplateCache(a),this.templateCaches[a]=d),d.load()},clear:function(){var a,b=arguments.length;if(b>0)for(a=0;a<b;a++)delete this.templateCaches[arguments[a]];else this.templateCaches={}}}),c.extend(e.TemplateCache.prototype,{load:function(){var a=this;return this.deferred?this.deferred.promise():(this.deferred=d.Deferred(),this.loadTemplate(this.templateId,function(b){a.template=b,a.deferred.resolve(b)}),this.deferred.promise())},loadTemplate:function(a,b){var c=d(a).html();if(!c||c.length===0){var e="Could not find template: '"+a+"'",f=new Error(e);throw f.name="NoTemplateError",f}c=this.compileTemplate(c),b.call(this,c)},compileTemplate:function(a){return c.template(a)}});var f=function(a,b,e){var f;a&&(f=a.call(e)),d.when(f).then(c.bind(b,e))};return e.init(),e}(Backbone,Backbone.Marionette,_,window.jQuery||window.Zepto||window.ender)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More