0% found this document useful (0 votes)
288 views51 pages

Marionette

This document describes the Backbone.Marionette.js library. It includes sections on Marionette itself, Backbone.BabySitter for managing child views, Backbone.Wreqr for handling commands and events, and how they work together.

Uploaded by

Sanko
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
288 views51 pages

Marionette

This document describes the Backbone.Marionette.js library. It includes sections on Marionette itself, Backbone.BabySitter for managing child views, Backbone.Wreqr for handling commands and events, and how they work together.

Uploaded by

Sanko
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 51

4/1/2015

backbone.marionette.js

BACKBONE.MARIONETTE.JS

MARIONETTEJS(BACKBONE.MARIONETTE)

v2.4.1
Copyright(c)2015DerickBailey,MutedSolutions,LLC.
DistributedunderMITlicense
http://marionettejs.com
/*!
* Includes BabySitter
* https://github.com/marionettejs/backbone.babysitter/
*
* Includes Wreqr
* https://github.com/marionettejs/backbone.wreqr/
*/

(function(root, factory) {
/* istanbul ignore next */
if (typeof define === 'function' && define.amd) {
define(['backbone', 'underscore'], function(Backbone, _) {
return (root.Marionette = root.Mn = factory(root, Backbone, _));
});
} else if (typeof exports !== 'undefined') {
var Backbone = require('backbone');
var _ = require('underscore');
module.exports = factory(root, Backbone, _);
} else {
root.Marionette = root.Mn = factory(root, root.Backbone, root._);
}
}(this, function(root, Backbone, _) {
'use strict';
/* istanbul ignore next */

BACKBONE.BABYSITTER

v0.1.6
Copyright(c)2015DerickBailey,MutedSolutions,LLC.
DistributedunderMITlicense
http://github.com/marionettejs/backbone.babysitter
(function(Backbone, _) {
"use strict";
var previousChildViewContainer = Backbone.ChildViewContainer;

BABYSITTER.CHILDVIEWCONTAINER

Provideacontainertostore,retrieveand
shutdownchildviews.
Backbone.ChildViewContainer = function(Backbone, _) {

CONTAINERCONSTRUCTOR

var Container = function(views) {


this._views = {};
this._indexByModel = {};
this._indexByCustom = {};
this._updateLength();
_.each(views, this.add, this);
};

http://marionettejs.com/annotatedsrc/backbone.marionette.html

1/51

4/1/2015

backbone.marionette.js

CONTAINERMETHODS

_.extend(Container.prototype, {

Addaviewtothiscontainer.Storestheview
bycidandmakesitsearchablebythemodel
cid(andmodelitself).Optionallyspecify
acustomkeytostoreanretrievetheview.
add: function(view, customIndex) {
var viewCid = view.cid;

storetheview
this._views[viewCid] = view;

indexitbymodel
if (view.model) {
this._indexByModel[view.model.cid] = viewCid;
}

indexbycustom
if (customIndex) {
this._indexByCustom[customIndex] = viewCid;
}
this._updateLength();
return this;
},

Findaviewbythemodelthatwasattachedto
it.Usesthemodelscidtofindit.
findByModel: function(model) {
return this.findByModelCid(model.cid);
},

Findaviewbythecidofthemodelthatwasattachedto
it.Usesthemodelscidtofindtheviewcidand
retrievetheviewusingit.
findByModelCid: function(modelCid) {
var viewCid = this._indexByModel[modelCid];
return this.findByCid(viewCid);
},

Findaviewbyacustomindexer.
findByCustom: function(index) {
var viewCid = this._indexByCustom[index];
return this.findByCid(viewCid);
},

Findbyindex.Thisisnotguaranteedtobea
stableindex.
findByIndex: function(index) {
return _.values(this._views)[index];
},

retrieveaviewbyitsciddirectly
findByCid: function(cid) {
return this._views[cid];
},

Removeaview
remove: function(view) {
var viewCid = view.cid;

deletemodelindex
if (view.model) {
delete this._indexByModel[view.model.cid];
}

deletecustomindex
_.any(this._indexByCustom, function(cid, key) {
if (cid === viewCid) {
delete this._indexByCustom[key];
return true;
}
}, this);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

2/51

4/1/2015

backbone.marionette.js

removetheviewfromthecontainer
delete this._views[viewCid];

updatethelength
this._updateLength();
return this;
},

Callamethodoneveryviewinthecontainer,
passingparameterstothecallmethodoneata
time,likefunction.call.
call: function(method) {
this.apply(method, _.tail(arguments));
},

Applyamethodoneveryviewinthecontainer,
passingparameterstothecallmethodoneata
time,likefunction.apply.
apply: function(method, args) {
_.each(this._views, function(view) {
if (_.isFunction(view[method])) {
view[method].apply(view, args || []);
}
});
},

Updatethe.lengthattributeonthiscontainer
_updateLength: function() {
this.length = _.size(this._views);
}
});

BorrowingthiscodefromBackbone.Collection:
http://backbonejs.org/docs/backbone.html#section106
MixinmethodsfromUnderscore,foriteration,andother
collectionrelatedfeatures.
var methods = [ "forEach", "each", "map", "find", "detect", "filter", "select", "reject", "every", "all", "some", "any", "include", "contains", "invoke",
_.each(methods, function(method) {
Container.prototype[method] = function() {
var views = _.values(this._views);
var args = [ views ].concat(_.toArray(arguments));
return _[method].apply(_, args);
};
});

returnthepublicAPI
return Container;
}(Backbone, _);
Backbone.ChildViewContainer.VERSION = "0.1.6";
Backbone.ChildViewContainer.noConflict = function() {
Backbone.ChildViewContainer = previousChildViewContainer;
return this;
};
return Backbone.ChildViewContainer;
})(Backbone, _);
/* istanbul ignore next */

BACKBONE.WREQR(BACKBONE.MARIONETTE)

v1.3.1
Copyright(c)2014DerickBailey,MutedSolutions,LLC.
DistributedunderMITlicense
http://github.com/marionettejs/backbone.wreqr
(function(Backbone, _) {
"use strict";
var previousWreqr = Backbone.Wreqr;
var Wreqr = Backbone.Wreqr = {};
Backbone.Wreqr.VERSION = "1.3.1";
Backbone.Wreqr.noConflict = function() {
Backbone.Wreqr = previousWreqr;
return this;
};

http://marionettejs.com/annotatedsrc/backbone.marionette.html

3/51

4/1/2015

backbone.marionette.js

HANDLERS

Aregistryoffunctionstocall,givenaname
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;

INSTANCEMEMBERS

_.extend(Handlers.prototype, Backbone.Events, {

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

Addahandlerforthegivenname,withan
optionalcontexttorunthehandlerwithin
setHandler: function(name, handler, context) {
var config = {
callback: handler,
context: context
};
this._wreqrHandlers[name] = config;
this.trigger("handler:add", name, handler, context);
},

Determinewhetherornotahandlerisregistered
hasHandler: function(name) {
return !!this._wreqrHandlers[name];
},

Getthecurrentlyregisteredhandlerfor
thespecifiedname.Throwsanexceptionif
nohandlerisfound.
getHandler: function(name) {
var config = this._wreqrHandlers[name];
if (!config) {
return;
}
return function() {
var args = Array.prototype.slice.apply(arguments);
return config.callback.apply(config.context, args);
};
},

Removeahandlerforthespecifiedname
removeHandler: function(name) {
delete this._wreqrHandlers[name];
},

Removeallhandlersfromthisregistry
removeAllHandlers: function() {
this._wreqrHandlers = {};
}

http://marionettejs.com/annotatedsrc/backbone.marionette.html

4/51

4/1/2015

backbone.marionette.js

});
return Handlers;
}(Backbone, _);

WREQR.COMMANDSTORAGE

Storeandretrievecommandsforexecution.
Wreqr.CommandStorage = function() {
"use strict";

Constructorfunction
var CommandStorage = function(options) {
this.options = options;
this._commands = {};
if (_.isFunction(this.initialize)) {
this.initialize(options);
}
};

Instancemethods
_.extend(CommandStorage.prototype, Backbone.Events, {

Getanobjectliteralbycommandname,thatcontains
thecommandNameandtheinstancesofallcommands
representedasanarrayofargumentstoprocess
getCommands: function(commandName) {
var commands = this._commands[commandName];

wedonthaveit,soaddit
if (!commands) {

buildtheconfiguration
commands = {
command: commandName,
instances: []
};

storeit
this._commands[commandName] = commands;
}
return commands;
},

Addacommandbyname,tothestorageandstorethe
argsforthecommand
addCommand: function(commandName, args) {
var command = this.getCommands(commandName);
command.instances.push(args);
},

ClearallcommandsforthegivencommandName
clearCommands: function(commandName) {
var command = this.getCommands(commandName);
command.instances = [];
}
});
return CommandStorage;
}();

WREQR.COMMANDS

Asimplecommandpatternimplementation.Registeracommand
handlerandexecuteit.
Wreqr.Commands = function(Wreqr) {
"use strict";
return Wreqr.Handlers.extend({

defaultstoragetype
storageType: Wreqr.CommandStorage,
constructor: function(options) {
this.options = options || {};

http://marionettejs.com/annotatedsrc/backbone.marionette.html

5/51

4/1/2015

backbone.marionette.js
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);
},

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

Internalmethodtohandlebulkexecutionofstoredcommands
_executeCommands: function(name, handler, context) {
var command = this.storage.getCommands(name);

loopthroughandexecuteallthestoredcommandinstances
_.each(command.instances, function(args) {
handler.apply(context, args);
});
this.storage.clearCommands(name);
},

Internalmethodtoinitializestorageeitherfromthetypes
storageTypeortheinstanceoptions.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

Asimplerequest/responseimplementation.Registera
requesthandler,andreturnaresponsefromit
Wreqr.RequestResponse = function(Wreqr) {
"use strict";
return Wreqr.Handlers.extend({
request: function() {
var name = arguments[0];
var args = Array.prototype.slice.call(arguments, 1);
if (this.hasHandler(name)) {
return this.getHandler(name).apply(this, args);
}
}
});
}(Wreqr);

EVENTAGGREGATOR

Apubsubobjectthatcanbeusedtodecouplevariousparts
ofanapplicationthrougheventdrivenarchitecture.
Wreqr.EventAggregator = function(Backbone, _) {
"use strict";
var EA = function() {};

CopytheextendfunctionusedbyBackbonesclasses
EA.extend = Backbone.Model.extend;

CopythebasicBackbone.Eventsontotheeventaggregator
_.extend(EA.prototype, Backbone.Events);
return EA;
}(Backbone, _);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

6/51

4/1/2015

backbone.marionette.js

WREQR.CHANNEL

Anobjectthatwrapsthethreemessagingsystems:
EventAggregator,RequestResponse,Commands
Wreqr.Channel = function(Wreqr) {
"use strict";
var Channel = function(channelName) {
this.vent = new Backbone.Wreqr.EventAggregator();
this.reqres = new Backbone.Wreqr.RequestResponse();
this.commands = new Backbone.Wreqr.Commands();
this.channelName = channelName;
};
_.extend(Channel.prototype, {

Removeallhandlersfromthemessagingsystemsofthischannel
reset: function() {
this.vent.off();
this.vent.stopListening();
this.reqres.removeAllHandlers();
this.commands.removeAllHandlers();
return this;
},

Connectahashofeventsoneforeachmessagingsystem
connectEvents: function(hash, context) {
this._connect("vent", hash, context);
return this;
},
connectCommands: function(hash, context) {
this._connect("commands", hash, context);
return this;
},
connectRequests: function(hash, context) {
this._connect("reqres", hash, context);
return this;
},

Attachthehandlerstoagivenmessagesystemtype
_connect: function(type, hash, context) {
if (!hash) {
return;
}
context = context || this;
var method = type === "vent" ? "on" : "setHandler";
_.each(hash, function(fn, eventName) {
this[type][method](eventName, _.bind(fn, context));
}, this);
}
});
return Channel;
}(Wreqr);

WREQR.RADIO

Anobjectthatletsyoucommunicatewithmanychannels.
Wreqr.radio = function(Wreqr) {
"use strict";
var Radio = function() {
this._channels = {};
this.vent = {};
this.commands = {};
this.reqres = {};
this._proxyMethods();
};
_.extend(Radio.prototype, {
channel: function(channelName) {
if (!channelName) {
throw new Error("Channel must receive a name");
}
return this._getChannel(channelName);
},
_getChannel: function(channelName) {
var channel = this._channels[channelName];
if (!channel) {
channel = new Wreqr.Channel(channelName);
this._channels[channelName] = channel;
}
return channel;

http://marionettejs.com/annotatedsrc/backbone.marionette.html

7/51

4/1/2015

backbone.marionette.js

},
_proxyMethods: function() {
_.each([ "vent", "commands", "reqres" ], function(system) {
_.each(messageSystems[system], function(method) {
this[system][method] = proxyMethod(this, system, method);
}, this);
}, this);
}
});
var messageSystems = {
vent: [ "on", "off", "trigger", "once", "stopListening", "listenTo", "listenToOnce" ],
commands: [ "execute", "setHandler", "setHandlers", "removeHandler", "removeAllHandlers" ],
reqres: [ "request", "setHandler", "setHandlers", "removeHandler", "removeAllHandlers" ]
};
var proxyMethod = function(radio, system, method) {
return function(channelName) {
var messageSystem = radio._getChannel(channelName)[system];
var args = Array.prototype.slice.call(arguments, 1);
return messageSystem[method].apply(messageSystem, args);
};
};
return new Radio();
}(Wreqr);
return Backbone.Wreqr;
})(Backbone, _);
var previousMarionette = root.Marionette;
var previousMn = root.Mn;
var Marionette = Backbone.Marionette = {};
Marionette.VERSION = '2.4.1';
Marionette.noConflict = function() {
root.Marionette = previousMarionette;
root.Mn = previousMn;
return this;
};
Backbone.Marionette = Marionette;

GettheDeferredcreatorforlateruse
Marionette.Deferred = Backbone.$.Deferred;
/* jshint unused: false *//* global console */

HELPERS

MARIONETTE.EXTEND

BorrowtheBackboneextendmethodsowecanuseitasneeded
Marionette.extend = Backbone.Model.extend;

MARIONETTE.ISNODEATTACHED

Determineifelisachildofthedocument
Marionette.isNodeAttached = function(el) {
return Backbone.$.contains(document.documentElement, el);
};

Mergekeysfromoptionsontothis
Marionette.mergeOptions = function(options, keys) {
if (!options) { return; }
_.extend(this, _.pick(options, keys));
};

MARIONETTE.GETOPTION

http://marionettejs.com/annotatedsrc/backbone.marionette.html

8/51

4/1/2015

backbone.marionette.js

Retrieveanobject,functionorothervaluefromatarget
objectoritsoptions,withoptionstakingprecedence.
Marionette.getOption = function(target, optionName) {
if (!target || !optionName) { return; }
if (target.options && (target.options[optionName] !== undefined)) {
return target.options[optionName];
} else {
return target[optionName];
}
};

ProxyMarionette.getOption
Marionette.proxyGetOption = function(optionName) {
return Marionette.getOption(this, optionName);
};

Similarto_.result,thisisasimplehelper
Ifafunctionisprovidedwecallitwithcontext
otherwisejustreturnthevalue.Ifthevalueis
undefinedreturnadefaultvalue
Marionette._getValue = function(value, context, params) {
if (_.isFunction(value)) {
value = params ? value.apply(context, params) : value.call(context);
}
return value;
};

MARIONETTE.NORMALIZEMETHODS

Passinamappingofevents=>functionsorfunctionnames
andreturnamappingofevents=>functions
Marionette.normalizeMethods = function(hash) {
return _.reduce(hash, function(normalizedHash, method, name) {
if (!_.isFunction(method)) {
method = this[method];
}
if (method) {
normalizedHash[name] = method;
}
return normalizedHash;
}, {}, this);
};

[email protected]
intoassociatedselector
Marionette.normalizeUIString = function(uiString, ui) {
return uiString.replace(/@ui\.[a-zA-Z_$0-9]*/g, function(r) {
return ui[r.slice(4)];
});
};

[email protected]
agivenkeyfortriggersandevents
swapsthe@uiwiththeassociatedselector.
Returnsanew,nonmutated,parsedeventshash.
Marionette.normalizeUIKeys = function(hash, ui) {
return _.reduce(hash, function(memo, val, key) {
var normalizedKey = Marionette.normalizeUIString(key, ui);
memo[normalizedKey] = val;
return memo;
}, {});
};

[email protected]
agivenvalueforregions
swapsthe@uiwiththeassociatedselector
Marionette.normalizeUIValues = function(hash, ui, properties) {
_.each(hash, function(val, key) {
if (_.isString(val)) {
hash[key] = Marionette.normalizeUIString(val, ui);
} else if (_.isObject(val) && _.isArray(properties)) {
_.extend(val, Marionette.normalizeUIValues(_.pick(val, properties), ui));
/* Value is an object, and we got an array of embedded property names to normalize. */
_.each(properties, function(property) {
var propertyVal = val[property];
if (_.isString(propertyVal)) {
val[property] = Marionette.normalizeUIString(propertyVal, ui);
}

http://marionettejs.com/annotatedsrc/backbone.marionette.html

9/51

4/1/2015

backbone.marionette.js
});

}
});
return hash;
};

MixinmethodsfromUnderscore,foriteration,andother
collectionrelatedfeatures.
BorrowingthiscodefromBackbone.Collection:
http://backbonejs.org/docs/backbone.html#section121
Marionette.actAsCollection = function(object, listProperty) {
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) {
object[method] = function() {
var list = _.values(_.result(this, listProperty));
var args = [list].concat(_.toArray(arguments));
return _[method].apply(_, args);
};
});
};
var deprecate = Marionette.deprecate = function(message, test) {
if (_.isObject(message)) {
message = (
message.prev + ' is going to be removed in the future. ' +
'Please use ' + message.next + ' instead.' +
(message.url ? ' See: ' + message.url : '')
);
}
if ((test === undefined || !test) && !deprecate._cache[message]) {
deprecate._warn('Deprecation warning: ' + message);
deprecate._cache[message] = true;
}
};
deprecate._warn = typeof console !== 'undefined' && (console.warn || console.log) || function() {};
deprecate._cache = {};
/* jshint maxstatements: 14, maxcomplexity: 7 */

TRIGGERMETHOD

Marionette._triggerMethod = (function() {

splittheeventnameonthe:
var splitter = /(^|:)(\w)/gi;

taketheeventsection(section1:section2:section3)
andturnitintouppercasename
function getEventName(match, prefix, eventName) {
return eventName.toUpperCase();
}
return function(context, event, args) {
var noEventArg = arguments.length < 3;
if (noEventArg) {
args = event;
event = args[0];
}

getthemethodnamefromtheeventname
var methodName = 'on' + event.replace(splitter, getEventName);
var method = context[methodName];
var result;

calltheonMethodNameifitexists
if (_.isFunction(method)) {

passallargs,excepttheeventname
result = method.apply(context, noEventArg ? _.rest(args) : args);
}

triggertheevent,ifatriggermethodexists

http://marionettejs.com/annotatedsrc/backbone.marionette.html

10/51

4/1/2015

backbone.marionette.js

if (_.isFunction(context.trigger)) {
if (noEventArg + args.length > 1) {
context.trigger.apply(context, noEventArg ? args : [event].concat(_.drop(args, 0)));
} else {
context.trigger(event);
}
}
return result;
};
})();

Triggeraneventand/oracorrespondingmethodname.Examples:
this.triggerMethod("foo")willtriggerthefooeventand

calltheonFoomethod.
this.triggerMethod("foo:bar")willtriggerthefoo:bareventand

calltheonFooBarmethod.
Marionette.triggerMethod = function(event) {
return Marionette._triggerMethod(this, arguments);
};

triggerMethodOninvokestriggerMethodonaspecificcontext
e.g.Marionette.triggerMethodOn(view, 'show')
willtriggerashoweventorinvokeonShowtheview.
Marionette.triggerMethodOn = function(context) {
var fnc = _.isFunction(context.triggerMethod) ?
context.triggerMethod :
Marionette.triggerMethod;
return fnc.apply(context, _.rest(arguments));
};

DOMREFRESH

Monitoraviewsstate,andafterithasbeenrenderedandshown
intheDOM,triggeradom:refresheventeverytimeitis
rerendered.
Marionette.MonitorDOMRefresh = function(view) {

trackwhentheviewhasbeenshownintheDOM,
usingaMarionette.Region(orbyothermeansoftriggeringshow)
function handleShow() {
view._isShown = true;
triggerDOMRefresh();
}

trackwhentheviewhasbeenrendered
function handleRender() {
view._isRendered = true;
triggerDOMRefresh();
}

Triggerthedom:refresheventandcorrespondingonDomRefreshmethod
function triggerDOMRefresh() {
if (view._isShown && view._isRendered && Marionette.isNodeAttached(view.el)) {
if (_.isFunction(view.triggerMethod)) {
view.triggerMethod('dom:refresh');
}
}
}
view.on({
show: handleShow,
render: handleRender
});
};
/* jshint maxparams: 5 */

BINDENTITYEVENTS&UNBINDENTITYEVENTS

http://marionettejs.com/annotatedsrc/backbone.marionette.html

11/51

4/1/2015

backbone.marionette.js

Thesemethodsareusedtobind/unbindabackboneentity(e.g.collection/model)
tomethodsonatargetobject.
Thefirstparameter,target,musthavetheBackbone.Eventsmodulemixedin.
Thesecondparameteristheentity(Backbone.Model,Backbone.Collectionor
anyobjectthathasBackbone.Eventsmixedin)tobindtheeventsfrom.
Thethirdparameterisahashof{event:name:eventHandler}
configuration.Multiplehandlerscanbeseparatedbyaspace.A
functioncanbesuppliedinsteadofastringhandlername.
(function(Marionette) {
'use strict';

Bindtheeventtohandlersspecifiedasastringof
handlernamesonthetargetobject
function bindFromStrings(target, entity, evt, methods) {
var methodNames = methods.split(/\s+/);
_.each(methodNames, function(methodName) {
var method = target[methodName];
if (!method) {
throw new Marionette.Error('Method "' + methodName +
'" was configured as an event handler, but does not exist.');
}
target.listenTo(entity, evt, method);
});
}

Bindtheeventtoasuppliedcallbackfunction
function bindToFunction(target, entity, evt, method) {
target.listenTo(entity, evt, method);
}

Bindtheeventtohandlersspecifiedasastringof
handlernamesonthetargetobject
function unbindFromStrings(target, entity, evt, methods) {
var methodNames = methods.split(/\s+/);
_.each(methodNames, function(methodName) {
var method = target[methodName];
target.stopListening(entity, evt, method);
});
}

Bindtheeventtoasuppliedcallbackfunction
function unbindToFunction(target, entity, evt, method) {
target.stopListening(entity, evt, method);
}

genericloopingfunction
function iterateEvents(target, entity, bindings, functionCallback, stringCallback) {
if (!entity || !bindings) { return; }

typecheckbindings
if (!_.isObject(bindings)) {
throw new Marionette.Error({
message: 'Bindings must be an object or function.',
url: 'marionette.functions.html#marionettebindentityevents'
});
}

allowthebindingstobeafunction
bindings = Marionette._getValue(bindings, target);

iteratethebindingsandbindthem
_.each(bindings, function(methods, evt) {

allowforafunctionasthehandler,
oralistofeventnamesasastring
if (_.isFunction(methods)) {
functionCallback(target, entity, evt, methods);
} else {
stringCallback(target, entity, evt, methods);
}

http://marionettejs.com/annotatedsrc/backbone.marionette.html

12/51

4/1/2015

backbone.marionette.js

});
}

ExportPublicAPI
Marionette.bindEntityEvents = function(target, entity, bindings) {
iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
};
Marionette.unbindEntityEvents = function(target, entity, bindings) {
iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
};

ProxybindEntityEvents
Marionette.proxyBindEntityEvents = function(entity, bindings) {
return Marionette.bindEntityEvents(this, entity, bindings);
};

ProxyunbindEntityEvents
Marionette.proxyUnbindEntityEvents = function(entity, bindings) {
return Marionette.unbindEntityEvents(this, entity, bindings);
};
})(Marionette);

ERROR

var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];


Marionette.Error = Marionette.extend.call(Error, {
urlRoot: 'http://marionettejs.com/docs/v' + Marionette.VERSION + '/',
constructor: function(message, options) {
if (_.isObject(message)) {
options = message;
message = options.message;
} else if (!options) {
options = {};
}
var error = Error.call(this, message);
_.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));
this.captureStackTrace();
if (options.url) {
this.url = this.urlRoot + options.url;
}
},
captureStackTrace: function() {
if (Error.captureStackTrace) {
Error.captureStackTrace(this, Marionette.Error);
}
},
toString: function() {
return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');
}
});
Marionette.Error.extend = Marionette.extend;

CALLBACKS

Asimplewayofmanagingacollectionofcallbacks
andexecutingthematalaterpointintime,usingjQuerys
Deferredobject.
Marionette.Callbacks = function() {
this._deferred = Marionette.Deferred();
this._callbacks = [];
};
_.extend(Marionette.Callbacks.prototype, {

Addacallbacktobeexecuted.Callbacksaddedhereare
guaranteedtoexecute,eveniftheyareaddedafterthe
runmethodiscalled.

http://marionettejs.com/annotatedsrc/backbone.marionette.html

13/51

4/1/2015

backbone.marionette.js

add: function(callback, contextOverride) {


var promise = _.result(this._deferred, 'promise');
this._callbacks.push({cb: callback, ctx: contextOverride});
promise.then(function(args) {
if (contextOverride) { args.context = contextOverride; }
callback.call(args.context, args.options);
});
},

Runallregisteredcallbackswiththecontextspecified.
Additionalcallbackscanbeaddedafterthishasbeenrun
andtheywillstillbeexecuted.
run: function(options, context) {
this._deferred.resolve({
options: options,
context: context
});
},

Resetsthelistofcallbackstoberun,allowingthesamelist
toberunmultipletimeswhenevertherunmethodiscalled.
reset: function() {
var callbacks = this._callbacks;
this._deferred = Marionette.Deferred();
this._callbacks = [];
_.each(callbacks, function(cb) {
this.add(cb.cb, cb.ctx);
}, this);
}
});

CONTROLLER

Amultipurposeobjecttouseasacontrollerfor
modulesandrouters,andasamediatorforworkflow
andcoordinationofotherobjects,views,andmore.
Marionette.Controller = function(options) {
this.options = options || {};
if (_.isFunction(this.initialize)) {
this.initialize(this.options);
}
};
Marionette.Controller.extend = Marionette.extend;

CONTROLLERMETHODS

