http://git-wip-us.apache.org/repos/asf/ambari/blob/529ef7f7/contrib/views/storm/src/main/resources/libs/bower/backbone.marionette/js/backbone.marionette.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/libs/bower/backbone.marionette/js/backbone.marionette.js b/contrib/views/storm/src/main/resources/libs/bower/backbone.marionette/js/backbone.marionette.js deleted file mode 100644 index 8714cd7..0000000 --- a/contrib/views/storm/src/main/resources/libs/bower/backbone.marionette/js/backbone.marionette.js +++ /dev/null @@ -1,3128 +0,0 @@ -// MarionetteJS (Backbone.Marionette) -// ---------------------------------- -// v2.3.2 -// -// Copyright (c)2015 Derick Bailey, Muted Solutions, LLC. -// Distributed under MIT license -// -// http://marionettejs.com - -(function(root, factory) { - - if (typeof define === 'function' && define.amd) { - define(['backbone', 'underscore', 'backbone.wreqr', 'backbone.babysitter'], function(Backbone, _) { - return (root.Marionette = root.Mn = factory(root, Backbone, _)); - }); - } else if (typeof exports !== 'undefined') { - var Backbone = require('backbone'); - var _ = require('underscore'); - var Wreqr = require('backbone.wreqr'); - var BabySitter = require('backbone.babysitter'); - module.exports = factory(root, Backbone, _); - } else { - root.Marionette = root.Mn = factory(root, root.Backbone, root._); - } - -}(this, function(root, Backbone, _) { - 'use strict'; - - var previousMarionette = root.Marionette; - var previousMn = root.Mn; - - var Marionette = Backbone.Marionette = {}; - - Marionette.VERSION = '2.3.2'; - - Marionette.noConflict = function() { - root.Marionette = previousMarionette; - root.Mn = previousMn; - return this; - }; - - // Get the Deferred creator for later use - Marionette.Deferred = Backbone.$.Deferred; - - /* jshint unused: false *//* global console */ - - // Helpers - // ------- - - // Marionette.extend - // ----------------- - - // Borrow the Backbone `extend` method so we can use it as needed - Marionette.extend = Backbone.Model.extend; - - // Marionette.isNodeAttached - // ------------------------- - - // Determine if `el` is a child of the document - Marionette.isNodeAttached = function(el) { - return Backbone.$.contains(document.documentElement, el); - }; - - - // Marionette.getOption - // -------------------- - - // Retrieve an object, function or other value from a target - // object or its `options`, with `options` taking precedence. - Marionette.getOption = function(target, optionName) { - if (!target || !optionName) { return; } - if (target.options && (target.options[optionName] !== undefined)) { - return target.options[optionName]; - } else { - return target[optionName]; - } - }; - - // Proxy `Marionette.getOption` - Marionette.proxyGetOption = function(optionName) { - return Marionette.getOption(this, optionName); - }; - - // Similar to `_.result`, this is a simple helper - // If a function is provided we call it with context - // otherwise just return the value. If the value is - // undefined return a default value - Marionette._getValue = function(value, context, params) { - if (_.isFunction(value)) { - // We need to ensure that params is not undefined - // to prevent `apply` from failing in ie8 - params = params || []; - - value = value.apply(context, params); - } - return value; - }; - - // Marionette.normalizeMethods - // ---------------------- - - // Pass in a mapping of events => functions or function names - // and return a mapping of events => functions - Marionette.normalizeMethods = function(hash) { - return _.reduce(hash, function(normalizedHash, method, name) { - if (!_.isFunction(method)) { - method = this[method]; - } - if (method) { - normalizedHash[name] = method; - } - return normalizedHash; - }, {}, this); - }; - - // utility method for parsing @ui. syntax strings - // into associated selector - Marionette.normalizeUIString = function(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 key for triggers and events - // swaps the @ui with the associated selector. - // Returns a new, non-mutated, parsed events hash. - Marionette.normalizeUIKeys = function(hash, ui) { - return _.reduce(hash, function(memo, val, key) { - var normalizedKey = Marionette.normalizeUIString(key, ui); - memo[normalizedKey] = val; - return memo; - }, {}); - }; - - // allows for the use of the @ui. syntax within - // a given value for regions - // swaps the @ui with the associated selector - Marionette.normalizeUIValues = function(hash, ui) { - _.each(hash, function(val, key) { - if (_.isString(val)) { - hash[key] = Marionette.normalizeUIString(val, ui); - } - }); - return hash; - }; - - // Mix in methods from Underscore, for iteration, and other - // collection related features. - // Borrowing this code from Backbone.Collection: - // http://backbonejs.org/docs/backbone.html#section-121 - Marionette.actAsCollection = function(object, listProperty) { - var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter', - 'select', 'reject', 'every', 'all', 'some', 'any', 'include', - 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest', - 'last', 'without', 'isEmpty', 'pluck']; - - _.each(methods, function(method) { - object[method] = function() { - var list = _.values(_.result(this, listProperty)); - var args = [list].concat(_.toArray(arguments)); - return _[method].apply(_, args); - }; - }); - }; - - var deprecate = Marionette.deprecate = function(message, test) { - if (_.isObject(message)) { - message = ( - message.prev + ' is going to be removed in the future. ' + - 'Please use ' + message.next + ' instead.' + - (message.url ? ' See: ' + message.url : '') - ); - } - - if ((test === undefined || !test) && !deprecate._cache[message]) { - deprecate._warn('Deprecation warning: ' + message); - deprecate._cache[message] = true; - } - }; - - deprecate._warn = typeof console !== 'undefined' && (console.warn || console.log) || function() {}; - deprecate._cache = {}; - - /* jshint maxstatements: 14, maxcomplexity: 7 */ - - // Trigger Method - // -------------- - - - Marionette._triggerMethod = (function() { - // split the event name on the ":" - var splitter = /(^|:)(\w)/gi; - - // take the event section ("section1:section2:section3") - // and turn it in to uppercase name - function getEventName(match, prefix, eventName) { - return eventName.toUpperCase(); - } - - return function(context, event, args) { - var noEventArg = arguments.length < 3; - if (noEventArg) { - args = event; - event = args[0]; - } - - // get the method name from the event name - var methodName = 'on' + event.replace(splitter, getEventName); - var method = context[methodName]; - var result; - - // call the onMethodName if it exists - if (_.isFunction(method)) { - // pass all args, except the event name - result = method.apply(context, noEventArg ? _.rest(args) : args); - } - - // trigger the event, if a trigger method exists - if (_.isFunction(context.trigger)) { - if (noEventArg + args.length > 1) { - context.trigger.apply(context, noEventArg ? args : [event].concat(_.rest(args, 0))); - } else { - context.trigger(event); - } - } - - return result; - }; - })(); - - // 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. - Marionette.triggerMethod = function(event) { - return Marionette._triggerMethod(this, arguments); - }; - - // triggerMethodOn invokes triggerMethod on a specific context - // - // e.g. `Marionette.triggerMethodOn(view, 'show')` - // will trigger a "show" event or invoke onShow the view. - Marionette.triggerMethodOn = function(context) { - var fnc = _.isFunction(context.triggerMethod) ? - context.triggerMethod : - Marionette.triggerMethod; - - return fnc.apply(context, _.rest(arguments)); - }; - - // DOM Refresh - // ----------- - - // Monitor a view's state, and after it has been rendered and shown - // in the DOM, trigger a "dom:refresh" event every time it is - // re-rendered. - - Marionette.MonitorDOMRefresh = function(view) { - - // track when the view has been shown in the DOM, - // using a Marionette.Region (or by other means of triggering "show") - function handleShow() { - view._isShown = true; - triggerDOMRefresh(); - } - - // track when the view has been rendered - function handleRender() { - view._isRendered = true; - triggerDOMRefresh(); - } - - // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method - function triggerDOMRefresh() { - if (view._isShown && view._isRendered && Marionette.isNodeAttached(view.el)) { - if (_.isFunction(view.triggerMethod)) { - view.triggerMethod('dom:refresh'); - } - } - } - - view.on({ - show: handleShow, - render: handleRender - }); - }; - - /* jshint maxparams: 5 */ - - // 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. - - (function(Marionette) { - 'use strict'; - - // Bind the event to handlers specified as a string of - // handler names on the target object - function bindFromStrings(target, entity, evt, methods) { - var methodNames = methods.split(/\s+/); - - _.each(methodNames, function(methodName) { - - var method = target[methodName]; - if (!method) { - throw new Marionette.Error('Method "' + methodName + - '" was configured as an event handler, but does not exist.'); - } - - target.listenTo(entity, evt, method); - }); - } - - // Bind the event to a supplied callback function - function bindToFunction(target, entity, evt, method) { - target.listenTo(entity, evt, method); - } - - // Bind the event to handlers specified as a string of - // handler names on the target object - function unbindFromStrings(target, entity, evt, methods) { - var methodNames = methods.split(/\s+/); - - _.each(methodNames, function(methodName) { - var method = target[methodName]; - target.stopListening(entity, evt, method); - }); - } - - // Bind the event to a supplied callback function - function unbindToFunction(target, entity, evt, method) { - target.stopListening(entity, evt, method); - } - - - // generic looping function - function iterateEvents(target, entity, bindings, functionCallback, stringCallback) { - if (!entity || !bindings) { return; } - - // type-check bindings - if (!_.isObject(bindings)) { - throw new Marionette.Error({ - message: 'Bindings must be an object or function.', - url: 'marionette.functions.html#marionettebindentityevents' - }); - } - - // allow the bindings to be a function - bindings = Marionette._getValue(bindings, target); - - // iterate the bindings and bind them - _.each(bindings, function(methods, evt) { - - // allow for a function as the handler, - // or a list of event names as a string - if (_.isFunction(methods)) { - functionCallback(target, entity, evt, methods); - } else { - stringCallback(target, entity, evt, methods); - } - - }); - } - - // Export Public API - Marionette.bindEntityEvents = function(target, entity, bindings) { - iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings); - }; - - Marionette.unbindEntityEvents = function(target, entity, bindings) { - iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings); - }; - - // Proxy `bindEntityEvents` - Marionette.proxyBindEntityEvents = function(entity, bindings) { - return Marionette.bindEntityEvents(this, entity, bindings); - }; - - // Proxy `unbindEntityEvents` - Marionette.proxyUnbindEntityEvents = function(entity, bindings) { - return Marionette.unbindEntityEvents(this, entity, bindings); - }; - })(Marionette); - - - // Error - // ----- - - var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number']; - - Marionette.Error = Marionette.extend.call(Error, { - urlRoot: 'http://marionettejs.com/docs/v' + Marionette.VERSION + '/', - - constructor: function(message, options) { - if (_.isObject(message)) { - options = message; - message = options.message; - } else if (!options) { - options = {}; - } - - var error = Error.call(this, message); - _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps)); - - this.captureStackTrace(); - - if (options.url) { - this.url = this.urlRoot + options.url; - } - }, - - captureStackTrace: function() { - if (Error.captureStackTrace) { - Error.captureStackTrace(this, Marionette.Error); - } - }, - - toString: function() { - return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : ''); - } - }); - - Marionette.Error.extend = Marionette.extend; - - // Callbacks - // --------- - - // A simple way of managing a collection of callbacks - // and executing them at a later point in time, using jQuery's - // `Deferred` object. - Marionette.Callbacks = function() { - this._deferred = Marionette.Deferred(); - this._callbacks = []; - }; - - _.extend(Marionette.Callbacks.prototype, { - - // Add a callback to be executed. Callbacks added here are - // guaranteed to execute, even if they are added after the - // `run` method is called. - add: function(callback, contextOverride) { - var promise = _.result(this._deferred, 'promise'); - - this._callbacks.push({cb: callback, ctx: contextOverride}); - - promise.then(function(args) { - if (contextOverride){ args.context = contextOverride; } - callback.call(args.context, args.options); - }); - }, - - // Run all registered callbacks with the context specified. - // Additional callbacks can be added after this has been run - // and they will still be executed. - run: function(options, context) { - this._deferred.resolve({ - options: options, - context: context - }); - }, - - // Resets the list of callbacks to be run, allowing the same list - // to be run multiple times - whenever the `run` method is called. - reset: function() { - var callbacks = this._callbacks; - this._deferred = Marionette.Deferred(); - this._callbacks = []; - - _.each(callbacks, function(cb) { - this.add(cb.cb, cb.ctx); - }, this); - } - }); - - // Controller - // ---------- - - // A multi-purpose object to use as a controller for - // modules and routers, and as a mediator for workflow - // and coordination of other objects, views, and more. - Marionette.Controller = function(options) { - this.options = options || {}; - - if (_.isFunction(this.initialize)) { - this.initialize(this.options); - } - }; - - Marionette.Controller.extend = Marionette.extend; - - // Controller Methods - // -------------- - - // Ensure it can trigger events with Backbone.Events - _.extend(Marionette.Controller.prototype, Backbone.Events, { - destroy: function() { - Marionette._triggerMethod(this, 'before:destroy', arguments); - Marionette._triggerMethod(this, 'destroy', arguments); - - this.stopListening(); - this.off(); - return this; - }, - - // import the `triggerMethod` to trigger events with corresponding - // methods if the method exists - triggerMethod: Marionette.triggerMethod, - - // Proxy `getOption` to enable getting options from this or this.options by name. - getOption: Marionette.proxyGetOption - - }); - - // Object - // ------ - - // A Base Class that other Classes should descend from. - // Object borrows many conventions and utilities from Backbone. - Marionette.Object = function(options) { - this.options = _.extend({}, _.result(this, 'options'), options); - - this.initialize.apply(this, arguments); - }; - - Marionette.Object.extend = Marionette.extend; - - // Object Methods - // -------------- - - // Ensure it can trigger events with Backbone.Events - _.extend(Marionette.Object.prototype, Backbone.Events, { - - //this is a noop method intended to be overridden by classes that extend from this base - initialize: function() {}, - - destroy: function() { - this.triggerMethod('before:destroy'); - this.triggerMethod('destroy'); - this.stopListening(); - }, - - // Import the `triggerMethod` to trigger events with corresponding - // methods if the method exists - triggerMethod: Marionette.triggerMethod, - - // Proxy `getOption` to enable getting options from this or this.options by name. - getOption: Marionette.proxyGetOption, - - // Proxy `bindEntityEvents` to enable binding view's events from another entity. - bindEntityEvents: Marionette.proxyBindEntityEvents, - - // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity. - unbindEntityEvents: Marionette.proxyUnbindEntityEvents - }); - - /* jshint maxcomplexity: 16, maxstatements: 45, maxlen: 120 */ - - // Region - // ------ - - // Manage the visual regions of your composite application. See - // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/ - - Marionette.Region = Marionette.Object.extend({ - constructor: function (options) { - - // set options temporarily so that we can get `el`. - // options will be overriden by Object.constructor - this.options = options || {}; - 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 Marionette.Error({ - name: 'NoElError', - message: 'An "el" must be specified for a region.' - }); - } - - this.$el = this.getEl(this.el); - Marionette.Object.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. Also calls an optional - // `onShow` and `onDestroy` method on your view, just after showing - // or just before destroying the view, respectively. - // The `preventDestroy` option can be used to prevent a view from - // the old view being destroyed on show. - // The `forceShow` option can be used to force a view to be - // re-rendered if it's already shown in the region. - show: function(view, options){ - if (!this._ensureElement()) { - return; - } - - this._ensureViewIsIntact(view); - - var showOptions = options || {}; - var isDifferentView = view !== this.currentView; - var preventDestroy = !!showOptions.preventDestroy; - var forceShow = !!showOptions.forceShow; - - // We are only changing the view if there is a current view to change to begin with - var isChangingView = !!this.currentView; - - // Only destroy the current view if we don't want to `preventDestroy` and if - // the view given in the first argument is different than `currentView` - var _shouldDestroyView = isDifferentView && !preventDestroy; - - // Only show the view given in the first argument if it is different than - // the current view or if we want to re-show the view. Note that if - // `_shouldDestroyView` is true, then `_shouldShowView` is also necessarily true. - var _shouldShowView = isDifferentView || forceShow; - - if (isChangingView) { - this.triggerMethod('before:swapOut', this.currentView, this, options); - } - - if (this.currentView) { - delete this.currentView._parent; - } - - if (_shouldDestroyView) { - this.empty(); - - // A `destroy` event is attached to the clean up manually removed views. - // We need to detach this event when a new view is going to be shown as it - // is no longer relevant. - } else if (isChangingView && _shouldShowView) { - this.currentView.off('destroy', this.empty, this); - } - - if (_shouldShowView) { - - // 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.once('destroy', this.empty, this); - view.render(); - - view._parent = this; - - if (isChangingView) { - this.triggerMethod('before:swap', view, this, options); - } - - this.triggerMethod('before:show', view, this, options); - Marionette.triggerMethodOn(view, 'before:show', view, this, options); - - if (isChangingView) { - this.triggerMethod('swapOut', this.currentView, this, options); - } - - // An array of views that we're about to display - var attachedRegion = Marionette.isNodeAttached(this.el); - - // The views that we're about to attach to the document - // It's important that we prevent _getNestedViews from being executed unnecessarily - // as it's a potentially-slow method - var displayedViews = []; - - var triggerBeforeAttach = showOptions.triggerBeforeAttach || this.triggerBeforeAttach; - var triggerAttach = showOptions.triggerAttach || this.triggerAttach; - - if (attachedRegion && triggerBeforeAttach) { - displayedViews = this._displayedViews(view); - this._triggerAttach(displayedViews, 'before:'); - } - - this.attachHtml(view); - this.currentView = view; - - if (attachedRegion && triggerAttach) { - displayedViews = this._displayedViews(view); - this._triggerAttach(displayedViews); - } - - if (isChangingView) { - this.triggerMethod('swap', view, this, options); - } - - this.triggerMethod('show', view, this, options); - Marionette.triggerMethodOn(view, 'show', view, this, options); - - return this; - } - - return this; - }, - - triggerBeforeAttach: true, - triggerAttach: true, - - _triggerAttach: function(views, prefix) { - var eventName = (prefix || '') + 'attach'; - _.each(views, function(view) { - Marionette.triggerMethodOn(view, eventName, view, this); - }, this); - }, - - _displayedViews: function(view) { - return _.union([view], _.result(view, '_getNestedViews') || []); - }, - - _ensureElement: function(){ - if (!_.isObject(this.el)) { - this.$el = this.getEl(this.el); - this.el = this.$el[0]; - } - - if (!this.$el || this.$el.length === 0) { - if (this.getOption('allowMissingEl')) { - return false; - } else { - throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM'); - } - } - return true; - }, - - _ensureViewIsIntact: function(view) { - if (!view) { - throw new Marionette.Error({ - name: 'ViewNotValid', - message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.' - }); - } - - if (view.isDestroyed) { - throw new Marionette.Error({ - name: 'ViewDestroyedError', - message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.' - }); - } - }, - - // 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(el) { - return Backbone.$(el, Marionette._getValue(this.options.parentEl, this)); - }, - - // Override this method to change how the new view is - // appended to the `$el` that the region is managing - attachHtml: function(view) { - this.$el.contents().detach(); - - this.el.appendChild(view.el); - }, - - // Destroy the current view, if there is one. If there is no - // current view, it does nothing and returns immediately. - empty: function() { - var view = this.currentView; - - // If there is no view in the region - // we should not remove anything - if (!view) { return; } - - view.off('destroy', this.empty, this); - this.triggerMethod('before:empty', view); - this._destroyView(); - this.triggerMethod('empty', view); - - // Remove region pointer to the currentView - delete this.currentView; - return this; - }, - - // call 'destroy' or 'remove', depending on which is found - // on the view (if showing a raw Backbone view or a Marionette View) - _destroyView: function() { - var view = this.currentView; - - if (view.destroy && !view.isDestroyed) { - view.destroy(); - } else if (view.remove) { - view.remove(); - - // appending isDestroyed to raw Backbone View allows regions - // to throw a ViewDestroyedError for this view - view.isDestroyed = true; - } - }, - - // Attach an existing view to the region. This - // will not call `render` or `onShow` for the new view, - // and will not replace the current HTML for the `el` - // of the region. - attachView: function(view) { - this.currentView = view; - return this; - }, - - // Checks whether a view is currently present within - // the region. Returns `true` if there is and `false` if - // no view is present. - hasView: function() { - 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() { - this.empty(); - - if (this.$el) { - this.el = this.$el.selector; - } - - delete this.$el; - return this; - } - - }, - - // Static Methods - { - - // Build an instance of a region by passing in a configuration object - // and a default region class to use if none is specified in the config. - // - // The config object should either be a string as a jQuery DOM selector, - // a Region class directly, or an object literal that specifies a selector, - // a custom regionClass, and any options to be supplied to the region: - // - // ```js - // { - // selector: "#foo", - // regionClass: MyCustomRegion, - // allowMissingEl: false - // } - // ``` - // - buildRegion: function(regionConfig, DefaultRegionClass) { - if (_.isString(regionConfig)) { - return this._buildRegionFromSelector(regionConfig, DefaultRegionClass); - } - - if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) { - return this._buildRegionFromObject(regionConfig, DefaultRegionClass); - } - - if (_.isFunction(regionConfig)) { - return this._buildRegionFromRegionClass(regionConfig); - } - - throw new Marionette.Error({ - message: 'Improper region configuration type.', - url: 'marionette.region.html#region-configuration-types' - }); - }, - - // Build the region from a string selector like '#foo-region' - _buildRegionFromSelector: function(selector, DefaultRegionClass) { - return new DefaultRegionClass({ el: selector }); - }, - - // Build the region from a configuration object - // ```js - // { selector: '#foo', regionClass: FooRegion, allowMissingEl: false } - // ``` - _buildRegionFromObject: function(regionConfig, DefaultRegionClass) { - var RegionClass = regionConfig.regionClass || DefaultRegionClass; - var options = _.omit(regionConfig, 'selector', 'regionClass'); - - if (regionConfig.selector && !options.el) { - options.el = regionConfig.selector; - } - - return new RegionClass(options); - }, - - // Build the region directly from a given `RegionClass` - _buildRegionFromRegionClass: function(RegionClass) { - return new RegionClass(); - } - }); - - // Region Manager - // -------------- - - // Manage one or more related `Marionette.Region` objects. - Marionette.RegionManager = Marionette.Controller.extend({ - constructor: function(options) { - this._regions = {}; - - Marionette.Controller.call(this, options); - - this.addRegions(this.getOption('regions')); - }, - - // Add multiple regions using an object literal or a - // function that returns an object literal, where - // each key becomes the region name, and each value is - // the region definition. - addRegions: function(regionDefinitions, defaults) { - regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments); - - return _.reduce(regionDefinitions, function(regions, definition, name) { - if (_.isString(definition)) { - definition = {selector: definition}; - } - if (definition.selector) { - definition = _.defaults({}, definition, defaults); - } - - regions[name] = this.addRegion(name, definition); - return regions; - }, {}, this); - }, - - // Add an individual region to the region manager, - // and return the region instance - addRegion: function(name, definition) { - var region; - - if (definition instanceof Marionette.Region) { - region = definition; - } else { - region = Marionette.Region.buildRegion(definition, Marionette.Region); - } - - this.triggerMethod('before:add:region', name, region); - - region._parent = this; - this._store(name, region); - - this.triggerMethod('add:region', name, region); - return region; - }, - - // Get a region by name - get: function(name) { - return this._regions[name]; - }, - - // Gets all the regions contained within - // the `regionManager` instance. - getRegions: function(){ - return _.clone(this._regions); - }, - - // Remove a region by name - removeRegion: function(name) { - var region = this._regions[name]; - this._remove(name, region); - - return region; - }, - - // Empty all regions in the region manager, and - // remove them - removeRegions: function() { - var regions = this.getRegions(); - _.each(this._regions, function(region, name) { - this._remove(name, region); - }, this); - - return regions; - }, - - // Empty all regions in the region manager, but - // leave them attached - emptyRegions: function() { - var regions = this.getRegions(); - _.invoke(regions, 'empty'); - return regions; - }, - - // Destroy all regions and shut down the region - // manager entirely - destroy: function() { - this.removeRegions(); - return Marionette.Controller.prototype.destroy.apply(this, arguments); - }, - - // internal method to store regions - _store: function(name, region) { - this._regions[name] = region; - this._setLength(); - }, - - // internal method to remove a region - _remove: function(name, region) { - this.triggerMethod('before:remove:region', name, region); - region.empty(); - region.stopListening(); - - delete region._parent; - delete this._regions[name]; - this._setLength(); - this.triggerMethod('remove:region', name, region); - }, - - // set the number of regions current held - _setLength: function() { - this.length = _.size(this._regions); - } - }); - - Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions'); - - - // Template Cache - // -------------- - - // Manage templates stored in `<script>` blocks, - // caching them for faster access. - Marionette.TemplateCache = function(templateId) { - this.templateId = templateId; - }; - - // TemplateCache object-level methods. Manage the template - // caches from these method calls instead of creating - // your own TemplateCache instances - _.extend(Marionette.TemplateCache, { - templateCaches: {}, - - // Get the specified template by id. Either - // retrieves the cached version, or loads it - // from the DOM. - get: function(templateId) { - var cachedTemplate = this.templateCaches[templateId]; - - if (!cachedTemplate) { - cachedTemplate = new Marionette.TemplateCache(templateId); - this.templateCaches[templateId] = cachedTemplate; - } - - return cachedTemplate.load(); - }, - - // Clear templates from the cache. If no arguments - // are specified, clears all templates: - // `clear()` - // - // If arguments are specified, clears each of the - // specified templates from the cache: - // `clear("#t1", "#t2", "...")` - clear: function() { - var i; - var args = _.toArray(arguments); - var length = args.length; - - if (length > 0) { - for (i = 0; i < length; i++) { - delete this.templateCaches[args[i]]; - } - } else { - this.templateCaches = {}; - } - } - }); - - // TemplateCache instance methods, allowing each - // template cache object to manage its own state - // and know whether or not it has been loaded - _.extend(Marionette.TemplateCache.prototype, { - - // Internal method to load the template - load: function() { - // Guard clause to prevent loading this template more than once - if (this.compiledTemplate) { - return this.compiledTemplate; - } - - // Load the template and compile it - var template = this.loadTemplate(this.templateId); - this.compiledTemplate = this.compileTemplate(template); - - return this.compiledTemplate; - }, - - // Load a template from the DOM, by default. Override - // this method to provide your own template retrieval - // For asynchronous loading with AMD/RequireJS, consider - // using a template-loader plugin as described here: - // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs - loadTemplate: function(templateId) { - var template = Backbone.$(templateId).html(); - - if (!template || template.length === 0) { - throw new Marionette.Error({ - name: 'NoTemplateError', - message: 'Could not find template: "' + templateId + '"' - }); - } - - return template; - }, - - // Pre-compile the template before caching it. Override - // this method if you do not need to pre-compile a template - // (JST / RequireJS for example) or if you want to change - // the template engine used (Handebars, etc). - compileTemplate: function(rawTemplate) { - return _.template(rawTemplate); - } - }); - - // Renderer - // -------- - - // Render a template with data by passing in the template - // selector and the data to render. - Marionette.Renderer = { - - // Render a template with data. The `template` parameter is - // passed to the `TemplateCache` object to retrieve the - // template function. Override this method to provide your own - // custom rendering and template handling for all of Marionette. - render: function(template, data) { - if (!template) { - throw new Marionette.Error({ - name: 'TemplateNotFoundError', - message: 'Cannot render the template since its false, null or undefined.' - }); - } - - var templateFunc = _.isFunction(template) ? template : Marionette.TemplateCache.get(template); - - return templateFunc(data); - } - }; - - - /* jshint maxlen: 114, nonew: false */ - // View - // ---- - - // The core view class that other Marionette views extend from. - Marionette.View = Backbone.View.extend({ - isDestroyed: false, - - constructor: function(options) { - _.bindAll(this, 'render'); - - options = Marionette._getValue(options, this); - - // this exposes view options to the view initializer - // this is a backfill since backbone removed the assignment - // of this.options - // at some point however this may be removed - this.options = _.extend({}, _.result(this, 'options'), options); - - this._behaviors = Marionette.Behaviors(this); - - Backbone.View.apply(this, arguments); - - Marionette.MonitorDOMRefresh(this); - this.on('show', this.onShowCalled); - }, - - // Get the template for this view - // instance. You can set a `template` attribute in the view - // definition or pass a `template: "whatever"` parameter in - // to the constructor options. - getTemplate: function() { - return this.getOption('template'); - }, - - // Serialize a model by returning its attributes. Clones - // the attributes to allow modification. - serializeModel: function(model){ - return model.toJSON.apply(model, _.rest(arguments)); - }, - - // Mix in template helper methods. Looks for a - // `templateHelpers` attribute, which can either be an - // object literal, or a function that returns an object - // literal. All methods and attributes from this object - // are copies to the object passed in. - mixinTemplateHelpers: function(target) { - target = target || {}; - var templateHelpers = this.getOption('templateHelpers'); - templateHelpers = Marionette._getValue(templateHelpers, this); - return _.extend(target, templateHelpers); - }, - - // normalize the keys of passed hash with the views `ui` selectors. - // `{"@ui.foo": "bar"}` - normalizeUIKeys: function(hash) { - var uiBindings = _.result(this, '_uiBindings'); - return Marionette.normalizeUIKeys(hash, uiBindings || _.result(this, 'ui')); - }, - - // normalize the values of passed hash with the views `ui` selectors. - // `{foo: "@ui.bar"}` - normalizeUIValues: function(hash) { - var ui = _.result(this, 'ui'); - var uiBindings = _.result(this, '_uiBindings'); - return Marionette.normalizeUIValues(hash, uiBindings || ui); - }, - - // Configure `triggers` to forward DOM events to view - // events. `triggers: {"click .foo": "do:foo"}` - configureTriggers: function() { - 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 _.reduce(triggers, function(events, value, key) { - events[key] = this._buildViewTrigger(value); - return events; - }, {}, this); - }, - - // Overriding Backbone.View's delegateEvents to handle - // the `triggers`, `modelEvents`, and `collectionEvents` configuration - delegateEvents: function(events) { - this._delegateDOMEvents(events); - this.bindEntityEvents(this.model, this.getOption('modelEvents')); - this.bindEntityEvents(this.collection, this.getOption('collectionEvents')); - - _.each(this._behaviors, function(behavior) { - behavior.bindEntityEvents(this.model, behavior.getOption('modelEvents')); - behavior.bindEntityEvents(this.collection, behavior.getOption('collectionEvents')); - }, this); - - return this; - }, - - // internal method to delegate DOM events and triggers - _delegateDOMEvents: function(eventsArg) { - var events = Marionette._getValue(eventsArg || this.events, this); - - // normalize ui keys - events = this.normalizeUIKeys(events); - if(_.isUndefined(eventsArg)) {this.events = events;} - - var combinedEvents = {}; - - // look up if this view has behavior events - var behaviorEvents = _.result(this, 'behaviorEvents') || {}; - var triggers = this.configureTriggers(); - var behaviorTriggers = _.result(this, 'behaviorTriggers') || {}; - - // behavior events will be overriden by view events and or triggers - _.extend(combinedEvents, behaviorEvents, events, triggers, behaviorTriggers); - - Backbone.View.prototype.delegateEvents.call(this, combinedEvents); - }, - - // Overriding Backbone.View's undelegateEvents to handle unbinding - // the `triggers`, `modelEvents`, and `collectionEvents` config - undelegateEvents: function() { - Backbone.View.prototype.undelegateEvents.apply(this, arguments); - - this.unbindEntityEvents(this.model, this.getOption('modelEvents')); - this.unbindEntityEvents(this.collection, this.getOption('collectionEvents')); - - _.each(this._behaviors, function(behavior) { - behavior.unbindEntityEvents(this.model, behavior.getOption('modelEvents')); - behavior.unbindEntityEvents(this.collection, behavior.getOption('collectionEvents')); - }, this); - - return this; - }, - - // Internal method, handles the `show` event. - onShowCalled: function() {}, - - // Internal helper method to verify whether the view hasn't been destroyed - _ensureViewIsIntact: function() { - if (this.isDestroyed) { - throw new Marionette.Error({ - name: 'ViewDestroyedError', - message: 'View (cid: "' + this.cid + '") has already been destroyed and cannot be used.' - }); - } - }, - - // Default `destroy` implementation, for removing a view from the - // DOM and unbinding it. Regions will call this method - // for you. You can specify an `onDestroy` method in your view to - // add custom code that is called after the view is destroyed. - destroy: function() { - if (this.isDestroyed) { return; } - - var args = _.toArray(arguments); - - this.triggerMethod.apply(this, ['before:destroy'].concat(args)); - - // mark as destroyed before doing the actual destroy, to - // prevent infinite loops within "destroy" event handlers - // that are trying to destroy other views - this.isDestroyed = true; - this.triggerMethod.apply(this, ['destroy'].concat(args)); - - // unbind UI elements - this.unbindUIElements(); - - // remove the view from the DOM - this.remove(); - - // Call destroy on each behavior after - // destroying the view. - // This unbinds event listeners - // that behaviors have registered for. - _.invoke(this._behaviors, 'destroy', args); - - return this; - }, - - bindUIElements: function() { - this._bindUIElements(); - _.invoke(this._behaviors, this._bindUIElements); - }, - - // This method binds the elements specified in the "ui" hash inside the view's code with - // the associated jQuery selectors. - _bindUIElements: function() { - if (!this.ui) { return; } - - // store the ui hash in _uiBindings so they can be reset later - // and so re-rendering the view will be able to find the bindings - if (!this._uiBindings) { - this._uiBindings = this.ui; - } - - // get the bindings result, as a function or otherwise - var bindings = _.result(this, '_uiBindings'); - - // empty the ui so we don't have anything to start with - this.ui = {}; - - // bind each of the selectors - _.each(bindings, function(selector, key) { - this.ui[key] = this.$(selector); - }, this); - }, - - // This method unbinds the elements specified in the "ui" hash - unbindUIElements: function() { - this._unbindUIElements(); - _.invoke(this._behaviors, this._unbindUIElements); - }, - - _unbindUIElements: function() { - if (!this.ui || !this._uiBindings) { return; } - - // delete all of the existing ui bindings - _.each(this.ui, function($el, name) { - delete this.ui[name]; - }, this); - - // reset the ui element to the original bindings configuration - this.ui = this._uiBindings; - delete this._uiBindings; - }, - - // Internal method to create an event handler for a given `triggerDef` like - // 'click:foo' - _buildViewTrigger: function(triggerDef) { - var hasOptions = _.isObject(triggerDef); - - var options = _.defaults({}, (hasOptions ? triggerDef : {}), { - preventDefault: true, - stopPropagation: true - }); - - var eventName = hasOptions ? options.event : triggerDef; - - return function(e) { - if (e) { - if (e.preventDefault && options.preventDefault) { - e.preventDefault(); - } - - if (e.stopPropagation && options.stopPropagation) { - e.stopPropagation(); - } - } - - var args = { - view: this, - model: this.model, - collection: this.collection - }; - - this.triggerMethod(eventName, args); - }; - }, - - setElement: function() { - var ret = Backbone.View.prototype.setElement.apply(this, arguments); - - // proxy behavior $el to the view's $el. - // This is needed because a view's $el proxy - // is not set until after setElement is called. - _.invoke(this._behaviors, 'proxyViewProperties', this); - - return ret; - }, - - // import the `triggerMethod` to trigger events with corresponding - // methods if the method exists - triggerMethod: function() { - var triggerMethod = Marionette._triggerMethod; - var ret = triggerMethod(this, arguments); - 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(behaviors[i], arguments); - } - - return ret; - }, - - // This method returns any views that are immediate - // children of this view - _getImmediateChildren: function() { - return []; - }, - - // Returns an array of every nested view within this view - _getNestedViews: function() { - var children = this._getImmediateChildren(); - - if (!children.length) { return children; } - - return _.reduce(children, function(memo, view) { - if (!view._getNestedViews) { return memo; } - return memo.concat(view._getNestedViews()); - }, children); - }, - - // Imports the "normalizeMethods" to transform hashes of - // events=>function references/names to a hash of events=>function references - normalizeMethods: Marionette.normalizeMethods, - - // Proxy `getOption` to enable getting options from this or this.options by name. - getOption: Marionette.proxyGetOption, - - // Proxy `bindEntityEvents` to enable binding view's events from another entity. - bindEntityEvents: Marionette.proxyBindEntityEvents, - - // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity. - unbindEntityEvents: Marionette.proxyUnbindEntityEvents - }); - - // Item View - // --------- - - // A single item view implementation that contains code for rendering - // with underscore.js templates, serializing the view's model or collection, - // and calling several methods on extended views, such as `onRender`. - Marionette.ItemView = Marionette.View.extend({ - - // Setting up the inheritance chain which allows changes to - // Marionette.View.prototype.constructor which allows overriding - constructor: function() { - Marionette.View.apply(this, arguments); - }, - - // Serialize the model or collection for the view. If a model is - // found, the view's `serializeModel` is called. If a collection is found, - // each model in the collection is serialized by calling - // the view's `serializeCollection` and put into an `items` array in - // the resulting data. If both are found, defaults to the model. - // You can override the `serializeData` method in your own view definition, - // to provide custom serialization for your view's data. - serializeData: function(){ - if (!this.model && !this.collection) { - return {}; - } - - var args = [this.model || this.collection]; - if (arguments.length) { - args.push.apply(args, arguments); - } - - if (this.model) { - return this.serializeModel.apply(this, args); - } else { - return { - items: this.serializeCollection.apply(this, args) - }; - } - }, - - // Serialize a collection by serializing each of its models. - serializeCollection: function(collection){ - return collection.toJSON.apply(collection, _.rest(arguments)); - }, - - // Render the view, defaulting to underscore.js templates. - // You can override this in your view definition to provide - // a very specific rendering for your view. In general, though, - // you should override the `Marionette.Renderer` object to - // change how Marionette renders views. - render: function() { - this._ensureViewIsIntact(); - - this.triggerMethod('before:render', this); - - this._renderTemplate(); - this.bindUIElements(); - - this.triggerMethod('render', this); - - return this; - }, - - // Internal method to render the template with the serialized data - // and template helpers via the `Marionette.Renderer` object. - // Throws an `UndefinedTemplateError` error if the template is - // any falsely value but literal `false`. - _renderTemplate: function() { - var template = this.getTemplate(); - - // Allow template-less item views - if (template === false) { - return; - } - - if (!template) { - throw new Marionette.Error({ - name: 'UndefinedTemplateError', - message: 'Cannot render the template since it is null or undefined.' - }); - } - - // Add in entity data and template helpers - var data = this.serializeData(); - data = this.mixinTemplateHelpers(data); - - // Render and add to el - var html = Marionette.Renderer.render(template, data, this); - this.attachElContent(html); - - return this; - }, - - // 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: function(html) { - // this.el.innerHTML = html; - // return this; - // } - // ``` - attachElContent: function(html) { - this.$el.html(html); - - return this; - } - }); - - /* jshint maxstatements: 14 */ - - // Collection View - // --------------- - - // A view that iterates over a Backbone.Collection - // and renders an individual child view for each model. - Marionette.CollectionView = Marionette.View.extend({ - - // used as the prefix for child view events - // that are forwarded through the collectionview - childViewEventPrefix: 'childview', - - // 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. - constructor: function(options){ - var initOptions = options || {}; - if (_.isUndefined(this.sort)){ - this.sort = _.isUndefined(initOptions.sort) ? true : initOptions.sort; - } - - this.once('render', this._initialEvents); - this._initChildViewStorage(); - - Marionette.View.apply(this, arguments); - - this.initRenderBuffer(); - }, - - // 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 - initRenderBuffer: function() { - this.elBuffer = document.createDocumentFragment(); - this._bufferedChildren = []; - }, - - startBuffering: function() { - this.initRenderBuffer(); - this.isBuffering = true; - }, - - endBuffering: function() { - this.isBuffering = false; - this._triggerBeforeShowBufferedChildren(); - this.attachBuffer(this, this.elBuffer); - this._triggerShowBufferedChildren(); - this.initRenderBuffer(); - }, - - _triggerBeforeShowBufferedChildren: function() { - if (this._isShown) { - _.each(this._bufferedChildren, _.partial(this._triggerMethodOnChild, 'before:show')); - } - }, - - _triggerShowBufferedChildren: function() { - if (this._isShown) { - _.each(this._bufferedChildren, _.partial(this._triggerMethodOnChild, 'show')); - - this._bufferedChildren = []; - } - }, - - // Internal method for _.each loops to call `Marionette.triggerMethodOn` on - // a child view - _triggerMethodOnChild: function(event, childView) { - Marionette.triggerMethodOn(childView, event); - }, - - // Configured the initial events that the collection view - // binds to. - _initialEvents: function() { - if (this.collection) { - this.listenTo(this.collection, 'add', this._onCollectionAdd); - this.listenTo(this.collection, 'remove', this._onCollectionRemove); - this.listenTo(this.collection, 'reset', this.render); - - if (this.sort) { - this.listenTo(this.collection, 'sort', this._sortViews); - } - } - }, - - // Handle a child added to the collection - _onCollectionAdd: function(child) { - this.destroyEmptyView(); - var ChildView = this.getChildView(child); - var index = this.collection.indexOf(child); - this.addChild(child, ChildView, index); - }, - - // get the child view by model it holds, and remove it - _onCollectionRemove: function(model) { - var view = this.children.findByModel(model); - this.removeChildView(view); - this.checkEmpty(); - }, - - // Override from `Marionette.View` to trigger show on child views - onShowCalled: function() { - this.children.each(_.partial(this._triggerMethodOnChild, 'show')); - }, - - // Render children views. Override this method to - // provide your own implementation of a render function for - // the collection view. - render: function() { - this._ensureViewIsIntact(); - this.triggerMethod('before:render', this); - this._renderChildren(); - this.triggerMethod('render', this); - return this; - }, - - // Render view after sorting. Override this method to - // change how the view renders after a `sort` on the collection. - // An example of this would be to only `renderChildren` in a `CompositeView` - // rather than the full view. - resortView: function() { - this.render(); - }, - - // 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() { - // check for any changes in sort order of views - var orderChanged = this.collection.find(function(item, index){ - var view = this.children.findByModel(item); - return !view || view._index !== index; - }, this); - - if (orderChanged) { - this.resortView(); - } - }, - - // Internal reference to what index a `emptyView` is. - _emptyViewIndex: -1, - - // Internal method. Separated so that CompositeView can have - // more control over events being triggered, around the rendering - // process - _renderChildren: function() { - this.destroyEmptyView(); - this.destroyChildren(); - - if (this.isEmpty(this.collection)) { - this.showEmptyView(); - } else { - this.triggerMethod('before:render:collection', this); - this.startBuffering(); - this.showCollection(); - this.endBuffering(); - this.triggerMethod('render:collection', this); - } - }, - - // Internal method to loop through collection and show each child view. - showCollection: function() { - var ChildView; - this.collection.each(function(child, index) { - ChildView = this.getChildView(child); - this.addChild(child, ChildView, index); - }, this); - }, - - // Internal method to show an empty view in place of - // a collection of child views, when the collection is empty - showEmptyView: function() { - var EmptyView = this.getEmptyView(); - - if (EmptyView && !this._showingEmptyView) { - this.triggerMethod('before:render:empty'); - - this._showingEmptyView = true; - var model = new Backbone.Model(); - this.addEmptyView(model, EmptyView); - - this.triggerMethod('render:empty'); - } - }, - - // 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() { - if (this._showingEmptyView) { - this.triggerMethod('before:remove:empty'); - - this.destroyChildren(); - delete this._showingEmptyView; - - this.triggerMethod('remove:empty'); - } - }, - - // Retrieve the empty view class - getEmptyView: function() { - return this.getOption('emptyView'); - }, - - // Render and show the emptyView. Similar to addChild method - // but "add:child" events are not fired, and the event from - // emptyView are not forwarded - addEmptyView: function(child, EmptyView) { - - // get the emptyViewOptions, falling back to childViewOptions - var emptyViewOptions = this.getOption('emptyViewOptions') || - this.getOption('childViewOptions'); - - if (_.isFunction(emptyViewOptions)){ - emptyViewOptions = emptyViewOptions.call(this, child, this._emptyViewIndex); - } - - // build the empty view - var view = this.buildChildView(child, EmptyView, emptyViewOptions); - - view._parent = this; - - // Proxy emptyView events - this.proxyChildEvents(view); - - // trigger the 'before:show' event on `view` if the collection view - // has already been shown - if (this._isShown) { - Marionette.triggerMethodOn(view, 'before:show'); - } - - // Store the `emptyView` like a `childView` so we can properly - // remove and/or close it later - this.children.add(view); - - // Render it and show it - this.renderChildView(view, this._emptyViewIndex); - - // call the 'show' method if the collection view - // has already been shown - if (this._isShown) { - Marionette.triggerMethodOn(view, 'show'); - } - }, - - // Retrieve the `childView` class, either from `this.options.childView` - // or from the `childView` in the object definition. The "options" - // takes precedence. - // This method receives the model that will be passed to the instance - // created from this `childView`. Overriding methods may use the child - // to determine what `childView` class to return. - getChildView: function(child) { - var childView = this.getOption('childView'); - - if (!childView) { - throw new Marionette.Error({ - name: 'NoChildViewError', - message: 'A "childView" must be specified' - }); - } - - return childView; - }, - - // Render the child's view and add it to the - // HTML for the collection view at a given index. - // This will also update the indices of later views in the collection - // in order to keep the children in sync with the collection. - addChild: function(child, ChildView, index) { - var childViewOptions = this.getOption('childViewOptions'); - childViewOptions = Marionette._getValue(childViewOptions, this, [child, index]); - - var view = this.buildChildView(child, ChildView, childViewOptions); - - // increment indices of views after this one - this._updateIndices(view, true, index); - - this._addChildView(view, index); - - view._parent = this; - - return view; - }, - - // Internal method. This decrements or increments the indices of views after the - // added/removed view to keep in sync with the collection. - _updateIndices: function(view, increment, index) { - if (!this.sort) { - return; - } - - if (increment) { - // assign the index to the view - view._index = index; - } - - // update the indexes of views after this one - this.children.each(function (laterView) { - if (laterView._index >= view._index) { - laterView._index += increment ? 1 : -1; - } - }); - }, - - - // Internal Method. Add the view to children and render it at - // the given index. - _addChildView: function(view, index) { - // set up the child view event forwarding - this.proxyChildEvents(view); - - this.triggerMethod('before:add:child', view); - - // Store the child view itself so we can properly - // remove and/or destroy it later - this.children.add(view); - this.renderChildView(view, index); - - if (this._isShown && !this.isBuffering) { - Marionette.triggerMethodOn(view, 'show'); - } - - this.triggerMethod('add:child', view); - }, - - // render the child view - renderChildView: function(view, index) { - view.render(); - this.attachHtml(this, view, index); - return view; - }, - - // Build a `childView` for a model in the collection. - buildChildView: function(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(view) { - - if (view) { - this.triggerMethod('before:remove:child', view); - // call 'destroy' or 'remove', depending on which is found - if (view.destroy) { view.destroy(); } - else if (view.remove) { view.remove(); } - - delete view._parent; - this.stopListening(view); - this.children.remove(view); - this.triggerMethod('remove:child', view); - - // decrement the index of views after this one - this._updateIndices(view, false); - } - - return view; - }, - - // check if the collection is empty - isEmpty: function() { - return !this.collection || this.collection.length === 0; - }, - - // If empty, show the empty view - checkEmpty: function() { - if (this.isEmpty(this.collection)) { - this.showEmptyView(); - } - }, - - // You might need to override this if you've overridden attachHtml - attachBuffer: function(collectionView, buffer) { - collectionView.$el.append(buffer); - }, - - // Append the HTML to the collection's `el`. - // Override this method to do something other - // than `.append`. - attachHtml: function(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.elBuffer.appendChild(childView.el); - collectionView._bufferedChildren.push(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(childView, index) { - var currentView; - var findPosition = this.sort && (index < this.children.length - 1); - if (findPosition) { - // Find the view after this one - currentView = this.children.find(function (view) { - return view._index === index + 1; - }); - } - - if (currentView) { - currentView.$el.before(childView.el); - return true; - } - - return false; - }, - - // Internal method. Append a view to the end of the $el - _insertAfter: function(childView) { - this.$el.append(childView.el); - }, - - // Internal method to set up the `children` object for - // storing all of the child views - _initChildViewStorage: function() { - this.children = new Backbone.ChildViewContainer(); - }, - - // Handle cleanup and other destroying needs for the collection of views - destroy: function() { - if (this.isDestroyed) { return; } - - this.triggerMethod('before:destroy:collection'); - this.destroyChildren(); - this.triggerMethod('destroy:collection'); - - return Marionette.View.prototype.destroy.apply(this, arguments); - }, - - // Destroy the child views that this collection view - // is holding on to, if any - destroyChildren: function() { - var childViews = this.children.map(_.identity); - this.children.each(this.removeChildView, this); - this.checkEmpty(); - return childViews; - }, - - // Set up the child view event forwarding. Uses a "childview:" - // prefix in front of all forwarded events. - proxyChildEvents: function(view) { - var prefix = this.getOption('childViewEventPrefix'); - - // Forward all child view events through the parent, - // prepending "childview:" to the event name - this.listenTo(view, 'all', function() { - var args = _.toArray(arguments); - var rootEvent = args[0]; - var childEvents = this.normalizeMethods(_.result(this, 'childEvents')); - - args[0] = prefix + ':' + rootEvent; - args.splice(1, 0, view); - - // call collectionView childEvent if defined - if (typeof childEvents !== 'undefined' && _.isFunction(childEvents[rootEvent])) { - childEvents[rootEvent].apply(this, args.slice(1)); - } - - this.triggerMethod.apply(this, args); - }, this); - }, - - _getImmediateChildren: function() { - return _.values(this.children._views); - } - }); - - /* jshint maxstatements: 17, maxlen: 117 */ - - // Composite View - // -------------- - - // Used for rendering a branch-leaf, hierarchical structure. - // Extends directly from CollectionView and also renders an - // a child view as `modelView`, for the top leaf - Marionette.CompositeView = Marionette.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() { - Marionette.CollectionView.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() { - - // Bind only after composite view is rendered to avoid adding child views - // to nonexistent childViewContainer - - if (this.collection) { - this.listenTo(this.collection, 'add', this._onCollectionAdd); - this.listenTo(this.collection, 'remove', this._onCollectionRemove); - this.listenTo(this.collection, 'reset', this._renderChildren); - - if (this.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 - getChildView: function(child) { - var childView = this.getOption('childView') || this.constructor; - - return childView; - }, - - // Serialize the model for the view. - // You can override the `serializeData` method in your own view - // definition, to provide custom serialization for your view's data. - serializeData: function() { - var data = {}; - - if (this.model){ - data = _.partial(this.serializeModel, this.model).apply(this, arguments); - } - - return data; - }, - - // Renders the model and the collection. - render: function() { - this._ensureViewIsIntact(); - this.isRendered = true; - this.resetChildViewContainer(); - - this.triggerMethod('before:render', this); - - this._renderTemplate(); - this._renderChildren(); - - this.triggerMethod('render', this); - return this; - }, - - _renderChildren: function() { - if (this.isRendered) { - Marionette.CollectionView.prototype._renderChildren.call(this); - } - }, - - // Render the root template that the children - // views are appended to - _renderTemplate: function() { - var data = {}; - data = this.serializeData(); - data = this.mixinTemplateHelpers(data); - - this.triggerMethod('before:render:template'); - - var template = this.getTemplate(); - var html = Marionette.Renderer.render(template, data, this); - this.attachElContent(html); - - // the ui bindings is done here and not at the end of render since they - // will not be available until after the model is rendered, but should be - // available before the collection is rendered. - this.bindUIElements(); - this.triggerMethod('render:template'); - }, - - // Attaches the content of the root. - // 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: function(html) { - // this.el.innerHTML = html; - // return this; - // } - // ``` - attachElContent: function(html) { - this.$el.html(html); - - return this; - }, - - // You might need to override this if you've overridden attachHtml - attachBuffer: function(compositeView, buffer) { - var $container = this.getChildViewContainer(compositeView); - $container.append(buffer); - }, - - // Internal method. Append a view to the end of the $el. - // Overidden from CollectionView to ensure view is appended to - // childViewContainer - _insertAfter: function (childView) { - var $container = this.getChildViewContainer(this, childView); - $container.append(childView.el); - }, - - // Internal method to ensure an `$childViewContainer` exists, for the - // `attachHtml` method to use. - getChildViewContainer: function(containerView, childView) { - if ('$childViewContainer' in containerView) { - return containerView.$childViewContainer; - } - - var container; - var childViewContainer = Marionette.getOption(containerView, 'childViewContainer'); - if (childViewContainer) { - - var selector = Marionette._getValue(childViewContainer, containerView); - - if (selector.charAt(0) === '@' && containerView.ui) { - container = containerView.ui[selector.substr(4)]; - } else { - container = containerView.$(selector); - } - - if (container.length <= 0) { - throw new Marionette.Error({ - name: 'ChildViewContainerMissingError', - message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer - }); - } - - } else { - container = containerView.$el; - } - - containerView.$childViewContainer = container; - return container; - }, - - // Internal method to reset the `$childViewContainer` on render - resetChildViewContainer: function() { - if (this.$childViewContainer) { - delete this.$childViewContainer; - } - } - }); - - // Layout View - // ----------- - - // Used for managing application layoutViews, nested layoutViews and - // multiple regions within an application or sub-application. - // - // A specialized view class that renders an area of HTML and then - // attaches `Region` instances to the specified `regions`. - // Used for composite view management and sub-application areas. - Marionette.LayoutView = Marionette.ItemView.extend({ - regionClass: Marionette.Region, - - // Ensure the regions are available when the `initialize` method - // is called. - constructor: function(options) { - options = options || {}; - - this._firstRender = true; - this._initializeRegions(options); - - Marionette.ItemView.call(this, options); - }, - - // LayoutView's render will use the existing region objects the - // first time it is called. Subsequent calls will destroy the - // views that the regions are showing and then reset the `el` - // for the regions to the newly rendered DOM elements. - render: function() { - this._ensureViewIsIntact(); - - if (this._firstRender) { - // if this is the first render, don't do anything to - // reset the regions - this._firstRender = false; - } else { - // If this is not the first render call, then we need to - // re-initialize the `el` for each region - this._reInitializeRegions(); - } - - return Marionette.ItemView.prototype.render.apply(this, arguments); - }, - - // Handle destroying regions, and then destroy the view itself. - destroy: function() { - if (this.isDestroyed) { return this; } - - this.regionManager.destroy(); - return Marionette.ItemView.prototype.destroy.apply(this, arguments); - }, - - // Add a single region, by name, to the layoutView - addRegion: function(name, definition) { - var regions = {}; - regions[name] = definition; - return this._buildRegions(regions)[name]; - }, - - // Add multiple regions as a {name: definition, name2: def2} object literal - addRegions: function(regions) { - this.regions = _.extend({}, this.regions, regions); - return this._buildRegions(regions); - }, - - // Remove a single region from the LayoutView, by name - removeRegion: function(name) { - delete this.regions[name]; - return this.regionManager.removeRegion(name); - }, - - // Provides alternative access to regions - // Accepts the region name - // getRegion('main') - getRegion: function(region) { - return this.regionManager.get(region); - }, - - // Get all regions - getRegions: function(){ - return this.regionManager.getRegions(); - }, - - // internal method to build regions - _buildRegions: function(regions) { - var defaults = { - regionClass: this.getOption('regionClass'), - parentEl: _.partial(_.result, this, 'el') - }; - - return this.regionManager.addRegions(regions, defaults); - }, - - // Internal method to initialize the regions that have been defined in a - // `regions` attribute on this layoutView. - _initializeRegions: function(options) { - var regions; - this._initRegionManager(); - - regions = Marionette._getValue(this.regions, this, [options]) || {}; - - // Enable users to define `regions` as instance options. - var regionOptions = this.getOption.call(options, 'regions'); - - // enable region options to be a function - regionOptions = Marionette._getValue(regionOptions, this, [options]); - - _.extend(regions, regionOptions); - - // Normalize region selectors hash to allow - // a user to use the @ui. syntax. - regions = this.normalizeUIValues(regions); - - this.addRegions(regions); - }, - - // Internal method to re-initialize all of the regions by updating the `el` that - // they point to - _reInitializeRegions: function() { - this.regionManager.invoke('reset'); - }, - - // Enable easy overriding of the default `RegionManager` - // for customized region interactions and business specific - // view logic for better control over single regions. - getRegionManager: function() { - return new Marionette.RegionManager(); - }, - - // Internal method to initialize the region manager - // and all regions in it - _initRegionManager: function() { - this.regionManager = this.getRegionManager(); - this.regionManager._parent = this; - - this.listenTo(this.regionManager, 'before:add:region', function(name) { - this.triggerMethod('before:add:region', name); - }); - - this.listenTo(this.regionManager, 'add:region', function(name, region) { - this[name] = region; - this.triggerMethod('add:region', name, region); - }); - - this.listenTo(this.regionManager, 'before:remove:region', function(name) { - this.triggerMethod('before:remove:region', name); - }); - - this.listenTo(this.regionManager, 'remove:region', function(name, region) { - delete this[name]; - this.triggerMethod('remove:region', name, region); - }); - }, - - _getImmediateChildren: function() { - return _.chain(this.regionManager.getRegions()) - .pluck('currentView') - .compact() - .value(); - } - }); - - - // Behavior - // -------- - - // 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. - - Marionette.Behavior = Marionette.Object.extend({ - constructor: function(options, view) { - // Setup reference to the view. - // this comes in handle when a behavior - // wants to directly talk up the chain - // to the view. - this.view = view; - this.defaults = _.result(this, 'defaults') || {}; - this.options = _.extend({}, this.defaults, options); - - Marionette.Object.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() { - this.stopListening(); - }, - - proxyViewProperties: function (view) { - this.$el = view.$el; - this.el = view.el; - } - }); - - /* jshint maxlen: 143 */ - // Behaviors - // --------- - - // Behaviors is a utility class that takes care of - // gluing your behavior instances to their given View. - // The most important part of this class is that you - // **MUST** override the class level behaviorsLookup - // method for things to work properly. - - Marionette.Behaviors = (function(Marionette, _) { - // Borrow event splitter from Backbone - var delegateEventSplitter = /^(\S+)\s*(.*)$/; - - function Behaviors(view, behaviors) { - - if (!_.isObject(view.behaviors)) { - return {}; - } - - // Behaviors defined on a view can be a flat object literal - // or it can be a function that returns an object. - behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors')); - - // Wraps several of the view's methods - // calling the methods first on each behavior - // and then eventually calling the method on the view. - Behaviors.wrap(view, behaviors, _.keys(methods)); - return behaviors; - } - - var methods = { - behaviorTriggers: function(behaviorTriggers, behaviors) { - var triggerBuilder = new BehaviorTriggersBuilder(this, behaviors); - return triggerBuilder.buildBehaviorTriggers(); - }, - - behaviorEvents: function(behaviorEvents, behaviors) { - var _behaviorsEvents = {}; - var viewUI = this._uiBindings || _.result(this, 'ui'); - - _.each(behaviors, function(b, i) { - var _events = {}; - var behaviorEvents = _.clone(_.result(b, 'events')) || {}; - var behaviorUI = b._uiBindings || _.result(b, 'ui'); - - // Construct an internal UI hash first using - // the views UI hash and then the behaviors 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. - var ui = _.extend({}, viewUI, behaviorUI); - - // Normalize behavior events hash to allow - // a user to use the @ui. syntax. - behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, ui); - - var j = 0; - _.each(behaviorEvents, function(behaviour, key) { - var match = key.match(delegateEventSplitter); - - // Set event name to be namespaced using the view cid, - // the behavior index, and the behavior event index - // to generate a non colliding event namespace - // http://api.jquery.com/event.namespace/ - var eventName = match[1] + '.' + [this.cid, i, j++, ' '].join(''), - selector = match[2]; - - var eventKey = eventName + selector; - var handler = _.isFunction(behaviour) ? behaviour : b[behaviour]; - - _events[eventKey] = _.bind(handler, b); - }, this); - - _behaviorsEvents = _.extend(_behaviorsEvents, _events); - }, this); - - return _behaviorsEvents; - } - }; - - _.extend(Behaviors, { - - // 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 - // } - // ``` - behaviorsLookup: function() { - throw new Marionette.Error({ - message: 'You must define where your behaviors are stored.', - url: 'marionette.behaviors.html#behaviorslookup' - }); - }, - - // Takes care of getting the behavior class - // given options and a key. - // If a user passes in options.behaviorClass - // default to using that. Otherwise delegate - // the lookup to the users `behaviorsLookup` implementation. - getBehaviorClass: function(options, key) { - if (options.behaviorClass) { - return options.behaviorClass; - } - - // Get behavior class can be either a flat object or a method - return Marionette._getValue(Behaviors.behaviorsLookup, this, [options, key])[key]; - }, - - // Iterate over the behaviors object, for each behavior - // instantiate it and get its grouped behaviors. - parseBehaviors: function(view, behaviors) { - return _.chain(behaviors).map(function(options, key) { - var BehaviorClass = Behaviors.getBehaviorClass(options, key); - - var behavior = new BehaviorClass(options, view); - var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors')); - - return [behavior].concat(nestedBehaviors); - }).flatten().value(); - }, - - // Wrap view internal methods so that they delegate to behaviors. For example, - // `onDestroy` should trigger destroy on all of the behaviors and then destroy itself. - // i.e. - // - // `view.delegateEvents = _.partial(methods.delegateEvents, view.delegateEvents, behaviors);` - wrap: function(view, behaviors, methodNames) { - _.each(methodNames, function(methodName) { - view[methodName] = _.partial(methods[methodName], view[methodName], behaviors); - }); - } - }); - - // Class to build handlers for `triggers` on behaviors - // for views - function BehaviorTriggersBuilder(view, behaviors) { - this._view = view; - this._viewUI = _.result(view, 'ui'); - this._behaviors = behaviors; - this._triggers = {}; - } - - _.extend(BehaviorTriggersBuilder.prototype, { - // Main method to build the triggers hash with event keys and handlers - buildBehaviorTriggers: function() { - _.each(this._behaviors, this._buildTriggerHandlersForBehavior, this); - return this._triggers; - }, - - // Internal method to build all trigger handlers for a given behavior - _buildTriggerHandlersForBehavior: function(behavior, i) { - var ui = _.extend({}, this._viewUI, _.result(behavior, 'ui')); - var triggersHash = _.clone(_.result(behavior, 'triggers')) || {}; - - triggersHash = Marionette.normalizeUIKeys(triggersHash, ui); - - _.each(triggersHash, _.bind(this._setHandlerForBehavior, this, behavior, i)); - }, - - // Internal method to create and assign the trigger handler for a given - // behavior - _setHandlerForBehavior: function(behavior, i, eventName, trigger) { - // Unique identifier for the `this._triggers` hash - var triggerKey = trigger.replace(/^\S+/, function(triggerName) { - return triggerName + '.' + 'behaviortriggers' + i; - }); - - this._triggers[triggerKey] = this._view._buildViewTrigger(eventName); - } - }); - - return Behaviors; - - })(Marionette, _); - - - // App Router - // ---------- - - // Reduce the boilerplate code of handling route events - // and then calling a single method on another object. - // Have your routers configured to call the method on - // your object, directly. - // - // Configure an AppRouter with `appRoutes`. - // - // App routers can only take one `controller` object. - // It is recommended that you divide your controller - // objects in to smaller pieces of related functionality - // and have multiple routers / controllers, instead of - // just one giant router and controller. - // - // You can also add standard routes to an AppRouter. - - Marionette.AppRouter = Backbone.Router.extend({ - - constructor: function(options) { - this.options = options || {}; - - Backbone.Router.apply(this, arguments); - - var appRoutes = this.getOption('appRoutes'); - var controller = this._getController(); - this.processAppRoutes(controller, appRoutes); - this.on('route', this._processOnRoute, this); - }, - - // Similar to route method on a Backbone Router but - // method is called on the controller - appRoute: function(route, methodName) { - var controller = this._getController(); - this._addAppRoute(controller, route, methodName); - }, - - // process the route event and trigger the onRoute - // method call, if it exists - _processOnRoute: function(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.getOption('appRoutes'))[routeName]; - this.onRoute(routeName, routePath, routeArgs); - } - }, - - // Internal method to proces
<TRUNCATED>