Skip to content

Instantly share code, notes, and snippets.

@paulfalgout
Created March 14, 2016 07:40
Show Gist options
  • Save paulfalgout/4e3ceaa0318f34c0f3c1 to your computer and use it in GitHub Desktop.
Save paulfalgout/4e3ceaa0318f34c0f3c1 to your computer and use it in GitHub Desktop.
bundled v3 build
// MarionetteJS (Backbone.Marionette)
// ----------------------------------
// v3.0.0-pre.2
//
// Copyright (c)2016 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
//
// http://marionettejs.com
/*!
* Includes BabySitter
* https://github.com/marionettejs/backbone.babysitter/
*
* Includes Radio
* https://github.com/marionettejs/backbone.radio/
*/
// Backbone.BabySitter
// -------------------
// v0.1.10
//
// Copyright (c)2015 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
//
// http://github.com/marionettejs/backbone.babysitter
(function(Backbone, _) {
"use strict";
var previousChildViewContainer = Backbone.ChildViewContainer;
// BabySitter.ChildViewContainer
// -----------------------------
//
// Provide a container to store, retrieve and
// shut down child views.
Backbone.ChildViewContainer = function(Backbone, _) {
// Container Constructor
// ---------------------
var Container = function(views) {
this._views = {};
this._indexByModel = {};
this._indexByCustom = {};
this._updateLength();
_.each(views, this.add, this);
};
// Container Methods
// -----------------
_.extend(Container.prototype, {
// Add a view to this container. Stores the view
// by `cid` and makes it searchable by the model
// cid (and model itself). Optionally specify
// a custom key to store an retrieve the view.
add: function(view, customIndex) {
var viewCid = view.cid;
// store the view
this._views[viewCid] = view;
// index it by model
if (view.model) {
this._indexByModel[view.model.cid] = viewCid;
}
// index by custom
if (customIndex) {
this._indexByCustom[customIndex] = viewCid;
}
this._updateLength();
return this;
},
// Find a view by the model that was attached to
// it. Uses the model's `cid` to find it.
findByModel: function(model) {
return this.findByModelCid(model.cid);
},
// Find a view by the `cid` of the model that was attached to
// it. Uses the model's `cid` to find the view `cid` and
// retrieve the view using it.
findByModelCid: function(modelCid) {
var viewCid = this._indexByModel[modelCid];
return this.findByCid(viewCid);
},
// Find a view by a custom indexer.
findByCustom: function(index) {
var viewCid = this._indexByCustom[index];
return this.findByCid(viewCid);
},
// Find by index. This is not guaranteed to be a
// stable index.
findByIndex: function(index) {
return _.values(this._views)[index];
},
// retrieve a view by its `cid` directly
findByCid: function(cid) {
return this._views[cid];
},
// Remove a view
remove: function(view) {
var viewCid = view.cid;
// delete model index
if (view.model) {
delete this._indexByModel[view.model.cid];
}
// delete custom index
_.any(this._indexByCustom, function(cid, key) {
if (cid === viewCid) {
delete this._indexByCustom[key];
return true;
}
}, this);
// remove the view from the container
delete this._views[viewCid];
// update the length
this._updateLength();
return this;
},
// Call a method on every view in the container,
// passing parameters to the call method one at a
// time, like `function.call`.
call: function(method) {
this.apply(method, _.tail(arguments));
},
// Apply a method on every view in the container,
// passing parameters to the call method one at a
// time, like `function.apply`.
apply: function(method, args) {
_.each(this._views, function(view) {
if (_.isFunction(view[method])) {
view[method].apply(view, args || []);
}
});
},
// Update the `.length` attribute on this container
_updateLength: function() {
this.length = _.size(this._views);
}
});
// Borrowing this code from Backbone.Collection:
// http://backbonejs.org/docs/backbone.html#section-106
//
// Mix in methods from Underscore, for iteration, and other
// collection related features.
var methods = [ "forEach", "each", "map", "find", "detect", "filter", "select", "reject", "every", "all", "some", "any", "include", "contains", "invoke", "toArray", "first", "initial", "rest", "last", "without", "isEmpty", "pluck", "reduce" ];
_.each(methods, function(method) {
Container.prototype[method] = function() {
var views = _.values(this._views);
var args = [ views ].concat(_.toArray(arguments));
return _[method].apply(_, args);
};
});
// return the public API
return Container;
}(Backbone, _);
Backbone.ChildViewContainer.VERSION = "0.1.10";
Backbone.ChildViewContainer.noConflict = function() {
Backbone.ChildViewContainer = previousChildViewContainer;
return this;
};
return Backbone.ChildViewContainer;
})(Backbone, _);// Backbone.Radio v1.0.2
(function(_, Backbone) {
"use strict";
var previousRadio = Backbone.Radio;
var Radio = Backbone.Radio = {};
Radio.VERSION = "1.0.2";
// This allows you to run multiple instances of Radio on the same
// webapp. After loading the new version, call `noConflict()` to
// get a reference to it. At the same time the old version will be
// returned to Backbone.Radio.
Radio.noConflict = function() {
Backbone.Radio = previousRadio;
return this;
};
// Whether or not we're in DEBUG mode or not. DEBUG mode helps you
// get around the issues of lack of warnings when events are mis-typed.
Radio.DEBUG = false;
// Format debug text.
Radio._debugText = function(warning, eventName, channelName) {
return warning + (channelName ? " on the " + channelName + " channel" : "") + ': "' + eventName + '"';
};
// This is the method that's called when an unregistered event was called.
// By default, it logs warning to the console. By overriding this you could
// make it throw an Error, for instance. This would make firing a nonexistent event
// have the same consequence as firing a nonexistent method on an Object.
Radio.debugLog = function(warning, eventName, channelName) {
if (Radio.DEBUG && console && console.warn) {
console.warn(Radio._debugText(warning, eventName, channelName));
}
};
var eventSplitter = /\s+/;
// An internal method used to handle Radio's method overloading for Requests.
// It's borrowed from Backbone.Events. It differs from Backbone's overload
// API (which is used in Backbone.Events) in that it doesn't support space-separated
// event names.
Radio._eventsApi = function(obj, action, name, rest) {
if (!name) {
return false;
}
var results = {};
// Handle event maps.
if (typeof name === "object") {
for (var key in name) {
var result = obj[action].apply(obj, [ key, name[key] ].concat(rest));
eventSplitter.test(key) ? _.extend(results, result) : results[key] = result;
}
return results;
}
// Handle space separated event names.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, l = names.length; i < l; i++) {
results[names[i]] = obj[action].apply(obj, [ names[i] ].concat(rest));
}
return results;
}
return false;
};
// An optimized way to execute callbacks.
Radio._callHandler = function(callback, context, args) {
var a1 = args[0], a2 = args[1], a3 = args[2];
switch (args.length) {
case 0:
return callback.call(context);
case 1:
return callback.call(context, a1);
case 2:
return callback.call(context, a1, a2);
case 3:
return callback.call(context, a1, a2, a3);
default:
return callback.apply(context, args);
}
};
// A helper used by `off` methods to the handler from the store
function removeHandler(store, name, callback, context) {
var event = store[name];
if ((!callback || (callback === event.callback || callback === event.callback._callback)) && (!context || context === event.context)) {
delete store[name];
return true;
}
}
function removeHandlers(store, name, callback, context) {
store || (store = {});
var names = name ? [ name ] : _.keys(store);
var matched = false;
for (var i = 0, length = names.length; i < length; i++) {
name = names[i];
// If there's no event by this name, log it and continue
// with the loop
if (!store[name]) {
continue;
}
if (removeHandler(store, name, callback, context)) {
matched = true;
}
}
return matched;
}
/*
* tune-in
* -------
* Get console logs of a channel's activity
*
*/
var _logs = {};
// This is to produce an identical function in both tuneIn and tuneOut,
// so that Backbone.Events unregisters it.
function _partial(channelName) {
return _logs[channelName] || (_logs[channelName] = _.partial(Radio.log, channelName));
}
_.extend(Radio, {
// Log information about the channel and event
log: function log(channelName, eventName) {
var args = _.rest(arguments, 2);
console.log("[" + channelName + '] "' + eventName + '"', args);
},
// Logs all events on this channel to the console. It sets an
// internal value on the channel telling it we're listening,
// then sets a listener on the Backbone.Events
tuneIn: function tuneIn(channelName) {
var channel = Radio.channel(channelName);
channel._tunedIn = true;
channel.on("all", _partial(channelName));
return this;
},
// Stop logging all of the activities on this channel to the console
tuneOut: function tuneOut(channelName) {
var channel = Radio.channel(channelName);
channel._tunedIn = false;
channel.off("all", _partial(channelName));
delete _logs[channelName];
return this;
}
});
/*
* Backbone.Radio.Requests
* -----------------------
* A messaging system for requesting data.
*
*/
function makeCallback(callback) {
return _.isFunction(callback) ? callback : function() {
return callback;
};
}
Radio.Requests = {
// Make a request
request: function request(name) {
var args = _.rest(arguments);
var results = Radio._eventsApi(this, "request", name, args);
if (results) {
return results;
}
var channelName = this.channelName;
var requests = this._requests;
// Check if we should log the request, and if so, do it
if (channelName && this._tunedIn) {
Radio.log.apply(this, [ channelName, name ].concat(args));
}
// If the request isn't handled, log it in DEBUG mode and exit
if (requests && (requests[name] || requests["default"])) {
var handler = requests[name] || requests["default"];
args = requests[name] ? args : arguments;
return Radio._callHandler(handler.callback, handler.context, args);
} else {
Radio.debugLog("An unhandled request was fired", name, channelName);
}
},
// Set up a handler for a request
reply: function reply(name, callback, context) {
if (Radio._eventsApi(this, "reply", name, [ callback, context ])) {
return this;
}
this._requests || (this._requests = {});
if (this._requests[name]) {
Radio.debugLog("A request was overwritten", name, this.channelName);
}
this._requests[name] = {
callback: makeCallback(callback),
context: context || this
};
return this;
},
// Set up a handler that can only be requested once
replyOnce: function replyOnce(name, callback, context) {
if (Radio._eventsApi(this, "replyOnce", name, [ callback, context ])) {
return this;
}
var self = this;
var once = _.once(function() {
self.stopReplying(name);
return makeCallback(callback).apply(this, arguments);
});
return this.reply(name, once, context);
},
// Remove handler(s)
stopReplying: function stopReplying(name, callback, context) {
if (Radio._eventsApi(this, "stopReplying", name)) {
return this;
}
// Remove everything if there are no arguments passed
if (!name && !callback && !context) {
delete this._requests;
} else if (!removeHandlers(this._requests, name, callback, context)) {
Radio.debugLog("Attempted to remove the unregistered request", name, this.channelName);
}
return this;
}
};
/*
* Backbone.Radio.channel
* ----------------------
* Get a reference to a channel by name.
*
*/
Radio._channels = {};
Radio.channel = function(channelName) {
if (!channelName) {
throw new Error("You must provide a name for the channel.");
}
if (Radio._channels[channelName]) {
return Radio._channels[channelName];
} else {
return Radio._channels[channelName] = new Radio.Channel(channelName);
}
};
/*
* Backbone.Radio.Channel
* ----------------------
* A Channel is an object that extends from Backbone.Events,
* and Radio.Requests.
*
*/
Radio.Channel = function(channelName) {
this.channelName = channelName;
};
_.extend(Radio.Channel.prototype, Backbone.Events, Radio.Requests, {
// Remove all handlers from the messaging systems of this channel
reset: function reset() {
this.off();
this.stopListening();
this.stopReplying();
return this;
}
});
/*
* Top-level API
* -------------
* Supplies the 'top-level API' for working with Channels directly
* from Backbone.Radio.
*
*/
var channel, args, systems = [ Backbone.Events, Radio.Commands, Radio.Requests ];
_.each(systems, function(system) {
_.each(system, function(method, methodName) {
Radio[methodName] = function(channelName) {
args = _.rest(arguments);
channel = this.channel(channelName);
return channel[methodName].apply(channel, args);
};
});
});
Radio.reset = function(channelName) {
var channels = !channelName ? this._channels : [ this._channels[channelName] ];
_.invoke(channels, "reset");
};
var backbone_radio = Radio;
return backbone_radio;
})(_, Backbone);
var Marionette = global['Mn'] = (function (Backbone,_$1,Radio,ChildViewContainer) {
'use strict';
Backbone = 'default' in Backbone ? Backbone['default'] : Backbone;
_$1 = 'default' in _$1 ? _$1['default'] : _$1;
Radio = 'default' in Radio ? Radio['default'] : Radio;
ChildViewContainer = 'default' in ChildViewContainer ? ChildViewContainer['default'] : ChildViewContainer;
var babelHelpers = {};
babelHelpers.toConsumableArray = function (arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
} else {
return Array.from(arr);
}
};
babelHelpers;
var version = "3.0.0-pre.2";
//Internal utility for creating context style global utils
var proxy = function proxy(method) {
return function (context) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return method.apply(context, args);
};
};
// Borrow the Backbone `extend` method so we can use it as needed
var extend = Backbone.Model.extend;
// Determine if `el` is a child of the document
var isNodeAttached = function isNodeAttached(el) {
return Backbone.$.contains(document.documentElement, el);
};
// Merge `keys` from `options` onto `this`
var mergeOptions = function mergeOptions(options, keys) {
if (!options) {
return;
}
_$1.extend(this, _$1.pick(options, keys));
};
// Marionette.getOption
// --------------------
// Retrieve an object, function or other value from the
// object or its `options`, with `options` taking precedence.
var getOption = function getOption(optionName) {
if (!optionName) {
return;
}
if (this.options && this.options[optionName] !== undefined) {
return this.options[optionName];
} else {
return this[optionName];
}
};
// Marionette.normalizeMethods
// ----------------------
// Pass in a mapping of events => functions or function names
// and return a mapping of events => functions
var normalizeMethods = function normalizeMethods(hash) {
return _$1.reduce(hash, function (normalizedHash, method, name) {
if (!_$1.isFunction(method)) {
method = this[method];
}
if (method) {
normalizedHash[name] = method;
}
return normalizedHash;
}, {}, this);
};
var deprecate = function deprecate(message, test) {
if (_$1.isObject(message)) {
message = message.prev + ' is going to be removed in the future. ' + 'Please use ' + message.next + ' instead.' + (message.url ? ' See: ' + message.url : '');
}
if (!Marionette.DEV_MODE) {
return;
}
if ((test === undefined || !test) && !deprecate._cache[message]) {
deprecate._warn('Deprecation warning: ' + message);
deprecate._cache[message] = true;
}
};
deprecate._console = typeof console !== 'undefined' ? console : {};
deprecate._warn = function () {
var warn = deprecate._console.warn || deprecate._console.log || function () {};
return warn.apply(deprecate._console, arguments);
};
deprecate._cache = {};
// split the event name on the ":"
var splitter = /(^|:)(\w)/gi;
// take the event section ("section1:section2:section3")
// and turn it in to uppercase name onSection1Section2Section3
function getEventName(match, prefix, eventName) {
return eventName.toUpperCase();
}
// Trigger an event and/or a corresponding method name. Examples:
//
// `this.triggerMethod("foo")` will trigger the "foo" event and
// call the "onFoo" method.
//
// `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
// call the "onFooBar" method.
function triggerMethod(event) {
// get the method name from the event name
var methodName = 'on' + event.replace(splitter, getEventName);
var method = getOption.call(this, methodName);
var result;
// call the onMethodName if it exists
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
if (_$1.isFunction(method)) {
// pass all args, except the event name
result = method.apply(this, args);
}
// trigger the event
this.trigger.apply(this, [event].concat(args));
return result;
}
// triggerMethodOn invokes triggerMethod on a specific context
//
// e.g. `Marionette.triggerMethodOn(view, 'show')`
// will trigger a "show" event or invoke onShow the view.
function triggerMethodOn(context) {
var fnc = _$1.isFunction(context.triggerMethod) ? context.triggerMethod : triggerMethod;
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
return fnc.apply(context, args);
}
// Trigger method on children unless a pure Backbone.View
function triggerMethodChildren(view, event, beforeEachTrigger) {
if (!view._getImmediateChildren) {
return;
}
_.each(view._getImmediateChildren(), function (child) {
if (beforeEachTrigger) {
beforeEachTrigger(child);
}
triggerMethodOn(child, event, child);
});
}
function setIsAttached(view) {
view._isAttached = true;
}
function unsetIsAttached(view) {
view._isAttached = false;
}
// Monitor a view's state, propagating attach/detach events to children and firing dom:refresh
// whenever a rendered view is attached or an attached view is rendered.
function monitorViewEvents(view) {
if (view._areViewEventsMonitored) {
return;
}
view._areViewEventsMonitored = true;
function handleBeforeAttach() {
triggerMethodChildren(view, 'before:attach');
}
function handleAttach() {
triggerMethodChildren(view, 'attach', setIsAttached);
triggerDOMRefresh();
}
function handleBeforeDetach() {
triggerMethodChildren(view, 'before:detach');
}
function handleDetach() {
triggerMethodChildren(view, 'detach', unsetIsAttached);
}
function handleRender() {
triggerDOMRefresh();
}
function triggerDOMRefresh() {
if (view._isAttached && view._isRendered) {
triggerMethodOn(view, 'dom:refresh', view);
}
}
view.on({
'before:attach': handleBeforeAttach,
'attach': handleAttach,
'before:detach': handleBeforeDetach,
'detach': handleDetach,
'render': handleRender
});
}
// Similar to `_.result`, this is a simple helper
// If a function is provided we call it with context
// otherwise just return the value. If the value is
// undefined return a default value
var getValue = function getValue(value) {
if (_$1.isFunction(value)) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return value.apply(this, args);
}
return value;
};
// Internal utility for setting options consistently across Mn
var _setOptions = function _setOptions() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
this.options = _$1.extend.apply(_$1, [{}, _$1.result(this, 'options')].concat(args));
};
var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];
var MarionetteError = extend.call(Error, {
urlRoot: 'http://marionettejs.com/docs/v' + version + '/',
constructor: function MarionetteError(message, options) {
if (_$1.isObject(message)) {
options = message;
message = options.message;
} else if (!options) {
options = {};
}
var error = Error.call(this, message);
_$1.extend(this, _$1.pick(error, errorProps), _$1.pick(options, errorProps));
this.captureStackTrace();
if (options.url) {
this.url = this.urlRoot + options.url;
}
},
captureStackTrace: function captureStackTrace() {
if (Error.captureStackTrace) {
Error.captureStackTrace(this, MarionetteError);
}
},
toString: function toString() {
return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');
}
});
MarionetteError.extend = extend;
// Bind/unbind the event to handlers specified as a string of
// handler names on the target object
function bindFromStrings(target, entity, evt, methods, actionName) {
var methodNames = methods.split(/\s+/);
_$1.each(methodNames, function (methodName) {
var method = target[methodName];
if (!method) {
throw new MarionetteError('Method "' + methodName + '" was configured as an event handler, but does not exist.');
}
target[actionName](entity, evt, method);
});
}
// generic looping function
function iterateEvents(target, entity, bindings, actionName) {
if (!entity || !bindings) {
return;
}
// type-check bindings
if (!_$1.isObject(bindings)) {
throw new MarionetteError({
message: 'Bindings must be an object.',
url: 'marionette.functions.html#marionettebindentityevents'
});
}
// iterate the bindings and bind/unbind them
_$1.each(bindings, function (method, evt) {
// allow for a list of method names as a string
if (_$1.isString(method)) {
bindFromStrings(target, entity, evt, method, actionName);
return;
}
target[actionName](entity, evt, method);
});
}
function bindEntityEvents(entity, bindings) {
iterateEvents(this, entity, bindings, 'listenTo');
}
function unbindEntityEvents(entity, bindings) {
iterateEvents(this, entity, bindings, 'stopListening');
}
var CommonMixin = {
getValue: getValue,
// Imports the "normalizeMethods" to transform hashes of
// events=>function references/names to a hash of events=>function references
normalizeMethods: normalizeMethods,
_setOptions: _setOptions,
// A handy way to merge passed-in options onto the instance
mergeOptions: mergeOptions,
// Enable getting options from this or this.options by name.
getOption: getOption,
// Enable binding view's events from another entity.
bindEntityEvents: bindEntityEvents,
// Enable unbinding view's events from another entity.
unbindEntityEvents: unbindEntityEvents
};
function iterateReplies(target, channel, bindings, actionName) {
if (!channel || !bindings) {
return;
}
// type-check bindings
if (!_$1.isObject(bindings)) {
throw new MarionetteError({
message: 'Bindings must be an object.',
url: 'marionette.functions.html#marionettebindradiorequests'
});
}
var normalizedRadioRequests = normalizeMethods.call(target, bindings);
channel[actionName](normalizedRadioRequests, target);
}
function bindRadioRequests(channel, bindings) {
iterateReplies(this, channel, bindings, 'reply');
}
function unbindRadioRequests(channel, bindings) {
iterateReplies(this, channel, bindings, 'stopReplying');
}
var RadioMixin = {
_initRadio: function _initRadio() {
var channelName = this.getValue(this.getOption('channelName'));
if (!channelName) {
return;
}
var channel = this._channel = Radio.channel(channelName);
var radioEvents = this.getValue(this.getOption('radioEvents'));
this.bindRadioEvents(channel, radioEvents);
var radioRequests = this.getValue(this.getOption('radioRequests'));
this.bindRadioRequests(channel, radioRequests);
this.on('destroy', this._destroyRadio);
},
_destroyRadio: function _destroyRadio() {
this._channel.stopReplying(null, null, this);
},
getChannel: function getChannel() {
return this._channel;
},
// Proxy `bindRadioEvents`
bindRadioEvents: bindEntityEvents,
// Proxy `unbindRadioEvents`
unbindRadioEvents: unbindEntityEvents,
// Proxy `bindRadioRequests`
bindRadioRequests: bindRadioRequests,
// Proxy `unbindRadioRequests`
unbindRadioRequests: unbindRadioRequests
};
// A Base Class that other Classes should descend from.
// Object borrows many conventions and utilities from Backbone.
var MarionetteObject = function MarionetteObject(options) {
this._setOptions(options);
this.cid = _$1.uniqueId(this.cidPrefix);
this._initRadio();
this.initialize.apply(this, arguments);
};
MarionetteObject.extend = extend;
// Object Methods
// --------------
// Ensure it can trigger events with Backbone.Events
_$1.extend(MarionetteObject.prototype, Backbone.Events, CommonMixin, RadioMixin, {
cidPrefix: 'mno',
// for parity with Marionette.AbstractView lifecyle
_isDestroyed: false,
isDestroyed: function isDestroyed() {
return this._isDestroyed();
},
//this is a noop method intended to be overridden by classes that extend from this base
initialize: function initialize() {},
destroy: function destroy() {
if (this._isDestroyed) {
return this;
}
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
this.triggerMethod.apply(this, ['before:destroy'].concat(args));
// mark as destroyed before doing the actual destroy, to
// prevent infinite loops within "destroy" event handlers
this._isDestroyed = true;
this.triggerMethod.apply(this, ['destroy'].concat(args));
this.stopListening();
return this;
},
triggerMethod: triggerMethod
});
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
var TemplateCache = function TemplateCache(templateId) {
this.templateId = templateId;
};
// TemplateCache object-level methods. Manage the template
// caches from these method calls instead of creating
// your own TemplateCache instances
_$1.extend(TemplateCache, {
templateCaches: {},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get: function get(templateId, options) {
var cachedTemplate = this.templateCaches[templateId];
if (!cachedTemplate) {
cachedTemplate = new TemplateCache(templateId);
this.templateCaches[templateId] = cachedTemplate;
}
return cachedTemplate.load(options);
},
// Clear templates from the cache. If no arguments
// are specified, clears all templates:
// `clear()`
//
// If arguments are specified, clears each of the
// specified templates from the cache:
// `clear("#t1", "#t2", "...")`
clear: function clear() {
var i;
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var length = args.length;
if (length > 0) {
for (i = 0; i < length; i++) {
delete this.templateCaches[args[i]];
}
} else {
this.templateCaches = {};
}
}
});
// TemplateCache instance methods, allowing each
// template cache object to manage its own state
// and know whether or not it has been loaded
_$1.extend(TemplateCache.prototype, {
// Internal method to load the template
load: function load(options) {
// Guard clause to prevent loading this template more than once
if (this.compiledTemplate) {
return this.compiledTemplate;
}
// Load the template and compile it
var template = this.loadTemplate(this.templateId, options);
this.compiledTemplate = this.compileTemplate(template, options);
return this.compiledTemplate;
},
// Load a template from the DOM, by default. Override
// this method to provide your own template retrieval
// For asynchronous loading with AMD/RequireJS, consider
// using a template-loader plugin as described here:
// https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
loadTemplate: function loadTemplate(templateId, options) {
var $template = Backbone.$(templateId);
if (!$template.length) {
throw new MarionetteError({
name: 'NoTemplateError',
message: 'Could not find template: "' + templateId + '"'
});
}
return $template.html();
},
// Pre-compile the template before caching it. Override
// this method if you do not need to pre-compile a template
// (JST / RequireJS for example) or if you want to change
// the template engine used (Handebars, etc).
compileTemplate: function compileTemplate(rawTemplate, options) {
return _$1.template(rawTemplate, options);
}
});
// Render a template with data by passing in the template
// selector and the data to render.
var Renderer = {
// Render a template with data. The `template` parameter is
// passed to the `TemplateCache` object to retrieve the
// template function. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render: function render(template, data) {
if (!template) {
throw new MarionetteError({
name: 'TemplateNotFoundError',
message: 'Cannot render the template since its false, null or undefined.'
});
}
var templateFunc = _$1.isFunction(template) ? template : TemplateCache.get(template);
return templateFunc(data);
}
};
// Takes care of getting the behavior class
// given options and a key.
// If a user passes in options.behaviorClass
// default to using that.
// If a user passes in a Behavior Class directly, use that
// Otherwise delegate the lookup to the users `behaviorsLookup` implementation.
function getBehaviorClass(options, key) {
if (options.behaviorClass) {
return options.behaviorClass;
//treat functions as a Behavior constructor
} else if (_$1.isFunction(options)) {
return options;
}
// behaviorsLookup can be either a flat object or a method
return getValue(Marionette.Behaviors.behaviorsLookup, options, key)[key];
}
// Iterate over the behaviors object, for each behavior
// instantiate it and get its grouped behaviors.
// This accepts a list of behaviors in either an object or array form
function parseBehaviors(view, behaviors) {
return _$1.chain(behaviors).map(function (options, key) {
var BehaviorClass = getBehaviorClass(options, key);
//if we're passed a class directly instead of an object
var _options = options === BehaviorClass ? {} : options;
var behavior = new BehaviorClass(_options, view);
var nestedBehaviors = parseBehaviors(view, _$1.result(behavior, 'behaviors'));
return [behavior].concat(nestedBehaviors);
}).flatten().value();
}
var BehaviorsMixin = {
_initBehaviors: function _initBehaviors() {
var behaviors = this.getValue(this.getOption('behaviors'));
// Behaviors defined on a view can be a flat object literal
// or it can be a function that returns an object.
this._behaviors = _$1.isObject(behaviors) ? parseBehaviors(this, behaviors) : {};
},
_getBehaviorTriggers: function _getBehaviorTriggers() {
var triggers = _$1.invoke(this._behaviors, 'getTriggers');
return _$1.extend.apply(_$1, [{}].concat(babelHelpers.toConsumableArray(triggers)));
},
_getBehaviorEvents: function _getBehaviorEvents() {
var events = _$1.invoke(this._behaviors, 'getEvents');
return _$1.extend.apply(_$1, [{}].concat(babelHelpers.toConsumableArray(events)));
},
// proxy behavior $el to the view's $el.
_proxyBehaviorViewProperties: function _proxyBehaviorViewProperties() {
_$1.invoke(this._behaviors, 'proxyViewProperties');
},
// delegate modelEvents and collectionEvents
_delegateBehaviorEntityEvents: function _delegateBehaviorEntityEvents() {
_$1.invoke(this._behaviors, 'delegateEntityEvents');
},
// undelegate modelEvents and collectionEvents
_undelegateBehaviorEntityEvents: function _undelegateBehaviorEntityEvents() {
_$1.invoke(this._behaviors, 'undelegateEntityEvents');
},
_destroyBehaviors: function _destroyBehaviors(args) {
// Call destroy on each behavior after
// destroying the view.
// This unbinds event listeners
// that behaviors have registered for.
_$1.invoke.apply(_$1, [this._behaviors, 'destroy'].concat(babelHelpers.toConsumableArray(args)));
},
_bindBehaviorUIElements: function _bindBehaviorUIElements() {
_$1.invoke(this._behaviors, 'bindUIElements');
},
_unbindBehaviorUIElements: function _unbindBehaviorUIElements() {
_$1.invoke(this._behaviors, 'unbindUIElements');
},
_triggerEventOnBehaviors: function _triggerEventOnBehaviors() {
var behaviors = this._behaviors;
// Use good ol' for as this is a very hot function
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
for (var i = 0, length = behaviors && behaviors.length; i < length; i++) {
triggerMethod.apply(behaviors[i], args);
}
}
};
var DelegateEntityEventsMixin = {
// Handle `modelEvents`, and `collectionEvents` configuration
_delegateEntityEvents: function _delegateEntityEvents(model, collection) {
this._undelegateEntityEvents(model, collection);
var modelEvents = this.getValue(this.getOption('modelEvents'));
bindEntityEvents.call(this, model, modelEvents);
var collectionEvents = this.getValue(this.getOption('collectionEvents'));
bindEntityEvents.call(this, collection, collectionEvents);
},
_undelegateEntityEvents: function _undelegateEntityEvents(model, collection) {
var modelEvents = this.getValue(this.getOption('modelEvents'));
unbindEntityEvents.call(this, model, modelEvents);
var collectionEvents = this.getValue(this.getOption('collectionEvents'));
unbindEntityEvents.call(this, collection, collectionEvents);
}
};
// Borrow event splitter from Backbone
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
function uniqueName(eventName, selector) {
return [eventName + _$1.uniqueId('.evt'), selector].join(' ');
}
// Set event name to be namespaced using a unique index
// to generate a non colliding event namespace
// http://api.jquery.com/event.namespace/
var getUniqueEventName = function getUniqueEventName(eventName) {
var match = eventName.match(delegateEventSplitter);
return uniqueName(match[1], match[2]);
};
// Internal method to create an event handler for a given `triggerDef` like
// 'click:foo'
function buildViewTrigger(view, triggerDef) {
if (_$1.isString(triggerDef)) {
triggerDef = { event: triggerDef };
}
var eventName = triggerDef.event;
var shouldPreventDefault = triggerDef.preventDefault !== false;
var shouldStopPropagation = triggerDef.stopPropagation !== false;
return function (e) {
if (shouldPreventDefault) {
e.preventDefault();
}
if (shouldStopPropagation) {
e.stopPropagation();
}
view.triggerMethod(eventName, view);
};
}
var TriggersMixin = {
// Configure `triggers` to forward DOM events to view
// events. `triggers: {"click .foo": "do:foo"}`
_getViewTriggers: function _getViewTriggers(view, triggers) {
// Configure the triggers, prevent default
// action and stop propagation of DOM events
return _$1.reduce(triggers, function (events, value, key) {
key = getUniqueEventName(key);
events[key] = buildViewTrigger(view, value);
return events;
}, {}, this);
}
};
// allows for the use of the @ui. syntax within
// a given key for triggers and events
// swaps the @ui with the associated selector.
// Returns a new, non-mutated, parsed events hash.
var _normalizeUIKeys = function _normalizeUIKeys(hash, ui) {
return _$1.reduce(hash, function (memo, val, key) {
var normalizedKey = normalizeUIString(key, ui);
memo[normalizedKey] = val;
return memo;
}, {});
};
// utility method for parsing @ui. syntax strings
// into associated selector
var normalizeUIString = function normalizeUIString(uiString, ui) {
return uiString.replace(/@ui\.[a-zA-Z_$0-9]*/g, function (r) {
return ui[r.slice(4)];
});
};
// allows for the use of the @ui. syntax within
// a given value for regions
// swaps the @ui with the associated selector
var _normalizeUIValues = function _normalizeUIValues(hash, ui, properties) {
_$1.each(hash, function (val, key) {
if (_$1.isString(val)) {
hash[key] = normalizeUIString(val, ui);
} else if (_$1.isObject(val) && _$1.isArray(properties)) {
_$1.extend(val, _normalizeUIValues(_$1.pick(val, properties), ui));
/* Value is an object, and we got an array of embedded property names to normalize. */
_$1.each(properties, function (property) {
var propertyVal = val[property];
if (_$1.isString(propertyVal)) {
val[property] = normalizeUIString(propertyVal, ui);
}
});
}
});
return hash;
};
var UIMixin = {
// normalize the keys of passed hash with the views `ui` selectors.
// `{"@ui.foo": "bar"}`
normalizeUIKeys: function normalizeUIKeys(hash) {
var uiBindings = this._getUIBindings();
return _normalizeUIKeys(hash, uiBindings);
},
// normalize the values of passed hash with the views `ui` selectors.
// `{foo: "@ui.bar"}`
normalizeUIValues: function normalizeUIValues(hash, properties) {
var uiBindings = this._getUIBindings();
return _normalizeUIValues(hash, uiBindings, properties);
},
_getUIBindings: function _getUIBindings() {
var uiBindings = _$1.result(this, '_uiBindings');
var ui = _$1.result(this, 'ui');
return uiBindings || ui;
},
// This method binds the elements specified in the "ui" hash inside the view's code with
// the associated jQuery selectors.
_bindUIElements: function _bindUIElements() {
if (!this.ui) {
return;
}
// store the ui hash in _uiBindings so they can be reset later
// and so re-rendering the view will be able to find the bindings
if (!this._uiBindings) {
this._uiBindings = this.ui;
}
// get the bindings result, as a function or otherwise
var bindings = _$1.result(this, '_uiBindings');
// empty the ui so we don't have anything to start with
this._ui = {};
// bind each of the selectors
_$1.each(bindings, function (selector, key) {
this._ui[key] = this.$(selector);
}, this);
this.ui = this._ui;
},
_unbindUIElements: function _unbindUIElements() {
if (!this.ui || !this._uiBindings) {
return;
}
// delete all of the existing ui bindings
_$1.each(this.ui, function ($el, name) {
delete this.ui[name];
}, this);
// reset the ui element to the original bindings configuration
this.ui = this._uiBindings;
delete this._uiBindings;
delete this._ui;
},
_getUI: function _getUI(name) {
return this._ui[name];
}
};
var ViewMixin = {
supportsRenderLifecycle: true,
supportsDestroyLifecycle: true,
_isDestroyed: false,
isDestroyed: function isDestroyed() {
return !!this._isDestroyed;
},
_isRendered: false,
isRendered: function isRendered() {
return !!this._isRendered;
},
_isAttached: false,
isAttached: function isAttached() {
return !!this._isAttached;
},
// Mix in template context methods. Looks for a
// `templateContext` attribute, which can either be an
// object literal, or a function that returns an object
// literal. All methods and attributes from this object
// are copies to the object passed in.
mixinTemplateContext: function mixinTemplateContext() {
var target = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var templateContext = this.getValue(this.getOption('templateContext'));
return _$1.extend(target, templateContext);
},
// Overriding Backbone.View's `delegateEvents` to handle
// `events` and `triggers`
delegateEvents: function delegateEvents(eventsArg) {
this._proxyBehaviorViewProperties();
this._buildEventProxies();
var viewEvents = this._getEvents(eventsArg);
if (typeof eventsArg === 'undefined') {
this.events = viewEvents;
}
var combinedEvents = _$1.extend({}, this._getBehaviorEvents(), viewEvents, this._getBehaviorTriggers(), this.getTriggers());
Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
return this;
},
_getEvents: function _getEvents(eventsArg) {
var events = this.getValue(eventsArg || this.events);
return this.normalizeUIKeys(events);
},
// Configure `triggers` to forward DOM events to view
// events. `triggers: {"click .foo": "do:foo"}`
getTriggers: function getTriggers() {
if (!this.triggers) {
return;
}
// Allow `triggers` to be configured as a function
var triggers = this.normalizeUIKeys(_$1.result(this, 'triggers'));
// Configure the triggers, prevent default
// action and stop propagation of DOM events
return this._getViewTriggers(this, triggers);
},
// Handle `modelEvents`, and `collectionEvents` configuration
delegateEntityEvents: function delegateEntityEvents() {
this._delegateEntityEvents(this.model, this.collection);
// bind each behaviors model and collection events
this._delegateBehaviorEntityEvents();
return this;
},
// Handle unbinding `modelEvents`, and `collectionEvents` configuration
undelegateEntityEvents: function undelegateEntityEvents() {
this._undelegateEntityEvents(this.model, this.collection);
// unbind each behaviors model and collection events
this._undelegateBehaviorEntityEvents();
return this;
},
// Internal helper method to verify whether the view hasn't been destroyed
_ensureViewIsIntact: function _ensureViewIsIntact() {
if (this._isDestroyed) {
throw new MarionetteError({
name: 'ViewDestroyedError',
message: 'View (cid: "' + this.cid + '") has already been destroyed and cannot be used.'
});
}
},
// Handle destroying the view and its children.
destroy: function destroy() {
if (this._isDestroyed) {
return this;
}
var shouldTriggerDetach = !!this._isAttached;
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
this.triggerMethod.apply(this, ['before:destroy'].concat(args));
if (shouldTriggerDetach) {
this.triggerMethod('before:detach', this);
}
// unbind UI elements
this.unbindUIElements();
// remove the view from the DOM
// https://github.com/jashkenas/backbone/blob/1.2.3/backbone.js#L1235
this._removeElement();
if (shouldTriggerDetach) {
this._isAttached = false;
this.triggerMethod('detach', this);
}
// remove children after the remove to prevent extra paints
this._removeChildren();
this._destroyBehaviors(args);
this._isDestroyed = true;
this._isRendered = false;
this.triggerMethod.apply(this, ['destroy'].concat(args));
this.stopListening();
return this;
},
bindUIElements: function bindUIElements() {
this._bindUIElements();
this._bindBehaviorUIElements();
return this;
},
// This method unbinds the elements specified in the "ui" hash
unbindUIElements: function unbindUIElements() {
this._unbindUIElements();
this._unbindBehaviorUIElements();
return this;
},
getUI: function getUI(name) {
this._ensureViewIsIntact();
return this._getUI(name);
},
// used as the prefix for child view events
// that are forwarded through the layoutview
childViewEventPrefix: 'childview',
// import the `triggerMethod` to trigger events with corresponding
// methods if the method exists
triggerMethod: function triggerMethod$$() {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
var ret = triggerMethod.apply(this, args);
this._triggerEventOnBehaviors.apply(this, args);
this._triggerEventOnParentLayout.apply(this, args);
return ret;
},
// Cache `childViewEvents` and `childViewTriggers`
_buildEventProxies: function _buildEventProxies() {
this._childViewEvents = this.getValue(this.getOption('childViewEvents'));
this._childViewTriggers = this.getValue(this.getOption('childViewTriggers'));
},
_triggerEventOnParentLayout: function _triggerEventOnParentLayout(eventName) {
var layoutView = this._parentView();
if (!layoutView) {
return;
}
// invoke triggerMethod on parent view
var eventPrefix = layoutView.getOption('childViewEventPrefix');
var prefixedEventName = eventPrefix + ':' + eventName;
for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
args[_key3 - 1] = arguments[_key3];
}
layoutView.triggerMethod.apply(layoutView, [prefixedEventName].concat(args));
// use the parent view's childViewEvents handler
var childViewEvents = layoutView.normalizeMethods(layoutView._childViewEvents);
if (!!childViewEvents && _$1.isFunction(childViewEvents[eventName])) {
childViewEvents[eventName].apply(layoutView, args);
}
// use the parent view's proxyEvent handlers
var childViewTriggers = layoutView._childViewTriggers;
// Call the event with the proxy name on the parent layout
if (childViewTriggers && _$1.isString(childViewTriggers[eventName])) {
layoutView.triggerMethod.apply(layoutView, [childViewTriggers[eventName]].concat(args));
}
},
// Walk the _parent tree until we find a view (if one exists).
// Returns the parent view hierarchically closest to this view.
_parentView: function _parentView() {
var parent = this._parent;
while (parent) {
if (parent instanceof View) {
return parent;
}
parent = parent._parent;
}
}
};
_$1.extend(ViewMixin, BehaviorsMixin, CommonMixin, DelegateEntityEventsMixin, TriggersMixin, UIMixin);
function destroyBackboneView(view) {
if (!view.supportsDestroyLifecycle) {
triggerMethodOn(view, 'before:destroy', view);
}
var shouldTriggerDetach = !!view._isAttached;
if (shouldTriggerDetach) {
triggerMethodOn(view, 'before:detach', view);
}
view.remove();
if (shouldTriggerDetach) {
view._isAttached = false;
triggerMethodOn(view, 'detach', view);
}
view._isDestroyed = true;
if (!view.supportsDestroyLifecycle) {
triggerMethodOn(view, 'destroy', view);
}
}
var Region = MarionetteObject.extend({
cidPrefix: 'mnr',
replaceElement: false,
_isReplaced: false,
constructor: function constructor(options) {
this._setOptions(options);
this._initEl = this.el = this.getOption('el');
// Handle when this.el is passed in as a $ wrapped element.
this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
if (!this.el) {
throw new MarionetteError({
name: 'NoElError',
message: 'An "el" must be specified for a region.'
});
}
this.$el = this.getEl(this.el);
MarionetteObject.call(this, options);
},
// Displays a backbone view instance inside of the region. Handles calling the `render`
// method for you. Reads content directly from the `el` attribute. The `preventDestroy`
// option can be used to prevent a view from the old view being destroyed on show.
show: function show(view, options) {
if (!this._ensureElement()) {
return;
}
this._ensureView(view);
if (view === this.currentView) {
return this;
}
this.triggerMethod('before:show', this, view, options);
monitorViewEvents(view);
this.empty(options);
// We need to listen for if a view is destroyed in a way other than through the region.
// If this happens we need to remove the reference to the currentView since once a view
// has been destroyed we can not reuse it.
view.on('destroy', this.empty, this);
// Make this region the view's parent.
// It's important that this parent binding happens before rendering so that any events
// the child may trigger during render can also be triggered on the child's ancestor views.
view._parent = this;
this._renderView(view);
this._attachView(view, options);
this.triggerMethod('show', this, view, options);
return this;
},
_renderView: function _renderView(view) {
if (view._isRendered) {
return;
}
if (!view.supportsRenderLifecycle) {
triggerMethodOn(view, 'before:render', view);
}
view.render();
if (!view.supportsRenderLifecycle) {
view._isRendered = true;
triggerMethodOn(view, 'render', view);
}
},
_attachView: function _attachView(view) {
var shouldTriggerAttach = !view._isAttached && isNodeAttached(this.el);
var shouldReplaceEl = !!this.getOption('replaceElement');
if (shouldTriggerAttach) {
triggerMethodOn(view, 'before:attach', view);
}
this.attachHtml(view, shouldReplaceEl);
if (shouldTriggerAttach) {
view._isAttached = true;
triggerMethodOn(view, 'attach', view);
}
this.currentView = view;
},
_ensureElement: function _ensureElement() {
if (!_$1.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 MarionetteError('An "el" must exist in DOM for this region ' + this.cid);
}
}
return true;
},
_ensureView: function _ensureView(view) {
if (!view) {
throw new MarionetteError({
name: 'ViewNotValid',
message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.'
});
}
if (view._isDestroyed) {
throw new MarionetteError({
name: 'ViewDestroyedError',
message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.'
});
}
},
// Override this method to change how the region finds the DOM element that it manages. Return
// a jQuery selector object scoped to a provided parent el or the document if none exists.
getEl: function getEl(el) {
return Backbone.$(el, this.getValue(this.getOption('parentEl')));
},
_replaceEl: function _replaceEl(view) {
// always restore the el to ensure the regions el is present before replacing
this._restoreEl();
var parent = this.el.parentNode;
parent.replaceChild(view.el, this.el);
this._isReplaced = true;
},
// Restore the region's element in the DOM.
_restoreEl: function _restoreEl() {
if (!this.currentView) {
return;
}
var view = this.currentView;
var parent = view.el.parentNode;
if (!parent) {
return;
}
parent.replaceChild(this.el, view.el);
this._isReplaced = false;
},
isReplaced: function isReplaced() {
return !!this._isReplaced;
},
// Override this method to change how the new view is appended to the `$el` that the
// region is managing
attachHtml: function attachHtml(view, shouldReplace) {
if (shouldReplace) {
// replace the region's node with the view's node
this._replaceEl(view);
} else {
this.el.appendChild(view.el);
}
},
// Destroy the current view, if there is one. If there is no current view, it does
// nothing and returns immediately.
empty: function empty(options) {
var view = this.currentView;
// If there is no view in the region we should not remove anything
if (!view) {
return this;
}
view.off('destroy', this.empty, this);
this.triggerMethod('before:empty', this, view);
if (this._isReplaced) {
this._restoreEl();
}
if (!view._isDestroyed) {
this._removeView(view, options);
}
delete this.currentView._parent;
delete this.currentView;
this.triggerMethod('empty', this, view);
return this;
},
_removeView: function _removeView(view) {
var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var preventDestroy = _ref.preventDestroy;
var shouldPreventDestroy = !!preventDestroy;
if (shouldPreventDestroy) {
this._detachView(view);
return;
}
if (view.destroy) {
view.destroy();
} else {
destroyBackboneView(view);
}
},
_detachView: function _detachView(view) {
var shouldTriggerDetach = !!view._isAttached;
if (shouldTriggerDetach) {
triggerMethodOn(view, 'before:detach', view);
}
this.$el.contents().detach();
if (shouldTriggerDetach) {
view._isAttached = false;
triggerMethodOn(view, 'detach', view);
}
},
// Checks whether a view is currently present within the region. Returns `true` if there is
// and `false` if no view is present.
hasView: function hasView() {
return !!this.currentView;
},
// Reset the region by destroying any existing view and clearing out the cached `$el`.
// The next time a view is shown via this region, the region will re-query the DOM for
// the region's `el`.
reset: function reset() {
this.empty();
if (this.$el) {
this.el = this._initEl;
}
delete this.$el;
return this;
},
destroy: function destroy() {
this.reset();
return MarionetteObject.prototype.destroy.apply(this, arguments);
}
});
var RegionsMixin = {
regionClass: Region,
// Internal method to initialize the regions that have been defined in a
// `regions` attribute on this View.
_initRegions: function _initRegions() {
// init regions hash
this.regions = this.regions || {};
this._regions = {};
this.addRegions(this.getValue(this.getOption('regions')));
},
// Internal method to re-initialize all of the regions by updating
// the `el` that they point to
_reInitRegions: function _reInitRegions() {
_$1.invoke(this._regions, 'reset');
},
// Add a single region, by name, to the View
addRegion: function addRegion(name, definition) {
var regions = {};
regions[name] = definition;
return this.addRegions(regions)[name];
},
// Add multiple regions as a {name: definition, name2: def2} object literal
addRegions: function addRegions(regions) {
// If there's nothing to add, stop here.
if (_$1.isEmpty(regions)) {
return;
}
// Normalize region selectors hash to allow
// a user to use the @ui. syntax.
regions = this.normalizeUIValues(regions, ['selector', 'el']);
// Add the regions definitions to the regions property
this.regions = _$1.extend({}, this.regions, regions);
return this._addRegions(regions);
},
// internal method to build and add regions
_addRegions: function _addRegions(regionDefinitions) {
return _$1.reduce(regionDefinitions, function (regions, definition, name) {
regions[name] = this._buildRegion(definition);
this._addRegion(regions[name], name);
return regions;
}, {}, this);
},
// return the region instance from the definition
_buildRegion: function _buildRegion(definition) {
if (definition instanceof Region) {
return definition;
}
return this._buildRegionFromDefinition(definition);
},
_buildRegionFromDefinition: function _buildRegionFromDefinition(definition) {
if (_$1.isString(definition)) {
return this._buildRegionFromObject({ el: definition });
}
if (_$1.isFunction(definition)) {
return this._buildRegionFromRegionClass(definition);
}
if (_$1.isObject(definition)) {
return this._buildRegionFromObject(definition);
}
throw new MarionetteError({
message: 'Improper region configuration type.',
url: 'marionette.region.html#region-configuration-types'
});
},
_buildRegionFromObject: function _buildRegionFromObject(definition) {
var RegionClass = definition.regionClass || this.getOption('regionClass');
var options = _$1.omit(definition, 'regionClass');
_$1.defaults(options, {
el: definition.selector,
parentEl: _$1.partial(_$1.result, this, 'el')
});
return new RegionClass(options);
},
// Build the region directly from a given `RegionClass`
_buildRegionFromRegionClass: function _buildRegionFromRegionClass(RegionClass) {
return new RegionClass({
parentEl: _$1.partial(_$1.result, this, 'el')
});
},
_addRegion: function _addRegion(region, name) {
this.triggerMethod('before:add:region', name, region);
region._parent = this;
this._regions[name] = region;
this.triggerMethod('add:region', name, region);
},
// Remove a single region from the View, by name
removeRegion: function removeRegion(name) {
var region = this._regions[name];
this._removeRegion(region, name);
return region;
},
// Remove all regions from the View
removeRegions: function removeRegions() {
var regions = this.getRegions();
_$1.each(this._regions, this._removeRegion, this);
return regions;
},
_removeRegion: function _removeRegion(region, name) {
this.triggerMethod('before:remove:region', name, region);
region.empty();
region.stopListening();
delete this.regions[name];
delete this._regions[name];
this.triggerMethod('remove:region', name, region);
},
// Empty all regions in the region manager, but
// leave them attached
emptyRegions: function emptyRegions() {
var regions = this.getRegions();
_$1.invoke(regions, 'empty');
return regions;
},
// Checks to see if view contains region
// Accepts the region name
// hasRegion('main')
hasRegion: function hasRegion(name) {
return !!this.getRegion(name);
},
// Provides access to regions
// Accepts the region name
// getRegion('main')
getRegion: function getRegion(name) {
return this._regions[name];
},
// Get all regions
getRegions: function getRegions() {
return _$1.clone(this._regions);
},
showChildView: function showChildView(name, view) {
var region = this.getRegion(name);
for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];
}
return region.show.apply(region, [view].concat(args));
},
getChildView: function getChildView(name) {
return this.getRegion(name).currentView;
}
};
// The standard view. Includes view events, automatic rendering
// of Underscore templates, nested views, and more.
var View = Backbone.View.extend({
constructor: function constructor(options) {
this.render = _$1.bind(this.render, this);
this._setOptions(options);
monitorViewEvents(this);
this._initBehaviors();
this._initRegions();
Backbone.View.prototype.constructor.call(this, this.options);
this.delegateEntityEvents();
},
// Serialize the view's model *or* collection, if
// it exists, for the template
serializeData: function serializeData() {
if (!this.model && !this.collection) {
return {};
}
// If we have a model, we serialize that
if (this.model) {
return this.serializeModel();
}
// Otherwise, we serialize the collection,
// making it available under the `items` property
return {
items: this.serializeCollection()
};
},
// Prepares the special `model` property of a view
// for being displayed in the template. By default
// we simply clone the attributes. Override this if
// you need a custom transformation for your view's model
serializeModel: function serializeModel() {
if (!this.model) {
return {};
}
return _$1.clone(this.model.attributes);
},
// Serialize a collection by cloning each of
// its model's attributes
serializeCollection: function serializeCollection() {
if (!this.collection) {
return {};
}
return this.collection.map(function (model) {
return _$1.clone(model.attributes);
});
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition to provide
// a very specific rendering for your view. In general, though,
// you should override the `Marionette.Renderer` object to
// change how Marionette renders views.
// Subsequent renders after the first will re-render all nested
// views.
render: function render() {
this._ensureViewIsIntact();
this.triggerMethod('before:render', this);
// If this is not the first render call, then we need to
// re-initialize the `el` for each region
if (this._isRendered) {
this._reInitRegions();
}
this._renderTemplate();
this.bindUIElements();
this._isRendered = true;
this.triggerMethod('render', this);
return this;
},
// Internal method to render the template with the serialized data
// and template context via the `Marionette.Renderer` object.
_renderTemplate: function _renderTemplate() {
var template = this.getTemplate();
// Allow template-less views
if (template === false) {
return;
}
// Add in entity data and template context
var data = this.mixinTemplateContext(this.serializeData());
// Render and add to el
var html = Renderer.render(template, data, this);
this.attachElContent(html);
},
// Get the template for this view
// instance. You can set a `template` attribute in the view
// definition or pass a `template: "whatever"` parameter in
// to the constructor options.
getTemplate: function getTemplate() {
return this.getOption('template');
},
// Attaches the content of a given view.
// This method can be overridden to optimize rendering,
// or to render in a non standard way.
//
// For example, using `innerHTML` instead of `$el.html`
//
// ```js
// attachElContent(html) {
// this.el.innerHTML = html;
// return this;
// }
// ```
attachElContent: function attachElContent(html) {
this.$el.html(html);
return this;
},
// called by ViewMixin destroy
_removeChildren: function _removeChildren() {
this.removeRegions();
},
_getImmediateChildren: function _getImmediateChildren() {
return _$1.chain(this.getRegions()).pluck('currentView').compact().value();
}
});
_$1.extend(View.prototype, ViewMixin, RegionsMixin);
// A view that iterates over a Backbone.Collection
// and renders an individual child view for each model.
var CollectionView = Backbone.View.extend({
// flag for maintaining the sorted order of the collection
sort: true,
// constructor
// option to pass `{sort: false}` to prevent the `CollectionView` from
// maintaining the sorted order of the collection.
// This will fallback onto appending childView's to the end.
//
// option to pass `{comparator: compFunction()}` to allow the `CollectionView`
// to use a custom sort order for the collection.
constructor: function constructor(options) {
this.render = _$1.bind(this.render, this);
this._setOptions(options);
monitorViewEvents(this);
this._initBehaviors();
this.once('render', this._initialEvents);
this._initChildViewStorage();
this._bufferedChildren = [];
Backbone.View.prototype.constructor.call(this, this.options);
this.delegateEntityEvents();
},
// Instead of inserting elements one by one into the page, it's much more performant to insert
// elements into a document fragment and then insert that document fragment into the page
_startBuffering: function _startBuffering() {
this._isBuffering = true;
},
_endBuffering: function _endBuffering() {
var shouldTriggerAttach = !!this._isAttached;
var triggerOnChildren = shouldTriggerAttach ? this._getImmediateChildren() : [];
this._isBuffering = false;
_$1.each(triggerOnChildren, function (child) {
triggerMethodOn(child, 'before:attach', child);
});
this.attachBuffer(this, this._createBuffer());
_$1.each(triggerOnChildren, function (child) {
child._isAttached = true;
triggerMethodOn(child, 'attach', child);
});
this._bufferedChildren = [];
},
// Configured the initial events that the collection view binds to.
_initialEvents: function _initialEvents() {
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);
}
}
},
// Handle a child added to the collection
_onCollectionAdd: function _onCollectionAdd(child, collection, opts) {
// `index` is present when adding with `at` since BB 1.2; indexOf fallback for < 1.2
var index = opts.at !== undefined && (opts.index || collection.indexOf(child));
// When filtered or when there is no initial index, calculate index.
if (this.getOption('filter') || index === false) {
index = _$1.indexOf(this._filteredSortedModels(index), child);
}
if (this._shouldAddChild(child, index)) {
this._destroyEmptyView();
var ChildView = this._getChildView(child);
this._addChild(child, ChildView, index);
}
},
// get the child view by model it holds, and remove it
_onCollectionRemove: function _onCollectionRemove(model) {
var view = this.children.findByModel(model);
this._removeChildView(view);
this._checkEmpty();
},
// Render children views. Override this method to provide your own implementation of a
// render function for the collection view.
render: function render() {
this._ensureViewIsIntact();
this.triggerMethod('before:render', this);
this._renderChildren();
this._isRendered = true;
this.triggerMethod('render', this);
return this;
},
// An efficient rendering used for filtering. Instead of modifying the whole DOM for the
// collection view, we are only adding or removing the related childrenViews.
setFilter: function setFilter(filter) {
var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var preventRender = _ref.preventRender;
var canBeRendered = this._isRendered && !this._isDestroyed;
var filterChanged = this.filter !== filter;
var shouldRender = canBeRendered && filterChanged && !preventRender;
if (shouldRender) {
this.triggerMethod('before:apply:filter', this);
var previousModels = this._filteredSortedModels();
this.filter = filter;
var models = this._filteredSortedModels();
this._applyModelDeltas(models, previousModels);
this.triggerMethod('apply:filter', this);
} else {
this.filter = filter;
}
},
// `removeFilter` is actually an alias for removing filters.
removeFilter: function removeFilter(options) {
this.setFilter(null, options);
},
// Calculate and apply difference by cid between `models` and `previousModels`.
_applyModelDeltas: function _applyModelDeltas(models, previousModels) {
var currentIds = {};
_$1.each(models, function (model, index) {
var addedChildNotExists = !this.children.findByModel(model);
if (addedChildNotExists) {
this._onCollectionAdd(model, this.collection, { at: index });
}
currentIds[model.cid] = true;
}, this);
_$1.each(previousModels, function (prevModel) {
var removedChildExists = !currentIds[prevModel.cid] && this.children.findByModel(prevModel);
if (removedChildExists) {
this._onCollectionRemove(prevModel);
}
}, this);
},
// Reorder DOM after sorting. When your element's rendering do not use their index,
// you can pass reorderOnSort: true to only reorder the DOM after a sort instead of
// rendering all the collectionView.
reorder: function reorder() {
var _this = this;
var children = this.children;
var models = this._filteredSortedModels();
var anyModelsAdded = _$1.some(models, function (model) {
return !children.findByModel(model);
});
// If there are any new models added due to filtering we need to add child views,
// so render as normal.
if (anyModelsAdded) {
this.render();
} else {
(function () {
// Get the DOM nodes in the same order as the models.
var elsToReorder = _$1.map(models, function (model, index) {
var view = children.findByModel(model);
view._index = index;
return view.el;
});
// Find the views that were children before but aren't in this new ordering.
var filteredOutViews = children.filter(function (view) {
return !_$1.contains(elsToReorder, view.el);
});
_this.triggerMethod('before:reorder', _this);
// Since append moves elements that are already in the DOM, appending the elements
// will effectively reorder them.
_this._appendReorderedChildren(elsToReorder);
// remove any views that have been filtered out
_$1.each(filteredOutViews, _this._removeChildView, _this);
_this._checkEmpty();
_this.triggerMethod('reorder', _this);
})();
}
},
// Render view after sorting. Override this method to change how the view renders
// after a `sort` on the collection.
resortView: function resortView() {
if (this.getOption('reorderOnSort')) {
this.reorder();
} else {
this._renderChildren();
}
},
// Internal method. This checks for any changes in the order of the collection.
// If the index of any view doesn't match, it will render.
_sortViews: function _sortViews() {
var models = this._filteredSortedModels();
// check for any changes in sort order of views
var orderChanged = _$1.find(models, function (item, index) {
var view = this.children.findByModel(item);
return !view || view._index !== index;
}, this);
if (orderChanged) {
this.resortView();
}
},
// Internal reference to what index a `emptyView` is.
_emptyViewIndex: -1,
// Internal method. Separated so that CompositeView can append to the childViewContainer
// if necessary
_appendReorderedChildren: function _appendReorderedChildren(children) {
this.$el.append(children);
},
// Internal method. Separated so that CompositeView can have more control over events
// being triggered, around the rendering process
_renderChildren: function _renderChildren() {
this._destroyEmptyView();
this._destroyChildren({ checkEmpty: false });
var models = this._filteredSortedModels();
if (this.isEmpty({ processedModels: models })) {
this._showEmptyView();
} else {
this.triggerMethod('before:render:children', this);
this._startBuffering();
this._showCollection(models);
this._endBuffering();
this.triggerMethod('render:children', this);
}
},
// Internal method to loop through collection and show each child view.
_showCollection: function _showCollection(models) {
_$1.each(models, function (child, index) {
var ChildView = this._getChildView(child);
this._addChild(child, ChildView, index);
}, this);
},
// Allow the collection to be sorted by a custom view comparator
_filteredSortedModels: function _filteredSortedModels(addedAt) {
if (!this.collection) {
return [];
}
var viewComparator = this.getViewComparator();
var models = this.collection.models;
addedAt = Math.min(Math.max(addedAt, 0), models.length - 1);
if (viewComparator) {
var addedModel = void 0;
// Preserve `at` location, even for a sorted view
if (addedAt) {
addedModel = models[addedAt];
models = models.slice(0, addedAt).concat(models.slice(addedAt + 1));
}
models = this._sortModelsBy(models, viewComparator);
if (addedModel) {
models.splice(addedAt, 0, addedModel);
}
}
// Filter after sorting in case the filter uses the index
models = this._filterModels(models);
return models;
},
// Filter an array of models, if a filter exists
_filterModels: function _filterModels(models) {
if (this.getOption('filter')) {
models = _$1.filter(models, function (model, index) {
return this._shouldAddChild(model, index);
}, this);
}
return models;
},
_sortModelsBy: function _sortModelsBy(models, comparator) {
if (typeof comparator === 'string') {
return _$1.sortBy(models, function (model) {
return model.get(comparator);
}, this);
} else if (comparator.length === 1) {
return _$1.sortBy(models, comparator, this);
} else {
return models.sort(_$1.bind(comparator, this));
}
},
// Internal method to show an empty view in place of a collection of child views,
// when the collection is empty
_showEmptyView: function _showEmptyView() {
var EmptyView = this.getEmptyView();
if (EmptyView && !this._showingEmptyView) {
this._showingEmptyView = true;
var model = new Backbone.Model();
var emptyViewOptions = this.getOption('emptyViewOptions') || this.getOption('childViewOptions');
if (_$1.isFunction(emptyViewOptions)) {
emptyViewOptions = emptyViewOptions.call(this, model, this._emptyViewIndex);
}
var view = this._buildChildView(model, EmptyView, emptyViewOptions);
this.triggerMethod('before:render:empty', this, view);
this._addChildView(view, 0);
this.triggerMethod('render:empty', this, view);
view._parent = this;
}
},
// Internal method to destroy an existing emptyView instance if one exists. Called when
// a collection view has been rendered empty, and then a child is added to the collection.
_destroyEmptyView: function _destroyEmptyView() {
if (this._showingEmptyView) {
this.triggerMethod('before:remove:empty', this);
this._destroyChildren();
delete this._showingEmptyView;
this.triggerMethod('remove:empty', this);
}
},
// Retrieve the empty view class
getEmptyView: function getEmptyView() {
return this.getOption('emptyView');
},
// Retrieve the `childView` class, either from `this.options.childView` or from
// the `childView` in the object definition. The "options" takes precedence.
// The `childView` property can be either a view class or a function that
// returns a view class. If it is a function, it will receive the model that
// will be passed to the view instance (created from the returned view class)
_getChildView: function _getChildView(child) {
var childView = this.getOption('childView');
if (!childView) {
throw new MarionetteError({
name: 'NoChildViewError',
message: 'A "childView" must be specified'
});
}
// first check if the `childView` is a view class (the common case)
// then check if it's a function (which we assume that returns a view class)
if (childView.prototype instanceof Backbone.View || childView === Backbone.View) {
return childView;
} else if (_$1.isFunction(childView)) {
return childView.call(this, child);
} else {
throw new MarionetteError({
name: 'InvalidChildViewError',
message: '"childView" must be a view class or a function that returns a view class'
});
}
},
// Render the child's view and add it to the HTML for the collection view at a given index.
// This will also update the indices of later views in the collection in order to keep the
// children in sync with the collection.
_addChild: function _addChild(child, ChildView, index) {
var childViewOptions = this.getValue(this.getOption('childViewOptions'), child, index);
var view = this._buildChildView(child, ChildView, childViewOptions);
// increment indices of views after this one
this._updateIndices(view, true, index);
this.triggerMethod('before:add:child', this, view);
this._addChildView(view, index);
this.triggerMethod('add:child', this, view);
view._parent = this;
return view;
},
// Internal method. This decrements or increments the indices of views after the added/removed
// view to keep in sync with the collection.
_updateIndices: function _updateIndices(view, increment, index) {
if (!this.getOption('sort')) {
return;
}
if (increment) {
// assign the index to the view
view._index = index;
}
// update the indexes of views after this one
this.children.each(function (laterView) {
if (laterView._index >= view._index) {
laterView._index += increment ? 1 : -1;
}
});
},
// Internal Method. Add the view to children and render it at the given index.
_addChildView: function _addChildView(view, index) {
// Only trigger attach if already attached and not buffering,
// otherwise _endBuffering() or Region#show() handles this.
var shouldTriggerAttach = !this._isBuffering && this._isAttached;
// set up the child view event forwarding
this._proxyChildEvents(view);
// Store the child view itself so we can properly remove and/or destroy it later
this.children.add(view);
if (!view.supportsRenderLifecycle) {
triggerMethodOn(view, 'before:render', view);
}
// Render view
view.render();
if (!view.supportsRenderLifecycle) {
view._isRendered = true;
triggerMethodOn(view, 'render', view);
}
if (shouldTriggerAttach) {
triggerMethodOn(view, 'before:attach', view);
}
// Attach view
this.attachHtml(this, view, index);
if (shouldTriggerAttach) {
view._isAttached = true;
triggerMethodOn(view, 'attach', view);
}
},
// Build a `childView` for a model in the collection.
_buildChildView: function _buildChildView(child, ChildViewClass, childViewOptions) {
var options = _$1.extend({ model: child }, childViewOptions);
var childView = new ChildViewClass(options);
monitorViewEvents(childView);
return childView;
},
// Remove the child view and destroy it. This function also updates the indices of later views
// in the collection in order to keep the children in sync with the collection.
_removeChildView: function _removeChildView(view) {
if (!view || view._isDestroyed) {
return;
}
this.triggerMethod('before:remove:child', this, view);
if (view.destroy) {
view.destroy();
} else {
destroyBackboneView(view);
}
delete view._parent;
this.stopListening(view);
this.children.remove(view);
this.triggerMethod('remove:child', this, view);
// decrement the index of views after this one
this._updateIndices(view, false);
},
// check if the collection is empty or optionally whether an array of pre-processed models is empty
isEmpty: function isEmpty(options) {
var models = void 0;
if (_$1.result(options, 'processedModels')) {
models = options.processedModels;
} else {
models = this.collection ? this.collection.models : [];
models = this._filterModels(models);
}
return models.length === 0;
},
// If empty, show the empty view
_checkEmpty: function _checkEmpty() {
if (this.isEmpty()) {
this._showEmptyView();
}
},
// You might need to override this if you've overridden attachHtml
attachBuffer: function attachBuffer(collectionView, buffer) {
collectionView.$el.append(buffer);
},
// Create a fragment buffer from the currently buffered children
_createBuffer: function _createBuffer() {
var elBuffer = document.createDocumentFragment();
_$1.each(this._bufferedChildren, function (b) {
elBuffer.appendChild(b.el);
});
return elBuffer;
},
// Append the HTML to the collection's `el`. Override this method to do something other
// than `.append`.
attachHtml: function attachHtml(collectionView, childView, index) {
if (collectionView._isBuffering) {
// buffering happens on reset events and initial renders
// in order to reduce the number of inserts into the
// document, which are expensive.
collectionView._bufferedChildren.splice(index, 0, childView);
} else {
// If we've already rendered the main collection, append
// the new child into the correct order if we need to. Otherwise
// append to the end.
if (!collectionView._insertBefore(childView, index)) {
collectionView._insertAfter(childView);
}
}
},
// Internal method. Check whether we need to insert the view into the correct position.
_insertBefore: function _insertBefore(childView, index) {
var currentView = void 0;
var findPosition = this.getOption('sort') && index < this.children.length - 1;
if (findPosition) {
// Find the view after this one
currentView = this.children.find(function (view) {
return view._index === index + 1;
});
}
if (currentView) {
currentView.$el.before(childView.el);
return true;
}
return false;
},
// Internal method. Append a view to the end of the $el
_insertAfter: function _insertAfter(childView) {
this.$el.append(childView.el);
},
// Internal method to set up the `children` object for storing all of the child views
_initChildViewStorage: function _initChildViewStorage() {
this.children = new ChildViewContainer();
},
// called by ViewMixin destroy
_removeChildren: function _removeChildren() {
this._destroyChildren({ checkEmpty: false });
},
// Destroy the child views that this collection view is holding on to, if any
_destroyChildren: function _destroyChildren() {
var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var checkEmpty = _ref2.checkEmpty;
this.triggerMethod('before:destroy:children', this);
var shouldCheckEmpty = checkEmpty !== false;
var childViews = this.children.map(_$1.identity);
this.children.each(this._removeChildView, this);
if (shouldCheckEmpty) {
this._checkEmpty();
}
this.triggerMethod('destroy:children', this);
return childViews;
},
// Return true if the given child should be shown. Return false otherwise.
// The filter will be passed (child, index, collection), where
// 'child' is the given model
// 'index' is the index of that model in the collection
// 'collection' is the collection referenced by this CollectionView
_shouldAddChild: function _shouldAddChild(child, index) {
var filter = this.getOption('filter');
return !_$1.isFunction(filter) || filter.call(this, child, index, this.collection);
},
// Set up the child view event forwarding. Uses a "childview:" prefix in front of all forwarded events.
_proxyChildEvents: function _proxyChildEvents(view) {
var prefix = this.getOption('childViewEventPrefix');
// Forward all child view events through the parent,
// prepending "childview:" to the event name
this.listenTo(view, 'all', function (eventName) {
var childEventName = prefix + ':' + eventName;
var childViewEvents = this.normalizeMethods(this._childViewEvents);
// call collectionView childViewEvent if defined
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
if (typeof childViewEvents !== 'undefined' && _$1.isFunction(childViewEvents[eventName])) {
childViewEvents[eventName].apply(this, args);
}
// use the parent view's proxyEvent handlers
var childViewTriggers = this._childViewTriggers;
// Call the event with the proxy name on the parent layout
if (childViewTriggers && _$1.isString(childViewTriggers[eventName])) {
this.triggerMethod.apply(this, [childViewTriggers[eventName]].concat(args));
}
this.triggerMethod.apply(this, [childEventName].concat(args));
});
},
_getImmediateChildren: function _getImmediateChildren() {
return _$1.values(this.children._views);
},
getViewComparator: function getViewComparator() {
return this.getOption('viewComparator');
}
});
_$1.extend(CollectionView.prototype, ViewMixin);
// Used for rendering a branch-leaf, hierarchical structure.
// Extends directly from CollectionView and also renders an
// a child view as `modelView`, for the top leaf
// @deprecated
var CompositeView = CollectionView.extend({
// Setting up the inheritance chain which allows changes to
// Marionette.CollectionView.prototype.constructor which allows overriding
// option to pass '{sort: false}' to prevent the CompositeView from
// maintaining the sorted order of the collection.
// This will fallback onto appending childView's to the end.
constructor: function constructor() {
deprecate('CompositeView is deprecated. Convert to View at your earliest convenience');
CollectionView.prototype.constructor.apply(this, arguments);
},
// Configured the initial events that the composite view
// binds to. Override this method to prevent the initial
// events, or to add your own initial events.
_initialEvents: function _initialEvents() {
// Bind only after composite view is rendered to avoid adding child views
// to nonexistent childViewContainer
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);
}
}
},
// Retrieve the `childView` to be used when rendering each of
// the items in the collection. The default is to return
// `this.childView` or Marionette.CompositeView if no `childView`
// has been defined. As happens in CollectionView, `childView` can
// be a function (which should return a view class).
_getChildView: function _getChildView(child) {
var childView = this.getOption('childView');
// for CompositeView, if `childView` is not specified, we'll get the same
// composite view class rendered for each child in the collection
// then check if the `childView` is a view class (the common case)
// finally check if it's a function (which we assume that returns a view class)
if (!childView) {
return this.constructor;
} else if (childView.prototype instanceof Backbone.View || childView === Backbone.View) {
return childView;
} else if (_$1.isFunction(childView)) {
return childView.call(this, child);
} else {
throw new MarionetteError({
name: 'InvalidChildViewError',
message: '"childView" must be a view class or a function that returns a view class'
});
}
},
// Return the serialized model
serializeData: function serializeData() {
return this.serializeModel();
},
// Renders the model and the collection.
render: function render() {
this._ensureViewIsIntact();
this._isRendering = true;
this.resetChildViewContainer();
this.triggerMethod('before:render', this);
this._renderTemplate();
this.bindUIElements();
this.renderChildren();
this._isRendering = false;
this._isRendered = true;
this.triggerMethod('render', this);
return this;
},
renderChildren: function renderChildren() {
if (this._isRendered || this._isRendering) {
CollectionView.prototype._renderChildren.call(this);
}
},
// You might need to override this if you've overridden attachHtml
attachBuffer: function attachBuffer(compositeView, buffer) {
var $container = this.getChildViewContainer(compositeView);
$container.append(buffer);
},
// Internal method. Append a view to the end of the $el.
// Overidden from CollectionView to ensure view is appended to
// childViewContainer
_insertAfter: function _insertAfter(childView) {
var $container = this.getChildViewContainer(this, childView);
$container.append(childView.el);
},
// Internal method. Append reordered childView'.
// Overidden from CollectionView to ensure reordered views
// are appended to childViewContainer
_appendReorderedChildren: function _appendReorderedChildren(children) {
var $container = this.getChildViewContainer(this);
$container.append(children);
},
// Internal method to ensure an `$childViewContainer` exists, for the
// `attachHtml` method to use.
getChildViewContainer: function getChildViewContainer(containerView, childView) {
if (!!containerView.$childViewContainer) {
return containerView.$childViewContainer;
}
var container = void 0;
var childViewContainer = getOption.call(containerView, 'childViewContainer');
if (childViewContainer) {
var selector = getValue.call(containerView, childViewContainer);
if (selector.charAt(0) === '@' && containerView.ui) {
container = containerView.ui[selector.substr(4)];
} else {
container = containerView.$(selector);
}
if (container.length <= 0) {
throw new MarionetteError({
name: 'ChildViewContainerMissingError',
message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer
});
}
} else {
container = containerView.$el;
}
containerView.$childViewContainer = container;
return container;
},
// Internal method to reset the `$childViewContainer` on render
resetChildViewContainer: function resetChildViewContainer() {
if (this.$childViewContainer) {
this.$childViewContainer = undefined;
}
}
});
// To prevent duplication but allow the best View organization
// Certain View methods are mixed directly into the deprecated CompositeView
var MixinFromView = _$1.pick(View.prototype, 'serializeModel', 'getTemplate', '_renderTemplate', 'attachElContent');
_$1.extend(CompositeView.prototype, MixinFromView);
var Behavior = MarionetteObject.extend({
cidPrefix: 'mnb',
constructor: function constructor(options, view) {
// Setup reference to the view.
// this comes in handle when a behavior
// wants to directly talk up the chain
// to the view.
this.view = view;
this.defaults = _$1.clone(_$1.result(this, 'defaults', {}));
this._setOptions(this.defaults, options);
// Construct an internal UI hash using
// the behaviors UI hash and then the view UI hash.
// This allows the user to use UI hash elements
// defined in the parent view as well as those
// defined in the given behavior.
// This order will help the reuse and share of a behavior
// between multiple views, while letting a view override a
// selector under an UI key.
this.ui = _$1.extend({}, _$1.result(this, 'ui'), _$1.result(view, 'ui'));
MarionetteObject.apply(this, arguments);
},
// proxy behavior $ method to the view
// this is useful for doing jquery DOM lookups
// scoped to behaviors view.
$: function $() {
return this.view.$.apply(this.view, arguments);
},
// Stops the behavior from listening to events.
// Overrides Object#destroy to prevent additional events from being triggered.
destroy: function destroy() {
this.stopListening();
return this;
},
proxyViewProperties: function proxyViewProperties() {
this.$el = this.view.$el;
this.el = this.view.el;
return this;
},
bindUIElements: function bindUIElements() {
this._bindUIElements();
return this;
},
unbindUIElements: function unbindUIElements() {
this._unbindUIElements();
return this;
},
getUI: function getUI(name) {
this.view._ensureViewIsIntact();
return this._getUI(name);
},
// Handle `modelEvents`, and `collectionEvents` configuration
delegateEntityEvents: function delegateEntityEvents() {
this._delegateEntityEvents(this.view.model, this.view.collection);
return this;
},
undelegateEntityEvents: function undelegateEntityEvents() {
this._undelegateEntityEvents(this.view.model, this.view.collection);
return this;
},
getEvents: function getEvents() {
// Normalize behavior events hash to allow
// a user to use the @ui. syntax.
var behaviorEvents = this.normalizeUIKeys(_$1.result(this, 'events'));
// binds the handler to the behavior and builds a unique eventName
return _$1.reduce(behaviorEvents, function (events, behaviorHandler, key) {
if (!_$1.isFunction(behaviorHandler)) {
behaviorHandler = this[behaviorHandler];
}
if (!behaviorHandler) {
return;
}
key = getUniqueEventName(key);
events[key] = _$1.bind(behaviorHandler, this);
return events;
}, {}, this);
},
// Internal method to build all trigger handlers for a given behavior
getTriggers: function getTriggers() {
if (!this.triggers) {
return;
}
// Normalize behavior triggers hash to allow
// a user to use the @ui. syntax.
var behaviorTriggers = this.normalizeUIKeys(_$1.result(this, 'triggers'));
return this._getViewTriggers(this.view, behaviorTriggers);
}
});
_$1.extend(Behavior.prototype, DelegateEntityEventsMixin, TriggersMixin, UIMixin);
// A container for a Marionette application.
var Application = MarionetteObject.extend({
cidPrefix: 'mna',
constructor: function constructor(options) {
this._setOptions(options);
this._initRegion();
MarionetteObject.prototype.constructor.apply(this, arguments);
},
regionClass: Region,
_initRegion: function _initRegion(options) {
var region = this.getOption('region');
var RegionClass = this.getOption('regionClass');
// if the region is a string expect an el or selector
// and instantiate a region
if (_$1.isString(region)) {
this._region = new RegionClass({
el: region
});
return;
}
this._region = region;
},
getRegion: function getRegion() {
return this._region;
},
showView: function showView(view) {
var region = this.getRegion();
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return region.show.apply(region, [view].concat(args));
},
getView: function getView() {
return this.getRegion().currentView;
},
// kick off all of the application's processes.
start: function start(options) {
this.triggerMethod('before:start', options);
this.triggerMethod('start', options);
}
});
var AppRouter = Backbone.Router.extend({
constructor: function constructor(options) {
this._setOptions(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);
},
// Similar to route method on a Backbone Router but
// method is called on the controller
appRoute: function appRoute(route, methodName) {
var controller = this._getController();
this._addAppRoute(controller, route, methodName);
},
// process the route event and trigger the onRoute
// method call, if it exists
_processOnRoute: function _processOnRoute(routeName, routeArgs) {
// make sure an onRoute before trying to call it
if (_$1.isFunction(this.onRoute)) {
// find the path that matches the current route
var routePath = _$1.invert(this.getOption('appRoutes'))[routeName];
this.onRoute(routeName, routePath, routeArgs);
}
},
// Internal method to process the `appRoutes` for the
// router, and turn them in to routes that trigger the
// specified method on the specified `controller`.
processAppRoutes: function processAppRoutes(controller, appRoutes) {
if (!appRoutes) {
return;
}
var routeNames = _$1.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
_$1.each(routeNames, function (route) {
this._addAppRoute(controller, route, appRoutes[route]);
}, this);
},
_getController: function _getController() {
return this.getOption('controller');
},
_addAppRoute: function _addAppRoute(controller, route, methodName) {
var method = controller[methodName];
if (!method) {
throw new MarionetteError('Method "' + methodName + '" was not found on the controller');
}
this.route(route, methodName, _$1.bind(method, controller));
},
triggerMethod: triggerMethod
});
_$1.extend(AppRouter.prototype, CommonMixin);
// Placeholder method to be extended by the user.
// The method should define the object that stores the behaviors.
// i.e.
//
// ```js
// Marionette.Behaviors.behaviorsLookup: function() {
// return App.Behaviors
// }
// ```
function behaviorsLookup() {
throw new MarionetteError({
message: 'You must define where your behaviors are stored.',
url: 'marionette.behaviors.md#behaviorslookup'
});
}
// Add Feature flags here
// e.g. 'class' => false
var FEATURES = {};
function isEnabled(name) {
return !!FEATURES[name];
}
function setEnabled(name, state) {
return FEATURES[name] = state;
}
var previousMarionette = Backbone.Marionette;
var Marionette = Backbone.Marionette = {};
// This allows you to run multiple instances of Marionette on the same
// webapp. After loading the new version, call `noConflict()` to
// get a reference to it. At the same time the old version will be
// returned to Backbone.Marionette.
Marionette.noConflict = function () {
Backbone.Marionette = previousMarionette;
return this;
};
// Utilities
Marionette.bindEntityEvents = proxy(bindEntityEvents);
Marionette.unbindEntityEvents = proxy(unbindEntityEvents);
Marionette.bindRadioEvents = proxy(bindEntityEvents);
Marionette.unbindRadioEvents = proxy(unbindEntityEvents);
Marionette.bindRadioRequests = proxy(bindRadioRequests);
Marionette.unbindRadioRequests = proxy(unbindRadioRequests);
Marionette.mergeOptions = proxy(mergeOptions);
Marionette.getOption = proxy(getOption);
Marionette.normalizeMethods = proxy(normalizeMethods);
Marionette.extend = extend;
Marionette.isNodeAttached = isNodeAttached;
Marionette.deprecate = deprecate;
Marionette.triggerMethod = proxy(triggerMethod);
Marionette.triggerMethodOn = triggerMethodOn;
Marionette.isEnabled = isEnabled;
Marionette.setEnabled = setEnabled;
Marionette.monitorViewEvents = monitorViewEvents;
Marionette.Behaviors = {};
Marionette.Behaviors.behaviorsLookup = behaviorsLookup;
// Classes
Marionette.Application = Application;
Marionette.AppRouter = AppRouter;
Marionette.Renderer = Renderer;
Marionette.TemplateCache = TemplateCache;
Marionette.View = View;
Marionette.CollectionView = CollectionView;
Marionette.CompositeView = CompositeView;
Marionette.Behavior = Behavior;
Marionette.Region = Region;
Marionette.Error = MarionetteError;
Marionette.Object = MarionetteObject;
// Configuration
Marionette.DEV_MODE = false;
Marionette.FEATURES = FEATURES;
Marionette.VERSION = version;
return Marionette;
}(Backbone,_,Backbone.Radio,Backbone.ChildViewContainer));
//# sourceMappingURL=backbone.marionette.js.map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment