Created
July 25, 2017 07:03
-
-
Save paulfalgout/5d3cadeef69436818eb92f92ad66ba71 to your computer and use it in GitHub Desktop.
Test Mn alias fix
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MarionetteJS (Backbone.Marionette) | |
// ---------------------------------- | |
// v3.3.1 | |
// | |
// Copyright (c)2017 Derick Bailey, Muted Solutions, LLC. | |
// Distributed under MIT license | |
// | |
// http://marionettejs.com | |
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('backbone'), require('underscore'), require('backbone.radio')) : | |
typeof define === 'function' && define.amd ? define(['backbone', 'underscore', 'backbone.radio'], factory) : | |
(global.Marionette = factory(global.Backbone,global._,global.Backbone.Radio)); | |
}(this, (function (Backbone,_,Radio) { 'use strict'; | |
Backbone = Backbone && 'default' in Backbone ? Backbone['default'] : Backbone; | |
_ = _ && 'default' in _ ? _['default'] : _; | |
Radio = Radio && 'default' in Radio ? Radio['default'] : Radio; | |
var version = "3.3.1"; | |
//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); | |
}; | |
}; | |
// Marionette.extend | |
// ----------------- | |
// Borrow the Backbone `extend` method so we can use it as needed | |
var extend = Backbone.Model.extend; | |
/* global console */ | |
var deprecate = function deprecate(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 (!Marionette.DEV_MODE) { | |
return; | |
} | |
if ((test === undefined || !test) && !deprecate._cache[message]) { | |
deprecate._warn('Deprecation warning: ' + message); | |
deprecate._cache[message] = true; | |
} | |
}; | |
/* istanbul ignore next: can't clear console */ | |
deprecate._console = typeof console !== 'undefined' ? console : {}; | |
deprecate._warn = function () { | |
var warn = deprecate._console.warn || deprecate._console.log || _.noop; | |
return warn.apply(deprecate._console, arguments); | |
}; | |
deprecate._cache = {}; | |
// Marionette.isNodeAttached | |
// ------------------------- | |
// Determine if `el` is a child of the document | |
var isNodeAttached = function isNodeAttached(el) { | |
return document.documentElement.contains(el && el.parentNode); | |
}; | |
// Merge `keys` from `options` onto `this` | |
var mergeOptions = function mergeOptions(options, keys) { | |
var _this = this; | |
if (!options) { | |
return; | |
} | |
_.each(keys, function (key) { | |
var option = options[key]; | |
if (option !== undefined) { | |
_this[key] = option; | |
} | |
}); | |
}; | |
// 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) { | |
var _this = this; | |
return _.reduce(hash, function (normalizedHash, method, name) { | |
if (!_.isFunction(method)) { | |
method = _this[method]; | |
} | |
if (method) { | |
normalizedHash[name] = method; | |
} | |
return normalizedHash; | |
}, {}); | |
}; | |
// Trigger Method | |
// -------------- | |
// 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(); | |
} | |
var getOnMethodName = _.memoize(function (event) { | |
return 'on' + event.replace(splitter, getEventName); | |
}); | |
// 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) { | |
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | |
args[_key - 1] = arguments[_key]; | |
} | |
// get the method name from the event name | |
var methodName = getOnMethodName(event); | |
var method = getOption.call(this, methodName); | |
var result = void 0; | |
// call the onMethodName if it exists | |
if (_.isFunction(method)) { | |
// pass all args, except the event name | |
result = method.apply(this, args); | |
} | |
// trigger the event | |
this.trigger.apply(this, arguments); | |
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) { | |
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | |
args[_key2 - 1] = arguments[_key2]; | |
} | |
if (_.isFunction(context.triggerMethod)) { | |
return context.triggerMethod.apply(context, args); | |
} | |
return triggerMethod.apply(context, args); | |
} | |
// DOM Refresh | |
// ----------- | |
// Trigger method on children unless a pure Backbone.View | |
function triggerMethodChildren(view, event, shouldTrigger) { | |
if (!view._getImmediateChildren) { | |
return; | |
} | |
_.each(view._getImmediateChildren(), function (child) { | |
if (!shouldTrigger(child)) { | |
return; | |
} | |
triggerMethodOn(child, event, child); | |
}); | |
} | |
function shouldTriggerAttach(view) { | |
return !view._isAttached; | |
} | |
function shouldAttach(view) { | |
if (!shouldTriggerAttach(view)) { | |
return false; | |
} | |
view._isAttached = true; | |
return true; | |
} | |
function shouldTriggerDetach(view) { | |
return view._isAttached; | |
} | |
function shouldDetach(view) { | |
if (!shouldTriggerDetach(view)) { | |
return false; | |
} | |
view._isAttached = false; | |
return true; | |
} | |
function triggerDOMRefresh(view) { | |
if (view._isAttached && view._isRendered) { | |
triggerMethodOn(view, 'dom:refresh', view); | |
} | |
} | |
function triggerDOMRemove(view) { | |
if (view._isAttached && view._isRendered) { | |
triggerMethodOn(view, 'dom:remove', view); | |
} | |
} | |
function handleBeforeAttach() { | |
triggerMethodChildren(this, 'before:attach', shouldTriggerAttach); | |
} | |
function handleAttach() { | |
triggerMethodChildren(this, 'attach', shouldAttach); | |
triggerDOMRefresh(this); | |
} | |
function handleBeforeDetach() { | |
triggerMethodChildren(this, 'before:detach', shouldTriggerDetach); | |
triggerDOMRemove(this); | |
} | |
function handleDetach() { | |
triggerMethodChildren(this, 'detach', shouldDetach); | |
} | |
function handleBeforeRender() { | |
triggerDOMRemove(this); | |
} | |
function handleRender() { | |
triggerDOMRefresh(this); | |
} | |
// 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; | |
view.on({ | |
'before:attach': handleBeforeAttach, | |
'attach': handleAttach, | |
'before:detach': handleBeforeDetach, | |
'detach': handleDetach, | |
'before:render': handleBeforeRender, | |
'render': handleRender | |
}); | |
} | |
// Error | |
// ----- | |
var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number']; | |
var MarionetteError = extend.call(Error, { | |
urlRoot: 'http://marionettejs.com/docs/v' + version + '/', | |
constructor: function constructor(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 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 Entity Events & Unbind Entity Events | |
// ----------------------------------------- | |
// | |
// These methods are used to bind/unbind a backbone "entity" (e.g. collection/model) | |
// to methods on a target object. | |
// | |
// The first parameter, `target`, must have the Backbone.Events module mixed in. | |
// | |
// The second parameter is the `entity` (Backbone.Model, Backbone.Collection or | |
// any object that has Backbone.Events mixed in) to bind the events from. | |
// | |
// The third parameter is a hash of { "event:name": "eventHandler" } | |
// configuration. Multiple handlers can be separated by a space. A | |
// function can be supplied instead of a string handler name. | |
// 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+/); | |
_.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 (!_.isObject(bindings)) { | |
throw new MarionetteError({ | |
message: 'Bindings must be an object.', | |
url: 'marionette.functions.html#marionettebindevents' | |
}); | |
} | |
// iterate the bindings and bind/unbind them | |
_.each(bindings, function (method, evt) { | |
// allow for a list of method names as a string | |
if (_.isString(method)) { | |
bindFromStrings(target, entity, evt, method, actionName); | |
return; | |
} | |
target[actionName](entity, evt, method); | |
}); | |
} | |
function bindEvents(entity, bindings) { | |
iterateEvents(this, entity, bindings, 'listenTo'); | |
return this; | |
} | |
function unbindEvents(entity, bindings) { | |
iterateEvents(this, entity, bindings, 'stopListening'); | |
return this; | |
} | |
// Bind/Unbind Radio Requests | |
// ----------------------------------------- | |
// | |
// These methods are used to bind/unbind a backbone.radio request | |
// to methods on a target object. | |
// | |
// The first parameter, `target`, will set the context of the reply method | |
// | |
// The second parameter is the `Radio.channel` to bind the reply to. | |
// | |
// The third parameter is a hash of { "request:name": "replyHandler" } | |
// configuration. A function can be supplied instead of a string handler name. | |
function iterateReplies(target, channel, bindings, actionName) { | |
if (!channel || !bindings) { | |
return; | |
} | |
// type-check bindings | |
if (!_.isObject(bindings)) { | |
throw new MarionetteError({ | |
message: 'Bindings must be an object.', | |
url: 'marionette.functions.html#marionettebindrequests' | |
}); | |
} | |
var normalizedRadioRequests = normalizeMethods.call(target, bindings); | |
channel[actionName](normalizedRadioRequests, target); | |
} | |
function bindRequests(channel, bindings) { | |
iterateReplies(this, channel, bindings, 'reply'); | |
return this; | |
} | |
function unbindRequests(channel, bindings) { | |
iterateReplies(this, channel, bindings, 'stopReplying'); | |
return this; | |
} | |
// Internal utility for setting options consistently across Mn | |
var setOptions = function setOptions(options) { | |
this.options = _.extend({}, _.result(this, 'options'), options); | |
}; | |
var CommonMixin = { | |
// 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. | |
bindEvents: bindEvents, | |
// Enable unbinding view's events from another entity. | |
unbindEvents: unbindEvents | |
}; | |
// MixinOptions | |
// - channelName | |
// - radioEvents | |
// - radioRequests | |
var RadioMixin = { | |
_initRadio: function _initRadio() { | |
var channelName = _.result(this, 'channelName'); | |
if (!channelName) { | |
return; | |
} | |
/* istanbul ignore next */ | |
if (!Radio) { | |
throw new MarionetteError({ | |
name: 'BackboneRadioMissing', | |
message: 'The dependency "backbone.radio" is missing.' | |
}); | |
} | |
var channel = this._channel = Radio.channel(channelName); | |
var radioEvents = _.result(this, 'radioEvents'); | |
this.bindEvents(channel, radioEvents); | |
var radioRequests = _.result(this, 'radioRequests'); | |
this.bindRequests(channel, radioRequests); | |
this.on('destroy', this._destroyRadio); | |
}, | |
_destroyRadio: function _destroyRadio() { | |
this._channel.stopReplying(null, null, this); | |
}, | |
getChannel: function getChannel() { | |
return this._channel; | |
}, | |
// Proxy `bindEvents` | |
bindEvents: bindEvents, | |
// Proxy `unbindEvents` | |
unbindEvents: unbindEvents, | |
// Proxy `bindRequests` | |
bindRequests: bindRequests, | |
// Proxy `unbindRequests` | |
unbindRequests: unbindRequests | |
}; | |
// Object | |
// ------ | |
var ClassOptions = ['channelName', 'radioEvents', 'radioRequests']; | |
// A Base Class that other Classes should descend from. | |
// Object borrows many conventions and utilities from Backbone. | |
var MarionetteObject = function MarionetteObject(options) { | |
if (!this.hasOwnProperty('options')) { | |
this._setOptions(options); | |
} | |
this.mergeOptions(options, ClassOptions); | |
this._setCid(); | |
this._initRadio(); | |
this.initialize.apply(this, arguments); | |
}; | |
MarionetteObject.extend = extend; | |
// Object Methods | |
// -------------- | |
// Ensure it can trigger events with Backbone.Events | |
_.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() {}, | |
_setCid: function _setCid() { | |
if (this.cid) { | |
return; | |
} | |
this.cid = _.uniqueId(this.cidPrefix); | |
}, | |
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', this].concat(args)); | |
this._isDestroyed = true; | |
this.triggerMethod.apply(this, ['destroy', this].concat(args)); | |
this.stopListening(); | |
return this; | |
}, | |
triggerMethod: triggerMethod | |
}); | |
// Template Cache | |
// -------------- | |
// 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 | |
_.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 = void 0; | |
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 | |
_.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 _.template(rawTemplate, options); | |
} | |
}); | |
// Implementation of the invoke method (http://underscorejs.org/#invoke) with support for | |
// lodash v3, v4, and underscore.js | |
var _invoke = _.invokeMap || _.invoke; | |
// MixinOptions | |
// - behaviors | |
// 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 (_.isFunction(options)) { | |
return options; | |
} | |
// behaviorsLookup can be either a flat object or a method | |
if (_.isFunction(Marionette.Behaviors.behaviorsLookup)) { | |
return Marionette.Behaviors.behaviorsLookup(options, key)[key]; | |
} | |
return Marionette.Behaviors.behaviorsLookup[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 _.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, _.result(behavior, 'behaviors')); | |
return [behavior].concat(nestedBehaviors); | |
}).flatten().value(); | |
} | |
var BehaviorsMixin = { | |
_initBehaviors: function _initBehaviors() { | |
this._behaviors = this._getBehaviors(); | |
}, | |
_getBehaviors: function _getBehaviors() { | |
var behaviors = _.result(this, 'behaviors'); | |
// Behaviors defined on a view can be a flat object literal | |
// or it can be a function that returns an object. | |
return _.isObject(behaviors) ? parseBehaviors(this, behaviors) : {}; | |
}, | |
_getBehaviorTriggers: function _getBehaviorTriggers() { | |
var triggers = _invoke(this._behaviors, 'getTriggers'); | |
return _.reduce(triggers, function (memo, _triggers) { | |
return _.extend(memo, _triggers); | |
}, {}); | |
}, | |
_getBehaviorEvents: function _getBehaviorEvents() { | |
var events = _invoke(this._behaviors, 'getEvents'); | |
return _.reduce(events, function (memo, _events) { | |
return _.extend(memo, _events); | |
}, {}); | |
}, | |
// proxy behavior $el to the view's $el. | |
_proxyBehaviorViewProperties: function _proxyBehaviorViewProperties() { | |
_invoke(this._behaviors, 'proxyViewProperties'); | |
}, | |
// delegate modelEvents and collectionEvents | |
_delegateBehaviorEntityEvents: function _delegateBehaviorEntityEvents() { | |
_invoke(this._behaviors, 'delegateEntityEvents'); | |
}, | |
// undelegate modelEvents and collectionEvents | |
_undelegateBehaviorEntityEvents: function _undelegateBehaviorEntityEvents() { | |
_invoke(this._behaviors, 'undelegateEntityEvents'); | |
}, | |
_destroyBehaviors: function _destroyBehaviors() { | |
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | |
args[_key] = arguments[_key]; | |
} | |
// Call destroy on each behavior after | |
// destroying the view. | |
// This unbinds event listeners | |
// that behaviors have registered for. | |
_invoke.apply(undefined, [this._behaviors, 'destroy'].concat(args)); | |
}, | |
// Remove a behavior | |
_removeBehavior: function _removeBehavior(behavior) { | |
// Don't worry about the clean up if the view is destroyed | |
if (this._isDestroyed) { | |
return; | |
} | |
this._behaviors = _.without(this._behaviors, behavior); | |
}, | |
_bindBehaviorUIElements: function _bindBehaviorUIElements() { | |
_invoke(this._behaviors, 'bindUIElements'); | |
}, | |
_unbindBehaviorUIElements: function _unbindBehaviorUIElements() { | |
_invoke(this._behaviors, 'unbindUIElements'); | |
}, | |
_triggerEventOnBehaviors: function _triggerEventOnBehaviors() { | |
var behaviors = this._behaviors; | |
// Use good ol' for as this is a very hot function | |
for (var i = 0, length = behaviors && behaviors.length; i < length; i++) { | |
triggerMethod.apply(behaviors[i], arguments); | |
} | |
} | |
}; | |
// MixinOptions | |
// - collectionEvents | |
// - modelEvents | |
var DelegateEntityEventsMixin = { | |
// Handle `modelEvents`, and `collectionEvents` configuration | |
_delegateEntityEvents: function _delegateEntityEvents(model, collection) { | |
this._undelegateEntityEvents(model, collection); | |
var modelEvents = _.result(this, 'modelEvents'); | |
bindEvents.call(this, model, modelEvents); | |
var collectionEvents = _.result(this, 'collectionEvents'); | |
bindEvents.call(this, collection, collectionEvents); | |
}, | |
_undelegateEntityEvents: function _undelegateEntityEvents(model, collection) { | |
var modelEvents = _.result(this, 'modelEvents'); | |
unbindEvents.call(this, model, modelEvents); | |
var collectionEvents = _.result(this, 'collectionEvents'); | |
unbindEvents.call(this, collection, collectionEvents); | |
} | |
}; | |
// Borrow event splitter from Backbone | |
var delegateEventSplitter = /^(\S+)\s*(.*)$/; | |
function uniqueName(eventName, selector) { | |
return '' + eventName + _.uniqueId('.evt') + ' ' + selector; | |
} | |
// 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]); | |
}; | |
// Add Feature flags here | |
// e.g. 'class' => false | |
var FEATURES = { | |
childViewEventPrefix: true, | |
triggersStopPropagation: true, | |
triggersPreventDefault: true | |
}; | |
function isEnabled(name) { | |
return !!FEATURES[name]; | |
} | |
function setEnabled(name, state) { | |
return FEATURES[name] = state; | |
} | |
// Internal method to create an event handler for a given `triggerDef` like | |
// 'click:foo' | |
function buildViewTrigger(view, triggerDef) { | |
if (_.isString(triggerDef)) { | |
triggerDef = { event: triggerDef }; | |
} | |
var eventName = triggerDef.event; | |
var shouldPreventDefault = !!triggerDef.preventDefault; | |
if (isEnabled('triggersPreventDefault')) { | |
shouldPreventDefault = triggerDef.preventDefault !== false; | |
} | |
var shouldStopPropagation = !!triggerDef.stopPropagation; | |
if (isEnabled('triggersStopPropagation')) { | |
shouldStopPropagation = triggerDef.stopPropagation !== false; | |
} | |
return function (event) { | |
if (shouldPreventDefault) { | |
event.preventDefault(); | |
} | |
if (shouldStopPropagation) { | |
event.stopPropagation(); | |
} | |
view.triggerMethod(eventName, view, event); | |
}; | |
} | |
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 _.reduce(triggers, function (events, value, key) { | |
key = getUniqueEventName(key); | |
events[key] = buildViewTrigger(view, value); | |
return events; | |
}, {}); | |
} | |
}; | |
// 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 _.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) { | |
_.each(hash, function (val, key) { | |
if (_.isString(val)) { | |
hash[key] = _normalizeUIString(val, ui); | |
} else if (_.isObject(val) && _.isArray(properties)) { | |
_.extend(val, _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] = _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 passed string with the views `ui` selectors. | |
// `"@ui.bar"` | |
normalizeUIString: function normalizeUIString(uiString) { | |
var uiBindings = this._getUIBindings(); | |
return _normalizeUIString(uiString, 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 = _.result(this, '_uiBindings'); | |
var ui = _.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() { | |
var _this = this; | |
if (!this.ui) { | |
return; | |
} | |
// store the ui hash in _uiBindings so they can be reset later | |
// and so re-rendering the view will be able to find the bindings | |
if (!this._uiBindings) { | |
this._uiBindings = this.ui; | |
} | |
// get the bindings result, as a function or otherwise | |
var bindings = _.result(this, '_uiBindings'); | |
// empty the ui so we don't have anything to start with | |
this._ui = {}; | |
// bind each of the selectors | |
_.each(bindings, function (selector, key) { | |
_this._ui[key] = _this.$(selector); | |
}); | |
this.ui = this._ui; | |
}, | |
_unbindUIElements: function _unbindUIElements() { | |
var _this2 = this; | |
if (!this.ui || !this._uiBindings) { | |
return; | |
} | |
// delete all of the existing ui bindings | |
_.each(this.ui, function ($el, name) { | |
delete _this2.ui[name]; | |
}); | |
// 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]; | |
} | |
}; | |
// DomApi | |
// --------- | |
// Performant method for returning the jQuery instance | |
function _getEl(el) { | |
return el instanceof Backbone.$ ? el : Backbone.$(el); | |
} | |
// Static setter | |
function setDomApi(mixin) { | |
this.prototype.Dom = _.extend({}, this.prototype.Dom, mixin); | |
} | |
var DomApi = { | |
// Returns a new HTML DOM node instance | |
createBuffer: function createBuffer() { | |
return document.createDocumentFragment(); | |
}, | |
// Lookup the `selector` string | |
// Selector may also be a DOM element | |
// Returns an array-like object of nodes | |
getEl: function getEl(selector) { | |
return _getEl(selector); | |
}, | |
// Finds the `selector` string with the el | |
// Returns an array-like object of nodes | |
findEl: function findEl(el, selector) { | |
var _$el = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _getEl(el); | |
return _$el.find(selector); | |
}, | |
// Detach `el` from the DOM without removing listeners | |
detachEl: function detachEl(el) { | |
var _$el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _getEl(el); | |
_$el.detach(); | |
}, | |
// Remove `oldEl` from the DOM and put `newEl` in its place | |
replaceEl: function replaceEl(newEl, oldEl) { | |
if (newEl === oldEl) { | |
return; | |
} | |
var parent = oldEl.parentNode; | |
if (!parent) { | |
return; | |
} | |
parent.replaceChild(newEl, oldEl); | |
}, | |
// Replace the contents of `el` with the HTML string of `html` | |
setContents: function setContents(el, html) { | |
var _$el = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _getEl(el); | |
_$el.html(html); | |
}, | |
// Takes the DOM node `el` and appends the DOM node `contents` | |
// to the end of the element's contents. | |
appendContents: function appendContents(el, contents) { | |
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, | |
_ref$_$el = _ref._$el, | |
_$el = _ref$_$el === undefined ? _getEl(el) : _ref$_$el, | |
_ref$_$contents = _ref._$contents, | |
_$contents = _ref$_$contents === undefined ? _getEl(contents) : _ref$_$contents; | |
_$el.append(_$contents); | |
}, | |
// Remove the inner contents of `el` from the DOM while leaving | |
// `el` itself in the DOM. | |
detachContents: function detachContents(el) { | |
var _$el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _getEl(el); | |
_$el.contents().detach(); | |
} | |
}; | |
// ViewMixin | |
// --------- | |
// MixinOptions | |
// - behaviors | |
// - childViewEventPrefix | |
// - childViewEvents | |
// - childViewTriggers | |
// - collectionEvents | |
// - modelEvents | |
// - triggers | |
// - ui | |
var ViewMixin = { | |
Dom: DomApi, | |
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; | |
}, | |
// 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 = _.extend({}, this._getBehaviorEvents(), viewEvents, this._getBehaviorTriggers(), this.getTriggers()); | |
Backbone.View.prototype.delegateEvents.call(this, combinedEvents); | |
return this; | |
}, | |
_getEvents: function _getEvents(eventsArg) { | |
var events = eventsArg || this.events; | |
if (_.isFunction(events)) { | |
return this.normalizeUIKeys(events.call(this)); | |
} | |
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(_.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; | |
}, | |
// 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', this].concat(args)); | |
if (shouldTriggerDetach) { | |
this.triggerMethod('before:detach', this); | |
} | |
// unbind UI elements | |
this.unbindUIElements(); | |
// remove the view from the DOM | |
this._removeElement(); | |
if (shouldTriggerDetach) { | |
this._isAttached = false; | |
this.triggerMethod('detach', this); | |
} | |
// remove children after the remove to prevent extra paints | |
this._removeChildren(); | |
this._isDestroyed = true; | |
this._isRendered = false; | |
// Destroy behaviors after _isDestroyed flag | |
this._destroyBehaviors.apply(this, args); | |
this.triggerMethod.apply(this, ['destroy', this].concat(args)); | |
this.stopListening(); | |
return this; | |
}, | |
// Equates to this.$el.remove | |
_removeElement: function _removeElement() { | |
this.$el.off().removeData(); | |
this.Dom.detachEl(this.el, this.$el); | |
}, | |
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) { | |
return this._getUI(name); | |
}, | |
// used as the prefix for child view events | |
// that are forwarded through the layoutview | |
childViewEventPrefix: function childViewEventPrefix() { | |
return isEnabled('childViewEventPrefix') ? 'childview' : false; | |
}, | |
// import the `triggerMethod` to trigger events with corresponding | |
// methods if the method exists | |
triggerMethod: function triggerMethod$$1() { | |
var ret = triggerMethod.apply(this, arguments); | |
this._triggerEventOnBehaviors.apply(this, arguments); | |
return ret; | |
}, | |
// Cache `childViewEvents` and `childViewTriggers` | |
_buildEventProxies: function _buildEventProxies() { | |
this._childViewEvents = _.result(this, 'childViewEvents'); | |
this._childViewTriggers = _.result(this, 'childViewTriggers'); | |
}, | |
_proxyChildViewEvents: function _proxyChildViewEvents(view) { | |
this.listenTo(view, 'all', this._childViewEventHandler); | |
}, | |
_childViewEventHandler: function _childViewEventHandler(eventName) { | |
var childViewEvents = this.normalizeMethods(this._childViewEvents); | |
// call collectionView childViewEvent if defined | |
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | |
args[_key2 - 1] = arguments[_key2]; | |
} | |
if (typeof childViewEvents !== 'undefined' && _.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 && _.isString(childViewTriggers[eventName])) { | |
this.triggerMethod.apply(this, [childViewTriggers[eventName]].concat(args)); | |
} | |
var prefix = _.result(this, 'childViewEventPrefix'); | |
if (prefix !== false) { | |
var childEventName = prefix + ':' + eventName; | |
this.triggerMethod.apply(this, [childEventName].concat(args)); | |
} | |
} | |
}; | |
_.extend(ViewMixin, BehaviorsMixin, CommonMixin, DelegateEntityEventsMixin, TriggersMixin, UIMixin); | |
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); | |
} | |
} | |
function destroyView(view) { | |
if (view.destroy) { | |
view.destroy(); | |
return; | |
} | |
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); | |
} | |
} | |
// Region | |
// ------ | |
var ClassOptions$2 = ['allowMissingEl', 'parentEl', 'replaceElement']; | |
var Region = MarionetteObject.extend({ | |
Dom: DomApi, | |
cidPrefix: 'mnr', | |
replaceElement: false, | |
_isReplaced: false, | |
_isSwappingView: false, | |
constructor: function constructor(options) { | |
this._setOptions(options); | |
this.mergeOptions(options, ClassOptions$2); | |
// getOption necessary because options.el may be passed as undefined | |
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(options)) { | |
return; | |
} | |
view = this._getView(view, options); | |
if (view === this.currentView) { | |
return this; | |
} | |
this._isSwappingView = !!this.currentView; | |
this.triggerMethod('before:show', this, view, options); | |
// Assume an attached view is already in the region for pre-existing DOM | |
if (!view._isAttached) { | |
this.empty(options); | |
} | |
this._setupChildView(view); | |
renderView(view); | |
this._attachView(view, options); | |
this.currentView = view; | |
this.triggerMethod('show', this, view, options); | |
this._isSwappingView = false; | |
return this; | |
}, | |
_setupChildView: function _setupChildView(view) { | |
monitorViewEvents(view); | |
this._proxyChildViewEvents(view); | |
// 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); | |
}, | |
_proxyChildViewEvents: function _proxyChildViewEvents(view) { | |
var parentView = this._parentView; | |
if (!parentView) { | |
return; | |
} | |
parentView._proxyChildViewEvents(view); | |
}, | |
_attachView: function _attachView(view) { | |
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | |
var shouldTriggerAttach = !view._isAttached && isNodeAttached(this.el); | |
var shouldReplaceEl = typeof options.replaceElement === 'undefined' ? !!_.result(this, 'replaceElement') : !!options.replaceElement; | |
if (shouldTriggerAttach) { | |
triggerMethodOn(view, 'before:attach', view); | |
} | |
if (shouldReplaceEl) { | |
this._replaceEl(view); | |
} else { | |
this.attachHtml(view); | |
} | |
if (shouldTriggerAttach) { | |
view._isAttached = true; | |
triggerMethodOn(view, 'attach', view); | |
} | |
}, | |
_ensureElement: function _ensureElement() { | |
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | |
if (!_.isObject(this.el)) { | |
this.$el = this.getEl(this.el); | |
this.el = this.$el[0]; | |
} | |
if (!this.$el || this.$el.length === 0) { | |
var allowMissingEl = typeof options.allowMissingEl === 'undefined' ? !!_.result(this, 'allowMissingEl') : !!options.allowMissingEl; | |
if (allowMissingEl) { | |
return false; | |
} else { | |
throw new MarionetteError('An "el" must exist in DOM for this region ' + this.cid); | |
} | |
} | |
return true; | |
}, | |
_getView: function _getView(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.' | |
}); | |
} | |
if (view instanceof Backbone.View) { | |
return view; | |
} | |
var viewOptions = this._getViewOptions(view); | |
return new View(viewOptions); | |
}, | |
// This allows for a template or a static string to be | |
// used as a template | |
_getViewOptions: function _getViewOptions(viewOptions) { | |
if (_.isFunction(viewOptions)) { | |
return { template: viewOptions }; | |
} | |
if (_.isObject(viewOptions)) { | |
return viewOptions; | |
} | |
var template = function template() { | |
return viewOptions; | |
}; | |
return { template: template }; | |
}, | |
// 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) { | |
var context = _.result(this, 'parentEl'); | |
if (context && _.isString(el)) { | |
var $el = this.Dom.findEl(context, el); | |
if ($el.length) { | |
return $el; | |
} | |
} | |
return this.Dom.getEl(el); | |
}, | |
_replaceEl: function _replaceEl(view) { | |
// always restore the el to ensure the regions el is present before replacing | |
this._restoreEl(); | |
view.on('before:destroy', this._restoreEl, this); | |
this.Dom.replaceEl(view.el, this.el); | |
this._isReplaced = true; | |
}, | |
// Restore the region's element in the DOM. | |
_restoreEl: function _restoreEl() { | |
// There is nothing to replace | |
if (!this._isReplaced) { | |
return; | |
} | |
var view = this.currentView; | |
if (!view) { | |
return; | |
} | |
this._detachView(view); | |
this._isReplaced = false; | |
}, | |
// Check to see if the region's el was replaced. | |
isReplaced: function isReplaced() { | |
return !!this._isReplaced; | |
}, | |
// Check to see if a view is being swapped by another | |
isSwappingView: function isSwappingView() { | |
return !!this._isSwappingView; | |
}, | |
// Override this method to change how the new view is appended to the `$el` that the | |
// region is managing | |
attachHtml: function attachHtml(view) { | |
this.Dom.appendContents(this.el, view.el, { _$el: this.$el, _$contents: 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() { | |
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { allowMissingEl: true }; | |
var view = this.currentView; | |
// If there is no view in the region we should only detach current html | |
if (!view) { | |
if (this._ensureElement(options)) { | |
this.detachHtml(); | |
} | |
return this; | |
} | |
var shouldDestroy = !options.preventDestroy; | |
if (!shouldDestroy) { | |
deprecate('The preventDestroy option is deprecated. Use Region#detachView'); | |
} | |
this._empty(view, shouldDestroy); | |
return this; | |
}, | |
_empty: function _empty(view, shouldDestroy) { | |
view.off('destroy', this._empty, this); | |
this.triggerMethod('before:empty', this, view); | |
this._restoreEl(); | |
delete this.currentView; | |
if (!view._isDestroyed) { | |
if (shouldDestroy) { | |
this.removeView(view); | |
} else { | |
this._detachView(view); | |
} | |
this._stopChildViewEvents(view); | |
} | |
this.triggerMethod('empty', this, view); | |
}, | |
_stopChildViewEvents: function _stopChildViewEvents(view) { | |
var parentView = this._parentView; | |
if (!parentView) { | |
return; | |
} | |
this._parentView.stopListening(view); | |
}, | |
destroyView: function destroyView$$1(view) { | |
if (view._isDestroyed) { | |
return view; | |
} | |
destroyView(view); | |
return view; | |
}, | |
removeView: function removeView(view) { | |
this.destroyView(view); | |
}, | |
// Empties the Region without destroying the view | |
// Returns the detached view | |
detachView: function detachView() { | |
var view = this.currentView; | |
if (!view) { | |
return; | |
} | |
this._empty(view); | |
return view; | |
}, | |
_detachView: function _detachView(view) { | |
var shouldTriggerDetach = !!view._isAttached; | |
var shouldRestoreEl = this._isReplaced; | |
if (shouldTriggerDetach) { | |
triggerMethodOn(view, 'before:detach', view); | |
} | |
if (shouldRestoreEl) { | |
this.Dom.replaceEl(this.el, view.el); | |
} else { | |
this.detachHtml(); | |
} | |
if (shouldTriggerDetach) { | |
view._isAttached = false; | |
triggerMethodOn(view, 'detach', view); | |
} | |
}, | |
// Override this method to change how the region detaches current content | |
detachHtml: function detachHtml() { | |
this.Dom.detachContents(this.el, this.$el); | |
}, | |
// 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(options) { | |
this.empty(options); | |
if (this.$el) { | |
this.el = this._initEl; | |
} | |
delete this.$el; | |
return this; | |
}, | |
destroy: function destroy(options) { | |
if (this._isDestroyed) { | |
return this; | |
} | |
this.reset(options); | |
if (this._name) { | |
this._parentView._removeReferences(this._name); | |
} | |
delete this._parentView; | |
delete this._name; | |
return MarionetteObject.prototype.destroy.apply(this, arguments); | |
} | |
}, { | |
setDomApi: setDomApi | |
}); | |
// return the region instance from the definition | |
var buildRegion = function (definition, defaults) { | |
if (definition instanceof Region) { | |
return definition; | |
} | |
return buildRegionFromDefinition(definition, defaults); | |
}; | |
function buildRegionFromDefinition(definition, defaults) { | |
var opts = _.extend({}, defaults); | |
if (_.isString(definition)) { | |
_.extend(opts, { el: definition }); | |
return buildRegionFromObject(opts); | |
} | |
if (_.isFunction(definition)) { | |
_.extend(opts, { regionClass: definition }); | |
return buildRegionFromObject(opts); | |
} | |
if (_.isObject(definition)) { | |
if (definition.selector) { | |
deprecate('The selector option on a Region definition object is deprecated. Use el to pass a selector string'); | |
} | |
_.extend(opts, { el: definition.selector }, definition); | |
return buildRegionFromObject(opts); | |
} | |
throw new MarionetteError({ | |
message: 'Improper region configuration type.', | |
url: 'marionette.region.html#region-configuration-types' | |
}); | |
} | |
function buildRegionFromObject(definition) { | |
var RegionClass = definition.regionClass; | |
var options = _.omit(definition, 'regionClass'); | |
return new RegionClass(options); | |
} | |
// MixinOptions | |
// - regions | |
// - regionClass | |
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(_.result(this, 'regions')); | |
}, | |
// Internal method to re-initialize all of the regions by updating | |
// the `el` that they point to | |
_reInitRegions: function _reInitRegions() { | |
_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 (_.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 = _.extend({}, this.regions, regions); | |
return this._addRegions(regions); | |
}, | |
// internal method to build and add regions | |
_addRegions: function _addRegions(regionDefinitions) { | |
var _this = this; | |
var defaults = { | |
regionClass: this.regionClass, | |
parentEl: _.partial(_.result, this, 'el') | |
}; | |
return _.reduce(regionDefinitions, function (regions, definition, name) { | |
regions[name] = buildRegion(definition, defaults); | |
_this._addRegion(regions[name], name); | |
return regions; | |
}, {}); | |
}, | |
_addRegion: function _addRegion(region, name) { | |
this.triggerMethod('before:add:region', this, name, region); | |
region._parentView = this; | |
region._name = name; | |
this._regions[name] = region; | |
this.triggerMethod('add:region', this, 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(); | |
_.each(this._regions, _.bind(this._removeRegion, this)); | |
return regions; | |
}, | |
_removeRegion: function _removeRegion(region, name) { | |
this.triggerMethod('before:remove:region', this, name, region); | |
region.destroy(); | |
this.triggerMethod('remove:region', this, name, region); | |
}, | |
// Called in a region's destroy | |
_removeReferences: function _removeReferences(name) { | |
delete this.regions[name]; | |
delete this._regions[name]; | |
}, | |
// Empty all regions in the region manager, but | |
// leave them attached | |
emptyRegions: function emptyRegions() { | |
var regions = this.getRegions(); | |
_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) { | |
if (!this._isRendered) { | |
this.render(); | |
} | |
return this._regions[name]; | |
}, | |
// Get all regions | |
_getRegions: function _getRegions() { | |
return _.clone(this._regions); | |
}, | |
getRegions: function getRegions() { | |
if (!this._isRendered) { | |
this.render(); | |
} | |
return this._getRegions(); | |
}, | |
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)); | |
}, | |
detachChildView: function detachChildView(name) { | |
return this.getRegion(name).detachView(); | |
}, | |
getChildView: function getChildView(name) { | |
return this.getRegion(name).currentView; | |
} | |
}; | |
// Renderer | |
// -------- | |
// 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 = _.isFunction(template) ? template : TemplateCache.get(template); | |
return templateFunc(data); | |
} | |
}; | |
// View | |
// --------- | |
var ClassOptions$1 = ['behaviors', 'childViewEventPrefix', 'childViewEvents', 'childViewTriggers', 'collectionEvents', 'events', 'modelEvents', 'regionClass', 'regions', 'template', 'templateContext', 'triggers', 'ui']; | |
// 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 = _.bind(this.render, this); | |
this._setOptions(options); | |
this.mergeOptions(options, ClassOptions$1); | |
monitorViewEvents(this); | |
this._initBehaviors(); | |
this._initRegions(); | |
var args = Array.prototype.slice.call(arguments); | |
args[0] = this.options; | |
Backbone.View.prototype.constructor.apply(this, args); | |
this.delegateEntityEvents(); | |
this._triggerEventOnBehaviors('initialize', this); | |
}, | |
// 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 _.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 _.clone(model.attributes); | |
}); | |
}, | |
// Overriding Backbone.View's `setElement` to handle | |
// if an el was previously defined. If so, the view might be | |
// rendered or attached on setElement. | |
setElement: function setElement() { | |
var hasEl = !!this.el; | |
Backbone.View.prototype.setElement.apply(this, arguments); | |
if (hasEl) { | |
this._isRendered = !!this.$el.length; | |
this._isAttached = isNodeAttached(this.el); | |
} | |
if (this._isRendered) { | |
this.bindUIElements(); | |
} | |
return this; | |
}, | |
// 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() { | |
if (this._isDestroyed) { | |
return this; | |
} | |
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) { | |
deprecate('template:false is deprecated. Use _.noop.'); | |
return; | |
} | |
// Add in entity data and template context | |
var data = this.mixinTemplateContext(this.serializeData()); | |
// Render and add to el | |
var html = this._renderHtml(template, data); | |
this.attachElContent(html); | |
}, | |
// Renders the data into the template | |
_renderHtml: function _renderHtml(template, data) { | |
return Renderer.render(template, data, this); | |
}, | |
// 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.template; | |
}, | |
// 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 = _.result(this, 'templateContext'); | |
return _.extend(target, templateContext); | |
}, | |
// 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.Dom.setContents(this.el, html, this.$el); | |
return this; | |
}, | |
// called by ViewMixin destroy | |
_removeChildren: function _removeChildren() { | |
this.removeRegions(); | |
}, | |
_getImmediateChildren: function _getImmediateChildren() { | |
return _.chain(this._getRegions()).map('currentView').compact().value(); | |
} | |
}, { | |
// Sets the renderer for the Marionette.View class | |
setRenderer: function setRenderer(renderer) { | |
this.prototype._renderHtml = renderer; | |
}, | |
setDomApi: setDomApi | |
}); | |
_.extend(View.prototype, ViewMixin, RegionsMixin); | |
// Mix in methods from Underscore, for iteration, and other | |
// collection related features. | |
// Borrowing this code from Backbone.Collection: | |
// https://github.com/jashkenas/backbone/blob/1.1.2/backbone.js#L962 | |
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', 'partition']; | |
var emulateCollection = function emulateCollection(object, listProperty) { | |
_.each(methods, function (method) { | |
object[method] = function () { | |
var list = _.result(this, listProperty); | |
var args = Array.prototype.slice.call(arguments); | |
return _[method].apply(_, [list].concat(args)); | |
}; | |
}); | |
}; | |
// Provide a container to store, retrieve and | |
// shut down child views. | |
var Container = function Container(views) { | |
this._views = {}; | |
this._indexByModel = {}; | |
this._indexByCustom = {}; | |
this._updateLength(); | |
_.each(views, _.bind(this.add, this)); | |
}; | |
emulateCollection(Container.prototype, '_getViews'); | |
// Container Methods | |
// ----------------- | |
_.extend(Container.prototype, { | |
_getViews: function _getViews() { | |
return _.values(this._views); | |
}, | |
// 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 add(view, customIndex) { | |
return this._add(view, customIndex)._updateLength(); | |
}, | |
// To be used when avoiding call _updateLength | |
// When you are done adding all your new views | |
// call _updateLength | |
_add: function _add(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; | |
} | |
return this; | |
}, | |
// Find a view by the model that was attached to | |
// it. Uses the model's `cid` to find it. | |
findByModel: function findByModel(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 findByModelCid(modelCid) { | |
var viewCid = this._indexByModel[modelCid]; | |
return this.findByCid(viewCid); | |
}, | |
// Find a view by a custom indexer. | |
findByCustom: function findByCustom(index) { | |
var viewCid = this._indexByCustom[index]; | |
return this.findByCid(viewCid); | |
}, | |
// Find by index. This is not guaranteed to be a | |
// stable index. | |
findByIndex: function findByIndex(index) { | |
return _.values(this._views)[index]; | |
}, | |
// retrieve a view by its `cid` directly | |
findByCid: function findByCid(cid) { | |
return this._views[cid]; | |
}, | |
// Remove a view | |
remove: function remove(view) { | |
return this._remove(view)._updateLength(); | |
}, | |
// To be used when avoiding call _updateLength | |
// When you are done adding all your new views | |
// call _updateLength | |
_remove: function _remove(view) { | |
var viewCid = view.cid; | |
// delete model index | |
if (view.model) { | |
delete this._indexByModel[view.model.cid]; | |
} | |
// delete custom index | |
_.some(this._indexByCustom, _.bind(function (cid, key) { | |
if (cid === viewCid) { | |
delete this._indexByCustom[key]; | |
return true; | |
} | |
}, this)); | |
// remove the view from the container | |
delete this._views[viewCid]; | |
return this; | |
}, | |
// Update the `.length` attribute on this container | |
_updateLength: function _updateLength() { | |
this.length = _.size(this._views); | |
return this; | |
} | |
}); | |
// Collection View | |
// --------------- | |
var ClassOptions$3 = ['behaviors', 'childView', 'childViewEventPrefix', 'childViewEvents', 'childViewOptions', 'childViewTriggers', 'collectionEvents', 'events', 'filter', 'emptyView', 'emptyViewOptions', 'modelEvents', 'reorderOnSort', 'sort', 'triggers', 'ui', 'viewComparator']; | |
// 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 `{viewComparator: compFunction()}` to allow the `CollectionView` | |
// to use a custom sort order for the collection. | |
constructor: function constructor(options) { | |
this.render = _.bind(this.render, this); | |
this._setOptions(options); | |
this.mergeOptions(options, ClassOptions$3); | |
monitorViewEvents(this); | |
this._initBehaviors(); | |
this.once('render', this._initialEvents); | |
this._initChildViewStorage(); | |
this._bufferedChildren = []; | |
var args = Array.prototype.slice.call(arguments); | |
args[0] = this.options; | |
Backbone.View.prototype.constructor.apply(this, args); | |
this.delegateEntityEvents(); | |
this._triggerEventOnBehaviors('initialize', this); | |
}, | |
// 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; | |
_.each(triggerOnChildren, function (child) { | |
triggerMethodOn(child, 'before:attach', child); | |
}); | |
this.attachBuffer(this, this._createBuffer()); | |
_.each(triggerOnChildren, function (child) { | |
child._isAttached = true; | |
triggerMethodOn(child, 'attach', child); | |
}); | |
this._bufferedChildren = []; | |
}, | |
_getImmediateChildren: function _getImmediateChildren() { | |
return _.values(this.children._views); | |
}, | |
// 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, 'update', this._onCollectionUpdate); | |
this.listenTo(this.collection, 'reset', this.render); | |
if (this.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.filter || index === false) { | |
index = _.indexOf(this._filteredSortedModels(index), child); | |
} | |
if (this._shouldAddChild(child, index)) { | |
this._destroyEmptyView(); | |
this._addChild(child, index); | |
} | |
}, | |
// Handle collection update model removals | |
_onCollectionUpdate: function _onCollectionUpdate(collection, options) { | |
var changes = options.changes; | |
this._removeChildModels(changes.removed); | |
}, | |
// Remove the child views and destroy them. | |
// This function also updates the indices of later views | |
// in the collection in order to keep the children in sync with the collection. | |
// "models" is an array of models and the corresponding views | |
// will be removed and destroyed from the CollectionView | |
_removeChildModels: function _removeChildModels(models) { | |
// Used to determine where to update the remaining | |
// sibling view indices after these views are removed. | |
var removedViews = this._getRemovedViews(models); | |
if (!removedViews.length) { | |
return; | |
} | |
this.children._updateLength(); | |
// decrement the index of views after this one | |
this._updateIndices(removedViews, false); | |
if (this.isEmpty()) { | |
this._showEmptyView(); | |
} | |
}, | |
// Returns the views that will be used for re-indexing | |
// through CollectionView#_updateIndices. | |
_getRemovedViews: function _getRemovedViews(models) { | |
var _this = this; | |
// Returning a view means something was removed. | |
return _.reduce(models, function (removingViews, model) { | |
var view = model && _this.children.findByModel(model); | |
if (!view || view._isDestroyed) { | |
return removingViews; | |
} | |
_this._removeChildView(view); | |
removingViews.push(view); | |
return removingViews; | |
}, []); | |
}, | |
_removeChildView: function _removeChildView(view) { | |
this.triggerMethod('before:remove:child', this, view); | |
this.children._remove(view); | |
destroyView(view); | |
this.stopListening(view); | |
this.triggerMethod('remove:child', this, view); | |
}, | |
// Overriding Backbone.View's `setElement` to handle | |
// if an el was previously defined. If so, the view might be | |
// attached on setElement. | |
setElement: function setElement() { | |
var hasEl = !!this.el; | |
Backbone.View.prototype.setElement.apply(this, arguments); | |
if (hasEl) { | |
this._isAttached = isNodeAttached(this.el); | |
} | |
return this; | |
}, | |
// Render children views. Override this method to provide your own implementation of a | |
// render function for the collection view. | |
render: function render() { | |
if (this._isDestroyed) { | |
return this; | |
} | |
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] : {}, | |
preventRender = _ref.preventRender; | |
var canBeRendered = this._isRendered && !this._isDestroyed; | |
var filterChanged = this.filter !== filter; | |
var shouldRender = canBeRendered && filterChanged && !preventRender; | |
if (shouldRender) { | |
var previousModels = this._filteredSortedModels(); | |
this.filter = filter; | |
var models = this._filteredSortedModels(); | |
this._applyModelDeltas(models, previousModels); | |
} else { | |
this.filter = filter; | |
} | |
return this; | |
}, | |
// `removeFilter` is actually an alias for removing filters. | |
removeFilter: function removeFilter(options) { | |
return this.setFilter(null, options); | |
}, | |
// Calculate and apply difference by cid between `models` and `previousModels`. | |
_applyModelDeltas: function _applyModelDeltas(models, previousModels) { | |
var _this2 = this; | |
var currentIds = {}; | |
_.each(models, function (model, index) { | |
var addedChildNotExists = !_this2.children.findByModel(model); | |
if (addedChildNotExists) { | |
_this2._onCollectionAdd(model, _this2.collection, { at: index }); | |
} | |
currentIds[model.cid] = true; | |
}); | |
var removeModels = _.filter(previousModels, function (prevModel) { | |
return !currentIds[prevModel.cid] && _this2.children.findByModel(prevModel); | |
}); | |
this._removeChildModels(removeModels); | |
}, | |
// 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 _this3 = this; | |
var children = this.children; | |
var models = this._filteredSortedModels(); | |
if (!models.length && this._showingEmptyView) { | |
return this; | |
} | |
var anyModelsAdded = _.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 { | |
var filteredOutModels = []; | |
// Get the DOM nodes in the same order as the models and | |
// find the model that were children before but aren't in this new order. | |
var elsToReorder = _.reduce(this.children._views, function (viewEls, view) { | |
var index = _.indexOf(models, view.model); | |
if (index === -1) { | |
filteredOutModels.push(view.model); | |
return viewEls; | |
} | |
view._index = index; | |
viewEls[index] = view.el; | |
return viewEls; | |
}, new Array(models.length)); | |
this.triggerMethod('before:reorder', this); | |
var elBuffer = this.Dom.createBuffer(); | |
_.each(elsToReorder, function (el) { | |
_this3.Dom.appendContents(elBuffer, el); | |
}); | |
// Since append moves elements that are already in the DOM, appending the elements | |
// will effectively reorder them. | |
this._appendReorderedChildren(elBuffer); | |
// remove any views that have been filtered out | |
this._removeChildModels(filteredOutModels); | |
this.triggerMethod('reorder', this); | |
} | |
return 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.reorderOnSort) { | |
this.reorder(); | |
} else { | |
this._renderChildren(); | |
} | |
return this; | |
}, | |
// 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 _this4 = this; | |
var models = this._filteredSortedModels(); | |
// check for any changes in sort order of views | |
var orderChanged = _.find(models, function (item, index) { | |
var view = _this4.children.findByModel(item); | |
return !view || view._index !== index; | |
}); | |
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.Dom.appendContents(this.el, children, { _$el: this.$el }); | |
}, | |
// Internal method. Separated so that CompositeView can have more control over events | |
// being triggered, around the rendering process | |
_renderChildren: function _renderChildren() { | |
if (this._isRendered) { | |
this._destroyEmptyView(); | |
this._destroyChildren(); | |
} | |
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); | |
} | |
}, | |
_createView: function _createView(model, index) { | |
var ChildView = this._getChildView(model); | |
var childViewOptions = this._getChildViewOptions(model, index); | |
var view = this.buildChildView(model, ChildView, childViewOptions); | |
return view; | |
}, | |
_setupChildView: function _setupChildView(view, index) { | |
monitorViewEvents(view); | |
// set up the child view event forwarding | |
this._proxyChildViewEvents(view); | |
if (this.sort) { | |
view._index = index; | |
} | |
}, | |
// Internal method to loop through collection and show each child view. | |
_showCollection: function _showCollection(models) { | |
_.each(models, _.bind(this._addChild, this)); | |
this.children._updateLength(); | |
}, | |
// Allow the collection to be sorted by a custom view comparator | |
_filteredSortedModels: function _filteredSortedModels(addedAt) { | |
if (!this.collection || !this.collection.length) { | |
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; | |
}, | |
getViewComparator: function getViewComparator() { | |
return this.viewComparator; | |
}, | |
// Filter an array of models, if a filter exists | |
_filterModels: function _filterModels(models) { | |
var _this5 = this; | |
if (this.filter) { | |
models = _.filter(models, function (model, index) { | |
return _this5._shouldAddChild(model, index); | |
}); | |
} | |
return models; | |
}, | |
_sortModelsBy: function _sortModelsBy(models, comparator) { | |
if (typeof comparator === 'string') { | |
return _.sortBy(models, function (model) { | |
return model.get(comparator); | |
}); | |
} else if (comparator.length === 1) { | |
return _.sortBy(models, _.bind(comparator, this)); | |
} else { | |
return _.clone(models).sort(_.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.emptyViewOptions || this.childViewOptions; | |
if (_.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); | |
} | |
}, | |
// 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() { | |
var emptyView = this.emptyView; | |
if (!emptyView) { | |
return; | |
} | |
return this._getView(emptyView); | |
}, | |
// Retrieve the `childView` class | |
// 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.childView; | |
if (!childView) { | |
throw new MarionetteError({ | |
name: 'NoChildViewError', | |
message: 'A "childView" must be specified' | |
}); | |
} | |
childView = this._getView(childView, child); | |
if (!childView) { | |
throw new MarionetteError({ | |
name: 'InvalidChildViewError', | |
message: '"childView" must be a view class or a function that returns a view class' | |
}); | |
} | |
return childView; | |
}, | |
// First check if the `view` is a view class (the common case) | |
// Then check if it's a function (which we assume that returns a view class) | |
_getView: function _getView(view, child) { | |
if (view.prototype instanceof Backbone.View || view === Backbone.View) { | |
return view; | |
} else if (_.isFunction(view)) { | |
return view.call(this, child); | |
} | |
}, | |
// Internal method for building and adding a child view | |
_addChild: function _addChild(child, index) { | |
var view = this._createView(child, index); | |
this.addChildView(view, index); | |
return view; | |
}, | |
_getChildViewOptions: function _getChildViewOptions(child, index) { | |
if (_.isFunction(this.childViewOptions)) { | |
return this.childViewOptions(child, index); | |
} | |
return this.childViewOptions; | |
}, | |
// 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. | |
addChildView: function addChildView(view, index) { | |
this.triggerMethod('before:add:child', this, view); | |
this._setupChildView(view, index); | |
// Store the child view itself so we can properly remove and/or destroy it later | |
if (this._isBuffering) { | |
// Add to children, but don't update children's length. | |
this.children._add(view); | |
} else { | |
// increment indices of views after this one | |
this._updateIndices(view, true); | |
this.children.add(view); | |
} | |
renderView(view); | |
this._attachView(view, index); | |
this.triggerMethod('add:child', this, view); | |
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(views, increment) { | |
if (!this.sort) { | |
return; | |
} | |
if (!increment) { | |
_.each(_.sortBy(this.children._views, '_index'), function (view, index) { | |
view._index = index; | |
}); | |
return; | |
} | |
var view = _.isArray(views) ? _.max(views, '_index') : views; | |
if (_.isObject(view)) { | |
// update the indexes of views after this one | |
_.each(this.children._views, function (laterView) { | |
if (laterView._index >= view._index) { | |
laterView._index += 1; | |
} | |
}); | |
} | |
}, | |
_attachView: function _attachView(view, index) { | |
// Only trigger attach if already attached and not buffering, | |
// otherwise _endBuffering() or Region#show() handles this. | |
var shouldTriggerAttach = !view._isAttached && !this._isBuffering && this._isAttached; | |
if (shouldTriggerAttach) { | |
triggerMethodOn(view, 'before: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 = _.extend({ model: child }, childViewOptions); | |
return new ChildViewClass(options); | |
}, | |
// 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 view; | |
} | |
this._removeChildView(view); | |
this.children._updateLength(); | |
// decrement the index of views after this one | |
this._updateIndices(view, false); | |
return view; | |
}, | |
// 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 (_.result(options, 'processedModels')) { | |
models = options.processedModels; | |
} else { | |
models = this.collection ? this.collection.models : []; | |
models = this._filterModels(models); | |
} | |
return models.length === 0; | |
}, | |
// You might need to override this if you've overridden attachHtml | |
attachBuffer: function attachBuffer(collectionView, buffer) { | |
this.Dom.appendContents(collectionView.el, buffer, { _$el: collectionView.$el }); | |
}, | |
// Create a fragment buffer from the currently buffered children | |
_createBuffer: function _createBuffer() { | |
var _this6 = this; | |
var elBuffer = this.Dom.createBuffer(); | |
_.each(this._bufferedChildren, function (b) { | |
_this6.Dom.appendContents(elBuffer, b.el, { _$contents: 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.sort && index < this.children.length - 1; | |
if (findPosition) { | |
// Find the view after this one | |
currentView = _.find(this.children._views, function (view) { | |
return view._index === index + 1; | |
}); | |
} | |
if (currentView) { | |
this.beforeEl(currentView.el, childView.el); | |
return true; | |
} | |
return false; | |
}, | |
// Override to handle DOM inserting differently | |
beforeEl: function beforeEl(el, siblings) { | |
this.$(el).before(siblings); | |
}, | |
// Internal method. Append a view to the end of the $el | |
_insertAfter: function _insertAfter(childView) { | |
this.Dom.appendContents(this.el, childView.el, { _$el: this.$el, _$contents: childView.$el }); | |
}, | |
// Internal method to set up the `children` object for storing all of the child views | |
_initChildViewStorage: function _initChildViewStorage() { | |
this.children = new Container(); | |
}, | |
// called by ViewMixin destroy | |
_removeChildren: function _removeChildren() { | |
this._destroyChildren(); | |
}, | |
// Destroy the child views that this collection view is holding on to, if any | |
_destroyChildren: function _destroyChildren(options) { | |
if (!this.children.length) { | |
return; | |
} | |
this.triggerMethod('before:destroy:children', this); | |
_.each(this.children._views, _.bind(this._removeChildView, this)); | |
this.children._updateLength(); | |
this.triggerMethod('destroy:children', this); | |
}, | |
// 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.filter; | |
return !_.isFunction(filter) || filter.call(this, child, index, this.collection); | |
} | |
}, { | |
setDomApi: setDomApi | |
}); | |
_.extend(CollectionView.prototype, ViewMixin); | |
// Provide a container to store, retrieve and | |
// shut down child views. | |
var Container$1 = function Container() { | |
this._init(); | |
}; | |
emulateCollection(Container$1.prototype, '_views'); | |
function stringComparator(comparator, view) { | |
return view.model && view.model.get(comparator); | |
} | |
// Container Methods | |
// ----------------- | |
_.extend(Container$1.prototype, { | |
// Initializes an empty container | |
_init: function _init() { | |
this._views = []; | |
this._viewsByCid = {}; | |
this._indexByModel = {}; | |
this._updateLength(); | |
}, | |
// Add a view to this container. Stores the view | |
// by `cid` and makes it searchable by the model | |
// cid (and model itself). Additionally it stores | |
// the view by index in the _views array | |
_add: function _add(view) { | |
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this._views.length; | |
var viewCid = view.cid; | |
// store the view | |
this._viewsByCid[viewCid] = view; | |
// index it by model | |
if (view.model) { | |
this._indexByModel[view.model.cid] = viewCid; | |
} | |
// add to end by default | |
this._views.splice(index, 0, view); | |
this._updateLength(); | |
}, | |
// Sort (mutate) and return the array of the child views. | |
_sort: function _sort(comparator) { | |
if (typeof comparator === 'string') { | |
comparator = _.partial(stringComparator, comparator); | |
return this._sortBy(comparator); | |
} | |
if (comparator.length === 1) { | |
return this._sortBy(comparator); | |
} | |
return this._views.sort(comparator); | |
}, | |
// Makes `_.sortBy` mutate the array to match `this._views.sort` | |
_sortBy: function _sortBy(comparator) { | |
var sortedViews = _.sortBy(this._views, comparator); | |
this._set(sortedViews); | |
return sortedViews; | |
}, | |
// Replace array contents without overwriting the reference. | |
_set: function _set(views) { | |
this._views.length = 0; | |
this._views.push.apply(this._views, views.slice(0)); | |
this._updateLength(); | |
}, | |
// Find a view by the model that was attached to it. | |
// Uses the model's `cid` to find it. | |
findByModel: function findByModel(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 findByModelCid(modelCid) { | |
var viewCid = this._indexByModel[modelCid]; | |
return this.findByCid(viewCid); | |
}, | |
// Find a view by index. | |
findByIndex: function findByIndex(index) { | |
return this._views[index]; | |
}, | |
// Find the index of a view instance | |
findIndexByView: function findIndexByView(view) { | |
return this._views.indexOf(view); | |
}, | |
// Retrieve a view by its `cid` directly | |
findByCid: function findByCid(cid) { | |
return this._viewsByCid[cid]; | |
}, | |
// Remove a view and clean up index references. | |
_remove: function _remove(view) { | |
if (!this._viewsByCid[view.cid]) { | |
return; | |
} | |
// delete model index | |
if (view.model) { | |
delete this._indexByModel[view.model.cid]; | |
} | |
// remove the view from the container | |
delete this._viewsByCid[view.cid]; | |
var index = this.findIndexByView(view); | |
this._views.splice(index, 1); | |
this._updateLength(); | |
}, | |
// Update the `.length` attribute on this container | |
_updateLength: function _updateLength() { | |
this.length = this._views.length; | |
} | |
}); | |
// Next Collection View | |
// --------------- | |
var ClassOptions$4 = ['behaviors', 'childView', 'childViewEventPrefix', 'childViewEvents', 'childViewOptions', 'childViewTriggers', 'collectionEvents', 'emptyView', 'emptyViewOptions', 'events', 'modelEvents', 'sortWithCollection', 'triggers', 'ui', 'viewComparator', 'viewFilter']; | |
// A view that iterates over a Backbone.Collection | |
// and renders an individual child view for each model. | |
var CollectionView$2 = Backbone.View.extend({ | |
// flag for maintaining the sorted order of the collection | |
sortWithCollection: true, | |
// constructor | |
constructor: function constructor(options) { | |
this._setOptions(options); | |
this.mergeOptions(options, ClassOptions$4); | |
monitorViewEvents(this); | |
this.once('render', this._initialEvents); | |
// This children container isn't really used by a render, but it provides | |
// the ability to check `this.children.length` prior to rendering | |
// It also allows for cases where only addChildView is used | |
this._initChildViewStorage(); | |
this._initBehaviors(); | |
var args = Array.prototype.slice.call(arguments); | |
args[0] = this.options; | |
Backbone.View.prototype.constructor.apply(this, args); | |
this._initEmptyRegion(); | |
this.delegateEntityEvents(); | |
this._triggerEventOnBehaviors('initialize', this); | |
}, | |
// Internal method to set up the `children` object for storing all of the child views | |
_initChildViewStorage: function _initChildViewStorage() { | |
this.children = new Container$1(); | |
}, | |
// Create an region to show the emptyView | |
_initEmptyRegion: function _initEmptyRegion() { | |
this.emptyRegion = new Region({ el: this.el, replaceElement: false }); | |
this.emptyRegion._parentView = this; | |
}, | |
// Configured the initial events that the collection view binds to. | |
_initialEvents: function _initialEvents() { | |
this.listenTo(this.collection, { | |
'sort': this._onCollectionSort, | |
'reset': this._onCollectionReset, | |
'update': this._onCollectionUpdate | |
}); | |
}, | |
// Internal method. This checks for any changes in the order of the collection. | |
// If the index of any view doesn't match, it will re-sort. | |
_onCollectionSort: function _onCollectionSort() { | |
var _this = this; | |
if (!this.sortWithCollection || this.viewComparator === false) { | |
return; | |
} | |
// If the data is changing we will handle the sort later | |
if (this.collection.length !== this.children.length) { | |
return; | |
} | |
// Additional check if the data is changing | |
var hasAddedModel = this.collection.some(function (model) { | |
return !_this.children.findByModel(model); | |
}); | |
if (hasAddedModel) { | |
return; | |
} | |
// If the only thing happening here is sorting, sort. | |
this.sort(); | |
}, | |
_onCollectionReset: function _onCollectionReset() { | |
this.render(); | |
}, | |
// Handle collection update model additions and removals | |
_onCollectionUpdate: function _onCollectionUpdate(collection, options) { | |
var changes = options.changes; | |
// Remove first since it'll be a shorter array lookup. | |
var removedViews = this._removeChildModels(changes.removed); | |
this._addChildModels(changes.added); | |
this._detachChildren(removedViews); | |
this._showChildren(); | |
// Destroy removed child views after all of the render is complete | |
this._removeChildViews(removedViews); | |
}, | |
_removeChildModels: function _removeChildModels(models) { | |
return _.map(models, _.bind(this._removeChildModel, this)); | |
}, | |
_removeChildModel: function _removeChildModel(model) { | |
var view = this.children.findByModel(model); | |
this._removeChild(view); | |
return view; | |
}, | |
_removeChild: function _removeChild(view) { | |
this.triggerMethod('before:remove:child', this, view); | |
this.children._remove(view); | |
this.triggerMethod('remove:child', this, view); | |
}, | |
// Added views are returned for consistency with _removeChildModels | |
_addChildModels: function _addChildModels(models) { | |
return _.map(models, _.bind(this._addChildModel, this)); | |
}, | |
_addChildModel: function _addChildModel(model) { | |
var view = this._createChildView(model); | |
this._addChild(view); | |
return view; | |
}, | |
_createChildView: function _createChildView(model) { | |
var ChildView = this._getChildView(model); | |
var childViewOptions = this._getChildViewOptions(model); | |
var view = this.buildChildView(model, ChildView, childViewOptions); | |
return view; | |
}, | |
_addChild: function _addChild(view, index) { | |
this.triggerMethod('before:add:child', this, view); | |
this._setupChildView(view); | |
this.children._add(view, index); | |
this.triggerMethod('add:child', this, view); | |
}, | |
// Retrieve the `childView` class | |
// 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.childView; | |
if (!childView) { | |
throw new MarionetteError({ | |
name: 'NoChildViewError', | |
message: 'A "childView" must be specified' | |
}); | |
} | |
childView = this._getView(childView, child); | |
if (!childView) { | |
throw new MarionetteError({ | |
name: 'InvalidChildViewError', | |
message: '"childView" must be a view class or a function that returns a view class' | |
}); | |
} | |
return childView; | |
}, | |
// First check if the `view` is a view class (the common case) | |
// Then check if it's a function (which we assume that returns a view class) | |
_getView: function _getView(view, child) { | |
if (view.prototype instanceof Backbone.View || view === Backbone.View) { | |
return view; | |
} else if (_.isFunction(view)) { | |
return view.call(this, child); | |
} | |
}, | |
_getChildViewOptions: function _getChildViewOptions(child) { | |
if (_.isFunction(this.childViewOptions)) { | |
return this.childViewOptions(child); | |
} | |
return this.childViewOptions; | |
}, | |
// Build a `childView` for a model in the collection. | |
// Override to customize the build | |
buildChildView: function buildChildView(child, ChildViewClass, childViewOptions) { | |
var options = _.extend({ model: child }, childViewOptions); | |
return new ChildViewClass(options); | |
}, | |
_setupChildView: function _setupChildView(view) { | |
monitorViewEvents(view); | |
// We need to listen for if a view is destroyed in a way other | |
// than through the CollectionView. | |
// If this happens we need to remove the reference to the view | |
// since once a view has been destroyed we can not reuse it. | |
view.on('destroy', this.removeChildView, this); | |
// set up the child view event forwarding | |
this._proxyChildViewEvents(view); | |
}, | |
// used by ViewMixin's `_childViewEventHandler` | |
_getImmediateChildren: function _getImmediateChildren() { | |
return this.children._views; | |
}, | |
// Overriding Backbone.View's `setElement` to handle | |
// if an el was previously defined. If so, the view might be | |
// attached on setElement. | |
setElement: function setElement() { | |
var hasEl = !!this.el; | |
Backbone.View.prototype.setElement.apply(this, arguments); | |
if (hasEl) { | |
this._isAttached = isNodeAttached(this.el); | |
} | |
return this; | |
}, | |
// Render children views. | |
render: function render() { | |
if (this._isDestroyed) { | |
return this; | |
} | |
this.triggerMethod('before:render', this); | |
this._destroyChildren(); | |
// After all children have been destroyed re-init the container | |
this.children._init(); | |
if (this.collection) { | |
this._addChildModels(this.collection.models); | |
} | |
this._showChildren(); | |
this._isRendered = true; | |
this.triggerMethod('render', this); | |
return this; | |
}, | |
// Sorts the children then filters and renders the results. | |
sort: function sort() { | |
if (this._isDestroyed) { | |
return this; | |
} | |
if (!this.children.length) { | |
return this; | |
} | |
this._showChildren(); | |
return this; | |
}, | |
_showChildren: function _showChildren() { | |
if (this.isEmpty()) { | |
this._showEmptyView(); | |
return; | |
} | |
this._sortChildren(); | |
this.filter(); | |
}, | |
// Returns true if the collectionView is considered empty. | |
// This is called twice during a render. Once to check the data, | |
// and again when views are filtered. Override this function to | |
// customize what empty means. | |
isEmpty: function isEmpty(allViewsFiltered) { | |
return allViewsFiltered || !this.children.length; | |
}, | |
_showEmptyView: function _showEmptyView() { | |
var EmptyView = this._getEmptyView(); | |
if (!EmptyView) { | |
return; | |
} | |
var options = this._getEmptyViewOptions(); | |
this.emptyRegion.show(new EmptyView(options)); | |
}, | |
// Retrieve the empty view class | |
_getEmptyView: function _getEmptyView() { | |
var emptyView = this.emptyView; | |
if (!emptyView) { | |
return; | |
} | |
return this._getView(emptyView); | |
}, | |
// Remove the emptyView | |
_destroyEmptyView: function _destroyEmptyView() { | |
// Only empty if a view is show so the region | |
// doesn't detach any other unrelated HTML | |
if (this.emptyRegion.hasView()) { | |
this.emptyRegion.empty(); | |
} | |
}, | |
// | |
_getEmptyViewOptions: function _getEmptyViewOptions() { | |
var emptyViewOptions = this.emptyViewOptions || this.childViewOptions; | |
if (_.isFunction(emptyViewOptions)) { | |
return emptyViewOptions.call(this); | |
} | |
return emptyViewOptions; | |
}, | |
// Sorts views by viewComparator and sets the children to the new order | |
_sortChildren: function _sortChildren() { | |
if (this.viewComparator === false) { | |
return; | |
} | |
this.triggerMethod('before:sort', this); | |
var viewComparator = this.getComparator(); | |
if (_.isFunction(viewComparator)) { | |
// Must use native bind to preserve length | |
viewComparator = viewComparator.bind(this); | |
} | |
this.children._sort(viewComparator); | |
this.triggerMethod('sort', this); | |
}, | |
// Sets the view's `viewComparator` and applies the sort if the view is ready. | |
// To prevent the render pass `{ preventRender: true }` as the 2nd argument. | |
setComparator: function setComparator(comparator) { | |
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | |
preventRender = _ref.preventRender; | |
var comparatorChanged = this.viewComparator !== comparator; | |
var shouldSort = comparatorChanged && !preventRender; | |
this.viewComparator = comparator; | |
if (shouldSort) { | |
this.sort(); | |
} | |
return this; | |
}, | |
// Clears the `viewComparator` and follows the same rules for rendering as `setComparator`. | |
removeComparator: function removeComparator(options) { | |
return this.setComparator(null, options); | |
}, | |
// If viewComparator is overriden it will be returned here. | |
// Additionally override this function to provide custom | |
// viewComparator logic | |
getComparator: function getComparator() { | |
return this.viewComparator || this._viewComparator; | |
}, | |
// Default internal view comparator that order the views by | |
// the order of the collection | |
_viewComparator: function _viewComparator(view) { | |
if (!this.collection) { | |
return; | |
} | |
return this.collection.indexOf(view.model); | |
}, | |
// This method re-filters the children views and re-renders the results | |
filter: function filter() { | |
if (this._isDestroyed) { | |
return this; | |
} | |
if (!this.children.length) { | |
return this; | |
} | |
var filteredViews = this._filterChildren(); | |
this._renderChildren(filteredViews); | |
return this; | |
}, | |
_filterChildren: function _filterChildren() { | |
var viewFilter = this._getFilter(); | |
if (!viewFilter) { | |
return this.children._views; | |
} | |
this.triggerMethod('before:filter', this); | |
var filteredViews = _.partition(this.children._views, _.bind(viewFilter, this)); | |
this._detachChildren(filteredViews[1]); | |
this.triggerMethod('filter', this); | |
return filteredViews[0]; | |
}, | |
// This method returns a function for the viewFilter | |
_getFilter: function _getFilter() { | |
var viewFilter = this.getFilter(); | |
if (!viewFilter) { | |
return false; | |
} | |
if (_.isFunction(viewFilter)) { | |
return viewFilter; | |
} | |
// Support filter predicates `{ fooFlag: true }` | |
if (_.isObject(viewFilter)) { | |
var matcher = _.matches(viewFilter); | |
return function (view) { | |
return matcher(view.model && view.model.attributes); | |
}; | |
} | |
// Filter by model attribute | |
if (_.isString(viewFilter)) { | |
return function (view) { | |
return view.model && view.model.get(viewFilter); | |
}; | |
} | |
throw new MarionetteError({ | |
name: 'InvalidViewFilterError', | |
message: '"viewFilter" must be a function, predicate object literal, a string indicating a model attribute, or falsy' | |
}); | |
}, | |
// Override this function to provide custom | |
// viewFilter logic | |
getFilter: function getFilter() { | |
return this.viewFilter; | |
}, | |
// Sets the view's `viewFilter` and applies the filter if the view is ready. | |
// To prevent the render pass `{ preventRender: true }` as the 2nd argument. | |
setFilter: function setFilter(filter) { | |
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | |
preventRender = _ref2.preventRender; | |
var filterChanged = this.viewFilter !== filter; | |
var shouldRender = filterChanged && !preventRender; | |
this.viewFilter = filter; | |
if (shouldRender) { | |
this.filter(); | |
} | |
return this; | |
}, | |
// Clears the `viewFilter` and follows the same rules for rendering as `setFilter`. | |
removeFilter: function removeFilter(options) { | |
return this.setFilter(null, options); | |
}, | |
_detachChildren: function _detachChildren(detachingViews) { | |
_.each(detachingViews, _.bind(this._detachChildView, this)); | |
}, | |
_detachChildView: function _detachChildView(view) { | |
var shouldTriggerDetach = !!view._isAttached; | |
if (shouldTriggerDetach) { | |
triggerMethodOn(view, 'before:detach', view); | |
} | |
this.detachHtml(view); | |
if (shouldTriggerDetach) { | |
view._isAttached = false; | |
triggerMethodOn(view, 'detach', view); | |
} | |
}, | |
// Override this method to change how the collectionView detaches a child view | |
detachHtml: function detachHtml(view) { | |
this.Dom.detachEl(view.el, view.$el); | |
}, | |
_renderChildren: function _renderChildren(views) { | |
if (this.isEmpty(!views.length)) { | |
this._showEmptyView(); | |
return; | |
} | |
this._destroyEmptyView(); | |
this.triggerMethod('before:render:children', this, views); | |
var els = this._getBuffer(views); | |
this._attachChildren(els, views); | |
this.triggerMethod('render:children', this, views); | |
}, | |
_attachChildren: function _attachChildren(els, views) { | |
var shouldTriggerAttach = !!this._isAttached; | |
views = shouldTriggerAttach ? views : []; | |
_.each(views, function (view) { | |
if (view._isAttached) { | |
return; | |
} | |
triggerMethodOn(view, 'before:attach', view); | |
}); | |
this.attachHtml(els); | |
_.each(views, function (view) { | |
if (view._isAttached) { | |
return; | |
} | |
view._isAttached = true; | |
triggerMethodOn(view, 'attach', view); | |
}); | |
}, | |
// Renders each view in children and creates a fragment buffer from them | |
_getBuffer: function _getBuffer(views) { | |
var _this2 = this; | |
var elBuffer = this.Dom.createBuffer(); | |
_.each(views, function (view) { | |
renderView(view); | |
_this2.Dom.appendContents(elBuffer, view.el, { _$contents: view.$el }); | |
}); | |
return elBuffer; | |
}, | |
// Override this method to do something other than `.append`. | |
// You can attach any HTML at this point including the els. | |
attachHtml: function attachHtml(els) { | |
this.Dom.appendContents(this.el, els, { _$el: this.$el }); | |
}, | |
// Render the child's view and add it to the HTML for the collection view at a given index, based on the current sort | |
addChildView: function addChildView(view, index) { | |
if (!view || view._isDestroyed) { | |
return view; | |
} | |
this._addChild(view, index); | |
this._showChildren(); | |
return view; | |
}, | |
// Detach a view from the children. Best used when adding a | |
// childView from `addChildView` | |
detachChildView: function detachChildView(view) { | |
this.removeChildView(view, { shouldDetach: true }); | |
return view; | |
}, | |
// Remove the child view and destroy it. Best used when adding a | |
// childView from `addChildView` | |
// The options argument is for internal use only | |
removeChildView: function removeChildView(view, options) { | |
if (!view) { | |
return view; | |
} | |
this._removeChildView(view, options); | |
this._removeChild(view); | |
if (this.isEmpty()) { | |
this._showEmptyView(); | |
} | |
return view; | |
}, | |
_removeChildViews: function _removeChildViews(views) { | |
_.each(views, _.bind(this._removeChildView, this)); | |
}, | |
_removeChildView: function _removeChildView(view) { | |
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | |
shouldDetach = _ref3.shouldDetach; | |
view.off('destroy', this.removeChildView, this); | |
if (shouldDetach) { | |
this._detachChildView(view); | |
} else { | |
this._destroyChildView(view); | |
} | |
this.stopListening(view); | |
}, | |
_destroyChildView: function _destroyChildView(view) { | |
if (view._isDestroyed) { | |
return; | |
} | |
destroyView(view); | |
}, | |
// called by ViewMixin destroy | |
_removeChildren: function _removeChildren() { | |
this._destroyChildren(); | |
this.emptyRegion.destroy(); | |
}, | |
// Destroy the child views that this collection view is holding on to, if any | |
_destroyChildren: function _destroyChildren() { | |
if (!this.children || !this.children.length) { | |
return; | |
} | |
this.triggerMethod('before:destroy:children', this); | |
_.each(this.children._views, _.bind(this._removeChildView, this)); | |
this.triggerMethod('destroy:children', this); | |
} | |
}, { | |
setDomApi: setDomApi | |
}); | |
_.extend(CollectionView$2.prototype, ViewMixin); | |
// Composite View | |
// -------------- | |
var ClassOptions$5 = ['childViewContainer', 'template', 'templateContext']; | |
// Used for rendering a branch-leaf, hierarchical structure. | |
// Extends directly from CollectionView | |
// @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(options) { | |
deprecate('CompositeView is deprecated. Convert to View at your earliest convenience'); | |
this.mergeOptions(options, ClassOptions$5); | |
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, 'update', this._onCollectionUpdate); | |
this.listenTo(this.collection, 'reset', this.renderChildren); | |
if (this.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.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; | |
} | |
childView = this._getView(childView, child); | |
if (!childView) { | |
throw new MarionetteError({ | |
name: 'InvalidChildViewError', | |
message: '"childView" must be a view class or a function that returns a view class' | |
}); | |
} | |
return childView; | |
}, | |
// Return the serialized model | |
serializeData: function serializeData() { | |
return this.serializeModel(); | |
}, | |
// Renders the model and the collection. | |
render: function render() { | |
if (this._isDestroyed) { | |
return this; | |
} | |
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); | |
this.Dom.appendContents($container[0], buffer, { _$el: $container }); | |
}, | |
// 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); | |
this.Dom.appendContents($container[0], childView.el, { _$el: $container, _$contents: 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); | |
this.Dom.appendContents($container[0], children, { _$el: $container }); | |
}, | |
// 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 = containerView.childViewContainer; | |
if (childViewContainer) { | |
var selector = _.result(containerView, 'childViewContainer'); | |
if (selector.charAt(0) === '@' && containerView.ui) { | |
container = containerView.ui[selector.substr(4)]; | |
} else { | |
container = this.$(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 = _.pick(View.prototype, 'serializeModel', 'getTemplate', '_renderTemplate', '_renderHtml', 'mixinTemplateContext', 'attachElContent'); | |
_.extend(CompositeView.prototype, MixinFromView); | |
// Behavior | |
// -------- | |
// A Behavior is an isolated set of DOM / | |
// user interactions that can be mixed into any View. | |
// Behaviors allow you to blackbox View specific interactions | |
// into portable logical chunks, keeping your views simple and your code DRY. | |
var ClassOptions$6 = ['collectionEvents', 'events', 'modelEvents', 'triggers', 'ui']; | |
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; | |
if (this.defaults) { | |
deprecate('Behavior defaults are deprecated. For similar functionality set options on the Behavior class.'); | |
} | |
this.defaults = _.clone(_.result(this, 'defaults', {})); | |
this._setOptions(this.defaults, options); | |
this.mergeOptions(this.options, ClassOptions$6); | |
// 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 = _.extend({}, _.result(this, 'ui'), _.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(); | |
this.view._removeBehavior(this); | |
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) { | |
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() { | |
var _this = this; | |
// Normalize behavior events hash to allow | |
// a user to use the @ui. syntax. | |
var behaviorEvents = this.normalizeUIKeys(_.result(this, 'events')); | |
// binds the handler to the behavior and builds a unique eventName | |
return _.reduce(behaviorEvents, function (events, behaviorHandler, key) { | |
if (!_.isFunction(behaviorHandler)) { | |
behaviorHandler = _this[behaviorHandler]; | |
} | |
if (!behaviorHandler) { | |
return; | |
} | |
key = getUniqueEventName(key); | |
events[key] = _.bind(behaviorHandler, _this); | |
return events; | |
}, {}); | |
}, | |
// 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(_.result(this, 'triggers')); | |
return this._getViewTriggers(this.view, behaviorTriggers); | |
} | |
}); | |
_.extend(Behavior.prototype, DelegateEntityEventsMixin, TriggersMixin, UIMixin); | |
// Application | |
// ----------- | |
var ClassOptions$7 = ['region', 'regionClass']; | |
// A container for a Marionette application. | |
var Application = MarionetteObject.extend({ | |
cidPrefix: 'mna', | |
constructor: function constructor(options) { | |
this._setOptions(options); | |
this.mergeOptions(options, ClassOptions$7); | |
this._initRegion(); | |
MarionetteObject.prototype.constructor.apply(this, arguments); | |
}, | |
regionClass: Region, | |
_initRegion: function _initRegion() { | |
var region = this.region; | |
if (!region) { | |
return; | |
} | |
var defaults = { | |
regionClass: this.regionClass | |
}; | |
this._region = buildRegion(region, defaults); | |
}, | |
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', this, options); | |
this.triggerMethod('start', this, options); | |
return this; | |
} | |
}); | |
// App Router | |
// ---------- | |
// Reduce the boilerplate code of handling route events | |
// and then calling a single method on another object, | |
// called a controller. | |
// Have your routers configured to call the method on | |
// your controller, directly. | |
// | |
// Configure an AppRouter with `appRoutes`. | |
// | |
// App routers can only take one `controller` object. | |
// It is recommended that you divide your controller | |
// objects in to smaller pieces of related functionality | |
// and have multiple routers / controllers, instead of | |
// just one giant router and controller. | |
// | |
// You can also add standard routes to an AppRouter. | |
var ClassOptions$8 = ['appRoutes', 'controller']; | |
var AppRouter = Backbone.Router.extend({ | |
constructor: function constructor(options) { | |
this._setOptions(options); | |
this.mergeOptions(options, ClassOptions$8); | |
Backbone.Router.apply(this, arguments); | |
var appRoutes = this.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); | |
return this; | |
}, | |
// 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 (_.isFunction(this.onRoute)) { | |
// find the path that matches the current route | |
var routePath = _.invert(this.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) { | |
var _this = this; | |
if (!appRoutes) { | |
return this; | |
} | |
var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes | |
_.each(routeNames, function (route) { | |
_this._addAppRoute(controller, route, appRoutes[route]); | |
}); | |
return this; | |
}, | |
_getController: function _getController() { | |
return this.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, _.bind(method, controller)); | |
}, | |
triggerMethod: triggerMethod | |
}); | |
_.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' | |
}); | |
} | |
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.bindEvents = proxy(bindEvents); | |
Marionette.unbindEvents = proxy(unbindEvents); | |
Marionette.bindRequests = proxy(bindRequests); | |
Marionette.unbindRequests = proxy(unbindRequests); | |
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.NextCollectionView = CollectionView$2; | |
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; | |
Marionette.DomApi = DomApi; | |
Marionette.setDomApi = function (mixin) { | |
CollectionView.setDomApi(mixin); | |
CompositeView.setDomApi(mixin); | |
CollectionView$2.setDomApi(mixin); | |
Region.setDomApi(mixin); | |
View.setDomApi(mixin); | |
}; | |
return Marionette; | |
}))); | |
this && this.Marionette && (this.Mn = this.Marionette); | |
//# sourceMappingURL=backbone.marionette.js.map |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment