From c1d41d73c60ad117b285fd7495b4c1801ca1873d Mon Sep 17 00:00:00 2001 From: Artem Denysov Date: Thu, 13 Oct 2016 18:41:11 +0300 Subject: [PATCH] Bump and build 3.1.0 (#3231) * Bump and build 3.1.0 * fix build --- bower.json | 2 +- gulp/build.js | 4 +- lib/backbone.marionette.js | 748 ++++++++++++++++------------- lib/backbone.marionette.js.map | 2 +- lib/backbone.marionette.min.js | 6 +- lib/backbone.marionette.min.js.map | 2 +- package.json | 7 +- 7 files changed, 431 insertions(+), 340 deletions(-) diff --git a/bower.json b/bower.json index 7748c7408c..07f39ac313 100644 --- a/bower.json +++ b/bower.json @@ -3,7 +3,7 @@ "description": "The Backbone Framework", "homepage": "http://marionettejs.org", "main": "./lib/backbone.marionette.js", - "version": "3.0.0", + "version": "3.1.0", "keywords": [ "backbone", "framework", diff --git a/gulp/build.js b/gulp/build.js index 34f7fe4111..de2c11a5bb 100644 --- a/gulp/build.js +++ b/gulp/build.js @@ -10,7 +10,7 @@ import runSequence from 'run-sequence'; import { rollup } from 'rollup'; import babel from 'rollup-plugin-babel'; import json from 'rollup-plugin-json'; -import preset from 'babel-preset-es2015-rollup'; +import preset from 'babel-preset-es2015'; import banner from './_banner'; import {name} from '../package.json'; @@ -40,7 +40,7 @@ function bundle(opts) { json(), babel({ sourceMaps: true, - presets: [ preset ], + presets: [['es2015', {modules: false}]], babelrc: false }) ] diff --git a/lib/backbone.marionette.js b/lib/backbone.marionette.js index 0979c94968..3f9a9bfd47 100644 --- a/lib/backbone.marionette.js +++ b/lib/backbone.marionette.js @@ -1,6 +1,6 @@ // MarionetteJS (Backbone.Marionette) // ---------------------------------- -// v3.0.0 +// v3.1.0 // // Copyright (c)2016 Derick Bailey, Muted Solutions, LLC. // Distributed under MIT license @@ -18,7 +18,7 @@ _ = 'default' in _ ? _['default'] : _; Radio = 'default' in Radio ? Radio['default'] : Radio; - var version = "3.0.0"; + var version = "3.1.0"; //Internal utility for creating context style global utils var proxy = function proxy(method) { @@ -63,10 +63,18 @@ // Merge `keys` from `options` onto `this` var mergeOptions = function mergeOptions(options, keys) { + var _this = this; + if (!options) { return; } - _.extend(this, _.pick(options, keys)); + + _.each(keys, function (key) { + var option = options[key]; + if (option !== undefined) { + _this[key] = option; + } + }); }; // Marionette.getOption @@ -113,6 +121,10 @@ 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 @@ -121,24 +133,23 @@ // `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 = 'on' + event.replace(splitter, getEventName); + var methodName = getOnMethodName(event); var method = getOption.call(this, methodName); var result = void 0; // call the onMethodName if it exists - - for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; - } - if (_.isFunction(method)) { // pass all args, except the event name result = method.apply(this, args); } // trigger the event - this.trigger.apply(this, [event].concat(args)); + this.trigger.apply(this, arguments); return result; } @@ -148,13 +159,15 @@ // e.g. `Marionette.triggerMethodOn(view, 'show')` // will trigger a "show" event or invoke onShow the view. function triggerMethodOn(context) { - var fnc = _.isFunction(context.triggerMethod) ? context.triggerMethod : triggerMethod; - for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } - return fnc.apply(context, args); + if (_.isFunction(context.triggerMethod)) { + return context.triggerMethod.apply(context, args); + } + + return triggerMethod.apply(context, args); } // Trigger method on children unless a pure Backbone.View @@ -194,41 +207,41 @@ return true; } - // 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; + function triggerDOMRefresh(view) { + if (view._isAttached && view._isRendered) { + triggerMethodOn(view, 'dom:refresh', view); } + } - view._areViewEventsMonitored = true; + function handleBeforeAttach() { + triggerMethodChildren(this, 'before:attach', shouldTriggerAttach); + } - function handleBeforeAttach() { - triggerMethodChildren(view, 'before:attach', shouldTriggerAttach); - } + function handleAttach() { + triggerMethodChildren(this, 'attach', shouldAttach); + triggerDOMRefresh(this); + } - function handleAttach() { - triggerMethodChildren(view, 'attach', shouldAttach); - triggerDOMRefresh(); - } + function handleBeforeDetach() { + triggerMethodChildren(this, 'before:detach', shouldTriggerDetach); + } - function handleBeforeDetach() { - triggerMethodChildren(view, 'before:detach', shouldTriggerDetach); - } + function handleDetach() { + triggerMethodChildren(this, 'detach', shouldDetach); + } - function handleDetach() { - triggerMethodChildren(view, 'detach', shouldDetach); - } + function handleRender() { + triggerDOMRefresh(this); + } - function handleRender() { - triggerDOMRefresh(); + // 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; } - function triggerDOMRefresh() { - if (view._isAttached && view._isRendered) { - triggerMethodOn(view, 'dom:refresh', view); - } - } + view._areViewEventsMonitored = true; view.on({ 'before:attach': handleBeforeAttach, @@ -591,15 +604,7 @@ var _invoke = _.invokeMap || _.invoke; - var toConsumableArray = function (arr) { - if (Array.isArray(arr)) { - for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; - - return arr2; - } else { - return Array.from(arr); - } - }; + function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } // MixinOptions // - behaviors @@ -651,11 +656,11 @@ }, _getBehaviorTriggers: function _getBehaviorTriggers() { var triggers = _invoke(this._behaviors, 'getTriggers'); - return _.extend.apply(_, [{}].concat(toConsumableArray(triggers))); + return _.extend.apply(_, [{}].concat(_toConsumableArray(triggers))); }, _getBehaviorEvents: function _getBehaviorEvents() { var events = _invoke(this._behaviors, 'getEvents'); - return _.extend.apply(_, [{}].concat(toConsumableArray(events))); + return _.extend.apply(_, [{}].concat(_toConsumableArray(events))); }, @@ -680,7 +685,7 @@ // destroying the view. // This unbinds event listeners // that behaviors have registered for. - _invoke.apply(undefined, [this._behaviors, 'destroy'].concat(toConsumableArray(args))); + _invoke.apply(undefined, [this._behaviors, 'destroy'].concat(_toConsumableArray(args))); }, _bindBehaviorUIElements: function _bindBehaviorUIElements() { _invoke(this._behaviors, 'bindUIElements'); @@ -691,13 +696,8 @@ _triggerEventOnBehaviors: function _triggerEventOnBehaviors() { var behaviors = this._behaviors; // Use good ol' for as this is a very hot function - - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - for (var i = 0, length = behaviors && behaviors.length; i < length; i++) { - triggerMethod.apply(behaviors[i], args); + triggerMethod.apply(behaviors[i], arguments); } } }; @@ -786,7 +786,7 @@ // 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); + var normalizedKey = _normalizeUIString(key, ui); memo[normalizedKey] = val; return memo; }, {}); @@ -794,7 +794,7 @@ // utility method for parsing @ui. syntax strings // into associated selector - var normalizeUIString = function normalizeUIString(uiString, ui) { + var _normalizeUIString = function _normalizeUIString(uiString, ui) { return uiString.replace(/@ui\.[a-zA-Z-_$0-9]*/g, function (r) { return ui[r.slice(4)]; }); @@ -806,14 +806,14 @@ var _normalizeUIValues = function _normalizeUIValues(hash, ui, properties) { _.each(hash, function (val, key) { if (_.isString(val)) { - hash[key] = normalizeUIString(val, ui); + 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); + val[property] = _normalizeUIString(propertyVal, ui); } }); } @@ -831,6 +831,14 @@ }, + // 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) { @@ -930,23 +938,6 @@ }, - // 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); - } - - return this; - }, - - // Overriding Backbone.View's `delegateEvents` to handle // `events` and `triggers` delegateEvents: function delegateEvents(eventsArg) { @@ -1095,14 +1086,10 @@ // import the `triggerMethod` to trigger events with corresponding // methods if the method exists triggerMethod: function triggerMethod$$() { - for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; - } + var ret = triggerMethod.apply(this, arguments); - var ret = triggerMethod.apply(this, args); - - this._triggerEventOnBehaviors.apply(this, args); - this._triggerEventOnParentLayout.apply(this, args); + this._triggerEventOnBehaviors.apply(this, arguments); + this._triggerEventOnParentLayout.apply(this, arguments); return ret; }, @@ -1113,49 +1100,55 @@ this._childViewEvents = _.result(this, 'childViewEvents'); this._childViewTriggers = _.result(this, 'childViewTriggers'); }, - _triggerEventOnParentLayout: function _triggerEventOnParentLayout(eventName) { + _triggerEventOnParentLayout: function _triggerEventOnParentLayout() { var layoutView = this._parentView(); if (!layoutView) { return; } - // invoke triggerMethod on parent view - var eventPrefix = _.result(layoutView, 'childViewEventPrefix'); - var prefixedEventName = eventPrefix + ':' + eventName; + layoutView._childViewEventHandler.apply(layoutView, arguments); + }, + + + // Walk the _parent tree until we find a view (if one exists). + // Returns the parent view hierarchically closest to this view. + _parentView: function _parentView() { + var parent = this._parent; - for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { - args[_key3 - 1] = arguments[_key3]; + while (parent) { + if (parent instanceof View) { + return parent; + } + parent = parent._parent; } + }, + _childViewEventHandler: function _childViewEventHandler(eventName) { + var childViewEvents = this.normalizeMethods(this._childViewEvents); - layoutView.triggerMethod.apply(layoutView, [prefixedEventName].concat(args)); + // call collectionView childViewEvent if defined - // use the parent view's childViewEvents handler - var childViewEvents = layoutView.normalizeMethods(layoutView._childViewEvents); + for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } - if (!!childViewEvents && _.isFunction(childViewEvents[eventName])) { - childViewEvents[eventName].apply(layoutView, args); + if (typeof childViewEvents !== 'undefined' && _.isFunction(childViewEvents[eventName])) { + childViewEvents[eventName].apply(this, args); } // use the parent view's proxyEvent handlers - var childViewTriggers = layoutView._childViewTriggers; + var childViewTriggers = this._childViewTriggers; // Call the event with the proxy name on the parent layout if (childViewTriggers && _.isString(childViewTriggers[eventName])) { - layoutView.triggerMethod.apply(layoutView, [childViewTriggers[eventName]].concat(args)); + this.triggerMethod.apply(this, [childViewTriggers[eventName]].concat(args)); } - }, + var prefix = _.result(this, 'childViewEventPrefix'); - // Walk the _parent tree until we find a view (if one exists). - // Returns the parent view hierarchically closest to this view. - _parentView: function _parentView() { - var parent = this._parent; + if (prefix !== false) { + var childEventName = prefix + ':' + eventName; - while (parent) { - if (parent instanceof View) { - return parent; - } - parent = parent._parent; + this.triggerMethod.apply(this, [childEventName].concat(args)); } } }; @@ -1238,7 +1231,7 @@ // 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); + view.on('destroy', this._empty, this); // Make this region the view's parent. // It's important that this parent binding happens before rendering so that any events @@ -1269,7 +1262,7 @@ } }, _attachView: function _attachView(view) { - var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + 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; @@ -1278,7 +1271,11 @@ triggerMethodOn(view, 'before:attach', view); } - this.attachHtml(view, shouldReplaceEl); + if (shouldReplaceEl) { + this._replaceEl(view); + } else { + this.attachHtml(view); + } if (shouldTriggerAttach) { view._isAttached = true; @@ -1288,7 +1285,7 @@ this.currentView = view; }, _ensureElement: function _ensureElement() { - var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (!_.isObject(this.el)) { this.$el = this.getEl(this.el); @@ -1371,20 +1368,15 @@ // Override this method to change how the new view is appended to the `$el` that the // region is managing - attachHtml: function attachHtml(view, shouldReplace) { - if (shouldReplace) { - // replace the region's node with the view's node - this._replaceEl(view); - } else { - this.el.appendChild(view.el); - } + attachHtml: function attachHtml(view) { + this.el.appendChild(view.el); }, // Destroy the current view, if there is one. If there is no current view, it does // nothing and returns immediately. empty: function empty() { - var options = arguments.length <= 0 || arguments[0] === undefined ? { allowMissingEl: true } : arguments[0]; + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { allowMissingEl: true }; var view = this.currentView; @@ -1396,7 +1388,17 @@ return this; } - view.off('destroy', this.empty, 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(); @@ -1404,21 +1406,14 @@ delete this.currentView; if (!view._isDestroyed) { - this._removeView(view, options); + this._removeView(view, shouldDestroy); delete view._parent; } this.triggerMethod('empty', this, view); - return this; }, - _removeView: function _removeView(view) { - var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - - var preventDestroy = _ref.preventDestroy; - - var shouldPreventDestroy = !!preventDestroy; - - if (shouldPreventDestroy) { + _removeView: function _removeView(view, shouldDestroy) { + if (!shouldDestroy) { this._detachView(view); return; } @@ -1429,6 +1424,17 @@ destroyBackboneView(view); } }, + detachView: function detachView() { + var view = this.currentView; + + if (!view) { + return; + } + + this._empty(view); + + return view; + }, _detachView: function _detachView(view) { var shouldTriggerDetach = !!view._isAttached; if (shouldTriggerDetach) { @@ -1476,6 +1482,54 @@ } }); + // return the region instance from the definition + function buildRegion (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 @@ -1532,60 +1586,17 @@ _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] = _this._buildRegion(definition); + regions[name] = buildRegion(definition, defaults); _this._addRegion(regions[name], name); return regions; }, {}); }, - - - // return the region instance from the definition - _buildRegion: function _buildRegion(definition) { - if (definition instanceof Region) { - return definition; - } - - return this._buildRegionFromDefinition(definition); - }, - _buildRegionFromDefinition: function _buildRegionFromDefinition(definition) { - if (_.isString(definition)) { - return this._buildRegionFromObject({ el: definition }); - } - - if (_.isFunction(definition)) { - return this._buildRegionFromRegionClass(definition); - } - - if (_.isObject(definition)) { - return this._buildRegionFromObject(definition); - } - - throw new MarionetteError({ - message: 'Improper region configuration type.', - url: 'marionette.region.html#region-configuration-types' - }); - }, - _buildRegionFromObject: function _buildRegionFromObject(definition) { - var RegionClass = definition.regionClass || this.regionClass; - - var options = _.omit(definition, 'regionClass'); - - _.defaults(options, { - el: definition.selector, - parentEl: _.partial(_.result, this, 'el') - }); - - return new RegionClass(options); - }, - - - // Build the region directly from a given `RegionClass` - _buildRegionFromRegionClass: function _buildRegionFromRegionClass(RegionClass) { - return new RegionClass({ - parentEl: _.partial(_.result, this, 'el') - }); - }, _addRegion: function _addRegion(region, name) { this.triggerMethod('before:add:region', this, name, region); @@ -1618,8 +1629,7 @@ _removeRegion: function _removeRegion(region, name) { this.triggerMethod('before:remove:region', this, name, region); - region.empty(); - region.stopListening(); + region.destroy(); delete this.regions[name]; delete this._regions[name]; @@ -1666,6 +1676,9 @@ 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; } @@ -1762,6 +1775,27 @@ }, + // 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, @@ -1824,7 +1858,7 @@ // 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 target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var templateContext = _.result(this, 'templateContext'); return _.extend(target, templateContext); @@ -2070,7 +2104,7 @@ _initialEvents: function _initialEvents() { if (this.collection) { this.listenTo(this.collection, 'add', this._onCollectionAdd); - this.listenTo(this.collection, 'remove', this._onCollectionRemove); + this.listenTo(this.collection, 'update', this._onCollectionUpdate); this.listenTo(this.collection, 'reset', this.render); if (this.sort) { @@ -2092,17 +2126,109 @@ if (this._shouldAddChild(child, index)) { this._destroyEmptyView(); - var ChildView = this._getChildView(child); - this._addChild(child, ChildView, index); + 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) { + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + var checkEmpty = _ref.checkEmpty; + + var shouldCheckEmpty = checkEmpty !== false; + + // 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 (shouldCheckEmpty) { + this._checkEmpty(); + } + }, + + + // 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 = _this.children.findByModel(model); + + if (!view || view._isDestroyed) { + return removingViews; + } + + _this._removeChildView(view); + + removingViews.push(view); + + return removingViews; + }, []); + }, + _findGreatestIndexedView: function _findGreatestIndexedView(views) { + + return _.reduce(views, function (greatestIndexedView, view) { + // Even if the index is `undefined`, a view will get returned. + if (!greatestIndexedView || greatestIndexedView._index < view._index) { + return view; + } + + return greatestIndexedView; + }, undefined); + }, + _removeChildView: function _removeChildView(view) { + this.triggerMethod('before:remove:child', this, view); + + this.children._remove(view); + if (view.destroy) { + view.destroy(); + } else { + destroyBackboneView(view); } + + delete view._parent; + this.stopListening(view); + this.triggerMethod('remove:child', this, view); }, - // get the child view by model it holds, and remove it - _onCollectionRemove: function _onCollectionRemove(model) { - var view = this.children.findByModel(model); - this.removeChildView(view); - this._checkEmpty(); + // 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; }, @@ -2121,9 +2247,9 @@ // An efficient rendering used for filtering. Instead of modifying the whole DOM for the // collection view, we are only adding or removing the related childrenViews. setFilter: function setFilter(filter) { - var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var preventRender = _ref.preventRender; + var preventRender = _ref2.preventRender; var canBeRendered = this._isRendered && !this._isDestroyed; var filterChanged = this.filter !== filter; @@ -2150,22 +2276,22 @@ // Calculate and apply difference by cid between `models` and `previousModels`. _applyModelDeltas: function _applyModelDeltas(models, previousModels) { - var _this = this; + var _this2 = this; var currentIds = {}; _.each(models, function (model, index) { - var addedChildNotExists = !_this.children.findByModel(model); + var addedChildNotExists = !_this2.children.findByModel(model); if (addedChildNotExists) { - _this._onCollectionAdd(model, _this.collection, { at: index }); + _this2._onCollectionAdd(model, _this2.collection, { at: index }); } currentIds[model.cid] = true; }); - _.each(previousModels, function (prevModel) { - var removedChildExists = !currentIds[prevModel.cid] && _this.children.findByModel(prevModel); - if (removedChildExists) { - _this._onCollectionRemove(prevModel); - } + + var removeModels = _.filter(previousModels, function (prevModel) { + return !currentIds[prevModel.cid] && _this2.children.findByModel(prevModel); }); + + this._removeChildModels(removeModels); }, @@ -2173,7 +2299,7 @@ // you can pass reorderOnSort: true to only reorder the DOM after a sort instead of // rendering all the collectionView. reorder: function reorder() { - var _this2 = this; + var _this3 = this; var children = this.children; var models = this._filteredSortedModels(); @@ -2192,29 +2318,36 @@ this.render(); } else { (function () { - // Get the DOM nodes in the same order as the models. - var elsToReorder = _.map(models, function (model, index) { - var view = children.findByModel(model); + + 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 = children.reduce(function (viewEls, view) { + var index = _.indexOf(models, view.model); + + if (index === -1) { + filteredOutModels.push(view.model); + return viewEls; + } + view._index = index; - return view.el; - }); - // Find the views that were children before but aren't in this new ordering. - var filteredOutViews = children.filter(function (view) { - return !_.contains(elsToReorder, view.el); - }); + viewEls[index] = view.el; - _this2.triggerMethod('before:reorder', _this2); + return viewEls; + }, new Array(models.length)); + + _this3.triggerMethod('before:reorder', _this3); // Since append moves elements that are already in the DOM, appending the elements // will effectively reorder them. - _this2._appendReorderedChildren(elsToReorder); + _this3._appendReorderedChildren(elsToReorder); // remove any views that have been filtered out - _.each(filteredOutViews, _.bind(_this2.removeChildView, _this2)); - _this2._checkEmpty(); + _this3._removeChildModels(filteredOutModels); - _this2.triggerMethod('reorder', _this2); + _this3.triggerMethod('reorder', _this3); })(); } return this; @@ -2236,13 +2369,13 @@ // 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 _this3 = this; + 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 = _this3.children.findByModel(item); + var view = _this4.children.findByModel(item); return !view || view._index !== index; }); @@ -2281,16 +2414,30 @@ 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) { + view._parent = this; + + monitorViewEvents(view); + + // set up the child view event forwarding + this._proxyChildEvents(view); + + if (this.sort) { + view._index = index; + } + }, // Internal method to loop through collection and show each child view. _showCollection: function _showCollection(models) { - var _this4 = this; - - _.each(models, function (child, index) { - var ChildView = _this4._getChildView(child); - _this4._addChild(child, ChildView, index); - }); + _.each(models, _.bind(this._addChild, this)); + this.children._updateLength(); }, @@ -2368,10 +2515,8 @@ var view = this.buildChildView(model, EmptyView, emptyViewOptions); this.triggerMethod('before:render:empty', this, view); - this._addChildView(view, 0); + this.addChildView(view, 0); this.triggerMethod('render:empty', this, view); - - view._parent = this; } }, @@ -2441,11 +2586,8 @@ // Internal method for building and adding a child view - _addChild: function _addChild(child, ChildView, index) { - var childViewOptions = this._getChildViewOptions(child, index); - - var view = this.buildChildView(child, ChildView, childViewOptions); - + _addChild: function _addChild(child, index) { + var view = this._createView(child, index); this.addChildView(view, index); return view; @@ -2464,13 +2606,21 @@ // children in sync with the collection. addChildView: function addChildView(view, index) { this.triggerMethod('before:add:child', this, view); + this._setupChildView(view, index); - // increment indices of views after this one - this._updateIndices(view, true, 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); + } - view._parent = this; + this._renderView(view); - this._addChildView(view, index); + this._attachView(view, index); this.triggerMethod('add:child', this, view); @@ -2480,15 +2630,12 @@ // Internal method. This decrements or increments the indices of views after the added/removed // view to keep in sync with the collection. - _updateIndices: function _updateIndices(view, increment, index) { + _updateIndices: function _updateIndices(views, increment) { if (!this.sort) { return; } - if (increment) { - // assign the index to the view - view._index = index; - } + var view = _.isArray(views) ? this._findGreatestIndexedView(views) : views; // update the indexes of views after this one this.children.each(function (laterView) { @@ -2497,39 +2644,31 @@ } }); }, - - - // Internal Method. Add the view to children and render it at the given index. - _addChildView: function _addChildView(view, index) { - // Only trigger attach if already attached and not buffering, - // otherwise _endBuffering() or Region#show() handles this. - var shouldTriggerAttach = !this._isBuffering && this._isAttached; - - monitorViewEvents(view); - - // set up the child view event forwarding - this._proxyChildEvents(view); - - // Store the child view itself so we can properly remove and/or destroy it later - this.children.add(view); + _renderView: function _renderView(view) { + if (view._isRendered) { + return; + } if (!view.supportsRenderLifecycle) { triggerMethodOn(view, 'before:render', view); } - // Render view view.render(); if (!view.supportsRenderLifecycle) { view._isRendered = true; triggerMethodOn(view, 'render', view); } + }, + _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); } - // Attach view this.attachHtml(this, view, index); if (shouldTriggerAttach) { @@ -2553,22 +2692,10 @@ return view; } - this.triggerMethod('before:remove:child', this, view); - - if (view.destroy) { - view.destroy(); - } else { - destroyBackboneView(view); - } - - delete view._parent; - this.stopListening(view); - this.children.remove(view); - this.triggerMethod('remove:child', this, view); - + this._removeChildView(view); + this.children._updateLength(); // decrement the index of views after this one this._updateIndices(view, false); - return view; }, @@ -2668,23 +2795,15 @@ // Destroy the child views that this collection view is holding on to, if any - _destroyChildren: function _destroyChildren() { - var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - var checkEmpty = _ref2.checkEmpty; - - this.triggerMethod('before:destroy:children', this); - var shouldCheckEmpty = checkEmpty !== false; - var childViews = this.children.map(_.identity); - - this.children.each(_.bind(this.removeChildView, this)); - - if (shouldCheckEmpty) { - this._checkEmpty(); + _destroyChildren: function _destroyChildren(options) { + if (!this.children.length) { + return; } + this.triggerMethod('before:destroy:children', this); + var childModels = this.children.map('model'); + this._removeChildModels(childModels, options); this.triggerMethod('destroy:children', this); - return childViews; }, @@ -2701,36 +2820,7 @@ // Set up the child view event forwarding. Uses a "childview:" prefix in front of all forwarded events. _proxyChildEvents: function _proxyChildEvents(view) { - var _this6 = this; - - var prefix = _.result(this, 'childViewEventPrefix'); - - // Forward all child view events through the parent, - // prepending "childview:" to the event name - this.listenTo(view, 'all', function (eventName) { - for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; - } - - var childEventName = prefix + ':' + eventName; - - var childViewEvents = _this6.normalizeMethods(_this6._childViewEvents); - - // call collectionView childViewEvent if defined - if (typeof childViewEvents !== 'undefined' && _.isFunction(childViewEvents[eventName])) { - childViewEvents[eventName].apply(_this6, args); - } - - // use the parent view's proxyEvent handlers - var childViewTriggers = _this6._childViewTriggers; - - // Call the event with the proxy name on the parent layout - if (childViewTriggers && _.isString(childViewTriggers[eventName])) { - _this6.triggerMethod.apply(_this6, [childViewTriggers[eventName]].concat(args)); - } - - _this6.triggerMethod.apply(_this6, [childEventName].concat(args)); - }); + this.listenTo(view, 'all', this._childViewEventHandler); } }); @@ -2767,7 +2857,7 @@ if (this.collection) { this.listenTo(this.collection, 'add', this._onCollectionAdd); - this.listenTo(this.collection, 'remove', this._onCollectionRemove); + this.listenTo(this.collection, 'update', this._onCollectionUpdate); this.listenTo(this.collection, 'reset', this.renderChildren); if (this.sort) { @@ -2986,6 +3076,8 @@ 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')); @@ -2993,15 +3085,15 @@ // binds the handler to the behavior and builds a unique eventName return _.reduce(behaviorEvents, function (events, behaviorHandler, key) { if (!_.isFunction(behaviorHandler)) { - behaviorHandler = this[behaviorHandler]; + behaviorHandler = _this[behaviorHandler]; } if (!behaviorHandler) { return; } key = getUniqueEventName(key); - events[key] = _.bind(behaviorHandler, this); + events[key] = _.bind(behaviorHandler, _this); return events; - }, {}, this); + }, {}); }, @@ -3040,20 +3132,18 @@ regionClass: Region, - _initRegion: function _initRegion(options) { + _initRegion: function _initRegion() { var region = this.region; - var RegionClass = this.regionClass; - // if the region is a string expect an el or selector - // and instantiate a region - if (_.isString(region)) { - this._region = new RegionClass({ - el: region - }); + if (!region) { return; } - this._region = region; + var defaults = { + regionClass: this.regionClass + }; + + this._region = buildRegion(region, defaults); }, getRegion: function getRegion() { return this._region; diff --git a/lib/backbone.marionette.js.map b/lib/backbone.marionette.js.map index b39f21fb39..b526be2bd7 100644 --- a/lib/backbone.marionette.js.map +++ b/lib/backbone.marionette.js.map @@ -1 +1 @@ -{"version":3,"file":"backbone.marionette.js","sources":["src/utils/proxy.js","src/utils/extend.js","src/utils/deprecate.js","src/common/is-node-attached.js","src/common/merge-options.js","src/common/get-option.js","src/common/normalize-methods.js","src/common/trigger-method.js","src/common/monitor-view-events.js","src/error.js","src/common/bind-events.js","src/common/bind-requests.js","src/utils/set-options.js","src/mixins/common.js","src/mixins/radio.js","src/object.js","src/template-cache.js","src/utils/invoke.js","src/mixins/behaviors.js","src/mixins/delegate-entity-events.js","src/utils/get-unique-event-name.js","src/mixins/triggers.js","src/mixins/ui.js","src/mixins/view.js","src/utils/destroy-backbone-view.js","src/region.js","src/mixins/regions.js","src/config/renderer.js","src/view.js","src/utils/emulate-collection.js","src/child-view-container.js","src/collection-view.js","src/composite-view.js","src/behavior.js","src/application.js","src/app-router.js","src/config/behaviors-lookup.js","src/config/features.js","src/backbone.marionette.js"],"sourcesContent":["//Internal utility for creating context style global utils\nconst proxy = function(method) {\n return function(context, ...args) {\n return method.apply(context, args);\n };\n};\n\nexport default proxy;\n","// Marionette.extend\n// -----------------\n\nimport Backbone from 'backbone';\n\n// Borrow the Backbone `extend` method so we can use it as needed\nconst extend = Backbone.Model.extend;\n\nexport default extend;\n","/* global console */\n\nimport _ from 'underscore';\n\nimport Marionette from '../backbone.marionette';\n\nconst deprecate = function(message, test) {\n if (_.isObject(message)) {\n message = (\n message.prev + ' is going to be removed in the future. ' +\n 'Please use ' + message.next + ' instead.' +\n (message.url ? ' See: ' + message.url : '')\n );\n }\n\n if (!Marionette.DEV_MODE) {\n return;\n }\n\n if ((test === undefined || !test) && !deprecate._cache[message]) {\n deprecate._warn('Deprecation warning: ' + message);\n deprecate._cache[message] = true;\n }\n};\n\ndeprecate._console = typeof console !== 'undefined' ? console : {};\ndeprecate._warn = function() {\n const warn = deprecate._console.warn || deprecate._console.log || _.noop;\n return warn.apply(deprecate._console, arguments);\n};\ndeprecate._cache = {};\n\nexport default deprecate;\n","// Marionette.isNodeAttached\n// -------------------------\n\nimport Backbone from 'backbone';\n\n// Determine if `el` is a child of the document\nconst isNodeAttached = function(el) {\n return Backbone.$.contains(document.documentElement, el);\n};\n\nexport default isNodeAttached;\n","import _ from 'underscore';\n\n// Merge `keys` from `options` onto `this`\nconst mergeOptions = function(options, keys) {\n if (!options) { return; }\n _.extend(this, _.pick(options, keys));\n};\n\nexport default mergeOptions;\n","// Marionette.getOption\n// --------------------\n\n// Retrieve an object, function or other value from the\n// object or its `options`, with `options` taking precedence.\nconst getOption = function(optionName) {\n if (!optionName) { return; }\n if (this.options && (this.options[optionName] !== undefined)) {\n return this.options[optionName];\n } else {\n return this[optionName];\n }\n};\n\nexport default getOption;\n","import _ from 'underscore';\n\n// Marionette.normalizeMethods\n// ----------------------\n\n// Pass in a mapping of events => functions or function names\n// and return a mapping of events => functions\nconst normalizeMethods = function(hash) {\n return _.reduce(hash, (normalizedHash, method, name) => {\n if (!_.isFunction(method)) {\n method = this[method];\n }\n if (method) {\n normalizedHash[name] = method;\n }\n return normalizedHash;\n }, {});\n};\n\nexport default normalizeMethods;\n","// Trigger Method\n// --------------\n\nimport _ from 'underscore';\nimport getOption from './get-option';\n\n// split the event name on the \":\"\nconst splitter = /(^|:)(\\w)/gi;\n\n// take the event section (\"section1:section2:section3\")\n// and turn it in to uppercase name onSection1Section2Section3\nfunction getEventName(match, prefix, eventName) {\n return eventName.toUpperCase();\n}\n\n// Trigger an event and/or a corresponding method name. Examples:\n//\n// `this.triggerMethod(\"foo\")` will trigger the \"foo\" event and\n// call the \"onFoo\" method.\n//\n// `this.triggerMethod(\"foo:bar\")` will trigger the \"foo:bar\" event and\n// call the \"onFooBar\" method.\nexport function triggerMethod(event, ...args) {\n // get the method name from the event name\n const methodName = 'on' + event.replace(splitter, getEventName);\n const method = getOption.call(this, methodName);\n let result;\n\n // call the onMethodName if it exists\n if (_.isFunction(method)) {\n // pass all args, except the event name\n result = method.apply(this, args);\n }\n\n // trigger the event\n this.trigger(event, ...args);\n\n return result;\n}\n\n// triggerMethodOn invokes triggerMethod on a specific context\n//\n// e.g. `Marionette.triggerMethodOn(view, 'show')`\n// will trigger a \"show\" event or invoke onShow the view.\nexport function triggerMethodOn(context, ...args) {\n const fnc = _.isFunction(context.triggerMethod) ? context.triggerMethod : triggerMethod;\n return fnc.apply(context, args);\n}\n","// DOM Refresh\n// -----------\n\nimport _ from 'underscore';\nimport { triggerMethodOn } from './trigger-method';\n\n// Trigger method on children unless a pure Backbone.View\nfunction triggerMethodChildren(view, event, shouldTrigger) {\n if (!view._getImmediateChildren) { return; }\n _.each(view._getImmediateChildren(), child => {\n if (!shouldTrigger(child)) { return; }\n triggerMethodOn(child, event, child);\n });\n}\n\nfunction shouldTriggerAttach(view) {\n return !view._isAttached;\n}\n\nfunction shouldAttach(view) {\n if (!shouldTriggerAttach(view)) { return false; }\n view._isAttached = true;\n return true;\n}\n\nfunction shouldTriggerDetach(view) {\n return view._isAttached;\n}\n\nfunction shouldDetach(view) {\n if (!shouldTriggerDetach(view)) { return false; }\n view._isAttached = false;\n return true;\n}\n\n// Monitor a view's state, propagating attach/detach events to children and firing dom:refresh\n// whenever a rendered view is attached or an attached view is rendered.\nfunction monitorViewEvents(view) {\n if (view._areViewEventsMonitored) { return; }\n\n view._areViewEventsMonitored = true;\n\n function handleBeforeAttach() {\n triggerMethodChildren(view, 'before:attach', shouldTriggerAttach);\n }\n\n function handleAttach() {\n triggerMethodChildren(view, 'attach', shouldAttach);\n triggerDOMRefresh();\n }\n\n function handleBeforeDetach() {\n triggerMethodChildren(view, 'before:detach', shouldTriggerDetach);\n }\n\n function handleDetach() {\n triggerMethodChildren(view, 'detach', shouldDetach);\n }\n\n function handleRender() {\n triggerDOMRefresh();\n }\n\n function triggerDOMRefresh() {\n if (view._isAttached && view._isRendered) {\n triggerMethodOn(view, 'dom:refresh', view);\n }\n }\n\n view.on({\n 'before:attach': handleBeforeAttach,\n 'attach': handleAttach,\n 'before:detach': handleBeforeDetach,\n 'detach': handleDetach,\n 'render': handleRender\n });\n}\n\nexport default monitorViewEvents;\n","// Error\n// -----\n\nimport _ from 'underscore';\nimport extend from './utils/extend';\nimport {version} from '../package.json';\n\nconst errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];\n\nconst MarionetteError = extend.call(Error, {\n urlRoot: `http://marionettejs.com/docs/v${version}/`,\n\n constructor(message, options) {\n if (_.isObject(message)) {\n options = message;\n message = options.message;\n } else if (!options) {\n options = {};\n }\n\n const error = Error.call(this, message);\n _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));\n\n this.captureStackTrace();\n\n if (options.url) {\n this.url = this.urlRoot + options.url;\n }\n },\n\n captureStackTrace() {\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, MarionetteError);\n }\n },\n\n toString() {\n return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');\n }\n});\n\nMarionetteError.extend = extend;\n\nexport default MarionetteError;\n","// Bind Entity Events & Unbind Entity Events\n// -----------------------------------------\n//\n// These methods are used to bind/unbind a backbone \"entity\" (e.g. collection/model)\n// to methods on a target object.\n//\n// The first parameter, `target`, must have the Backbone.Events module mixed in.\n//\n// The second parameter is the `entity` (Backbone.Model, Backbone.Collection or\n// any object that has Backbone.Events mixed in) to bind the events from.\n//\n// The third parameter is a hash of { \"event:name\": \"eventHandler\" }\n// configuration. Multiple handlers can be separated by a space. A\n// function can be supplied instead of a string handler name.\n\nimport _ from 'underscore';\nimport MarionetteError from '../error';\n\n// Bind/unbind the event to handlers specified as a string of\n// handler names on the target object\nfunction bindFromStrings(target, entity, evt, methods, actionName) {\n const methodNames = methods.split(/\\s+/);\n\n _.each(methodNames, function(methodName) {\n const method = target[methodName];\n if (!method) {\n throw new MarionetteError(`Method \"${methodName}\" was configured as an event handler, but does not exist.`);\n }\n\n target[actionName](entity, evt, method);\n });\n}\n\n// generic looping function\nfunction iterateEvents(target, entity, bindings, actionName) {\n if (!entity || !bindings) { return; }\n\n // type-check bindings\n if (!_.isObject(bindings)) {\n throw new MarionetteError({\n message: 'Bindings must be an object.',\n url: 'marionette.functions.html#marionettebindevents'\n });\n }\n\n // iterate the bindings and bind/unbind them\n _.each(bindings, function(method, evt) {\n\n // allow for a list of method names as a string\n if (_.isString(method)) {\n bindFromStrings(target, entity, evt, method, actionName);\n return;\n }\n\n target[actionName](entity, evt, method);\n });\n}\n\nfunction bindEvents(entity, bindings) {\n iterateEvents(this, entity, bindings, 'listenTo');\n return this;\n}\n\nfunction unbindEvents(entity, bindings) {\n iterateEvents(this, entity, bindings, 'stopListening');\n return this;\n}\n\n// Export Public API\nexport {\n bindEvents,\n unbindEvents\n};\n","// Bind/Unbind Radio Requests\n// -----------------------------------------\n//\n// These methods are used to bind/unbind a backbone.radio request\n// to methods on a target object.\n//\n// The first parameter, `target`, will set the context of the reply method\n//\n// The second parameter is the `Radio.channel` to bind the reply to.\n//\n// The third parameter is a hash of { \"request:name\": \"replyHandler\" }\n// configuration. A function can be supplied instead of a string handler name.\n\nimport _ from 'underscore';\nimport normalizeMethods from './normalize-methods';\nimport MarionetteError from '../error';\n\nfunction iterateReplies(target, channel, bindings, actionName) {\n if (!channel || !bindings) { return; }\n\n // type-check bindings\n if (!_.isObject(bindings)) {\n throw new MarionetteError({\n message: 'Bindings must be an object.',\n url: 'marionette.functions.html#marionettebindrequests'\n });\n }\n\n const normalizedRadioRequests = normalizeMethods.call(target, bindings);\n\n channel[actionName](normalizedRadioRequests, target);\n}\n\nfunction bindRequests(channel, bindings) {\n iterateReplies(this, channel, bindings, 'reply');\n return this;\n}\n\nfunction unbindRequests(channel, bindings) {\n iterateReplies(this, channel, bindings, 'stopReplying');\n return this;\n}\n\nexport {\n bindRequests,\n unbindRequests\n};\n","import _ from 'underscore';\n\n// Internal utility for setting options consistently across Mn\nconst setOptions = function(...args) {\n this.options = _.extend({}, _.result(this, 'options'), ...args);\n};\n\nexport default setOptions;\n","import _setOptions from '../utils/set-options';\nimport getOption from '../common/get-option';\nimport mergeOptions from '../common/merge-options';\nimport normalizeMethods from '../common/normalize-methods';\nimport {\n bindEvents,\n unbindEvents\n} from '../common/bind-events';\n\nexport default {\n\n // Imports the \"normalizeMethods\" to transform hashes of\n // events=>function references/names to a hash of events=>function references\n normalizeMethods: normalizeMethods,\n\n _setOptions: _setOptions,\n\n // A handy way to merge passed-in options onto the instance\n mergeOptions: mergeOptions,\n\n // Enable getting options from this or this.options by name.\n getOption: getOption,\n\n // Enable binding view's events from another entity.\n bindEvents: bindEvents,\n\n // Enable unbinding view's events from another entity.\n unbindEvents: unbindEvents\n};\n","import _ from 'underscore';\nimport Radio from 'backbone.radio';\n\nimport {\n bindRequests,\n unbindRequests\n} from '../common/bind-requests';\n\nimport {\n bindEvents,\n unbindEvents\n} from '../common/bind-events';\n\nimport MarionetteError from '../error';\n\n// MixinOptions\n// - channelName\n// - radioEvents\n// - radioRequests\n\nexport default {\n\n _initRadio() {\n const channelName = _.result(this, 'channelName');\n\n if (!channelName) {\n return;\n }\n\n /* istanbul ignore next */\n if (!Radio) {\n throw new MarionetteError({\n name: 'BackboneRadioMissing',\n message: 'The dependency \"backbone.radio\" is missing.'\n });\n }\n\n const channel = this._channel = Radio.channel(channelName);\n\n const radioEvents = _.result(this, 'radioEvents');\n this.bindEvents(channel, radioEvents);\n\n const radioRequests = _.result(this, 'radioRequests');\n this.bindRequests(channel, radioRequests);\n\n this.on('destroy', this._destroyRadio);\n },\n\n _destroyRadio() {\n this._channel.stopReplying(null, null, this);\n },\n\n getChannel() {\n return this._channel;\n },\n\n // Proxy `bindEvents`\n bindEvents: bindEvents,\n\n // Proxy `unbindEvents`\n unbindEvents: unbindEvents,\n\n // Proxy `bindRequests`\n bindRequests: bindRequests,\n\n // Proxy `unbindRequests`\n unbindRequests: unbindRequests\n\n};\n","// Object\n// ------\n\nimport _ from 'underscore';\nimport Backbone from 'backbone';\nimport extend from './utils/extend';\nimport { triggerMethod } from './common/trigger-method';\nimport CommonMixin from './mixins/common';\nimport RadioMixin from './mixins/radio';\n\nconst ClassOptions = [\n 'channelName',\n 'radioEvents',\n 'radioRequests'\n];\n\n// A Base Class that other Classes should descend from.\n// Object borrows many conventions and utilities from Backbone.\nconst MarionetteObject = function(options) {\n this._setOptions(options);\n this.mergeOptions(options, ClassOptions);\n this.cid = _.uniqueId(this.cidPrefix);\n this._initRadio();\n this.initialize.apply(this, arguments);\n};\n\nMarionetteObject.extend = extend;\n\n// Object Methods\n// --------------\n\n// Ensure it can trigger events with Backbone.Events\n_.extend(MarionetteObject.prototype, Backbone.Events, CommonMixin, RadioMixin, {\n cidPrefix: 'mno',\n\n // for parity with Marionette.AbstractView lifecyle\n _isDestroyed: false,\n\n isDestroyed() {\n return this._isDestroyed;\n },\n\n //this is a noop method intended to be overridden by classes that extend from this base\n initialize() {},\n\n destroy(...args) {\n if (this._isDestroyed) { return this; }\n\n this.triggerMethod('before:destroy', this, ...args);\n\n this._isDestroyed = true;\n this.triggerMethod('destroy', this, ...args);\n this.stopListening();\n\n return this;\n },\n\n triggerMethod: triggerMethod\n});\n\nexport default MarionetteObject;\n","// Template Cache\n// --------------\n\nimport _ from 'underscore';\nimport Backbone from 'backbone';\nimport MarionetteError from './error';\n\n// Manage templates stored in `