Vous êtes sur la page 1sur 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);
};

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

allowsfortheuseofthe@ui.syntaxwithin
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;
}, {});
};

allowsfortheuseofthe@ui.syntaxwithin
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
ausertousethe@ui.syntax.
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
ausertousethe@ui.syntax.
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

Vous aimerez peut-être aussi