EnsureitcantriggereventswithBackbone.Events
_.extend(Marionette.Controller.prototype, Backbone.Events, {
destroy: function() {
Marionette._triggerMethod(this, 'before:destroy', arguments);
Marionette._triggerMethod(this, 'destroy', arguments);
this.stopListening();
this.off();
return this;
},

importthetriggerMethodtotriggereventswithcorresponding
methodsifthemethodexists
triggerMethod: Marionette.triggerMethod,

Ahandywaytomergeoptionsontotheinstance
mergeOptions: Marionette.mergeOptions,

ProxygetOptiontoenablegettingoptionsfromthisorthis.optionsbyname.
getOption: Marionette.proxyGetOption
});

http://marionettejs.com/annotatedsrc/backbone.marionette.html

14/51

4/1/2015

backbone.marionette.js

OBJECT

ABaseClassthatotherClassesshoulddescendfrom.
ObjectborrowsmanyconventionsandutilitiesfromBackbone.
Marionette.Object = function(options) {
this.options = _.extend({}, _.result(this, 'options'), options);
this.initialize.apply(this, arguments);
};
Marionette.Object.extend = Marionette.extend;

OBJECTMETHODS

EnsureitcantriggereventswithBackbone.Events
_.extend(Marionette.Object.prototype, Backbone.Events, {

thisisanoopmethodintendedtobeoverriddenbyclassesthatextendfromthisbase
initialize: function() {},
destroy: function() {
this.triggerMethod('before:destroy');
this.triggerMethod('destroy');
this.stopListening();
return this;
},

ImportthetriggerMethodtotriggereventswithcorresponding
methodsifthemethodexists
triggerMethod: Marionette.triggerMethod,

Ahandywaytomergeoptionsontotheinstance
mergeOptions: Marionette.mergeOptions,

ProxygetOptiontoenablegettingoptionsfromthisorthis.optionsbyname.
getOption: Marionette.proxyGetOption,

ProxybindEntityEventstoenablebindingviewseventsfromanotherentity.
bindEntityEvents: Marionette.proxyBindEntityEvents,

ProxyunbindEntityEventstoenableunbindingviewseventsfromanotherentity.
unbindEntityEvents: Marionette.proxyUnbindEntityEvents
});
/* jshint maxcomplexity: 16, maxstatements: 45, maxlen: 120 */

REGION

Managethevisualregionsofyourcompositeapplication.See
http://lostechies.com/derickbailey/2011/12/12/compositejsappsregionsandregionmanagers/
Marionette.Region = Marionette.Object.extend({
constructor: function(options) {

setoptionstemporarilysothatwecangetel.
optionswillbeoverridenbyObject.constructor
this.options = options || {};
this.el = this.getOption('el');

Handlewhenthis.elispassedinasa$wrappedelement.
this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;

http://marionettejs.com/annotatedsrc/backbone.marionette.html

15/51

4/1/2015

backbone.marionette.js

if (!this.el) {
throw new Marionette.Error({
name: 'NoElError',
message: 'An "el" must be specified for a region.'
});
}
this.$el = this.getEl(this.el);
Marionette.Object.call(this, options);
},

Displaysabackboneviewinstanceinsideoftheregion.
Handlescallingtherendermethodforyou.Readscontent
directlyfromtheelattribute.Alsocallsanoptional
onShowandonDestroymethodonyourview,justaftershowing
orjustbeforedestroyingtheview,respectively.
ThepreventDestroyoptioncanbeusedtopreventaviewfrom
theoldviewbeingdestroyedonshow.
TheforceShowoptioncanbeusedtoforceaviewtobe
rerenderedifitsalreadyshownintheregion.
show: function(view, options) {
if (!this._ensureElement()) {
return;
}
this._ensureViewIsIntact(view);
var
var
var
var

showOptions
isDifferentView
preventDestroy
forceShow

=
=
=
=

options || {};
view !== this.currentView;
!!showOptions.preventDestroy;
!!showOptions.forceShow;

Weareonlychangingtheviewifthereisacurrentviewtochangetobeginwith
var isChangingView = !!this.currentView;

OnlydestroythecurrentviewifwedontwanttopreventDestroyandif
theviewgiveninthefirstargumentisdifferentthancurrentView
var _shouldDestroyView = isDifferentView && !preventDestroy;

Onlyshowtheviewgiveninthefirstargumentifitisdifferentthan
thecurrentvieworifwewanttoreshowtheview.Notethatif
_shouldDestroyViewistrue,then_shouldShowViewisalsonecessarilytrue.
var _shouldShowView = isDifferentView || forceShow;
if (isChangingView) {
this.triggerMethod('before:swapOut', this.currentView, this, options);
}
if (this.currentView) {
delete this.currentView._parent;
}
if (_shouldDestroyView) {
this.empty();

Adestroyeventisattachedtothecleanupmanuallyremovedviews.
Weneedtodetachthiseventwhenanewviewisgoingtobeshownasit
isnolongerrelevant.
} else if (isChangingView && _shouldShowView) {
this.currentView.off('destroy', this.empty, this);
}
if (_shouldShowView) {

Weneedtolistenforifaviewisdestroyed
inawayotherthanthroughtheregion.
Ifthishappensweneedtoremovethereference
tothecurrentViewsinceonceaviewhasbeendestroyed
wecannotreuseit.
view.once('destroy', this.empty, this);
view.render();
view._parent = this;
if (isChangingView) {
this.triggerMethod('before:swap', view, this, options);
}
this.triggerMethod('before:show', view, this, options);
Marionette.triggerMethodOn(view, 'before:show', view, this, options);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

16/51

4/1/2015

backbone.marionette.js
if (isChangingView) {
this.triggerMethod('swapOut', this.currentView, this, options);
}

Anarrayofviewsthatwereabouttodisplay
var attachedRegion = Marionette.isNodeAttached(this.el);

Theviewsthatwereabouttoattachtothedocument
Itsimportantthatweprevent_getNestedViewsfrombeingexecutedunnecessarily
asitsapotentiallyslowmethod
var displayedViews = [];
var triggerBeforeAttach = showOptions.triggerBeforeAttach || this.triggerBeforeAttach;
var triggerAttach = showOptions.triggerAttach || this.triggerAttach;
if (attachedRegion && triggerBeforeAttach) {
displayedViews = this._displayedViews(view);
this._triggerAttach(displayedViews, 'before:');
}
this.attachHtml(view);
this.currentView = view;
if (attachedRegion && triggerAttach) {
displayedViews = this._displayedViews(view);
this._triggerAttach(displayedViews);
}
if (isChangingView) {
this.triggerMethod('swap', view, this, options);
}
this.triggerMethod('show', view, this, options);
Marionette.triggerMethodOn(view, 'show', view, this, options);
return this;
}
return this;
},
triggerBeforeAttach: true,
triggerAttach: true,
_triggerAttach: function(views, prefix) {
var eventName = (prefix || '') + 'attach';
_.each(views, function(view) {
Marionette.triggerMethodOn(view, eventName, view, this);
}, this);
},
_displayedViews: function(view) {
return _.union([view], _.result(view, '_getNestedViews') || []);
},
_ensureElement: function() {
if (!_.isObject(this.el)) {
this.$el = this.getEl(this.el);
this.el = this.$el[0];
}
if (!this.$el || this.$el.length === 0) {
if (this.getOption('allowMissingEl')) {
return false;
} else {
throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM');
}
}
return true;
},
_ensureViewIsIntact: function(view) {
if (!view) {
throw new Marionette.Error({
name: 'ViewNotValid',
message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.'
});
}
if (view.isDestroyed) {
throw new Marionette.Error({
name: 'ViewDestroyedError',
message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.'
});
}
},

http://marionettejs.com/annotatedsrc/backbone.marionette.html

17/51

4/1/2015

backbone.marionette.js

OverridethismethodtochangehowtheregionfindstheDOM
elementthatitmanages.ReturnajQueryselectorobjectscoped
toaprovidedparentelorthedocumentifnoneexists.
getEl: function(el) {
return Backbone.$(el, Marionette._getValue(this.options.parentEl, this));
},

Overridethismethodtochangehowthenewviewis
appendedtothe$elthattheregionismanaging
attachHtml: function(view) {
this.$el.contents().detach();
this.el.appendChild(view.el);
},

Destroythecurrentview,ifthereisone.Ifthereisno
currentview,itdoesnothingandreturnsimmediately.
empty: function(options) {
var view = this.currentView;
var preventDestroy = Marionette._getValue(options, 'preventDestroy', this);

Ifthereisnoviewintheregion
weshouldnotremoveanything
if (!view) { return; }
view.off('destroy', this.empty, this);
this.triggerMethod('before:empty', view);
if (!preventDestroy) {
this._destroyView();
}
this.triggerMethod('empty', view);

RemoveregionpointertothecurrentView
delete this.currentView;
if (preventDestroy) {
this.$el.contents().detach();
}
return this;
},

calldestroyorremove,dependingonwhichisfound
ontheview(ifshowingarawBackbonevieworaMarionetteView)
_destroyView: function() {
var view = this.currentView;
if (view.destroy && !view.isDestroyed) {
view.destroy();
} else if (view.remove) {
view.remove();

appendingisDestroyedtorawBackboneViewallowsregions
tothrowaViewDestroyedErrorforthisview
view.isDestroyed = true;
}
},

Attachanexistingviewtotheregion.This
willnotcallrenderoronShowforthenewview,
andwillnotreplacethecurrentHTMLfortheel
oftheregion.
attachView: function(view) {
this.currentView = view;
return this;
},

Checkswhetheraviewiscurrentlypresentwithin
theregion.Returnstrueifthereisandfalseif
noviewispresent.
hasView: function() {
return !!this.currentView;
},

Resettheregionbydestroyinganyexistingviewand
clearingoutthecached$el.Thenexttimeaview
isshownviathisregion,theregionwillrequerythe

http://marionettejs.com/annotatedsrc/backbone.marionette.html

18/51

4/1/2015

backbone.marionette.js

DOMfortheregionsel.
reset: function() {
this.empty();
if (this.$el) {
this.el = this.$el.selector;
}
delete this.$el;
return this;
}
},

StaticMethods
{

Buildaninstanceofaregionbypassinginaconfigurationobject
andadefaultregionclasstouseifnoneisspecifiedintheconfig.
TheconfigobjectshouldeitherbeastringasajQueryDOMselector,
aRegionclassdirectly,oranobjectliteralthatspecifiesaselector,
acustomregionClass,andanyoptionstobesuppliedtotheregion:
{
selector: "#foo",
regionClass: MyCustomRegion,
allowMissingEl: false
}
buildRegion: function(regionConfig, DefaultRegionClass) {
if (_.isString(regionConfig)) {
return this._buildRegionFromSelector(regionConfig, DefaultRegionClass);
}
if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) {
return this._buildRegionFromObject(regionConfig, DefaultRegionClass);
}
if (_.isFunction(regionConfig)) {
return this._buildRegionFromRegionClass(regionConfig);
}
throw new Marionette.Error({
message: 'Improper region configuration type.',
url: 'marionette.region.html#region-configuration-types'
});
},

Buildtheregionfromastringselectorlike#fooregion
_buildRegionFromSelector: function(selector, DefaultRegionClass) {
return new DefaultRegionClass({el: selector});
},

Buildtheregionfromaconfigurationobject
{ selector: '#foo', regionClass: FooRegion, allowMissingEl: false }
_buildRegionFromObject: function(regionConfig, DefaultRegionClass) {
var RegionClass = regionConfig.regionClass || DefaultRegionClass;
var options = _.omit(regionConfig, 'selector', 'regionClass');
if (regionConfig.selector && !options.el) {
options.el = regionConfig.selector;
}
return new RegionClass(options);
},

BuildtheregiondirectlyfromagivenRegionClass
_buildRegionFromRegionClass: function(RegionClass) {
return new RegionClass();
}
});

REGIONMANAGER

ManageoneormorerelatedMarionette.Regionobjects.
Marionette.RegionManager = Marionette.Controller.extend({
constructor: function(options) {
this._regions = {};

http://marionettejs.com/annotatedsrc/backbone.marionette.html

19/51

4/1/2015

backbone.marionette.js

this.length = 0;
Marionette.Controller.call(this, options);
this.addRegions(this.getOption('regions'));
},

Addmultipleregionsusinganobjectliteralora
functionthatreturnsanobjectliteral,where
eachkeybecomestheregionname,andeachvalueis
theregiondefinition.
addRegions: function(regionDefinitions, defaults) {
regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments);
return _.reduce(regionDefinitions, function(regions, definition, name) {
if (_.isString(definition)) {
definition = {selector: definition};
}
if (definition.selector) {
definition = _.defaults({}, definition, defaults);
}
regions[name] = this.addRegion(name, definition);
return regions;
}, {}, this);
},

Addanindividualregiontotheregionmanager,
andreturntheregioninstance
addRegion: function(name, definition) {
var region;
if (definition instanceof Marionette.Region) {
region = definition;
} else {
region = Marionette.Region.buildRegion(definition, Marionette.Region);
}
this.triggerMethod('before:add:region', name, region);
region._parent = this;
this._store(name, region);
this.triggerMethod('add:region', name, region);
return region;
},

Getaregionbyname
get: function(name) {
return this._regions[name];
},

Getsalltheregionscontainedwithin
theregionManagerinstance.
getRegions: function() {
return _.clone(this._regions);
},

Removearegionbyname
removeRegion: function(name) {
var region = this._regions[name];
this._remove(name, region);
return region;
},

Emptyallregionsintheregionmanager,and
removethem
removeRegions: function() {
var regions = this.getRegions();
_.each(this._regions, function(region, name) {
this._remove(name, region);
}, this);
return regions;
},

Emptyallregionsintheregionmanager,but
leavethemattached
emptyRegions: function() {
var regions = this.getRegions();
_.invoke(regions, 'empty');

http://marionettejs.com/annotatedsrc/backbone.marionette.html

20/51

4/1/2015

backbone.marionette.js

return regions;
},

Destroyallregionsandshutdowntheregion
managerentirely
destroy: function() {
this.removeRegions();
return Marionette.Controller.prototype.destroy.apply(this, arguments);
},

internalmethodtostoreregions
_store: function(name, region) {
if (!this._regions[name]) {
this.length++;
}
this._regions[name] = region;
},

internalmethodtoremovearegion
_remove: function(name, region) {
this.triggerMethod('before:remove:region', name, region);
region.empty();
region.stopListening();
delete region._parent;
delete this._regions[name];
this.length--;
this.triggerMethod('remove:region', name, region);
}
});
Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions');

TEMPLATECACHE

Managetemplatesstoredin<script>blocks,
cachingthemforfasteraccess.
Marionette.TemplateCache = function(templateId) {
this.templateId = templateId;
};

TemplateCacheobjectlevelmethods.Managethetemplate
cachesfromthesemethodcallsinsteadofcreating
yourownTemplateCacheinstances
_.extend(Marionette.TemplateCache, {
templateCaches: {},

Getthespecifiedtemplatebyid.Either
retrievesthecachedversion,orloadsit
fromtheDOM.
get: function(templateId, options) {
var cachedTemplate = this.templateCaches[templateId];
if (!cachedTemplate) {
cachedTemplate = new Marionette.TemplateCache(templateId);
this.templateCaches[templateId] = cachedTemplate;
}
return cachedTemplate.load(options);
},

Cleartemplatesfromthecache.Ifnoarguments
arespecified,clearsalltemplates:
clear()

Ifargumentsarespecified,clearseachofthe
specifiedtemplatesfromthecache:
clear("#t1", "#t2", "...")
clear: function() {
var i;
var args = _.toArray(arguments);
var length = args.length;
if (length > 0) {
for (i = 0; i < length; i++) {
delete this.templateCaches[args[i]];

http://marionettejs.com/annotatedsrc/backbone.marionette.html

21/51

4/1/2015

backbone.marionette.js

}
} else {
this.templateCaches = {};
}
}
});

TemplateCacheinstancemethods,allowingeach
templatecacheobjecttomanageitsownstate
andknowwhetherornotithasbeenloaded
_.extend(Marionette.TemplateCache.prototype, {

Internalmethodtoloadthetemplate
load: function(options) {

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

Loadthetemplateandcompileit
var template = this.loadTemplate(this.templateId, options);
this.compiledTemplate = this.compileTemplate(template, options);
return this.compiledTemplate;
},

LoadatemplatefromtheDOM,bydefault.Override
thismethodtoprovideyourowntemplateretrieval
ForasynchronousloadingwithAMD/RequireJS,consider
usingatemplateloaderpluginasdescribedhere:
https://github.com/marionettejs/backbone.marionette/wiki/Usingmarionettewithrequirejs
loadTemplate: function(templateId, options) {
var template = Backbone.$(templateId).html();
if (!template || template.length === 0) {
throw new Marionette.Error({
name: 'NoTemplateError',
message: 'Could not find template: "' + templateId + '"'
});
}
return template;
},

Precompilethetemplatebeforecachingit.Override
thismethodifyoudonotneedtoprecompileatemplate
(JST/RequireJSforexample)orifyouwanttochange
thetemplateengineused(Handebars,etc).
compileTemplate: function(rawTemplate, options) {
return _.template(rawTemplate, options);
}
});

RENDERER

Renderatemplatewithdatabypassinginthetemplate
selectorandthedatatorender.
Marionette.Renderer = {

Renderatemplatewithdata.Thetemplateparameteris
passedtotheTemplateCacheobjecttoretrievethe
templatefunction.Overridethismethodtoprovideyourown
customrenderingandtemplatehandlingforallofMarionette.
render: function(template, data) {
if (!template) {
throw new Marionette.Error({
name: 'TemplateNotFoundError',
message: 'Cannot render the template since its false, null or undefined.'
});
}
var templateFunc = _.isFunction(template) ? template : Marionette.TemplateCache.get(template);
return templateFunc(data);
}

http://marionettejs.com/annotatedsrc/backbone.marionette.html

22/51

4/1/2015

backbone.marionette.js

};

/* jshint maxlen: 114, nonew: false */

VIEW

ThecoreviewclassthatotherMarionetteviewsextendfrom.
Marionette.View = Backbone.View.extend({
isDestroyed: false,
constructor: function(options) {
_.bindAll(this, 'render');
options = Marionette._getValue(options, this);

thisexposesviewoptionstotheviewinitializer
thisisabackfillsincebackboneremovedtheassignment
ofthis.options
atsomepointhoweverthismayberemoved
this.options = _.extend({}, _.result(this, 'options'), options);
this._behaviors = Marionette.Behaviors(this);
Backbone.View.call(this, this.options);
Marionette.MonitorDOMRefresh(this);
},

Getthetemplateforthisview
instance.Youcansetatemplateattributeintheview
definitionorpassatemplate: "whatever"parameterin
totheconstructoroptions.
getTemplate: function() {
return this.getOption('template');
},

Serializeamodelbyreturningitsattributes.Clones
theattributestoallowmodification.
serializeModel: function(model) {
return model.toJSON.apply(model, _.rest(arguments));
},

Mixintemplatehelpermethods.Looksfora
templateHelpersattribute,whichcaneitherbean
objectliteral,orafunctionthatreturnsanobject
literal.Allmethodsandattributesfromthisobject
arecopiestotheobjectpassedin.
mixinTemplateHelpers: function(target) {
target = target || {};
var templateHelpers = this.getOption('templateHelpers');
templateHelpers = Marionette._getValue(templateHelpers, this);
return _.extend(target, templateHelpers);
},

normalizethekeysofpassedhashwiththeviewsuiselectors.
{"@ui.foo": "bar"}
normalizeUIKeys: function(hash) {
var uiBindings = _.result(this, '_uiBindings');
return Marionette.normalizeUIKeys(hash, uiBindings || _.result(this, 'ui'));
},

normalizethevaluesofpassedhashwiththeviewsuiselectors.
{foo: "@ui.bar"}
normalizeUIValues: function(hash, properties) {
var ui = _.result(this, 'ui');
var uiBindings = _.result(this, '_uiBindings');
return Marionette.normalizeUIValues(hash, uiBindings || ui, properties);
},

ConfiguretriggerstoforwardDOMeventstoview
events.triggers: {"click .foo": "do:foo"}
configureTriggers: function() {
if (!this.triggers) { return; }

http://marionettejs.com/annotatedsrc/backbone.marionette.html

23/51

4/1/2015

backbone.marionette.js

Allowtriggerstobeconfiguredasafunction
var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));

Configurethetriggers,preventdefault
actionandstoppropagationofDOMevents
return _.reduce(triggers, function(events, value, key) {
events[key] = this._buildViewTrigger(value);
return events;
}, {}, this);
},

OverridingBackbone.ViewsdelegateEventstohandle
thetriggers,modelEvents,andcollectionEventsconfiguration
delegateEvents: function(events) {
this._delegateDOMEvents(events);
this.bindEntityEvents(this.model, this.getOption('modelEvents'));
this.bindEntityEvents(this.collection, this.getOption('collectionEvents'));
_.each(this._behaviors, function(behavior) {
behavior.bindEntityEvents(this.model, behavior.getOption('modelEvents'));
behavior.bindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
}, this);
return this;
},

internalmethodtodelegateDOMeventsandtriggers
_delegateDOMEvents: function(eventsArg) {
var events = Marionette._getValue(eventsArg || this.events, this);

normalizeuikeys
events = this.normalizeUIKeys(events);
if (_.isUndefined(eventsArg)) {this.events = events;}
var combinedEvents = {};

lookupifthisviewhasbehaviorevents
var behaviorEvents = _.result(this, 'behaviorEvents') || {};
var triggers = this.configureTriggers();
var behaviorTriggers = _.result(this, 'behaviorTriggers') || {};

behavioreventswillbeoverridenbyvieweventsandortriggers
_.extend(combinedEvents, behaviorEvents, events, triggers, behaviorTriggers);
Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
},

OverridingBackbone.ViewsundelegateEventstohandleunbinding
thetriggers,modelEvents,andcollectionEventsconfig
undelegateEvents: function() {
Backbone.View.prototype.undelegateEvents.apply(this, arguments);
this.unbindEntityEvents(this.model, this.getOption('modelEvents'));
this.unbindEntityEvents(this.collection, this.getOption('collectionEvents'));
_.each(this._behaviors, function(behavior) {
behavior.unbindEntityEvents(this.model, behavior.getOption('modelEvents'));
behavior.unbindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
}, this);
return this;
},

Internalhelpermethodtoverifywhethertheviewhasntbeendestroyed
_ensureViewIsIntact: function() {
if (this.isDestroyed) {
throw new Marionette.Error({
name: 'ViewDestroyedError',
message: 'View (cid: "' + this.cid + '") has already been destroyed and cannot be used.'
});
}
},

Defaultdestroyimplementation,forremovingaviewfromthe
DOMandunbindingit.Regionswillcallthismethod
foryou.YoucanspecifyanonDestroymethodinyourviewto
addcustomcodethatiscalledaftertheviewisdestroyed.
destroy: function() {
if (this.isDestroyed) { return this; }

http://marionettejs.com/annotatedsrc/backbone.marionette.html

24/51

4/1/2015

backbone.marionette.js

var args = _.toArray(arguments);


this.triggerMethod.apply(this, ['before:destroy'].concat(args));

markasdestroyedbeforedoingtheactualdestroy,to
preventinfiniteloopswithindestroyeventhandlers
thataretryingtodestroyotherviews
this.isDestroyed = true;
this.triggerMethod.apply(this, ['destroy'].concat(args));

unbindUIelements
this.unbindUIElements();
this.isRendered = false;

removetheviewfromtheDOM
this.remove();

Calldestroyoneachbehaviorafter
destroyingtheview.
Thisunbindseventlisteners
thatbehaviorshaveregisteredfor.
_.invoke(this._behaviors, 'destroy', args);
return this;
},
bindUIElements: function() {
this._bindUIElements();
_.invoke(this._behaviors, this._bindUIElements);
},

Thismethodbindstheelementsspecifiedintheuihashinsidetheviewscodewith
theassociatedjQueryselectors.
_bindUIElements: function() {
if (!this.ui) { return; }

storetheuihashin_uiBindingssotheycanberesetlater
andsorerenderingtheviewwillbeabletofindthebindings
if (!this._uiBindings) {
this._uiBindings = this.ui;
}

getthebindingsresult,asafunctionorotherwise
var bindings = _.result(this, '_uiBindings');

emptytheuisowedonthaveanythingtostartwith
this.ui = {};

bindeachoftheselectors
_.each(bindings, function(selector, key) {
this.ui[key] = this.$(selector);
}, this);
},

Thismethodunbindstheelementsspecifiedintheuihash
unbindUIElements: function() {
this._unbindUIElements();
_.invoke(this._behaviors, this._unbindUIElements);
},
_unbindUIElements: function() {
if (!this.ui || !this._uiBindings) { return; }

deletealloftheexistinguibindings
_.each(this.ui, function($el, name) {
delete this.ui[name];
}, this);

resettheuielementtotheoriginalbindingsconfiguration
this.ui = this._uiBindings;
delete this._uiBindings;
},

http://marionettejs.com/annotatedsrc/backbone.marionette.html

25/51

4/1/2015

backbone.marionette.js

InternalmethodtocreateaneventhandlerforagiventriggerDeflike
click:foo
_buildViewTrigger: function(triggerDef) {
var hasOptions = _.isObject(triggerDef);
var options = _.defaults({}, (hasOptions ? triggerDef : {}), {
preventDefault: true,
stopPropagation: true
});
var eventName = hasOptions ? options.event : triggerDef;
return function(e) {
if (e) {
if (e.preventDefault && options.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation && options.stopPropagation) {
e.stopPropagation();
}
}
var args = {
view: this,
model: this.model,
collection: this.collection
};
this.triggerMethod(eventName, args);
};
},
setElement: function() {
var ret = Backbone.View.prototype.setElement.apply(this, arguments);

proxybehavior$eltotheviews$el.
Thisisneededbecauseaviews$elproxy
isnotsetuntilaftersetElementiscalled.
_.invoke(this._behaviors, 'proxyViewProperties', this);
return ret;
},

importthetriggerMethodtotriggereventswithcorresponding
methodsifthemethodexists
triggerMethod: function() {
var ret = Marionette._triggerMethod(this, arguments);
this._triggerEventOnBehaviors(arguments);
this._triggerEventOnParentLayout(arguments[0], _.rest(arguments));
return ret;
},
_triggerEventOnBehaviors: function(args) {
var triggerMethod = Marionette._triggerMethod;
var behaviors = this._behaviors;

Usegoodolforasthisisaveryhotfunction
for (var i = 0, length = behaviors && behaviors.length; i < length; i++) {
triggerMethod(behaviors[i], args);
}
},
_triggerEventOnParentLayout: function(eventName, args) {
var layoutView = this._parentLayoutView();
if (!layoutView) {
return;
}

invoketriggerMethodonparentview
var eventPrefix = Marionette.getOption(layoutView, 'childViewEventPrefix');
var prefixedEventName = eventPrefix + ':' + eventName;
Marionette._triggerMethod(layoutView, [prefixedEventName, this].concat(args));

calltheparentviewschildEventshandler
var childEvents = Marionette.getOption(layoutView, 'childEvents');
var normalizedChildEvents = layoutView.normalizeMethods(childEvents);
if (!!normalizedChildEvents && _.isFunction(normalizedChildEvents[eventName])) {
normalizedChildEvents[eventName].apply(layoutView, [this].concat(args));
}
},

