diff --git a/bower.json b/bower.json index 963672405b..b708e6d2da 100644 --- a/bower.json +++ b/bower.json @@ -3,7 +3,7 @@ "description": "The Backbone Framework", "homepage": "https://marionettejs.com/", "main": "./lib/backbone.marionette.js", - "version": "3.4.4", + "version": "3.5.0", "license": "MIT", "keywords": [ "backbone", diff --git a/changelog.md b/changelog.md index e448e4e118..d1fde58f52 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,18 @@ +### v3.5.0 [view commit logs](https://github.com/marionettejs/backbone.marionette/compare/v3.4.4...v3.5.0) + +#### Features +* `NextCollectionView`'s `filter` event now returns the attaching and detached views. +* `unbindEvents` and `unbindRequests` can now be called without handlers to remove all handlers from an entity. + +#### Fixes +* If an event handler on a behavior was undefined it would remove any prior defined handler. +* When a behavior is destroyed it will now undelegate the behavior events and triggers. +* When a view was added a performance check on `NextCollectionView` would sometimes prevent existing views from sorting correctly. +* `NextCollectionView` `viewFilter` will now be called with the same arguments with underscore or lodash. + +#### Deprecations +* Multiple handlers for a single event. If needed, use a single handler to call multiple methods. + ### v3.4.4 [view commit logs](https://github.com/marionettejs/backbone.marionette/compare/v3.4.3...v3.4.4) #### Fixes diff --git a/lib/backbone.marionette.esm.js b/lib/backbone.marionette.esm.js index e1e9fd42aa..06d82769d2 100644 --- a/lib/backbone.marionette.esm.js +++ b/lib/backbone.marionette.esm.js @@ -2,7 +2,7 @@ * @license * MarionetteJS (Backbone.Marionette) * ---------------------------------- -* v3.4.4 +* v3.5.0 * * Copyright (c)2017 Derick Bailey, Muted Solutions, LLC. * Distributed under MIT license @@ -15,7 +15,7 @@ import Backbone from 'backbone'; import _ from 'underscore'; import Radio from 'backbone.radio'; -var version = "3.4.4"; +var version = "3.5.0"; //Internal utility for creating context style global utils var proxy = function proxy(method) { @@ -333,6 +333,10 @@ MarionetteError.extend = extend; function bindFromStrings(target, entity, evt, methods, actionName) { var methodNames = methods.split(/\s+/); + if (methodNames.length > 1) { + deprecate('Multiple handlers for a single event are deprecated. If needed, use a single handler to call multiple methods.'); + } + _.each(methodNames, function (methodName) { var method = target[methodName]; if (!method) { @@ -345,10 +349,6 @@ function bindFromStrings(target, entity, evt, methods, actionName) { // generic looping function function iterateEvents(target, entity, bindings, actionName) { - if (!entity || !bindings) { - return; - } - // type-check bindings if (!_.isObject(bindings)) { throw new MarionetteError({ @@ -371,11 +371,24 @@ function iterateEvents(target, entity, bindings, actionName) { } function bindEvents(entity, bindings) { + if (!entity || !bindings) { + return this; + } + iterateEvents(this, entity, bindings, 'listenTo'); return this; } function unbindEvents(entity, bindings) { + if (!entity) { + return this; + } + + if (!bindings) { + this.stopListening(entity); + return this; + } + iterateEvents(this, entity, bindings, 'stopListening'); return this; } @@ -394,10 +407,6 @@ function unbindEvents(entity, bindings) { // configuration. A function can be supplied instead of a string handler name. function iterateReplies(target, channel, bindings, actionName) { - if (!channel || !bindings) { - return; - } - // type-check bindings if (!_.isObject(bindings)) { throw new MarionetteError({ @@ -412,11 +421,24 @@ function iterateReplies(target, channel, bindings, actionName) { } function bindRequests(channel, bindings) { + if (!channel || !bindings) { + return this; + } + iterateReplies(this, channel, bindings, 'reply'); return this; } function unbindRequests(channel, bindings) { + if (!channel) { + return this; + } + + if (!bindings) { + channel.stopReplying(null, null, this); + return this; + } + iterateReplies(this, channel, bindings, 'stopReplying'); return this; } @@ -772,6 +794,10 @@ var BehaviorsMixin = { if (this._isDestroyed) { return; } + + // Remove behavior-only triggers and events + this.undelegate('.trig' + behavior.cid + ' .' + behavior.cid); + this._behaviors = _.without(this._behaviors, behavior); }, _bindBehaviorUIElements: function _bindBehaviorUIElements() { @@ -816,16 +842,12 @@ var DelegateEntityEventsMixin = { // Borrow event splitter from Backbone var delegateEventSplitter = /^(\S+)\s*(.*)$/; -function uniqueName(eventName, selector) { - return '' + eventName + _.uniqueId('.evt') + ' ' + selector; -} - // Set event name to be namespaced using a unique index // to generate a non colliding event namespace // http://api.jquery.com/event.namespace/ -var getUniqueEventName = function getUniqueEventName(eventName) { +var getNamespacedEventName = function getNamespacedEventName(eventName, namespace) { var match = eventName.match(delegateEventSplitter); - return uniqueName(match[1], match[2]); + return match[1] + "." + namespace + " " + match[2]; }; // Add Feature flags here @@ -883,10 +905,12 @@ var TriggersMixin = { // Configure `triggers` to forward DOM events to view // events. `triggers: {"click .foo": "do:foo"}` _getViewTriggers: function _getViewTriggers(view, triggers) { + var _this = this; + // Configure the triggers, prevent default // action and stop propagation of DOM events return _.reduce(triggers, function (events, value, key) { - key = getUniqueEventName(key); + key = getNamespacedEventName(key, 'trig' + _this.cid); events[key] = buildViewTrigger(view, value); return events; }, {}); @@ -3226,17 +3250,17 @@ _.extend(Container$1.prototype, { // Sort (mutate) and return the array of the child views. - _sort: function _sort(comparator) { + _sort: function _sort(comparator, context) { if (typeof comparator === 'string') { comparator = _.partial(stringComparator, comparator); return this._sortBy(comparator); } if (comparator.length === 1) { - return this._sortBy(comparator); + return this._sortBy(_.bind(comparator, context)); } - return this._views.sort(comparator); + return this._views.sort(_.bind(comparator, context)); }, @@ -3707,14 +3731,12 @@ var CollectionView$2 = Backbone.View.extend({ return; } - this.triggerMethod('before:sort', this); + // If children are sorted prevent added to end perf + delete this._addedViews; - if (_.isFunction(viewComparator)) { - // Must use native bind to preserve length - viewComparator = viewComparator.bind(this); - } + this.triggerMethod('before:sort', this); - this.children._sort(viewComparator); + this.children._sort(viewComparator, this); this.triggerMethod('sort', this); }, @@ -3784,18 +3806,16 @@ var CollectionView$2 = Backbone.View.extend({ return this; }, - _isAddedAtEnd: function _isAddedAtEnd(addedView, index, addedViews) { - var viewIndex = this.children._views.length - addedViews.length + index; - return addedView === this.children._views[viewIndex]; - }, _filterChildren: function _filterChildren() { + var _this2 = this; + var viewFilter = this._getFilter(); var addedViews = this._addedViews; delete this._addedViews; if (!viewFilter) { - if (!this.sortWithCollection && addedViews && _.every(addedViews, _.bind(this._isAddedAtEnd, this))) { + if (addedViews) { return addedViews; } @@ -3804,13 +3824,18 @@ var CollectionView$2 = Backbone.View.extend({ this.triggerMethod('before:filter', this); - var filteredViews = _.partition(this.children._views, _.bind(viewFilter, this)); + var attachViews = []; + var detachViews = []; - this._detachChildren(filteredViews[1]); + _.each(this.children._views, function (view, key, children) { + (viewFilter.call(_this2, view, key, children) ? attachViews : detachViews).push(view); + }); + + this._detachChildren(detachViews); - this.triggerMethod('filter', this); + this.triggerMethod('filter', this, attachViews, detachViews); - return filteredViews[0]; + return attachViews; }, @@ -3942,13 +3967,13 @@ var CollectionView$2 = Backbone.View.extend({ // Renders each view in children and creates a fragment buffer from them _getBuffer: function _getBuffer(views) { - var _this2 = this; + var _this3 = this; var elBuffer = this.Dom.createBuffer(); _.each(views, function (view) { renderView(view); - _this2.Dom.appendContents(elBuffer, view.el, { _$contents: view.$el }); + _this3.Dom.appendContents(elBuffer, view.el, { _$contents: view.$el }); }); return elBuffer; @@ -3986,8 +4011,11 @@ var CollectionView$2 = Backbone.View.extend({ return view; } + // Only cache views if added to the end + if (!index || index >= this.children.length) { + this._addedViews = [view]; + } this._addChild(view, index); - this._addedViews = [view]; this._showChildren(); return view; @@ -4358,9 +4386,9 @@ var Behavior = MarionetteObject.extend({ behaviorHandler = _this[behaviorHandler]; } if (!behaviorHandler) { - return; + return events; } - key = getUniqueEventName(key); + key = getNamespacedEventName(key, _this.cid); events[key] = _.bind(behaviorHandler, _this); return events; }, {}); diff --git a/lib/backbone.marionette.esm.js.map b/lib/backbone.marionette.esm.js.map index a5b74d1816..b071f0c8e2 100644 --- a/lib/backbone.marionette.esm.js.map +++ b/lib/backbone.marionette.esm.js.map @@ -1 +1 @@ -{"version":3,"file":"backbone.marionette.esm.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/config/features.js","src/mixins/triggers.js","src/mixins/ui.js","src/config/dom.js","src/mixins/view.js","src/common/view.js","src/region.js","src/common/build-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/next-child-view-container.js","src/next-collection-view.js","src/composite-view.js","src/behavior.js","src/application.js","src/app-router.js","src/config/behaviors-lookup.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\n/* istanbul ignore next: can't clear console */\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\n// Determine if `el` is a child of the document\nconst isNodeAttached = function(el) {\n return document.documentElement.contains(el && el.parentNode);\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\n _.each(keys, (key) => {\n const option = options[key];\n if (option !== undefined) {\n this[key] = option;\n }\n });\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\nconst getOnMethodName = _.memoize(function(event) {\n return 'on' + event.replace(splitter, getEventName);\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 = getOnMethodName(event);\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.apply(this, arguments);\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 if (_.isFunction(context.triggerMethod)) {\n return context.triggerMethod.apply(context, args);\n }\n\n return triggerMethod.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\nfunction triggerDOMRefresh(view) {\n if (view._isAttached && view._isRendered) {\n triggerMethodOn(view, 'dom:refresh', view);\n }\n}\n\nfunction triggerDOMRemove(view) {\n if (view._isAttached && view._isRendered) {\n triggerMethodOn(view, 'dom:remove', view);\n }\n}\n\nfunction handleBeforeAttach() {\n triggerMethodChildren(this, 'before:attach', shouldTriggerAttach);\n}\n\nfunction handleAttach() {\n triggerMethodChildren(this, 'attach', shouldAttach);\n triggerDOMRefresh(this);\n}\n\nfunction handleBeforeDetach() {\n triggerMethodChildren(this, 'before:detach', shouldTriggerDetach);\n triggerDOMRemove(this);\n}\n\nfunction handleDetach() {\n triggerMethodChildren(this, 'detach', shouldDetach);\n}\n\nfunction handleBeforeRender() {\n triggerDOMRemove(this);\n}\n\nfunction handleRender() {\n triggerDOMRefresh(this);\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 || view.monitorViewEvents === false) { return; }\n\n view._areViewEventsMonitored = true;\n\n view.on({\n 'before:attach': handleBeforeAttach,\n 'attach': handleAttach,\n 'before:detach': handleBeforeDetach,\n 'detach': handleDetach,\n 'before:render': handleBeforeRender,\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(options) {\n this.options = _.extend({}, _.result(this, 'options'), options);\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,\n\n _setOptions,\n\n // A handy way to merge passed-in options onto the instance\n mergeOptions,\n\n // Enable getting options from this or this.options by name.\n getOption,\n\n // Enable binding view's events from another entity.\n bindEvents,\n\n // Enable unbinding view's events from another entity.\n 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 if (!this.hasOwnProperty('options')) {\n this._setOptions(options);\n }\n this.mergeOptions(options, ClassOptions);\n this._setCid();\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 _setCid() {\n if (this.cid) { return; }\n this.cid = _.uniqueId(this.cidPrefix);\n },\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\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 `