// MarionetteJS (Backbone.Marionette)
// ----------------------------------
// v1.0.4
//
// Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
//
// http://marionettejs.com
/ * !
* Includes BabySitter
* https : //github.com/marionettejs/backbone.babysitter/
*
* Includes Wreqr
* https : //github.com/marionettejs/backbone.wreqr/
* /
// Backbone.BabySitter
// -------------------
// v0.0.6
//
// Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
//
// http://github.com/babysitterjs/backbone.babysitter
// Backbone.ChildViewContainer
// ---------------------------
//
// Provide a container to store, retrieve and
// shut down child views.
Backbone . ChildViewContainer = ( function ( Backbone , _ ) {
// Container Constructor
// ---------------------
var Container = function ( views ) {
this . _views = { } ;
this . _indexByModel = { } ;
this . _indexByCustom = { } ;
this . _updateLength ( ) ;
_ . each ( views , this . add , this ) ;
} ;
// Container Methods
// -----------------
_ . extend ( Container . prototype , {
// Add a view to this container. Stores the view
// by `cid` and makes it searchable by the model
// cid (and model itself). Optionally specify
// a custom key to store an retrieve the view.
add : function ( view , customIndex ) {
var viewCid = view . cid ;
// store the view
this . _views [ viewCid ] = view ;
// index it by model
if ( view . model ) {
this . _indexByModel [ view . model . cid ] = viewCid ;
}
// index by custom
if ( customIndex ) {
this . _indexByCustom [ customIndex ] = viewCid ;
}
this . _updateLength ( ) ;
} ,
// Find a view by the model that was attached to
// it. Uses the model's `cid` to find it.
findByModel : function ( model ) {
return this . findByModelCid ( model . cid ) ;
} ,
// Find a view by the `cid` of the model that was attached to
// it. Uses the model's `cid` to find the view `cid` and
// retrieve the view using it.
findByModelCid : function ( modelCid ) {
var viewCid = this . _indexByModel [ modelCid ] ;
return this . findByCid ( viewCid ) ;
} ,
// Find a view by a custom indexer.
findByCustom : function ( index ) {
var viewCid = this . _indexByCustom [ index ] ;
return this . findByCid ( viewCid ) ;
} ,
// Find by index. This is not guaranteed to be a
// stable index.
findByIndex : function ( index ) {
return _ . values ( this . _views ) [ index ] ;
} ,
// retrieve a view by it's `cid` directly
findByCid : function ( cid ) {
return this . _views [ cid ] ;
} ,
// Remove a view
remove : function ( view ) {
var viewCid = view . cid ;
// delete model index
if ( view . model ) {
delete this . _indexByModel [ view . model . cid ] ;
}
// delete custom index
_ . any ( this . _indexByCustom , function ( cid , key ) {
if ( cid === viewCid ) {
delete this . _indexByCustom [ key ] ;
return true ;
}
} , this ) ;
// remove the view from the container
delete this . _views [ viewCid ] ;
// update the length
this . _updateLength ( ) ;
} ,
// Call a method on every view in the container,
// passing parameters to the call method one at a
// time, like `function.call`.
call : function ( method ) {
this . apply ( method , _ . tail ( arguments ) ) ;
} ,
// Apply a method on every view in the container,
// passing parameters to the call method one at a
// time, like `function.apply`.
apply : function ( method , args ) {
_ . each ( this . _views , function ( view ) {
if ( _ . isFunction ( view [ method ] ) ) {
view [ method ] . apply ( view , args || [ ] ) ;
}
} ) ;
} ,
// Update the `.length` attribute on this container
_updateLength : function ( ) {
this . length = _ . size ( this . _views ) ;
}
} ) ;
// Borrowing this code from Backbone.Collection:
// http://backbonejs.org/docs/backbone.html#section-106
//
// Mix in methods from Underscore, for iteration, and other
// collection related features.
var methods = [ 'forEach' , 'each' , 'map' , 'find' , 'detect' , 'filter' ,
'select' , 'reject' , 'every' , 'all' , 'some' , 'any' , 'include' ,
'contains' , 'invoke' , 'toArray' , 'first' , 'initial' , 'rest' ,
'last' , 'without' , 'isEmpty' , 'pluck' ] ;
_ . each ( methods , function ( method ) {
Container . prototype [ method ] = function ( ) {
var views = _ . values ( this . _views ) ;
var args = [ views ] . concat ( _ . toArray ( arguments ) ) ;
return _ [ method ] . apply ( _ , args ) ;
} ;
} ) ;
// return the public API
return Container ;
} ) ( Backbone , _ ) ;
// Backbone.Wreqr (Backbone.Marionette)
// ----------------------------------
// v0.2.0
//
// Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
//
// http://github.com/marionettejs/backbone.wreqr
Backbone . Wreqr = ( function ( Backbone , Marionette , _ ) {
"use strict" ;
var Wreqr = { } ;
// Handlers
// --------
// A registry of functions to call, given a name
Wreqr . Handlers = ( function ( Backbone , _ ) {
"use strict" ;
// Constructor
// -----------
var Handlers = function ( options ) {
this . options = options ;
this . _wreqrHandlers = { } ;
if ( _ . isFunction ( this . initialize ) ) {
this . initialize ( options ) ;
}
} ;
Handlers . extend = Backbone . Model . extend ;
// Instance Members
// ----------------
_ . extend ( Handlers . prototype , Backbone . Events , {
// Add multiple handlers using an object literal configuration
setHandlers : function ( handlers ) {
_ . each ( handlers , function ( handler , name ) {
var context = null ;
if ( _ . isObject ( handler ) && ! _ . isFunction ( handler ) ) {
context = handler . context ;
handler = handler . callback ;
}
this . setHandler ( name , handler , context ) ;
} , this ) ;
} ,
// Add a handler for the given name, with an
// optional context to run the handler within
setHandler : function ( name , handler , context ) {
var config = {
callback : handler ,
context : context
} ;
this . _wreqrHandlers [ name ] = config ;
this . trigger ( "handler:add" , name , handler , context ) ;
} ,
// Determine whether or not a handler is registered
hasHandler : function ( name ) {
return ! ! this . _wreqrHandlers [ name ] ;
} ,
// Get the currently registered handler for
// the specified name. Throws an exception if
// no handler is found.
getHandler : function ( name ) {
var config = this . _wreqrHandlers [ name ] ;
if ( ! config ) {
throw new Error ( "Handler not found for '" + name + "'" ) ;
}
return function ( ) {
var args = Array . prototype . slice . apply ( arguments ) ;
return config . callback . apply ( config . context , args ) ;
} ;
} ,
// Remove a handler for the specified name
removeHandler : function ( name ) {
delete this . _wreqrHandlers [ name ] ;
} ,
// Remove all handlers from this registry
removeAllHandlers : function ( ) {
this . _wreqrHandlers = { } ;
}
} ) ;
return Handlers ;
} ) ( Backbone , _ ) ;
// Wreqr.CommandStorage
// --------------------
//
// Store and retrieve commands for execution.
Wreqr . CommandStorage = ( function ( ) {
"use strict" ;
// Constructor function
var CommandStorage = function ( options ) {
this . options = options ;
this . _commands = { } ;
if ( _ . isFunction ( this . initialize ) ) {
this . initialize ( options ) ;
}
} ;
// Instance methods
_ . extend ( CommandStorage . prototype , Backbone . Events , {
// Get an object literal by command name, that contains
// the `commandName` and the `instances` of all commands
// represented as an array of arguments to process
getCommands : function ( commandName ) {
var commands = this . _commands [ commandName ] ;
// we don't have it, so add it
if ( ! commands ) {
// build the configuration
commands = {
command : commandName ,
instances : [ ]
} ;
// store it
this . _commands [ commandName ] = commands ;
}
return commands ;
} ,
// Add a command by name, to the storage and store the
// args for the command
addCommand : function ( commandName , args ) {
var command = this . getCommands ( commandName ) ;
command . instances . push ( args ) ;
} ,
// Clear all commands for the given `commandName`
clearCommands : function ( commandName ) {
var command = this . getCommands ( commandName ) ;
command . instances = [ ] ;
}
} ) ;
return CommandStorage ;
} ) ( ) ;
// Wreqr.Commands
// --------------
//
// A simple command pattern implementation. Register a command
// handler and execute it.
Wreqr . Commands = ( function ( Wreqr ) {
"use strict" ;
return Wreqr . Handlers . extend ( {
// default storage type
storageType : Wreqr . CommandStorage ,
constructor : function ( options ) {
this . options = options || { } ;
this . _initializeStorage ( this . options ) ;
this . on ( "handler:add" , this . _executeCommands , this ) ;
var args = Array . prototype . slice . call ( arguments ) ;
Wreqr . Handlers . prototype . constructor . apply ( this , args ) ;
} ,
// Execute a named command with the supplied args
execute : function ( name , args ) {
name = arguments [ 0 ] ;
args = Array . prototype . slice . call ( arguments , 1 ) ;
if ( this . hasHandler ( name ) ) {
this . getHandler ( name ) . apply ( this , args ) ;
} else {
this . storage . addCommand ( name , args ) ;
}
} ,
// Internal method to handle bulk execution of stored commands
_executeCommands : function ( name , handler , context ) {
var command = this . storage . getCommands ( name ) ;
// loop through and execute all the stored command instances
_ . each ( command . instances , function ( args ) {
handler . apply ( context , args ) ;
} ) ;
this . storage . clearCommands ( name ) ;
} ,
// Internal method to initialize storage either from the type's
// `storageType` or the instance `options.storageType`.
_initializeStorage : function ( options ) {
var storage ;
var StorageType = options . storageType || this . storageType ;
if ( _ . isFunction ( StorageType ) ) {
storage = new StorageType ( ) ;
} else {
storage = StorageType ;
}
this . storage = storage ;
}
} ) ;
} ) ( Wreqr ) ;
// Wreqr.RequestResponse
// ---------------------
//
// A simple request/response implementation. Register a
// request handler, and return a response from it
Wreqr . RequestResponse = ( function ( Wreqr ) {
"use strict" ;
return Wreqr . Handlers . extend ( {
request : function ( ) {
var name = arguments [ 0 ] ;
var args = Array . prototype . slice . call ( arguments , 1 ) ;
return this . getHandler ( name ) . apply ( this , args ) ;
}
} ) ;
} ) ( Wreqr ) ;
// Event Aggregator
// ----------------
// A pub-sub object that can be used to decouple various parts
// of an application through event-driven architecture.
Wreqr . EventAggregator = ( function ( Backbone , _ ) {
"use strict" ;
var EA = function ( ) { } ;
// Copy the `extend` function used by Backbone's classes
EA . extend = Backbone . Model . extend ;
// Copy the basic Backbone.Events on to the event aggregator
_ . extend ( EA . prototype , Backbone . Events ) ;
return EA ;
} ) ( Backbone , _ ) ;
return Wreqr ;
} ) ( Backbone , Backbone . Marionette , _ ) ;
var Marionette = ( function ( global , Backbone , _ ) {
"use strict" ;
// Define and export the Marionette namespace
var Marionette = { } ;
Backbone . Marionette = Marionette ;
// Get the DOM manipulator for later use
Marionette . $ = Backbone . $ ;
// Helpers
// -------
// For slicing `arguments` in functions
var protoSlice = Array . prototype . slice ;
function slice ( args ) {
return protoSlice . call ( args ) ;
}
function throwError ( message , name ) {
var error = new Error ( message ) ;
error . name = name || 'Error' ;
throw error ;
}
// Marionette.extend
// -----------------
// Borrow the Backbone `extend` method so we can use it as needed
Marionette . extend = Backbone . Model . extend ;
// Marionette.getOption
// --------------------
// Retrieve an object, function or other value from a target
// object or its `options`, with `options` taking precedence.
Marionette . getOption = function ( target , optionName ) {
if ( ! target || ! optionName ) { return ; }
var value ;
if ( target . options && ( optionName in target . options ) && ( target . options [ optionName ] !== undefined ) ) {
value = target . options [ optionName ] ;
} else {
value = target [ optionName ] ;
}
return value ;
} ;
// Trigger an event and a corresponding method name. Examples:
//
// `this.triggerMethod("foo")` will trigger the "foo" event and
// call the "onFoo" method.
//
// `this.triggerMethod("foo:bar") will trigger the "foo:bar" event and
// call the "onFooBar" method.
Marionette . triggerMethod = ( function ( ) {
// split the event name on the :
var splitter = /(^|:)(\w)/gi ;
// take the event section ("section1:section2:section3")
// and turn it in to uppercase name
function getEventName ( match , prefix , eventName ) {
return eventName . toUpperCase ( ) ;
}
// actual triggerMethod name
var triggerMethod = function ( event ) {
// get the method name from the event name
var methodName = 'on' + event . replace ( splitter , getEventName ) ;
var method = this [ methodName ] ;
// trigger the event
this . trigger . apply ( this , arguments ) ;
// call the onMethodName if it exists
if ( _ . isFunction ( method ) ) {
// pass all arguments, except the event name
return method . apply ( this , _ . tail ( arguments ) ) ;
}
} ;
return triggerMethod ;
} ) ( ) ;
// DOMRefresh
// ----------
//
// Monitor a view's state, and after it has been rendered and shown
// in the DOM, trigger a "dom:refresh" event every time it is
// re-rendered.
Marionette . MonitorDOMRefresh = ( function ( ) {
// track when the view has been rendered
function handleShow ( view ) {
view . _isShown = true ;
triggerDOMRefresh ( view ) ;
}
// track when the view has been shown in the DOM,
// using a Marionette.Region (or by other means of triggering "show")
function handleRender ( view ) {
view . _isRendered = true ;
triggerDOMRefresh ( view ) ;
}
// Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
function triggerDOMRefresh ( view ) {
if ( view . _isShown && view . _isRendered ) {
if ( _ . isFunction ( view . triggerMethod ) ) {
view . triggerMethod ( "dom:refresh" ) ;
}
}
}
// Export public API
return function ( view ) {
view . listenTo ( view , "show" , function ( ) {
handleShow ( view ) ;
} ) ;
view . listenTo ( view , "render" , function ( ) {
handleRender ( view ) ;
} ) ;
} ;
} ) ( ) ;
// Marionette.bindEntityEvents & unbindEntityEvents
// ---------------------------
//
// These methods are used to bind/unbind a backbone "entity" (collection/model)
// to methods on a target object.
//
// The first parameter, `target`, must have a `listenTo` method from the
// EventBinder object.
//
// The second parameter is the entity (Backbone.Model or Backbone.Collection)
// to bind the events from.
//
// The third parameter is a hash of { "event:name": "eventHandler" }
// configuration. Multiple handlers can be separated by a space. A
// function can be supplied instead of a string handler name.
( function ( Marionette ) {
"use strict" ;
// Bind the event to handlers specified as a string of
// handler names on the target object
function bindFromStrings ( target , entity , evt , methods ) {
var methodNames = methods . split ( /\s+/ ) ;
_ . each ( methodNames , function ( methodName ) {
var method = target [ methodName ] ;
if ( ! method ) {
throwError ( "Method '" + methodName + "' was configured as an event handler, but does not exist." ) ;
}
target . listenTo ( entity , evt , method , target ) ;
} ) ;
}
// Bind the event to a supplied callback function
function bindToFunction ( target , entity , evt , method ) {
target . listenTo ( entity , evt , method , target ) ;
}
// Bind the event to handlers specified as a string of
// handler names on the target object
function unbindFromStrings ( target , entity , evt , methods ) {
var methodNames = methods . split ( /\s+/ ) ;
_ . each ( methodNames , function ( methodName ) {
var method = target [ methodName ] ;
target . stopListening ( entity , evt , method , target ) ;
} ) ;
}
// Bind the event to a supplied callback function
function unbindToFunction ( target , entity , evt , method ) {
target . stopListening ( entity , evt , method , target ) ;
}
// generic looping function
function iterateEvents ( target , entity , bindings , functionCallback , stringCallback ) {
if ( ! entity || ! bindings ) { return ; }
// allow the bindings to be a function
if ( _ . isFunction ( bindings ) ) {
bindings = bindings . call ( target ) ;
}
// iterate the bindings and bind them
_ . each ( bindings , function ( methods , evt ) {
// allow for a function as the handler,
// or a list of event names as a string
if ( _ . isFunction ( methods ) ) {
functionCallback ( target , entity , evt , methods ) ;
} else {
stringCallback ( target , entity , evt , methods ) ;
}
} ) ;
}
// Export Public API
Marionette . bindEntityEvents = function ( target , entity , bindings ) {
iterateEvents ( target , entity , bindings , bindToFunction , bindFromStrings ) ;
} ;
Marionette . unbindEntityEvents = function ( target , entity , bindings ) {
iterateEvents ( target , entity , bindings , unbindToFunction , unbindFromStrings ) ;
} ;
} ) ( Marionette ) ;
// 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 = Marionette . $ . Deferred ( ) ;
this . _callbacks = [ ] ;
} ;
_ . extend ( Marionette . Callbacks . prototype , {
// Add a callback to be executed. Callbacks added here are
// guaranteed to execute, even if they are added after the
// `run` method is called.
add : function ( callback , contextOverride ) {
this . _callbacks . push ( { cb : callback , ctx : contextOverride } ) ;
this . _deferred . done ( function ( context , options ) {
if ( contextOverride ) { context = contextOverride ; }
callback . call ( context , options ) ;
} ) ;
} ,
// Run all registered callbacks with the context specified.
// Additional callbacks can be added after this has been run
// and they will still be executed.
run : function ( options , context ) {
this . _deferred . resolve ( context , options ) ;
} ,
// Resets the list of callbacks to be run, allowing the same list
// to be run multiple times - whenever the `run` method is called.
reset : function ( ) {
var callbacks = this . _callbacks ;
this . _deferred = Marionette . $ . Deferred ( ) ;
this . _callbacks = [ ] ;
_ . each ( callbacks , function ( cb ) {
this . add ( cb . cb , cb . ctx ) ;
} , this ) ;
}
} ) ;
// Marionette Controller
// ---------------------
//
// A multi-purpose object to use as a controller for
// modules and routers, and as a mediator for workflow
// and coordination of other objects, views, and more.
Marionette . Controller = function ( options ) {
this . triggerMethod = Marionette . triggerMethod ;
this . options = options || { } ;
if ( _ . isFunction ( this . initialize ) ) {
this . initialize ( this . options ) ;
}
} ;
Marionette . Controller . extend = Marionette . extend ;
// Controller Methods
// --------------
// Ensure it can trigger events with Backbone.Events
_ . extend ( Marionette . Controller . prototype , Backbone . Events , {
close : function ( ) {
this . stopListening ( ) ;
this . triggerMethod ( "close" ) ;
this . unbind ( ) ;
}
} ) ;
// 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 || { } ;
this . el = Marionette . getOption ( this , "el" ) ;
if ( ! this . el ) {
var err = new Error ( "An 'el' must be specified for a region." ) ;
err . name = "NoElError" ;
throw err ;
}
if ( this . initialize ) {
var args = Array . prototype . slice . apply ( arguments ) ;
this . initialize . apply ( this , args ) ;
}
} ;
// Region Type methods
// -------------------
_ . extend ( Marionette . Region , {
// Build an instance of a region by passing in a configuration object
// and a default region type to use if none is specified in the config.
//
// The config object should either be a string as a jQuery DOM selector,
// a Region type directly, or an object literal that specifies both
// a selector and regionType:
//
// ```js
// {
// selector: "#foo",
// regionType: MyCustomRegion
// }
// ```
//
buildRegion : function ( regionConfig , defaultRegionType ) {
var regionIsString = ( typeof regionConfig === "string" ) ;
var regionSelectorIsString = ( typeof regionConfig . selector === "string" ) ;
var regionTypeIsUndefined = ( typeof regionConfig . regionType === "undefined" ) ;
var regionIsType = ( typeof regionConfig === "function" ) ;
if ( ! regionIsType && ! regionIsString && ! regionSelectorIsString ) {
throw new Error ( "Region must be specified as a Region type, a selector string or an object with selector property" ) ;
}
var selector , RegionType ;
// get the selector for the region
if ( regionIsString ) {
selector = regionConfig ;
}
if ( regionConfig . selector ) {
selector = regionConfig . selector ;
}
// get the type for the region
if ( regionIsType ) {
RegionType = regionConfig ;
}
if ( ! regionIsType && regionTypeIsUndefined ) {
RegionType = defaultRegionType ;
}
if ( regionConfig . regionType ) {
RegionType = regionConfig . regionType ;
}
// build the region instance
var region = new RegionType ( {
el : selector
} ) ;
// override the `getEl` function if we have a parentEl
// this must be overridden to ensure the selector is found
// on the first use of the region. if we try to assign the
// region's `el` to `parentEl.find(selector)` in the object
// literal to build the region, the element will not be
// guaranteed to be in the DOM already, and will cause problems
if ( regionConfig . parentEl ) {
region . getEl = function ( selector ) {
var parentEl = regionConfig . parentEl ;
if ( _ . isFunction ( parentEl ) ) {
parentEl = parentEl ( ) ;
}
return parentEl . find ( selector ) ;
} ;
}
return region ;
}
} ) ;
// Region Instance Methods
// -----------------------
_ . extend ( Marionette . Region . prototype , Backbone . Events , {
// Displays a backbone view instance inside of the region.
// Handles calling the `render` method for you. Reads content
// directly from the `el` attribute. Also calls an optional
// `onShow` and `close` method on your view, just after showing
// or just before closing the view, respectively.
show : function ( view ) {
this . ensureEl ( ) ;
var isViewClosed = view . isClosed || _ . isUndefined ( view . $el ) ;
var isDifferentView = view !== this . currentView ;
if ( isDifferentView ) {
this . close ( ) ;
}
view . render ( ) ;
if ( isDifferentView || isViewClosed ) {
this . open ( view ) ;
}
this . currentView = view ;
Marionette . triggerMethod . call ( this , "show" , view ) ;
Marionette . triggerMethod . call ( view , "show" ) ;
} ,
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 Marionette . $ ( selector ) ;
} ,
// Override this method to change how the new view is
// appended to the `$el` that the region is managing
open : function ( view ) {
this . $el . empty ( ) . append ( view . el ) ;
} ,
// Close the current view, if there is one. If there is no
// current view, it does nothing and returns immediately.
close : function ( ) {
var view = this . currentView ;
if ( ! view || view . isClosed ) { return ; }
// call 'close' or 'remove', depending on which is found
if ( view . close ) { view . close ( ) ; }
else if ( view . remove ) { view . remove ( ) ; }
Marionette . triggerMethod . call ( this , "close" ) ;
delete this . currentView ;
} ,
// Attach an existing view to the region. This
// will not call `render` or `onShow` for the new view,
// and will not replace the current HTML for the `el`
// of the region.
attachView : function ( view ) {
this . currentView = view ;
} ,
// Reset the region by closing any existing view and
// clearing out the cached `$el`. The next time a view
// is shown via this region, the region will re-query the
// DOM for the region's `el`.
reset : function ( ) {
this . close ( ) ;
delete this . $el ;
}
} ) ;
// Copy the `extend` function used by Backbone's classes
Marionette . Region . extend = Marionette . extend ;
// Marionette.RegionManager
// ------------------------
//
// Manage one or more related `Marionette.Region` objects.
Marionette . RegionManager = ( function ( Marionette ) {
var RegionManager = Marionette . Controller . extend ( {
constructor : function ( options ) {
this . _regions = { } ;
Marionette . Controller . prototype . constructor . call ( this , options ) ;
} ,
// Add multiple regions using an object literal, where
// each key becomes the region name, and each value is
// the region definition.
addRegions : function ( regionDefinitions , defaults ) {
var regions = { } ;
_ . each ( regionDefinitions , function ( definition , name ) {
if ( typeof definition === "string" ) {
definition = { selector : definition } ;
}
if ( definition . selector ) {
definition = _ . defaults ( { } , definition , defaults ) ;
}
var region = this . addRegion ( name , definition ) ;
regions [ name ] = region ;
} , this ) ;
return regions ;
} ,
// Add an individual region to the region manager,
// and return the region instance
addRegion : function ( name , definition ) {
var region ;
var isObject = _ . isObject ( definition ) ;
var isString = _ . isString ( definition ) ;
var hasSelector = ! ! definition . selector ;
if ( isString || ( isObject && hasSelector ) ) {
region = Marionette . Region . buildRegion ( definition , Marionette . Region ) ;
} else if ( _ . isFunction ( definition ) ) {
region = Marionette . Region . buildRegion ( definition , Marionette . Region ) ;
} else {
region = definition ;
}
this . _store ( name , region ) ;
this . triggerMethod ( "region:add" , name , region ) ;
return region ;
} ,
// Get a region by name
get : function ( name ) {
return this . _regions [ name ] ;
} ,
// Remove a region by name
removeRegion : function ( name ) {
var region = this . _regions [ name ] ;
this . _remove ( name , region ) ;
} ,
// Close all regions in the region manager, and
// remove them
removeRegions : function ( ) {
_ . each ( this . _regions , function ( region , name ) {
this . _remove ( name , region ) ;
} , this ) ;
} ,
// Close all regions in the region manager, but
// leave them attached
closeRegions : function ( ) {
_ . each ( this . _regions , function ( region , name ) {
region . close ( ) ;
} , this ) ;
} ,
// Close all regions and shut down the region
// manager entirely
close : function ( ) {
this . removeRegions ( ) ;
var args = Array . prototype . slice . call ( arguments ) ;
Marionette . Controller . prototype . close . apply ( this , args ) ;
} ,
// internal method to store regions
_store : function ( name , region ) {
this . _regions [ name ] = region ;
this . _setLength ( ) ;
} ,
// internal method to remove a region
_remove : function ( name , region ) {
region . close ( ) ;
delete this . _regions [ name ] ;
this . _setLength ( ) ;
this . triggerMethod ( "region:remove" , name , region ) ;
} ,
// set the number of regions current held
_setLength : function ( ) {
this . length = _ . size ( this . _regions ) ;
}
} ) ;
// Borrowing this code from Backbone.Collection:
// http://backbonejs.org/docs/backbone.html#section-106
//
// Mix in methods from Underscore, for iteration, and other
// collection related features.
var methods = [ 'forEach' , 'each' , 'map' , 'find' , 'detect' , 'filter' ,
'select' , 'reject' , 'every' , 'all' , 'some' , 'any' , 'include' ,
'contains' , 'invoke' , 'toArray' , 'first' , 'initial' , 'rest' ,
'last' , 'without' , 'isEmpty' , 'pluck' ] ;
_ . each ( methods , function ( method ) {
RegionManager . prototype [ method ] = function ( ) {
var regions = _ . values ( this . _regions ) ;
var args = [ regions ] . concat ( _ . toArray ( arguments ) ) ;
return _ [ method ] . apply ( _ , args ) ;
} ;
} ) ;
return RegionManager ;
} ) ( Marionette ) ;
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette . TemplateCache = function ( templateId ) {
this . templateId = templateId ;
} ;
// TemplateCache object-level methods. Manage the template
// caches from these method calls instead of creating
// your own TemplateCache instances
_ . extend ( Marionette . TemplateCache , {
templateCaches : { } ,
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get : function ( templateId ) {
var 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 args = slice ( arguments ) ;
var length = args . length ;
if ( length > 0 ) {
for ( i = 0 ; i < length ; i ++ ) {
delete this . templateCaches [ args [ i ] ] ;
}
} else {
this . templateCaches = { } ;
}
}
} ) ;
// TemplateCache instance methods, allowing each
// template cache object to manage its own state
// and know whether or not it has been loaded
_ . extend ( Marionette . TemplateCache . prototype , {
// Internal method to load the template
load : function ( ) {
// Guard clause to prevent loading this template more than once
if ( this . compiledTemplate ) {
return this . compiledTemplate ;
}
// Load the template and compile it
var template = this . loadTemplate ( this . templateId ) ;
this . compiledTemplate = this . compileTemplate ( template ) ;
return this . compiledTemplate ;
} ,
// Load a template from the DOM, by default. Override
// this method to provide your own template retrieval
// For asynchronous loading with AMD/RequireJS, consider
// using a template-loader plugin as described here:
// https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
loadTemplate : function ( templateId ) {
var template = Marionette . $ ( templateId ) . html ( ) ;
if ( ! template || template . length === 0 ) {
throwError ( "Could not find template: '" + templateId + "'" , "NoTemplateError" ) ;
}
return template ;
} ,
// Pre-compile the template before caching it. Override
// this method if you do not need to pre-compile a template
// (JST / RequireJS for example) or if you want to change
// the template engine used (Handebars, etc).
compileTemplate : function ( rawTemplate ) {
return _ . template ( rawTemplate ) ;
}
} ) ;
// Renderer
// --------
// Render a template with data by passing in the template
// selector and the data to render.
Marionette . Renderer = {
// Render a template with data. The `template` parameter is
// passed to the `TemplateCache` object to retrieve the
// template function. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render : function ( template , data ) {
if ( ! template ) {
var error = new Error ( "Cannot render the template since it's false, null or undefined." ) ;
error . name = "TemplateNotFoundError" ;
throw error ;
}
var templateFunc ;
if ( typeof template === "function" ) {
templateFunc = template ;
} else {
templateFunc = Marionette . TemplateCache . get ( template ) ;
}
return templateFunc ( data ) ;
}
} ;
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette . View = Backbone . View . extend ( {
constructor : function ( ) {
_ . bindAll ( this , "render" ) ;
var args = Array . prototype . slice . apply ( arguments ) ;
Backbone . View . prototype . constructor . apply ( this , args ) ;
Marionette . MonitorDOMRefresh ( this ) ;
this . listenTo ( this , "show" , this . onShowCalled , this ) ;
} ,
// import the "triggerMethod" to trigger events with corresponding
// methods if the method exists
triggerMethod : Marionette . triggerMethod ,
// Get the template for this view
// instance. You can set a `template` attribute in the view
// definition or pass a `template: "whatever"` parameter in
// to the constructor options.
getTemplate : function ( ) {
return Marionette . getOption ( this , "template" ) ;
} ,
// 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 triggerEvents = { } ;
// Allow `triggers` to be configured as a function
var triggers = _ . result ( this , "triggers" ) ;
// Configure the triggers, prevent default
// action and stop propagation of DOM events
_ . each ( triggers , function ( value , key ) {
// build the event handler function for the DOM event
triggerEvents [ key ] = function ( e ) {
// stop the event in its tracks
if ( e && e . preventDefault ) { e . preventDefault ( ) ; }
if ( e && e . stopPropagation ) { e . stopPropagation ( ) ; }
// build the args for the event
var args = {
view : this ,
model : this . model ,
collection : this . collection
} ;
// trigger the event
this . triggerMethod ( value , args ) ;
} ;
} , this ) ;
return triggerEvents ;
} ,
// Overriding Backbone.View's delegateEvents to handle
// the `triggers`, `modelEvents`, and `collectionEvents` configuration
delegateEvents : function ( events ) {
this . _delegateDOMEvents ( events ) ;
Marionette . bindEntityEvents ( this , this . model , Marionette . getOption ( this , "modelEvents" ) ) ;
Marionette . bindEntityEvents ( this , this . collection , Marionette . getOption ( this , "collectionEvents" ) ) ;
} ,
// internal method to delegate DOM events and triggers
_delegateDOMEvents : 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 ) ;
} ,
// Overriding Backbone.View's undelegateEvents to handle unbinding
// the `triggers`, `modelEvents`, and `collectionEvents` config
undelegateEvents : function ( ) {
var args = Array . prototype . slice . call ( arguments ) ;
Backbone . View . prototype . undelegateEvents . apply ( this , args ) ;
Marionette . unbindEntityEvents ( this , this . model , Marionette . getOption ( this , "modelEvents" ) ) ;
Marionette . unbindEntityEvents ( this , this . collection , Marionette . getOption ( this , "collectionEvents" ) ) ;
} ,
// Internal method, handles the `show` event.
onShowCalled : function ( ) { } ,
// Default `close` implementation, for removing a view from the
// DOM and unbinding it. Regions will call this method
// for you. You can specify an `onClose` method in your view to
// add custom code that is called after the view is closed.
close : function ( ) {
if ( this . isClosed ) { return ; }
// allow the close to be stopped by returning `false`
// from the `onBeforeClose` method
var shouldClose = this . triggerMethod ( "before:close" ) ;
if ( shouldClose === false ) {
return ;
}
// mark as closed before doing the actual close, to
// prevent infinite loops within "close" event handlers
// that are trying to close other views
this . isClosed = true ;
this . triggerMethod ( "close" ) ;
// unbind UI elements
this . unbindUIElements ( ) ;
// remove the view from the DOM
this . remove ( ) ;
} ,
// This method binds the elements specified in the "ui" hash inside the view's code with
// the associated jQuery selectors.
bindUIElements : function ( ) {
if ( ! this . ui ) { return ; }
// store the ui hash in _uiBindings so they can be reset later
// and so re-rendering the view will be able to find the bindings
if ( ! this . _uiBindings ) {
this . _uiBindings = this . ui ;
}
// get the bindings result, as a function or otherwise
var bindings = _ . result ( this , "_uiBindings" ) ;
// empty the ui so we don't have anything to start with
this . ui = { } ;
// bind each of the selectors
_ . each ( _ . keys ( bindings ) , function ( key ) {
var selector = bindings [ key ] ;
this . ui [ key ] = this . $ ( selector ) ;
} , this ) ;
} ,
// This method unbinds the elements specified in the "ui" hash
unbindUIElements : function ( ) {
if ( ! this . ui ) { return ; }
// delete all of the existing ui bindings
_ . each ( this . ui , function ( $el , name ) {
delete this . ui [ name ] ;
} , this ) ;
// reset the ui element to the original bindings configuration
this . ui = this . _uiBindings ;
delete this . _uiBindings ;
}
} ) ;
// 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 ( {
// Setting up the inheritance chain which allows changes to
// Marionette.View.prototype.constructor which allows overriding
constructor : function ( ) {
Marionette . View . prototype . constructor . apply ( this , slice ( arguments ) ) ;
} ,
// 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 to provide
// a very specific rendering for your view. In general, though,
// you should override the `Marionette.Renderer` object to
// change how Marionette renders views.
render : function ( ) {
this . isClosed = false ;
this . triggerMethod ( "before:render" , this ) ;
this . triggerMethod ( "item:before:render" , this ) ;
var data = this . serializeData ( ) ;
data = this . mixinTemplateHelpers ( data ) ;
var template = this . getTemplate ( ) ;
var html = Marionette . Renderer . render ( template , data ) ;
this . $el . html ( html ) ;
this . bindUIElements ( ) ;
this . triggerMethod ( "render" , this ) ;
this . triggerMethod ( "item:rendered" , this ) ;
return this ;
} ,
// Override the default close event to add a few
// more events that are triggered.
close : function ( ) {
if ( this . isClosed ) { return ; }
this . triggerMethod ( 'item:before:close' ) ;
Marionette . View . prototype . close . apply ( this , slice ( arguments ) ) ;
this . triggerMethod ( '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 ( {
// used as the prefix for item view events
// that are forwarded through the collectionview
itemViewEventPrefix : "itemview" ,
// constructor
constructor : function ( options ) {
this . _initChildViewStorage ( ) ;
Marionette . View . prototype . constructor . apply ( this , slice ( arguments ) ) ;
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 . listenTo ( this . collection , "add" , this . addChildView , this ) ;
this . listenTo ( this . collection , "remove" , this . removeItemView , this ) ;
this . listenTo ( this . collection , "reset" , this . render , this ) ;
}
} ,
// Handle a child item added to the collection
addChildView : function ( item , collection , options ) {
this . closeEmptyView ( ) ;
var ItemView = this . getItemView ( item ) ;
var index = this . collection . indexOf ( item ) ;
this . addItemView ( item , ItemView , index ) ;
} ,
// Override from `Marionette.View` to guarantee the `onShow` method
// of child views is called.
onShowCalled : function ( ) {
this . children . each ( function ( child ) {
Marionette . triggerMethod . call ( child , "show" ) ;
} ) ;
} ,
// Internal method to trigger the before render callbacks
// and events
triggerBeforeRender : function ( ) {
this . triggerMethod ( "before:render" , this ) ;
this . triggerMethod ( "collection:before:render" , this ) ;
} ,
// Internal method to trigger the rendered callbacks and
// events
triggerRendered : function ( ) {
this . triggerMethod ( "render" , this ) ;
this . triggerMethod ( "collection:rendered" , this ) ;
} ,
// Render the collection of items. Override this method to
// provide your own implementation of a render function for
// the collection view.
render : function ( ) {
this . isClosed = false ;
this . triggerBeforeRender ( ) ;
this . _renderChildren ( ) ;
this . triggerRendered ( ) ;
return this ;
} ,
// Internal method. Separated so that CompositeView can have
// more control over events being triggered, around the rendering
// process
_renderChildren : function ( ) {
this . closeEmptyView ( ) ;
this . closeChildren ( ) ;
if ( this . collection && this . collection . length > 0 ) {
this . showCollection ( ) ;
} else {
this . showEmptyView ( ) ;
}
} ,
// Internal method to loop through each item in the
// collection view and show it
showCollection : function ( ) {
var ItemView ;
this . collection . each ( function ( item , index ) {
ItemView = this . getItemView ( item ) ;
this . addItemView ( item , ItemView , index ) ;
} , this ) ;
} ,
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView : function ( ) {
var EmptyView = Marionette . getOption ( this , "emptyView" ) ;
if ( EmptyView && ! this . _showingEmptyView ) {
this . _showingEmptyView = true ;
var model = new Backbone . Model ( ) ;
this . addItemView ( model , EmptyView , 0 ) ;
}
} ,
// Internal method to close an existing emptyView instance
// if one exists. Called when a collection view has been
// rendered empty, and then an item is added to the collection.
closeEmptyView : function ( ) {
if ( this . _showingEmptyView ) {
this . closeChildren ( ) ;
delete this . _showingEmptyView ;
}
} ,
// Retrieve the itemView type, either from `this.options.itemView`
// or from the `itemView` in the object definition. The "options"
// takes precedence.
getItemView : function ( item ) {
var itemView = Marionette . getOption ( this , "itemView" ) ;
if ( ! itemView ) {
throwError ( "An `itemView` must be specified" , "NoItemViewError" ) ;
}
return itemView ;
} ,
// Render the child item's view and add it to the
// HTML for the collection view.
addItemView : function ( item , ItemView , index ) {
// get the itemViewOptions if any were specified
var itemViewOptions = Marionette . getOption ( this , "itemViewOptions" ) ;
if ( _ . isFunction ( itemViewOptions ) ) {
itemViewOptions = itemViewOptions . call ( this , item , index ) ;
}
// build the view
var view = this . buildItemView ( item , ItemView , itemViewOptions ) ;
// set up the child view event forwarding
this . addChildViewEventForwarding ( view ) ;
// this view is about to be added
this . triggerMethod ( "before:item:added" , view ) ;
// Store the child view itself so we can properly
// remove and/or close it later
this . children . add ( view ) ;
// Render it and show it
this . renderItemView ( view , index ) ;
// call the "show" method if the collection view
// has already been shown
if ( this . _isShown ) {
Marionette . triggerMethod . call ( view , "show" ) ;
}
// this view was added
this . triggerMethod ( "after:item:added" , view ) ;
} ,
// Set up the child view event forwarding. Uses an "itemview:"
// prefix in front of all forwarded events.
addChildViewEventForwarding : function ( view ) {
var prefix = Marionette . getOption ( this , "itemViewEventPrefix" ) ;
// Forward all child item view events through the parent,
// prepending "itemview:" to the event name
this . listenTo ( view , "all" , function ( ) {
var args = slice ( arguments ) ;
args [ 0 ] = prefix + ":" + args [ 0 ] ;
args . splice ( 1 , 0 , view ) ;
Marionette . triggerMethod . apply ( this , args ) ;
} , this ) ;
} ,
// render the item view
renderItemView : function ( view , index ) {
view . render ( ) ;
this . appendHtml ( this , view , index ) ;
} ,
// Build an `itemView` for every model in the collection.
buildItemView : function ( item , ItemViewType , itemViewOptions ) {
var options = _ . extend ( { model : item } , itemViewOptions ) ;
return new ItemViewType ( options ) ;
} ,
// get the child view by item it holds, and remove it
removeItemView : function ( item ) {
var view = this . children . findByModel ( item ) ;
this . removeChildView ( view ) ;
this . checkEmpty ( ) ;
} ,
// Remove the child view and close it
removeChildView : function ( view ) {
// shut down the child view properly,
// including events that the collection has from it
if ( view ) {
this . stopListening ( view ) ;
// call 'close' or 'remove', depending on which is found
if ( view . close ) { view . close ( ) ; }
else if ( view . remove ) { view . remove ( ) ; }
this . children . remove ( view ) ;
}
this . triggerMethod ( "item:removed" , view ) ;
} ,
// helper to show the empty view if the collection is empty
checkEmpty : function ( ) {
// check if we're empty now, and if we are, show the
// empty view
if ( ! this . collection || this . collection . length === 0 ) {
this . showEmptyView ( ) ;
}
} ,
// Append the HTML to the collection's `el`.
// Override this method to do something other
// then `.append`.
appendHtml : function ( collectionView , itemView , index ) {
collectionView . $el . append ( itemView . el ) ;
} ,
// Internal method to set up the `children` object for
// storing all of the child views
_initChildViewStorage : function ( ) {
this . children = new Backbone . ChildViewContainer ( ) ;
} ,
// Handle cleanup and other closing needs for
// the collection of views.
close : function ( ) {
if ( this . isClosed ) { return ; }
this . triggerMethod ( "collection:before:close" ) ;
this . closeChildren ( ) ;
this . triggerMethod ( "collection:closed" ) ;
Marionette . View . prototype . close . apply ( this , slice ( arguments ) ) ;
} ,
// Close the child views that this collection view
// is holding on to, if any
closeChildren : function ( ) {
this . children . each ( function ( child ) {
this . removeChildView ( child ) ;
} , this ) ;
this . checkEmpty ( ) ;
}
} ) ;
// 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 ( {
// Setting up the inheritance chain which allows changes to
// Marionette.CollectionView.prototype.constructor which allows overriding
constructor : function ( ) {
Marionette . CollectionView . prototype . constructor . apply ( this , slice ( arguments ) ) ;
} ,
// Configured the initial events that the composite view
// binds to. Override this method to prevent the initial
// events, or to add your own initial events.
_initialEvents : function ( ) {
if ( this . collection ) {
this . listenTo ( this . collection , "add" , this . addChildView , this ) ;
this . listenTo ( this . collection , "remove" , this . removeItemView , this ) ;
this . listenTo ( this . collection , "reset" , this . _renderChildren , this ) ;
}
} ,
// Retrieve the `itemView` to be used when rendering each of
// the items in the collection. The default is to return
// `this.itemView` or Marionette.CompositeView if no `itemView`
// has been defined
getItemView : function ( item ) {
var itemView = Marionette . getOption ( this , "itemView" ) || this . constructor ;
if ( ! itemView ) {
throwError ( "An `itemView` must be specified" , "NoItemViewError" ) ;
}
return itemView ;
} ,
// Serialize the collection for the view.
// 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 ( ) ;
}
return data ;
} ,
// 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 ( ) {
this . isRendered = true ;
this . isClosed = false ;
this . resetItemViewContainer ( ) ;
this . triggerBeforeRender ( ) ;
var html = this . renderModel ( ) ;
this . $el . html ( html ) ;
// the ui bindings is done here and not at the end of render since they
// will not be available until after the model is rendered, but should be
// available before the collection is rendered.
this . bindUIElements ( ) ;
this . triggerMethod ( "composite:model:rendered" ) ;
this . _renderChildren ( ) ;
this . triggerMethod ( "composite:rendered" ) ;
this . triggerRendered ( ) ;
return this ;
} ,
_renderChildren : function ( ) {
if ( this . isRendered ) {
Marionette . CollectionView . prototype . _renderChildren . call ( this ) ;
this . triggerMethod ( "composite:collection:rendered" ) ;
}
} ,
// Render an individual model, if we have one, as
// part of a composite view (branch / leaf). For example:
// a treeview.
renderModel : function ( ) {
var data = { } ;
data = this . serializeData ( ) ;
data = this . mixinTemplateHelpers ( data ) ;
var template = this . getTemplate ( ) ;
return Marionette . Renderer . render ( template , data ) ;
} ,
// Appends the `el` of itemView instances to the specified
// `itemViewContainer` (a jQuery selector). Override this method to
// provide custom logic of how the child item view instances have their
// HTML appended to the composite view instance.
appendHtml : function ( cv , iv , index ) {
var $container = this . getItemViewContainer ( cv ) ;
$container . append ( iv . el ) ;
} ,
// Internal method to ensure an `$itemViewContainer` exists, for the
// `appendHtml` method to use.
getItemViewContainer : function ( containerView ) {
if ( "$itemViewContainer" in containerView ) {
return containerView . $itemViewContainer ;
}
var container ;
if ( containerView . itemViewContainer ) {
var selector = _ . result ( containerView , "itemViewContainer" ) ;
container = containerView . $ ( selector ) ;
if ( container . length <= 0 ) {
throwError ( "The specified `itemViewContainer` was not found: " + containerView . itemViewContainer , "ItemViewContainerMissingError" ) ;
}
} else {
container = containerView . $el ;
}
containerView . $itemViewContainer = container ;
return container ;
} ,
// Internal method to reset the `$itemViewContainer` on render
resetItemViewContainer : function ( ) {
if ( this . $itemViewContainer ) {
delete this . $itemViewContainer ;
}
}
} ) ;
// Layout
// ------
// Used for managing application layouts, nested layouts and
// multiple regions within an application or sub-application.
//
// A specialized view type that renders an area of HTML and then
// attaches `Region` instances to the specified `regions`.
// Used for composite view management and sub-application areas.
Marionette . Layout = Marionette . ItemView . extend ( {
regionType : Marionette . Region ,
// Ensure the regions are available when the `initialize` method
// is called.
constructor : function ( options ) {
options = options || { } ;
this . _firstRender = true ;
this . _initializeRegions ( options ) ;
Marionette . ItemView . prototype . constructor . call ( this , options ) ;
} ,
// Layout's render will use the existing region objects the
// first time it is called. Subsequent calls will close the
// views that the regions are showing and then reset the `el`
// for the regions to the newly rendered DOM elements.
render : function ( ) {
if ( this . _firstRender ) {
// if this is the first render, don't do anything to
// reset the regions
this . _firstRender = false ;
} else if ( this . isClosed ) {
// a previously closed layout means we need to
// completely re-initialize the regions
this . _initializeRegions ( ) ;
} else {
// If this is not the first render call, then we need to
// re-initializing the `el` for each region
this . _reInitializeRegions ( ) ;
}
var args = Array . prototype . slice . apply ( arguments ) ;
var result = Marionette . ItemView . prototype . render . apply ( this , args ) ;
return result ;
} ,
// Handle closing regions, and then close the view itself.
close : function ( ) {
if ( this . isClosed ) { return ; }
this . regionManager . close ( ) ;
var args = Array . prototype . slice . apply ( arguments ) ;
Marionette . ItemView . prototype . close . apply ( this , args ) ;
} ,
// Add a single region, by name, to the layout
addRegion : function ( name , definition ) {
var regions = { } ;
regions [ name ] = definition ;
return this . addRegions ( regions ) [ name ] ;
} ,
// Add multiple regions as a {name: definition, name2: def2} object literal
addRegions : function ( regions ) {
this . regions = _ . extend ( this . regions || { } , regions ) ;
return this . _buildRegions ( regions ) ;
} ,
// Remove a single region from the Layout, by name
removeRegion : function ( name ) {
return this . regionManager . removeRegion ( name ) ;
} ,
// internal method to build regions
_buildRegions : function ( regions ) {
var that = this ;
var defaults = {
parentEl : function ( ) { return that . $el ; }
} ;
return this . regionManager . addRegions ( regions , defaults ) ;
} ,
// Internal method to initialize the regions that have been defined in a
// `regions` attribute on this layout.
_initializeRegions : function ( options ) {
var regions ;
this . _initRegionManager ( ) ;
if ( _ . isFunction ( this . regions ) ) {
regions = this . regions ( options ) ;
} else {
regions = this . regions || { } ;
}
this . addRegions ( regions ) ;
} ,
// Internal method to re-initialize all of the regions by updating the `el` that
// they point to
_reInitializeRegions : function ( ) {
this . regionManager . closeRegions ( ) ;
this . regionManager . each ( function ( region ) {
region . reset ( ) ;
} ) ;
} ,
// Internal method to initialize the region manager
// and all regions in it
_initRegionManager : function ( ) {
this . regionManager = new Marionette . RegionManager ( ) ;
this . listenTo ( this . regionManager , "region:add" , function ( name , region ) {
this [ name ] = region ;
this . trigger ( "region:add" , name , region ) ;
} ) ;
this . listenTo ( this . regionManager , "region:remove" , function ( name , region ) {
delete this [ name ] ;
this . trigger ( "region:remove" , name , region ) ;
} ) ;
}
} ) ;
// 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 pieces 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 . apply ( this , slice ( arguments ) ) ;
this . options = options ;
if ( this . appRoutes ) {
var controller = Marionette . getOption ( this , "controller" ) ;
this . processAppRoutes ( controller , this . appRoutes ) ;
}
} ,
// Internal method to process the `appRoutes` for the
// router, and turn them in to routes that trigger the
// specified method on the specified `controller`.
processAppRoutes : function ( controller , appRoutes ) {
var routeNames = _ . keys ( appRoutes ) . reverse ( ) ; // Backbone requires reverted order of routes
_ . each ( routeNames , function ( route ) {
var methodName = appRoutes [ route ] ;
var method = controller [ methodName ] ;
if ( ! method ) {
throw new Error ( "Method '" + methodName + "' was not found on the controller" ) ;
}
this . route ( route , methodName , _ . bind ( method , controller ) ) ;
} , this ) ;
}
} ) ;
// 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 . _initRegionManager ( ) ;
this . _initCallbacks = new Marionette . Callbacks ( ) ;
this . vent = new Backbone . Wreqr . EventAggregator ( ) ;
this . commands = new Backbone . Wreqr . Commands ( ) ;
this . reqres = new Backbone . Wreqr . RequestResponse ( ) ;
this . submodules = { } ;
_ . extend ( this , options ) ;
this . triggerMethod = Marionette . triggerMethod ;
} ;
_ . extend ( Marionette . Application . prototype , Backbone . Events , {
// Command execution, facilitated by Backbone.Wreqr.Commands
execute : function ( ) {
var args = Array . prototype . slice . apply ( arguments ) ;
this . commands . execute . apply ( this . commands , args ) ;
} ,
// Request/response, facilitated by Backbone.Wreqr.RequestResponse
request : function ( ) {
var args = Array . prototype . slice . apply ( arguments ) ;
return this . reqres . request . apply ( this . reqres , args ) ;
} ,
// 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 . triggerMethod ( "initialize:before" , options ) ;
this . _initCallbacks . run ( options , this ) ;
this . triggerMethod ( "initialize:after" , options ) ;
this . triggerMethod ( "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 ) {
return this . _regionManager . addRegions ( regions ) ;
} ,
// Removes a region from your app.
// Accepts the regions name
// removeRegion('myRegion')
removeRegion : function ( region ) {
this . _regionManager . removeRegion ( region ) ;
} ,
// Create a module, attached to the application
module : function ( moduleNames , moduleDefinition ) {
// slice the args, and add this application object as the
// first argument of the array
var args = slice ( arguments ) ;
args . unshift ( this ) ;
// see the Marionette.Module object for more information
return Marionette . Module . create . apply ( Marionette . Module , args ) ;
} ,
// Internal method to set up the region manager
_initRegionManager : function ( ) {
this . _regionManager = new Marionette . RegionManager ( ) ;
this . listenTo ( this . _regionManager , "region:add" , function ( name , region ) {
this [ name ] = region ;
} ) ;
this . listenTo ( this . _regionManager , "region:remove" , function ( name , region ) {
delete this [ name ] ;
} ) ;
}
} ) ;
// Copy the `extend` function used by Backbone's classes
Marionette . Application . extend = Marionette . extend ;
// Module
// ------
// A simple module system, used to create privacy and encapsulation in
// Marionette applications
Marionette . Module = function ( moduleName , app ) {
this . moduleName = moduleName ;
// store sub-modules
this . submodules = { } ;
this . _setupInitializersAndFinalizers ( ) ;
// store the configuration for this module
this . app = app ;
this . startWithParent = true ;
this . triggerMethod = Marionette . triggerMethod ;
} ;
// Extend the Module prototype with events / listenTo, so that the module
// can be used as an event aggregator or pub/sub.
_ . extend ( Marionette . Module . prototype , Backbone . Events , {
// Initializer for a specific module. Initializers are run when the
// module's `start` method is called.
addInitializer : function ( callback ) {
this . _initializerCallbacks . add ( callback ) ;
} ,
// Finalizers are run when a module is stopped. They are used to teardown
// and finalize any variables, references, events and other code that the
// module had set up.
addFinalizer : function ( callback ) {
this . _finalizerCallbacks . add ( callback ) ;
} ,
// Start the module, and run all of its initializers
start : function ( options ) {
// Prevent re-starting a module that is already started
if ( this . _isInitialized ) { return ; }
// start the sub-modules (depth-first hierarchy)
_ . each ( this . submodules , function ( mod ) {
// check to see if we should start the sub-module with this parent
if ( mod . startWithParent ) {
mod . start ( options ) ;
}
} ) ;
// run the callbacks to "start" the current module
this . triggerMethod ( "before:start" , options ) ;
this . _initializerCallbacks . run ( options , this ) ;
this . _isInitialized = true ;
this . triggerMethod ( "start" , options ) ;
} ,
// Stop this module by running its finalizers and then stop all of
// the sub-modules for this module
stop : function ( ) {
// if we are not initialized, don't bother finalizing
if ( ! this . _isInitialized ) { return ; }
this . _isInitialized = false ;
Marionette . triggerMethod . call ( this , "before:stop" ) ;
// stop the sub-modules; depth-first, to make sure the
// sub-modules are stopped / finalized before parents
_ . each ( this . submodules , function ( mod ) { mod . stop ( ) ; } ) ;
// run the finalizers
this . _finalizerCallbacks . run ( undefined , this ) ;
// reset the initializers and finalizers
this . _initializerCallbacks . reset ( ) ;
this . _finalizerCallbacks . reset ( ) ;
Marionette . triggerMethod . call ( this , "stop" ) ;
} ,
// Configure the module with a definition function and any custom args
// that are to be passed in to the definition function
addDefinition : function ( moduleDefinition , customArgs ) {
this . _runModuleDefinition ( moduleDefinition , customArgs ) ;
} ,
// Internal method: run the module definition function with the correct
// arguments
_runModuleDefinition : function ( definition , customArgs ) {
if ( ! definition ) { return ; }
// build the correct list of arguments for the module definition
var args = _ . flatten ( [
this ,
this . app ,
Backbone ,
Marionette ,
Marionette . $ , _ ,
customArgs
] ) ;
definition . apply ( this , args ) ;
} ,
// Internal method: set up new copies of initializers and finalizers.
// Calling this method will wipe out all existing initializers and
// finalizers.
_setupInitializersAndFinalizers : function ( ) {
this . _initializerCallbacks = new Marionette . Callbacks ( ) ;
this . _finalizerCallbacks = new Marionette . Callbacks ( ) ;
}
} ) ;
// Type methods to create modules
_ . extend ( Marionette . Module , {
// Create a module, hanging off the app parameter as the parent object.
create : function ( app , moduleNames , moduleDefinition ) {
var module = app ;
// get the custom args passed in after the module definition and
// get rid of the module name and definition function
var customArgs = slice ( arguments ) ;
customArgs . splice ( 0 , 3 ) ;
// split the module names and get the length
moduleNames = moduleNames . split ( "." ) ;
var length = moduleNames . length ;
// store the module definition for the last module in the chain
var moduleDefinitions = [ ] ;
moduleDefinitions [ length - 1 ] = moduleDefinition ;
// Loop through all the parts of the module definition
_ . each ( moduleNames , function ( moduleName , i ) {
var parentModule = module ;
module = this . _getModule ( parentModule , moduleName , app ) ;
this . _addModuleDefinition ( parentModule , module , moduleDefinitions [ i ] , customArgs ) ;
} , this ) ;
// Return the last module in the definition chain
return module ;
} ,
_getModule : function ( parentModule , moduleName , app , def , args ) {
// Get an existing module of this name if we have one
var module = parentModule [ moduleName ] ;
if ( ! module ) {
// Create a new module if we don't have one
module = new Marionette . Module ( moduleName , app ) ;
parentModule [ moduleName ] = module ;
// store the module on the parent
parentModule . submodules [ moduleName ] = module ;
}
return module ;
} ,
_addModuleDefinition : function ( parentModule , module , def , args ) {
var fn ;
var startWithParent ;
if ( _ . isFunction ( def ) ) {
// if a function is supplied for the module definition
fn = def ;
startWithParent = true ;
} else if ( _ . isObject ( def ) ) {
// if an object is supplied
fn = def . define ;
startWithParent = def . startWithParent ;
} else {
// if nothing is supplied
startWithParent = true ;
}
// add module definition if needed
if ( fn ) {
module . addDefinition ( fn , args ) ;
}
// `and` the two together, ensuring a single `false` will prevent it
// from starting with the parent
module . startWithParent = module . startWithParent && startWithParent ;
// setup auto-start if needed
if ( module . startWithParent && ! module . startWithParentIsConfigured ) {
// only configure this once
module . startWithParentIsConfigured = true ;
// add the module initializer config
parentModule . addInitializer ( function ( options ) {
if ( module . startWithParent ) {
module . start ( options ) ;
}
} ) ;
}
}
} ) ;
return Marionette ;
} ) ( this , Backbone , _ ) ;