http://marionettejs.com/annotatedsrc/backbone.marionette.html

26/51

4/1/2015

backbone.marionette.js

Thismethodreturnsanyviewsthatareimmediate
childrenofthisview
_getImmediateChildren: function() {
return [];
},

Returnsanarrayofeverynestedviewwithinthisview
_getNestedViews: function() {
var children = this._getImmediateChildren();
if (!children.length) { return children; }
return _.reduce(children, function(memo, view) {
if (!view._getNestedViews) { return memo; }
return memo.concat(view._getNestedViews());
}, children);
},

Internalutilityforbuildinganancestor
viewtreelist.
_getAncestors: function() {
var ancestors = [];
var parent = this._parent;
while (parent) {
ancestors.push(parent);
parent = parent._parent;
}
return ancestors;
},

Returnsthecontainingparentview.
_parentLayoutView: function() {
var ancestors = this._getAncestors();
return _.find(ancestors, function(parent) {
return parent instanceof Marionette.LayoutView;
});
},

ImportsthenormalizeMethodstotransformhashesof
events=>functionreferences/namestoahashofevents=>functionreferences
normalizeMethods: Marionette.normalizeMethods,

Ahandywaytomergepassedinoptionsontotheinstance
mergeOptions: Marionette.mergeOptions,

ProxygetOptiontoenablegettingoptionsfromthisorthis.optionsbyname.
getOption: Marionette.proxyGetOption,

ProxybindEntityEventstoenablebindingviewseventsfromanotherentity.
bindEntityEvents: Marionette.proxyBindEntityEvents,

ProxyunbindEntityEventstoenableunbindingviewseventsfromanotherentity.
unbindEntityEvents: Marionette.proxyUnbindEntityEvents
});

ITEMVIEW

Asingleitemviewimplementationthatcontainscodeforrendering
withunderscore.jstemplates,serializingtheviewsmodelorcollection,
andcallingseveralmethodsonextendedviews,suchasonRender.
Marionette.ItemView = Marionette.View.extend({

Settinguptheinheritancechainwhichallowschangesto
Marionette.View.prototype.constructorwhichallowsoverriding
constructor: function() {
Marionette.View.apply(this, arguments);
},

Serializethemodelorcollectionfortheview.Ifamodelis

http://marionettejs.com/annotatedsrc/backbone.marionette.html

27/51

4/1/2015

backbone.marionette.js

found,theviewsserializeModeliscalled.Ifacollectionisfound,
eachmodelinthecollectionisserializedbycalling
theviewsserializeCollectionandputintoanitemsarrayin
theresultingdata.Ifbotharefound,defaultstothemodel.
YoucanoverridetheserializeDatamethodinyourownviewdefinition,
toprovidecustomserializationforyourviewsdata.
serializeData: function() {
if (!this.model && !this.collection) {
return {};
}
var args = [this.model || this.collection];
if (arguments.length) {
args.push.apply(args, arguments);
}
if (this.model) {
return this.serializeModel.apply(this, args);
} else {
return {
items: this.serializeCollection.apply(this, args)
};
}
},

Serializeacollectionbyserializingeachofitsmodels.
serializeCollection: function(collection) {
return collection.toJSON.apply(collection, _.rest(arguments));
},

Rendertheview,defaultingtounderscore.jstemplates.
Youcanoverridethisinyourviewdefinitiontoprovide
averyspecificrenderingforyourview.Ingeneral,though,
youshouldoverridetheMarionette.Rendererobjectto
changehowMarionetterendersviews.
render: function() {
this._ensureViewIsIntact();
this.triggerMethod('before:render', this);
this._renderTemplate();
this.isRendered = true;
this.bindUIElements();
this.triggerMethod('render', this);
return this;
},

Internalmethodtorenderthetemplatewiththeserializeddata
andtemplatehelpersviatheMarionette.Rendererobject.
ThrowsanUndefinedTemplateErrorerrorifthetemplateis
anyfalselyvaluebutliteralfalse.
_renderTemplate: function() {
var template = this.getTemplate();

Allowtemplatelessitemviews
if (template === false) {
return;
}
if (!template) {
throw new Marionette.Error({
name: 'UndefinedTemplateError',
message: 'Cannot render the template since it is null or undefined.'
});
}

Addinentitydataandtemplatehelpers
var data = this.mixinTemplateHelpers(this.serializeData());

Renderandaddtoel
var html = Marionette.Renderer.render(template, data, this);
this.attachElContent(html);
return this;
},

Attachesthecontentofagivenview.
Thismethodcanbeoverriddentooptimizerendering,
ortorenderinanonstandardway.

http://marionettejs.com/annotatedsrc/backbone.marionette.html

28/51

4/1/2015

backbone.marionette.js

Forexample,usinginnerHTMLinsteadof$el.html
attachElContent: function(html) {
this.el.innerHTML = html;
return this;
}
attachElContent: function(html) {
this.$el.html(html);
return this;
}
});
/* jshint maxstatements: 14 */

COLLECTIONVIEW

AviewthatiteratesoveraBackbone.Collection
andrendersanindividualchildviewforeachmodel.
Marionette.CollectionView = Marionette.View.extend({

usedastheprefixforchildviewevents
thatareforwardedthroughthecollectionview
childViewEventPrefix: 'childview',

flagformaintainingthesortedorderofthecollection
sort: true,

constructor
optiontopass{sort: false}topreventtheCollectionViewfrom
maintainingthesortedorderofthecollection.
ThiswillfallbackontoappendingchildViewstotheend.
optiontopass{comparator: compFunction()}toallowtheCollectionView
touseacustomsortorderforthecollection.
constructor: function(options) {
this.once('render', this._initialEvents);
this._initChildViewStorage();
Marionette.View.apply(this, arguments);
this.on('show', this._onShowCalled);
this.initRenderBuffer();
},

Insteadofinsertingelementsonebyoneintothepage,
itsmuchmoreperformanttoinsertelementsintoadocument
fragmentandtheninsertthatdocumentfragmentintothepage
initRenderBuffer: function() {
this._bufferedChildren = [];
},
startBuffering: function() {
this.initRenderBuffer();
this.isBuffering = true;
},
endBuffering: function() {
this.isBuffering = false;
this._triggerBeforeShowBufferedChildren();
this.attachBuffer(this);
this._triggerShowBufferedChildren();
this.initRenderBuffer();
},
_triggerBeforeShowBufferedChildren: function() {
if (this._isShown) {
_.each(this._bufferedChildren, _.partial(this._triggerMethodOnChild, 'before:show'));
}
},
_triggerShowBufferedChildren: function() {
if (this._isShown) {
_.each(this._bufferedChildren, _.partial(this._triggerMethodOnChild, 'show'));
this._bufferedChildren = [];

http://marionettejs.com/annotatedsrc/backbone.marionette.html

29/51

4/1/2015

backbone.marionette.js

}
},

Internalmethodfor_.eachloopstocallMarionette.triggerMethodOnon
achildview
_triggerMethodOnChild: function(event, childView) {
Marionette.triggerMethodOn(childView, event);
},

Configuredtheinitialeventsthatthecollectionview
bindsto.
_initialEvents: function() {
if (this.collection) {
this.listenTo(this.collection, 'add', this._onCollectionAdd);
this.listenTo(this.collection, 'remove', this._onCollectionRemove);
this.listenTo(this.collection, 'reset', this.render);
if (this.getOption('sort')) {
this.listenTo(this.collection, 'sort', this._sortViews);
}
}
},

Handleachildaddedtothecollection
_onCollectionAdd: function(child, collection, opts) {
var index;
if (opts.at !== undefined) {
index = opts.at;
} else {
index = _.indexOf(this._filteredSortedModels(), child);
}
if (this._shouldAddChild(child, index)) {
this.destroyEmptyView();
var ChildView = this.getChildView(child);
this.addChild(child, ChildView, index);
}
},

getthechildviewbymodelitholds,andremoveit
_onCollectionRemove: function(model) {
var view = this.children.findByModel(model);
this.removeChildView(view);
this.checkEmpty();
},
_onShowCalled: function() {
this.children.each(_.partial(this._triggerMethodOnChild, 'show'));
},

Renderchildrenviews.Overridethismethodto
provideyourownimplementationofarenderfunctionfor
thecollectionview.
render: function() {
this._ensureViewIsIntact();
this.triggerMethod('before:render', this);
this._renderChildren();
this.isRendered = true;
this.triggerMethod('render', this);
return this;
},

ReorderDOMaftersorting.Whenyourelementsrendering
donotusetheirindex,youcanpassreorderOnSort:true
toonlyreordertheDOMafterasortinsteadofrendering
allthecollectionView
reorder: function() {
var children = this.children;
var models = this._filteredSortedModels();
var modelsChanged = _.find(models, function(model) {
return !children.findByModel(model);
});

Ifthemodelsweredisplayinghavechangedduetofiltering
Weneedtoaddand/orremovechildviews
Sorenderasnormal
if (modelsChanged) {
this.render();
} else {

gettheDOMnodesinthesameorderasthemodels

http://marionettejs.com/annotatedsrc/backbone.marionette.html

30/51

4/1/2015

backbone.marionette.js
var els = _.map(models, function(model) {
return children.findByModel(model).el;
});

sinceappendmoveselementsthatarealreadyintheDOM,
appendingtheelementswilleffectivelyreorderthem
this.triggerMethod('before:reorder');
this._appendReorderedChildren(els);
this.triggerMethod('reorder');
}
},

Renderviewaftersorting.Overridethismethodto
changehowtheviewrendersafterasortonthecollection.
AnexampleofthiswouldbetoonlyrenderChildreninaCompositeView
ratherthanthefullview.
resortView: function() {
if (Marionette.getOption(this, 'reorderOnSort')) {
this.reorder();
} else {
this.render();
}
},

Internalmethod.Thischecksforanychangesintheorderofthecollection.
Iftheindexofanyviewdoesntmatch,itwillrender.
_sortViews: function() {
var models = this._filteredSortedModels();

checkforanychangesinsortorderofviews
var orderChanged = _.find(models, function(item, index) {
var view = this.children.findByModel(item);
return !view || view._index !== index;
}, this);
if (orderChanged) {
this.resortView();
}
},

InternalreferencetowhatindexaemptyViewis.
_emptyViewIndex: -1,

Internalmethod.SeparatedsothatCompositeViewcanappendtothechildViewContainer
ifnecessary
_appendReorderedChildren: function(children) {
this.$el.append(children);
},

Internalmethod.SeparatedsothatCompositeViewcanhave
morecontrolovereventsbeingtriggered,aroundtherendering
process
_renderChildren: function() {
this.destroyEmptyView();
this.destroyChildren();
if (this.isEmpty(this.collection)) {
this.showEmptyView();
} else {
this.triggerMethod('before:render:collection', this);
this.startBuffering();
this.showCollection();
this.endBuffering();
this.triggerMethod('render:collection', this);

Ifwehaveshownchildrenandnonehavepassedthefilter,showtheemptyview
if (this.children.isEmpty()) {
this.showEmptyView();
}
}
},

Internalmethodtoloopthroughcollectionandshoweachchildview.
showCollection: function() {
var ChildView;
var models = this._filteredSortedModels();
_.each(models, function(child, index) {

http://marionettejs.com/annotatedsrc/backbone.marionette.html

31/51

4/1/2015

backbone.marionette.js

ChildView = this.getChildView(child);
this.addChild(child, ChildView, index);
}, this);
},

Allowthecollectiontobesortedbyacustomviewcomparator
_filteredSortedModels: function() {
var models;
var viewComparator = this.getViewComparator();
if (viewComparator) {
if (_.isString(viewComparator) || viewComparator.length === 1) {
models = this.collection.sortBy(viewComparator, this);
} else {
models = _.clone(this.collection.models).sort(_.bind(viewComparator, this));
}
} else {
models = this.collection.models;
}

Filteraftersortingincasethefilterusestheindex
if (this.getOption('filter')) {
models = _.filter(models, function(model, index) {
return this._shouldAddChild(model, index);
}, this);
}
return models;
},

Internalmethodtoshowanemptyviewinplaceof
acollectionofchildviews,whenthecollectionisempty
showEmptyView: function() {
var EmptyView = this.getEmptyView();
if (EmptyView && !this._showingEmptyView) {
this.triggerMethod('before:render:empty');
this._showingEmptyView = true;
var model = new Backbone.Model();
this.addEmptyView(model, EmptyView);
this.triggerMethod('render:empty');
}
},

InternalmethodtodestroyanexistingemptyViewinstance
ifoneexists.Calledwhenacollectionviewhasbeen
renderedempty,andthenachildisaddedtothecollection.
destroyEmptyView: function() {
if (this._showingEmptyView) {
this.triggerMethod('before:remove:empty');
this.destroyChildren();
delete this._showingEmptyView;
this.triggerMethod('remove:empty');
}
},

Retrievetheemptyviewclass
getEmptyView: function() {
return this.getOption('emptyView');
},

RenderandshowtheemptyView.SimilartoaddChildmethod
butadd:childeventsarenotfired,andtheeventfrom
emptyViewarenotforwarded
addEmptyView: function(child, EmptyView) {

gettheemptyViewOptions,fallingbacktochildViewOptions
var emptyViewOptions = this.getOption('emptyViewOptions') ||
this.getOption('childViewOptions');
if (_.isFunction(emptyViewOptions)) {
emptyViewOptions = emptyViewOptions.call(this, child, this._emptyViewIndex);
}

buildtheemptyview
var view = this.buildChildView(child, EmptyView, emptyViewOptions);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

32/51

4/1/2015

backbone.marionette.js

view._parent = this;

ProxyemptyViewevents
this.proxyChildEvents(view);

triggerthebefore:showeventonviewifthecollectionview
hasalreadybeenshown
if (this._isShown) {
Marionette.triggerMethodOn(view, 'before:show');
}

StoretheemptyViewlikeachildViewsowecanproperly
removeand/orcloseitlater
this.children.add(view);

Renderitandshowit
this.renderChildView(view, this._emptyViewIndex);

calltheshowmethodifthecollectionview
hasalreadybeenshown
if (this._isShown) {
Marionette.triggerMethodOn(view, 'show');
}
},

RetrievethechildViewclass,eitherfromthis.options.childView
orfromthechildViewintheobjectdefinition.Theoptions
takesprecedence.
Thismethodreceivesthemodelthatwillbepassedtotheinstance
createdfromthischildView.Overridingmethodsmayusethechild
todeterminewhatchildViewclasstoreturn.
getChildView: function(child) {
var childView = this.getOption('childView');
if (!childView) {
throw new Marionette.Error({
name: 'NoChildViewError',
message: 'A "childView" must be specified'
});
}
return childView;
},

Renderthechildsviewandaddittothe
HTMLforthecollectionviewatagivenindex.
Thiswillalsoupdatetheindicesoflaterviewsinthecollection
inordertokeepthechildreninsyncwiththecollection.
addChild: function(child, ChildView, index) {
var childViewOptions = this.getOption('childViewOptions');
childViewOptions = Marionette._getValue(childViewOptions, this, [child, index]);
var view = this.buildChildView(child, ChildView, childViewOptions);

incrementindicesofviewsafterthisone
this._updateIndices(view, true, index);
this._addChildView(view, index);
view._parent = this;
return view;
},

Internalmethod.Thisdecrementsorincrementstheindicesofviewsafterthe
added/removedviewtokeepinsyncwiththecollection.
_updateIndices: function(view, increment, index) {
if (!this.getOption('sort')) {
return;
}
if (increment) {

assigntheindextotheview
view._index = index;
}

http://marionettejs.com/annotatedsrc/backbone.marionette.html

33/51

4/1/2015

backbone.marionette.js

updatetheindexesofviewsafterthisone
this.children.each(function(laterView) {
if (laterView._index >= view._index) {
laterView._index += increment ? 1 : -1;
}
});
},

InternalMethod.Addtheviewtochildrenandrenderitat
thegivenindex.
_addChildView: function(view, index) {

setupthechildvieweventforwarding
this.proxyChildEvents(view);
this.triggerMethod('before:add:child', view);

triggerthebefore:showeventonviewifthecollectionview
hasalreadybeenshown
if (this._isShown && !this.isBuffering) {
Marionette.triggerMethodOn(view, 'before:show');
}

Storethechildviewitselfsowecanproperly
removeand/ordestroyitlater
this.children.add(view);
this.renderChildView(view, index);
if (this._isShown && !this.isBuffering) {
Marionette.triggerMethodOn(view, 'show');
}
this.triggerMethod('add:child', view);
},

renderthechildview
renderChildView: function(view, index) {
view.render();
this.attachHtml(this, view, index);
return view;
},

BuildachildViewforamodelinthecollection.
buildChildView: function(child, ChildViewClass, childViewOptions) {
var options = _.extend({model: child}, childViewOptions);
return new ChildViewClass(options);
},

Removethechildviewanddestroyit.
Thisfunctionalsoupdatestheindicesof
laterviewsinthecollectioninordertokeep
thechildreninsyncwiththecollection.
removeChildView: function(view) {
if (view) {
this.triggerMethod('before:remove:child', view);

calldestroyorremove,dependingonwhichisfound
if (view.destroy) {
view.destroy();
} else if (view.remove) {
view.remove();
}
delete view._parent;
this.stopListening(view);
this.children.remove(view);
this.triggerMethod('remove:child', view);

decrementtheindexofviewsafterthisone
this._updateIndices(view, false);
}
return view;
},

checkifthecollectionisempty
isEmpty: function() {

http://marionettejs.com/annotatedsrc/backbone.marionette.html

34/51

4/1/2015

backbone.marionette.js

return !this.collection || this.collection.length === 0;


},

Ifempty,showtheemptyview
checkEmpty: function() {
if (this.isEmpty(this.collection)) {
this.showEmptyView();
}
},

YoumightneedtooverridethisifyouveoverriddenattachHtml
attachBuffer: function(collectionView) {
collectionView.$el.append(this._createBuffer(collectionView));
},

Createafragmentbufferfromthecurrentlybufferedchildren
_createBuffer: function(collectionView) {
var elBuffer = document.createDocumentFragment();
_.each(collectionView._bufferedChildren, function(b) {
elBuffer.appendChild(b.el);
});
return elBuffer;
},

AppendtheHTMLtothecollectionsel.
Overridethismethodtodosomethingother
than.append.
attachHtml: function(collectionView, childView, index) {
if (collectionView.isBuffering) {

bufferinghappensonreseteventsandinitialrenders
inordertoreducethenumberofinsertsintothe
document,whichareexpensive.
collectionView._bufferedChildren.splice(index, 0, childView);
} else {

Ifwevealreadyrenderedthemaincollection,append
thenewchildintothecorrectorderifweneedto.Otherwise
appendtotheend.
if (!collectionView._insertBefore(childView, index)) {
collectionView._insertAfter(childView);
}
}
},

Internalmethod.Checkwhetherweneedtoinserttheviewinto
thecorrectposition.
_insertBefore: function(childView, index) {
var currentView;
var findPosition = this.getOption('sort') && (index < this.children.length - 1);
if (findPosition) {

Findtheviewafterthisone
currentView = this.children.find(function(view) {
return view._index === index + 1;
});
}
if (currentView) {
currentView.$el.before(childView.el);
return true;
}
return false;
},

Internalmethod.Appendaviewtotheendofthe$el
_insertAfter: function(childView) {
this.$el.append(childView.el);
},

Internalmethodtosetupthechildrenobjectfor
storingallofthechildviews
_initChildViewStorage: function() {
this.children = new Backbone.ChildViewContainer();
},

http://marionettejs.com/annotatedsrc/backbone.marionette.html

35/51

4/1/2015

backbone.marionette.js

Handlecleanupandotherdestroyingneedsforthecollectionofviews
destroy: function() {
if (this.isDestroyed) { return this; }
this.triggerMethod('before:destroy:collection');
this.destroyChildren();
this.triggerMethod('destroy:collection');
return Marionette.View.prototype.destroy.apply(this, arguments);
},

Destroythechildviewsthatthiscollectionview
isholdingonto,ifany
destroyChildren: function() {
var childViews = this.children.map(_.identity);
this.children.each(this.removeChildView, this);
this.checkEmpty();
return childViews;
},

Returntrueifthegivenchildshouldbeshown
Returnfalseotherwise
Thefilterwillbepassed(child,index,collection)
Where
childisthegivenmodel
indexistheindexofthatmodelinthecollection
collectionisthecollectionreferencedbythisCollectionView
_shouldAddChild: function(child, index) {
var filter = this.getOption('filter');
return !_.isFunction(filter) || filter.call(this, child, index, this.collection);
},

Setupthechildvieweventforwarding.Usesachildview:
prefixinfrontofallforwardedevents.
proxyChildEvents: function(view) {
var prefix = this.getOption('childViewEventPrefix');

Forwardallchildvieweventsthroughtheparent,
prependingchildview:totheeventname
this.listenTo(view, 'all', function() {
var args = _.toArray(arguments);
var rootEvent = args[0];
var childEvents = this.normalizeMethods(_.result(this, 'childEvents'));
args[0] = prefix + ':' + rootEvent;
args.splice(1, 0, view);

callcollectionViewchildEventifdefined
if (typeof childEvents !== 'undefined' && _.isFunction(childEvents[rootEvent])) {
childEvents[rootEvent].apply(this, args.slice(1));
}
this.triggerMethod.apply(this, args);
});
},
_getImmediateChildren: function() {
return _.values(this.children._views);
},
getViewComparator: function() {
return this.getOption('viewComparator');
}
});
/* jshint maxstatements: 17, maxlen: 117 */

COMPOSITEVIEW

Usedforrenderingabranchleaf,hierarchicalstructure.
ExtendsdirectlyfromCollectionViewandalsorendersan
achildviewasmodelView,forthetopleaf
Marionette.CompositeView = Marionette.CollectionView.extend({

Settinguptheinheritancechainwhichallowschangesto
Marionette.CollectionView.prototype.constructorwhichallowsoverriding
optiontopass{sort:false}topreventtheCompositeViewfrom

http://marionettejs.com/annotatedsrc/backbone.marionette.html

36/51

4/1/2015

backbone.marionette.js

maintainingthesortedorderofthecollection.
ThiswillfallbackontoappendingchildViewstotheend.
constructor: function() {
Marionette.CollectionView.apply(this, arguments);
},

Configuredtheinitialeventsthatthecompositeview
bindsto.Overridethismethodtopreventtheinitial
events,ortoaddyourowninitialevents.
_initialEvents: function() {

Bindonlyaftercompositeviewisrenderedtoavoidaddingchildviews
tononexistentchildViewContainer
if (this.collection) {
this.listenTo(this.collection, 'add', this._onCollectionAdd);
this.listenTo(this.collection, 'remove', this._onCollectionRemove);
this.listenTo(this.collection, 'reset', this._renderChildren);
if (this.getOption('sort')) {
this.listenTo(this.collection, 'sort', this._sortViews);
}
}
},

RetrievethechildViewtobeusedwhenrenderingeachof
theitemsinthecollection.Thedefaultistoreturn
this.childVieworMarionette.CompositeViewifnochildView
hasbeendefined
getChildView: function(child) {
var childView = this.getOption('childView') || this.constructor;
return childView;
},

Serializethemodelfortheview.
YoucanoverridetheserializeDatamethodinyourownview
definition,toprovidecustomserializationforyourviewsdata.
serializeData: function() {
var data = {};
if (this.model) {
data = _.partial(this.serializeModel, this.model).apply(this, arguments);
}
return data;
},

Rendersthemodelandthecollection.
render: function() {
this._ensureViewIsIntact();
this._isRendering = true;
this.resetChildViewContainer();
this.triggerMethod('before:render', this);
this._renderTemplate();
this._renderChildren();
this._isRendering = false;
this.isRendered = true;
this.triggerMethod('render', this);
return this;
},
_renderChildren: function() {
if (this.isRendered || this._isRendering) {
Marionette.CollectionView.prototype._renderChildren.call(this);
}
},

Rendertheroottemplatethatthechildren
viewsareappendedto
_renderTemplate: function() {
var data = {};
data = this.serializeData();
data = this.mixinTemplateHelpers(data);
this.triggerMethod('before:render:template');
var template = this.getTemplate();
var html = Marionette.Renderer.render(template, data, this);
this.attachElContent(html);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

37/51

4/1/2015

backbone.marionette.js

theuibindingsisdonehereandnotattheendofrendersincethey
willnotbeavailableuntilafterthemodelisrendered,butshouldbe
availablebeforethecollectionisrendered.
this.bindUIElements();
this.triggerMethod('render:template');
},

Attachesthecontentoftheroot.
Thismethodcanbeoverriddentooptimizerendering,
ortorenderinanonstandardway.
Forexample,usinginnerHTMLinsteadof$el.html
attachElContent: function(html) {
this.el.innerHTML = html;
return this;
}
attachElContent: function(html) {
this.$el.html(html);
return this;
},

YoumightneedtooverridethisifyouveoverriddenattachHtml
attachBuffer: function(compositeView) {
var $container = this.getChildViewContainer(compositeView);
$container.append(this._createBuffer(compositeView));
},

Internalmethod.Appendaviewtotheendofthe$el.
OveriddenfromCollectionViewtoensureviewisappendedto
childViewContainer
_insertAfter: function(childView) {
var $container = this.getChildViewContainer(this, childView);
$container.append(childView.el);
},

Internalmethod.AppendreorderedchildView.
OveriddenfromCollectionViewtoensurereorderedviews
areappendedtochildViewContainer
_appendReorderedChildren: function(children) {
var $container = this.getChildViewContainer(this);
$container.append(children);
},

Internalmethodtoensurean$childViewContainerexists,forthe
attachHtmlmethodtouse.
getChildViewContainer: function(containerView, childView) {
if ('$childViewContainer' in containerView) {
return containerView.$childViewContainer;
}
var container;
var childViewContainer = Marionette.getOption(containerView, 'childViewContainer');
if (childViewContainer) {
var selector = Marionette._getValue(childViewContainer, containerView);
if (selector.charAt(0) === '@' && containerView.ui) {
container = containerView.ui[selector.substr(4)];
} else {
container = containerView.$(selector);
}
if (container.length <= 0) {
throw new Marionette.Error({
name: 'ChildViewContainerMissingError',
message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer
});
}
} else {
container = containerView.$el;
}
containerView.$childViewContainer = container;
return container;
},

Internalmethodtoresetthe$childViewContaineronrender
resetChildViewContainer: function() {

http://marionettejs.com/annotatedsrc/backbone.marionette.html

38/51

4/1/2015

backbone.marionette.js

if (this.$childViewContainer) {
delete this.$childViewContainer;
}
}
});

LAYOUTVIEW

UsedformanagingapplicationlayoutViews,nestedlayoutViewsand
multipleregionswithinanapplicationorsubapplication.
AspecializedviewclassthatrendersanareaofHTMLandthen
attachesRegioninstancestothespecifiedregions.
Usedforcompositeviewmanagementandsubapplicationareas.
Marionette.LayoutView = Marionette.ItemView.extend({
regionClass: Marionette.Region,
options: {
destroyImmediate: false
},

usedastheprefixforchildviewevents
thatareforwardedthroughthelayoutview
childViewEventPrefix: 'childview',

Ensuretheregionsareavailablewhentheinitializemethod
iscalled.
constructor: function(options) {
options = options || {};
this._firstRender = true;
this._initializeRegions(options);
Marionette.ItemView.call(this, options);
},

LayoutViewsrenderwillusetheexistingregionobjectsthe
firsttimeitiscalled.Subsequentcallswilldestroythe
viewsthattheregionsareshowingandthenresettheel
fortheregionstothenewlyrenderedDOMelements.
render: function() {
this._ensureViewIsIntact();
if (this._firstRender) {

ifthisisthefirstrender,dontdoanythingto
resettheregions
this._firstRender = false;
} else {

Ifthisisnotthefirstrendercall,thenweneedto
reinitializetheelforeachregion
this._reInitializeRegions();
}
return Marionette.ItemView.prototype.render.apply(this, arguments);
},

Handledestroyingregions,andthendestroytheviewitself.
destroy: function() {
if (this.isDestroyed) { return this; }

2134:REMOVEPARENTELEMENTBEFOREDESTROYINGTHECHILDVIEWS,SO
removingthechildviewsdoesntretriggerrepaints
if (this.getOption('destroyImmediate') === true) {
this.$el.remove();
}
this.regionManager.destroy();
return Marionette.ItemView.prototype.destroy.apply(this, arguments);
},
showChildView: function(regionName, view) {
return this.getRegion(regionName).show(view);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

39/51

4/1/2015

backbone.marionette.js

},
getChildView: function(regionName) {
return this.getRegion(regionName).currentView;
},

Addasingleregion,byname,tothelayoutView
addRegion: function(name, definition) {
var regions = {};
regions[name] = definition;
return this._buildRegions(regions)[name];
},

Addmultipleregionsasa{name:definition,name2:def2}objectliteral
addRegions: function(regions) {
this.regions = _.extend({}, this.regions, regions);
return this._buildRegions(regions);
},

RemoveasingleregionfromtheLayoutView,byname
removeRegion: function(name) {
delete this.regions[name];
return this.regionManager.removeRegion(name);
},

Providesalternativeaccesstoregions
Acceptstheregionname
getRegion(main)
getRegion: function(region) {
return this.regionManager.get(region);
},

Getallregions
getRegions: function() {
return this.regionManager.getRegions();
},

internalmethodtobuildregions
_buildRegions: function(regions) {
var defaults = {
regionClass: this.getOption('regionClass'),
parentEl: _.partial(_.result, this, 'el')
};
return this.regionManager.addRegions(regions, defaults);
},

Internalmethodtoinitializetheregionsthathavebeendefinedina
regionsattributeonthislayoutView.
_initializeRegions: function(options) {
var regions;
this._initRegionManager();
regions = Marionette._getValue(this.regions, this, [options]) || {};

Enableuserstodefineregionsasinstanceoptions.
var regionOptions = this.getOption.call(options, 'regions');

enableregionoptionstobeafunction
regionOptions = Marionette._getValue(regionOptions, this, [options]);
_.extend(regions, regionOptions);

Normalizeregionselectorshashtoallow
[email protected].
regions = this.normalizeUIValues(regions, ['selector', 'el']);
this.addRegions(regions);
},

Internalmethodtoreinitializealloftheregionsbyupdatingtheelthat
theypointto
_reInitializeRegions: function() {
this.regionManager.invoke('reset');
},

http://marionettejs.com/annotatedsrc/backbone.marionette.html

40/51

4/1/2015

backbone.marionette.js

EnableeasyoverridingofthedefaultRegionManager
forcustomizedregioninteractionsandbusinessspecific
viewlogicforbettercontroloversingleregions.
getRegionManager: function() {
return new Marionette.RegionManager();
},

Internalmethodtoinitializetheregionmanager
andallregionsinit
_initRegionManager: function() {
this.regionManager = this.getRegionManager();
this.regionManager._parent = this;
this.listenTo(this.regionManager, 'before:add:region', function(name) {
this.triggerMethod('before:add:region', name);
});
this.listenTo(this.regionManager, 'add:region', function(name, region) {
this[name] = region;
this.triggerMethod('add:region', name, region);
});
this.listenTo(this.regionManager, 'before:remove:region', function(name) {
this.triggerMethod('before:remove:region', name);
});
this.listenTo(this.regionManager, 'remove:region', function(name, region) {
delete this[name];
this.triggerMethod('remove:region', name, region);
});
},
_getImmediateChildren: function() {
return _.chain(this.regionManager.getRegions())
.pluck('currentView')
.compact()
.value();
}
});

BEHAVIOR

ABehaviorisanisolatedsetofDOM/
userinteractionsthatcanbemixedintoanyView.
BehaviorsallowyoutoblackboxViewspecificinteractions
intoportablelogicalchunks,keepingyourviewssimpleandyourcodeDRY.
Marionette.Behavior = Marionette.Object.extend({
constructor: function(options, view) {

Setupreferencetotheview.
thiscomesinhandlewhenabehavior
wantstodirectlytalkupthechain
totheview.
this.view = view;
this.defaults = _.result(this, 'defaults') || {};
this.options = _.extend({}, this.defaults, options);

ConstructaninternalUIhashusing
theviewsUIhashandthenthebehaviorsUIhash.
ThisallowstheusertouseUIhashelements
definedintheparentviewaswellasthose
definedinthegivenbehavior.
this.ui = _.extend({}, _.result(view, 'ui'), _.result(this, 'ui'));
Marionette.Object.apply(this, arguments);
},

proxybehavior$methodtotheview
thisisusefulfordoingjqueryDOMlookups
scopedtobehaviorsview.
$: function() {
return this.view.$.apply(this.view, arguments);
},

Stopsthebehaviorfromlisteningtoevents.
OverridesObject#destroytopreventadditionaleventsfrombeingtriggered.
destroy: function() {

http://marionettejs.com/annotatedsrc/backbone.marionette.html

41/51

4/1/2015

backbone.marionette.js

this.stopListening();
return this;
},
proxyViewProperties: function(view) {
this.$el = view.$el;
this.el = view.el;
}
});
/* jshint maxlen: 143 */

BEHAVIORS

Behaviorsisautilityclassthattakescareof
gluingyourbehaviorinstancestotheirgivenView.
Themostimportantpartofthisclassisthatyou
MUSToverridetheclasslevelbehaviorsLookup
methodforthingstoworkproperly.
Marionette.Behaviors = (function(Marionette, _) {

BorroweventsplitterfromBackbone
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
function Behaviors(view, behaviors) {
if (!_.isObject(view.behaviors)) {
return {};
}

Behaviorsdefinedonaviewcanbeaflatobjectliteral
oritcanbeafunctionthatreturnsanobject.
behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors'));

Wrapsseveraloftheviewsmethods
callingthemethodsfirstoneachbehavior
andtheneventuallycallingthemethodontheview.
Behaviors.wrap(view, behaviors, _.keys(methods));
return behaviors;
}
var methods = {
behaviorTriggers: function(behaviorTriggers, behaviors) {
var triggerBuilder = new BehaviorTriggersBuilder(this, behaviors);
return triggerBuilder.buildBehaviorTriggers();
},
behaviorEvents: function(behaviorEvents, behaviors) {
var _behaviorsEvents = {};
_.each(behaviors, function(b, i) {
var _events = {};
var behaviorEvents = _.clone(_.result(b, 'events')) || {};

Normalizebehavioreventshashtoallow
[email protected].
behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, getBehaviorsUI(b));
var j = 0;
_.each(behaviorEvents, function(behaviour, key) {
var match
= key.match(delegateEventSplitter);

Seteventnametobenamespacedusingtheviewcid,
thebehaviorindex,andthebehavioreventindex
togenerateanoncollidingeventnamespace
http://api.jquery.com/event.namespace/
var eventName = match[1] + '.' + [this.cid, i, j++, ' '].join('');
var selector = match[2];
var eventKey
var handler

= eventName + selector;
= _.isFunction(behaviour) ? behaviour : b[behaviour];

_events[eventKey] = _.bind(handler, b);


}, this);
_behaviorsEvents = _.extend(_behaviorsEvents, _events);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

42/51

4/1/2015

backbone.marionette.js
}, this);
return _behaviorsEvents;

}
};
_.extend(Behaviors, {

Placeholdermethodtobeextendedbytheuser.
Themethodshoulddefinetheobjectthatstoresthebehaviors.
i.e.
Marionette.Behaviors.behaviorsLookup: function() {
return App.Behaviors
}
behaviorsLookup: function() {
throw new Marionette.Error({
message: 'You must define where your behaviors are stored.',
url: 'marionette.behaviors.html#behaviorslookup'
});
},

Takescareofgettingthebehaviorclass
givenoptionsandakey.
Ifauserpassesinoptions.behaviorClass
defaulttousingthat.Otherwisedelegate
thelookuptotheusersbehaviorsLookupimplementation.
getBehaviorClass: function(options, key) {
if (options.behaviorClass) {
return options.behaviorClass;
}

Getbehaviorclasscanbeeitheraflatobjectoramethod
return Marionette._getValue(Behaviors.behaviorsLookup, this, [options, key])[key];
},

Iterateoverthebehaviorsobject,foreachbehavior
instantiateitandgetitsgroupedbehaviors.
parseBehaviors: function(view, behaviors) {
return _.chain(behaviors).map(function(options, key) {
var BehaviorClass = Behaviors.getBehaviorClass(options, key);
var behavior = new BehaviorClass(options, view);
var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors'));
return [behavior].concat(nestedBehaviors);
}).flatten().value();
},

Wrapviewinternalmethodssothattheydelegatetobehaviors.Forexample,
onDestroyshouldtriggerdestroyonallofthebehaviorsandthendestroyitself.
i.e.
view.delegateEvents = _.partial(methods.delegateEvents, view.delegateEvents, behaviors);
wrap: function(view, behaviors, methodNames) {
_.each(methodNames, function(methodName) {
view[methodName] = _.partial(methods[methodName], view[methodName], behaviors);
});
}
});

Classtobuildhandlersfortriggersonbehaviors
forviews
function BehaviorTriggersBuilder(view, behaviors) {
this._view
= view;
this._behaviors = behaviors;
this._triggers = {};
}
_.extend(BehaviorTriggersBuilder.prototype, {

Mainmethodtobuildthetriggershashwitheventkeysandhandlers
buildBehaviorTriggers: function() {
_.each(this._behaviors, this._buildTriggerHandlersForBehavior, this);
return this._triggers;
},

Internalmethodtobuildalltriggerhandlersforagivenbehavior
_buildTriggerHandlersForBehavior: function(behavior, i) {
var triggersHash = _.clone(_.result(behavior, 'triggers')) || {};

http://marionettejs.com/annotatedsrc/backbone.marionette.html

43/51

4/1/2015

backbone.marionette.js
triggersHash = Marionette.normalizeUIKeys(triggersHash, getBehaviorsUI(behavior));

_.each(triggersHash, _.bind(this._setHandlerForBehavior, this, behavior, i));


},

Internalmethodtocreateandassignthetriggerhandlerforagiven
behavior
_setHandlerForBehavior: function(behavior, i, eventName, trigger) {

Uniqueidentifierforthethis._triggershash
var triggerKey = trigger.replace(/^\S+/, function(triggerName) {
return triggerName + '.' + 'behaviortriggers' + i;
});
this._triggers[triggerKey] = this._view._buildViewTrigger(eventName);
}
});
function getBehaviorsUI(behavior) {
return behavior._uiBindings || behavior.ui;
}
return Behaviors;
})(Marionette, _);

APPROUTER

Reducetheboilerplatecodeofhandlingrouteevents
andthencallingasinglemethodonanotherobject.
Haveyourroutersconfiguredtocallthemethodon
yourobject,directly.
ConfigureanAppRouterwithappRoutes.
Approuterscanonlytakeonecontrollerobject.
Itisrecommendedthatyoudivideyourcontroller
objectsintosmallerpiecesofrelatedfunctionality
andhavemultiplerouters/controllers,insteadof
justonegiantrouterandcontroller.
YoucanalsoaddstandardroutestoanAppRouter.
Marionette.AppRouter = Backbone.Router.extend({
constructor: function(options) {
this.options = options || {};
Backbone.Router.apply(this, arguments);
var appRoutes = this.getOption('appRoutes');
var controller = this._getController();
this.processAppRoutes(controller, appRoutes);
this.on('route', this._processOnRoute, this);
},

SimilartoroutemethodonaBackboneRouterbut
methodiscalledonthecontroller
appRoute: function(route, methodName) {
var controller = this._getController();
this._addAppRoute(controller, route, methodName);
},

processtherouteeventandtriggertheonRoute
methodcall,ifitexists
_processOnRoute: function(routeName, routeArgs) {

makesureanonRoutebeforetryingtocallit
if (_.isFunction(this.onRoute)) {

findthepaththatmatchesthecurrentroute
var routePath = _.invert(this.getOption('appRoutes'))[routeName];
this.onRoute(routeName, routePath, routeArgs);
}
},

http://marionettejs.com/annotatedsrc/backbone.marionette.html

44/51

4/1/2015

backbone.marionette.js

InternalmethodtoprocesstheappRoutesforthe
router,andturnthemintoroutesthattriggerthe
specifiedmethodonthespecifiedcontroller.
processAppRoutes: function(controller, appRoutes) {
if (!appRoutes) { return; }
var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
_.each(routeNames, function(route) {
this._addAppRoute(controller, route, appRoutes[route]);
}, this);
},
_getController: function() {
return this.getOption('controller');
},
_addAppRoute: function(controller, route, methodName) {
var method = controller[methodName];
if (!method) {
throw new Marionette.Error('Method "' + methodName + '" was not found on the controller');
}
this.route(route, methodName, _.bind(method, controller));
},
mergeOptions: Marionette.mergeOptions,

ProxygetOptiontoenablegettingoptionsfromthisorthis.optionsbyname.
getOption: Marionette.proxyGetOption,
triggerMethod: Marionette.triggerMethod,
bindEntityEvents: Marionette.proxyBindEntityEvents,
unbindEntityEvents: Marionette.proxyUnbindEntityEvents
});

APPLICATION

Containandmanagethecompositeapplicationasawhole.
StoresandstartsupRegionobjects,includesan
eventaggregatorasapp.vent
Marionette.Application = Marionette.Object.extend({
constructor: function(options) {
this._initializeRegions(options);
this._initCallbacks = new Marionette.Callbacks();
this.submodules = {};
_.extend(this, options);
this._initChannel();
Marionette.Object.call(this, options);
},

Commandexecution,facilitatedbyBackbone.Wreqr.Commands
execute: function() {
this.commands.execute.apply(this.commands, arguments);
},

Request/response,facilitatedbyBackbone.Wreqr.RequestResponse
request: function() {
return this.reqres.request.apply(this.reqres, arguments);
},

Addaninitializerthatiseitherrunatwhenthestart
methodiscalled,orrunimmediatelyifaddedafterstart
hasalreadybeencalled.
addInitializer: function(initializer) {
this._initCallbacks.add(initializer);
},

kickoffalloftheapplicationsprocesses.
initializesalloftheregionsthathavebeenadded
totheapp,andrunsalloftheinitializerfunctions
start: function(options) {
this.triggerMethod('before:start', options);
this._initCallbacks.run(options, this);
this.triggerMethod('start', options);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

45/51

4/1/2015

backbone.marionette.js

},

Addregionstoyourapp.
AcceptsahashofnamedstringsorRegionobjects
addRegions({something:#someRegion})
addRegions({something:Region.extend({el:#someRegion})})
addRegions: function(regions) {
return this._regionManager.addRegions(regions);
},

Emptyallregionsintheapp,withoutremovingthem
emptyRegions: function() {
return this._regionManager.emptyRegions();
},

Removesaregionfromyourapp,byname
Acceptstheregionsname
removeRegion(myRegion)
removeRegion: function(region) {
return this._regionManager.removeRegion(region);
},

Providesalternativeaccesstoregions
Acceptstheregionname
getRegion(main)
getRegion: function(region) {
return this._regionManager.get(region);
},

Getalltheregionsfromtheregionmanager
getRegions: function() {
return this._regionManager.getRegions();
},

Createamodule,attachedtotheapplication
module: function(moduleNames, moduleDefinition) {

Overwritethemoduleclassiftheuserspecifiesone
var ModuleClass = Marionette.Module.getClass(moduleDefinition);
var args = _.toArray(arguments);
args.unshift(this);

seetheMarionette.Moduleobjectformoreinformation
return ModuleClass.create.apply(ModuleClass, args);
},

EnableeasyoverridingofthedefaultRegionManager
forcustomizedregioninteractionsandbusinessspecific
viewlogicforbettercontroloversingleregions.
getRegionManager: function() {
return new Marionette.RegionManager();
},

Internalmethodtoinitializetheregionsthathavebeendefinedina
regionsattributeontheapplicationinstance
_initializeRegions: function(options) {
var regions = _.isFunction(this.regions) ? this.regions(options) : this.regions || {};
this._initRegionManager();

Enableuserstodefineregionsininstanceoptions.
var optionRegions = Marionette.getOption(options, 'regions');

Enableregionoptionstobeafunction
if (_.isFunction(optionRegions)) {
optionRegions = optionRegions.call(this, options);
}

Overwritecurrentregionswiththosepassedinoptions
_.extend(regions, optionRegions);
this.addRegions(regions);

http://marionettejs.com/annotatedsrc/backbone.marionette.html

46/51

4/1/2015

backbone.marionette.js

return this;
},

Internalmethodtosetuptheregionmanager
_initRegionManager: function() {
this._regionManager = this.getRegionManager();
this._regionManager._parent = this;
this.listenTo(this._regionManager, 'before:add:region', function() {
Marionette._triggerMethod(this, 'before:add:region', arguments);
});
this.listenTo(this._regionManager, 'add:region', function(name, region) {
this[name] = region;
Marionette._triggerMethod(this, 'add:region', arguments);
});
this.listenTo(this._regionManager, 'before:remove:region', function() {
Marionette._triggerMethod(this, 'before:remove:region', arguments);
});
this.listenTo(this._regionManager, 'remove:region', function(name) {
delete this[name];
Marionette._triggerMethod(this, 'remove:region', arguments);
});
},

InternalmethodtosetuptheWreqr.radiochannel
_initChannel: function() {
this.channelName = _.result(this, 'channelName') || 'global';
this.channel = _.result(this, 'channel') || Backbone.Wreqr.radio.channel(this.channelName);
this.vent = _.result(this, 'vent') || this.channel.vent;
this.commands = _.result(this, 'commands') || this.channel.commands;
this.reqres = _.result(this, 'reqres') || this.channel.reqres;
}
});
/* jshint maxparams: 9 */

MODULE

Asimplemodulesystem,usedtocreateprivacyandencapsulationin
Marionetteapplications
Marionette.Module = function(moduleName, app, options) {
this.moduleName = moduleName;
this.options = _.extend({}, this.options, options);

Allowforausertooveridetheinitialize
foragivenmoduleinstance.
this.initialize = options.initialize || this.initialize;

Setupaninternalstoreforsubmodules.
this.submodules = {};
this._setupInitializersAndFinalizers();

Setaninternalreferencetotheapp
withinamodule.
this.app = app;
if (_.isFunction(this.initialize)) {
this.initialize(moduleName, app, this.options);
}
};
Marionette.Module.extend = Marionette.extend;

ExtendtheModuleprototypewithevents/listenTo,sothatthemodule
canbeusedasaneventaggregatororpub/sub.
_.extend(Marionette.Module.prototype, Backbone.Events, {

Bydefaultmodulesstartwiththeirparents.
startWithParent: true,

Initializeisanemptyfunctionbydefault.Overrideitwithyourown
initializationlogicwhenextendingMarionette.Module.

http://marionettejs.com/annotatedsrc/backbone.marionette.html

47/51

4/1/2015

backbone.marionette.js

initialize: function() {},

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

Finalizersarerunwhenamoduleisstopped.Theyareusedtoteardown
andfinalizeanyvariables,references,eventsandothercodethatthe
modulehadsetup.
addFinalizer: function(callback) {
this._finalizerCallbacks.add(callback);
},

Startthemodule,andrunallofitsinitializers
start: function(options) {

Preventrestartingamodulethatisalreadystarted
if (this._isInitialized) { return; }

startthesubmodules(depthfirsthierarchy)
_.each(this.submodules, function(mod) {

checktoseeifweshouldstartthesubmodulewiththisparent
if (mod.startWithParent) {
mod.start(options);
}
});

runthecallbackstostartthecurrentmodule
this.triggerMethod('before:start', options);
this._initializerCallbacks.run(options, this);
this._isInitialized = true;
this.triggerMethod('start', options);
},

Stopthismodulebyrunningitsfinalizersandthenstopallof
thesubmodulesforthismodule
stop: function() {

ifwearenotinitialized,dontbotherfinalizing
if (!this._isInitialized) { return; }
this._isInitialized = false;
this.triggerMethod('before:stop');

stopthesubmodulesdepthfirst,tomakesurethe
submodulesarestopped/finalizedbeforeparents
_.invoke(this.submodules, 'stop');

runthefinalizers
this._finalizerCallbacks.run(undefined, this);

resettheinitializersandfinalizers
this._initializerCallbacks.reset();
this._finalizerCallbacks.reset();
this.triggerMethod('stop');
},

Configurethemodulewithadefinitionfunctionandanycustomargs
thataretobepassedintothedefinitionfunction
addDefinition: function(moduleDefinition, customArgs) {
this._runModuleDefinition(moduleDefinition, customArgs);
},

Internalmethod:runthemoduledefinitionfunctionwiththecorrect
arguments
_runModuleDefinition: function(definition, customArgs) {

http://marionettejs.com/annotatedsrc/backbone.marionette.html

48/51

4/1/2015

backbone.marionette.js

Ifthereisnodefinitionshortcircutthemethod.
if (!definition) { return; }

buildthecorrectlistofargumentsforthemoduledefinition
var args = _.flatten([
this,
this.app,
Backbone,
Marionette,
Backbone.$, _,
customArgs
]);
definition.apply(this, args);
},

Internalmethod:setupnewcopiesofinitializersandfinalizers.
Callingthismethodwillwipeoutallexistinginitializersand
finalizers.
_setupInitializersAndFinalizers: function() {
this._initializerCallbacks = new Marionette.Callbacks();
this._finalizerCallbacks = new Marionette.Callbacks();
},

importthetriggerMethodtotriggereventswithcorresponding
methodsifthemethodexists
triggerMethod: Marionette.triggerMethod
});

Classmethodstocreatemodules
_.extend(Marionette.Module, {

Createamodule,hangingofftheappparameterastheparentobject.
create: function(app, moduleNames, moduleDefinition) {
var module = app;

getthecustomargspassedinafterthemoduledefinitionand
getridofthemodulenameanddefinitionfunction
var customArgs = _.drop(arguments, 3);

Splitthemodulenamesandgetthenumberofsubmodules.
i.e.anexamplemodulenameofDoge.Wow.Amazewould
thenhavethepotentialfor3moduledefinitions.
moduleNames = moduleNames.split('.');
var length = moduleNames.length;

storethemoduledefinitionforthelastmoduleinthechain
var moduleDefinitions = [];
moduleDefinitions[length - 1] = moduleDefinition;

Loopthroughallthepartsofthemoduledefinition
_.each(moduleNames, function(moduleName, i) {
var parentModule = module;
module = this._getModule(parentModule, moduleName, app, moduleDefinition);
this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
}, this);

Returnthelastmoduleinthedefinitionchain
return module;
},
_getModule: function(parentModule, moduleName, app, def, args) {
var options = _.extend({}, def);
var ModuleClass = this.getClass(def);

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

Createanewmoduleifwedonthaveone
module = new ModuleClass(moduleName, app, options);
parentModule[moduleName] = module;

storethemoduleontheparent

http://marionettejs.com/annotatedsrc/backbone.marionette.html

49/51

4/1/2015

backbone.marionette.js
parentModule.submodules[moduleName] = module;

}
return module;
},

MODULECLASSES
Moduleclassescanbeusedasanalternativetothedefinepattern.
TheextendfunctionofaModuleisidenticaltotheextendfunctions
onotherBackboneandMarionetteclasses.
ThisallowsmodulelifecyleeventslikeonStartandonStoptobecalleddirectly.
getClass: function(moduleDefinition) {
var ModuleClass = Marionette.Module;
if (!moduleDefinition) {
return ModuleClass;
}

Ifallofthemodulesfunctionalityisdefinedinsideitsclass,
thentheclasscanbepassedindirectly.MyApp.module("Foo", FooModule).
if (moduleDefinition.prototype instanceof ModuleClass) {
return moduleDefinition;
}
return moduleDefinition.moduleClass || ModuleClass;
},

AddthemoduledefinitionandaddastartWithParentinitializerfunction.
Thisiscomplicatedbecausemoduledefinitionsareheavilyoverloaded
andsupportananonymousfunction,moduleclass,oroptionsobject
_addModuleDefinition: function(parentModule, module, def, args) {
var fn = this._getDefine(def);
var startWithParent = this._getStartWithParent(def, module);
if (fn) {
module.addDefinition(fn, args);
}
this._addStartWithParent(parentModule, module, startWithParent);
},
_getStartWithParent: function(def, module) {
var swp;
if (_.isFunction(def) && (def.prototype instanceof Marionette.Module)) {
swp = module.constructor.prototype.startWithParent;
return _.isUndefined(swp) ? true : swp;
}
if (_.isObject(def)) {
swp = def.startWithParent;
return _.isUndefined(swp) ? true : swp;
}
return true;
},
_getDefine: function(def) {
if (_.isFunction(def) && !(def.prototype instanceof Marionette.Module)) {
return def;
}
if (_.isObject(def)) {
return def.define;
}
return null;
},
_addStartWithParent: function(parentModule, module, startWithParent) {
module.startWithParent = module.startWithParent && startWithParent;
if (!module.startWithParent || !!module.startWithParentIsConfigured) {
return;
}
module.startWithParentIsConfigured = true;
parentModule.addInitializer(function(options) {
if (module.startWithParent) {
module.start(options);
}
});
}
});

http://marionettejs.com/annotatedsrc/backbone.marionette.html

50/51

4/1/2015

backbone.marionette.js

return Marionette;
}));

http://marionettejs.com/annotatedsrc/backbone.marionette.html

51/51

You might also like