diff --git a/bower.json b/bower.json index 780eaf8e4e..e6454b8156 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.5.1", + "version": "4.0.0-beta.1", "license": "MIT", "keywords": [ "backbone", diff --git a/lib/backbone.marionette.esm.js b/lib/backbone.marionette.esm.js index 73812e1999..214c90eed5 100644 --- a/lib/backbone.marionette.esm.js +++ b/lib/backbone.marionette.esm.js @@ -2,9 +2,9 @@ * @license * MarionetteJS (Backbone.Marionette) * ---------------------------------- -* v3.5.1 +* v4.0.0-beta.1 * -* Copyright (c)2017 Derick Bailey, Muted Solutions, LLC. +* Copyright (c)2018 Derick Bailey, Muted Solutions, LLC. * Distributed under MIT license * * http://marionettejs.com @@ -15,7 +15,7 @@ import Backbone from 'backbone'; import _ from 'underscore'; import Radio from 'backbone.radio'; -var version = "3.5.1"; +var version = "4.0.0-beta.1"; //Internal utility for creating context style global utils var proxy = function proxy(method) { @@ -34,151 +34,189 @@ var proxy = function proxy(method) { // Borrow the Backbone `extend` method so we can use it as needed var extend = Backbone.Model.extend; -/* global console */ +// Marionette.normalizeMethods +// ---------------------- -var deprecate = function deprecate(message, test) { - if (_.isObject(message)) { - message = message.prev + ' is going to be removed in the future. ' + 'Please use ' + message.next + ' instead.' + (message.url ? ' See: ' + message.url : ''); - } +// Pass in a mapping of events => functions or function names +// and return a mapping of events => functions +var normalizeMethods$1 = function normalizeMethods(hash) { + var _this = this; - if (!Marionette.DEV_MODE) { + if (!hash) { return; } - if ((test === undefined || !test) && !deprecate._cache[message]) { - deprecate._warn('Deprecation warning: ' + message); - deprecate._cache[message] = true; - } + return _.reduce(hash, function (normalizedHash, method, name) { + if (!_.isFunction(method)) { + method = _this[method]; + } + if (method) { + normalizedHash[name] = method; + } + return normalizedHash; + }, {}); }; -/* istanbul ignore next: can't clear console */ -deprecate._console = typeof console !== 'undefined' ? console : {}; -deprecate._warn = function () { - var warn = deprecate._console.warn || deprecate._console.log || _.noop; - return warn.apply(deprecate._console, arguments); -}; -deprecate._cache = {}; +// Error +// ----- -// Marionette.isNodeAttached -// ------------------------- +var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number', 'url']; -// Determine if `el` is a child of the document -var isNodeAttached = function isNodeAttached(el) { - return document.documentElement.contains(el && el.parentNode); -}; +var MarionetteError = extend.call(Error, { + urlRoot: 'http://marionettejs.com/docs/v' + version + '/', -// Merge `keys` from `options` onto `this` -var mergeOptions = function mergeOptions(options, keys) { - var _this = this; + url: '', - if (!options) { - return; - } + constructor: function constructor(options) { + var error = Error.call(this, options.message); + _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps)); - _.each(keys, function (key) { - var option = options[key]; - if (option !== undefined) { - _this[key] = option; + if (Error.captureStackTrace) { + this.captureStackTrace(); } - }); -}; -// Marionette.getOption -// -------------------- + this.url = this.urlRoot + this.url; + }, + captureStackTrace: function captureStackTrace() { + Error.captureStackTrace(this, MarionetteError); + }, + toString: function toString() { + return this.name + ': ' + this.message + ' See: ' + this.url; + } +}); -// Retrieve an object, function or other value from the -// object or its `options`, with `options` taking precedence. -var getOption = function getOption(optionName) { - if (!optionName) { - return; +// 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 normalizeBindings(context, bindings) { + if (!_.isObject(bindings)) { + throw new MarionetteError({ + message: 'Bindings must be an object.', + url: 'common.html#bindevents' + }); } - if (this.options && this.options[optionName] !== undefined) { - return this.options[optionName]; - } else { - return this[optionName]; + + return normalizeMethods$1.call(context, bindings); +} + +function bindEvents$1(entity, bindings) { + if (!entity || !bindings) { + return this; } -}; -// Marionette.normalizeMethods -// ---------------------- + this.listenTo(entity, normalizeBindings(this, bindings)); -// Pass in a mapping of events => functions or function names -// and return a mapping of events => functions -var normalizeMethods = function normalizeMethods(hash) { - var _this = this; + return this; +} - return _.reduce(hash, function (normalizedHash, method, name) { - if (!_.isFunction(method)) { - method = _this[method]; - } - if (method) { - normalizedHash[name] = method; - } - return normalizedHash; - }, {}); -}; +function unbindEvents$1(entity, bindings) { + if (!entity) { + return this; + } -// Trigger Method -// -------------- + if (!bindings) { + this.stopListening(entity); + return this; + } -// split the event name on the ":" -var splitter = /(^|:)(\w)/gi; + this.stopListening(entity, normalizeBindings(this, bindings)); -// take the event section ("section1:section2:section3") -// and turn it in to uppercase name onSection1Section2Section3 -function getEventName(match, prefix, eventName) { - return eventName.toUpperCase(); + return this; } -var getOnMethodName = _.memoize(function (event) { - return 'on' + event.replace(splitter, getEventName); -}); - -// Trigger an event and/or a corresponding method name. Examples: +// Bind/Unbind Radio Requests +// ----------------------------------------- // -// `this.triggerMethod("foo")` will trigger the "foo" event and -// call the "onFoo" method. +// These methods are used to bind/unbind a backbone.radio request +// to methods on a target object. // -// `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]; +// The first parameter, `target`, will set the context of the reply method +// +// The second parameter is the `Radio.channel` to bind the reply to. +// +// The third parameter is a hash of { "request:name": "replyHandler" } +// configuration. A function can be supplied instead of a string handler name. + +function normalizeBindings$1(context, bindings) { + if (!_.isObject(bindings)) { + throw new MarionetteError({ + message: 'Bindings must be an object.', + url: 'common.html#bindrequests' + }); } - // get the method name from the event name - var methodName = getOnMethodName(event); - var method = getOption.call(this, methodName); - var result = void 0; + return normalizeMethods$1.call(context, bindings); +} - // call the onMethodName if it exists - if (_.isFunction(method)) { - // pass all args, except the event name - result = method.apply(this, args); +function bindRequests$1(channel, bindings) { + if (!channel || !bindings) { + return this; } - // trigger the event - this.trigger.apply(this, arguments); + channel.reply(normalizeBindings$1(this, bindings), this); - return result; + return this; } -// triggerMethodOn invokes triggerMethod on a specific context -// -// e.g. `Marionette.triggerMethodOn(view, 'show')` -// will trigger a "show" event or invoke onShow the view. -function triggerMethodOn(context) { - for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - args[_key2 - 1] = arguments[_key2]; +function unbindRequests$1(channel, bindings) { + if (!channel) { + return this; } - if (_.isFunction(context.triggerMethod)) { - return context.triggerMethod.apply(context, args); + if (!bindings) { + channel.stopReplying(null, null, this); + return this; } - return triggerMethod.apply(context, args); + channel.stopReplying(normalizeBindings$1(this, bindings)); + + return this; } +// Marionette.getOption +// -------------------- + +// Retrieve an object, function or other value from the +// object or its `options`, with `options` taking precedence. +var getOption$1 = function getOption(optionName) { + if (!optionName) { + return; + } + if (this.options && this.options[optionName] !== undefined) { + return this.options[optionName]; + } else { + return this[optionName]; + } +}; + +// Merge `keys` from `options` onto `this` +var mergeOptions$1 = function mergeOptions(options, keys) { + var _this = this; + + if (!options) { + return; + } + + _.each(keys, function (key) { + var option = options[key]; + if (option !== undefined) { + _this[key] = option; + } + }); +}; + // DOM Refresh // ----------- @@ -191,7 +229,7 @@ function triggerMethodChildren(view, event, shouldTrigger) { if (!shouldTrigger(child)) { return; } - triggerMethodOn(child, event, child); + child.triggerMethod(event, child); }); } @@ -221,13 +259,13 @@ function shouldDetach(view) { function triggerDOMRefresh(view) { if (view._isAttached && view._isRendered) { - triggerMethodOn(view, 'dom:refresh', view); + view.triggerMethod('dom:refresh', view); } } function triggerDOMRemove(view) { if (view._isAttached && view._isRendered) { - triggerMethodOn(view, 'dom:remove', view); + view.triggerMethod('dom:remove', view); } } @@ -276,197 +314,111 @@ function monitorViewEvents(view) { }); } -// Error -// ----- - -var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number']; - -var MarionetteError = extend.call(Error, { - urlRoot: 'http://marionettejs.com/docs/v' + version + '/', +// Trigger Method +// -------------- - constructor: function constructor(message, options) { - if (_.isObject(message)) { - options = message; - message = options.message; - } else if (!options) { - options = {}; - } +// split the event name on the ":" +var splitter = /(^|:)(\w)/gi; - var error = Error.call(this, message); - _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps)); +// Only calc getOnMethodName once +var methodCache = {}; - this.captureStackTrace(); +// take the event section ("section1:section2:section3") +// and turn it in to uppercase name onSection1Section2Section3 +function getEventName(match, prefix, eventName) { + return eventName.toUpperCase(); +} - if (options.url) { - this.url = this.urlRoot + options.url; - } - }, - captureStackTrace: function captureStackTrace() { - if (Error.captureStackTrace) { - Error.captureStackTrace(this, MarionetteError); - } - }, - toString: function toString() { - return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : ''); +var getOnMethodName = function getOnMethodName(event) { + if (!methodCache[event]) { + methodCache[event] = 'on' + event.replace(splitter, getEventName); } -}); -MarionetteError.extend = extend; + return methodCache[event]; +}; -// 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. +// Trigger an event and/or a corresponding method name. Examples: // -// The second parameter is the `entity` (Backbone.Model, Backbone.Collection or -// any object that has Backbone.Events mixed in) to bind the events from. +// `this.triggerMethod("foo")` will trigger the "foo" event and +// call the "onFoo" method. // -// The third parameter is a hash of { "event:name": "eventHandler" } -// configuration. Multiple handlers can be separated by a space. A -// function can be supplied instead of a string handler name. - -// Bind/unbind the event to handlers specified as a string of -// handler names on the target object -function bindFromStrings(target, entity, evt, methods, actionName) { - var methodNames = methods.split(/\s+/); +// `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and +// call the "onFooBar" method. +function triggerMethod$1(event) { + // get the method name from the event name + var methodName = getOnMethodName(event); + var method = getOption$1.call(this, methodName); + var result = void 0; - if (methodNames.length > 1) { - deprecate('Multiple handlers for a single event are deprecated. If needed, use a single handler to call multiple methods.'); + // call the onMethodName if it exists + if (_.isFunction(method)) { + // pass all args, except the event name + result = method.apply(this, _.drop(arguments)); } - _.each(methodNames, function (methodName) { - var method = target[methodName]; - if (!method) { - throw new MarionetteError('Method "' + methodName + '" was configured as an event handler, but does not exist.'); - } + // trigger the event + this.trigger.apply(this, arguments); - target[actionName](entity, evt, method); - }); + return result; } -// generic looping function -function iterateEvents(target, entity, bindings, actionName) { - // type-check bindings - if (!_.isObject(bindings)) { - throw new MarionetteError({ - message: 'Bindings must be an object.', - url: 'marionette.functions.html#marionettebindevents' - }); - } +var Events = { + triggerMethod: triggerMethod$1 +}; - // iterate the bindings and bind/unbind them - _.each(bindings, function (method, evt) { +var CommonMixin = { - // allow for a list of method names as a string - if (_.isString(method)) { - bindFromStrings(target, entity, evt, method, actionName); - return; - } + // Imports the "normalizeMethods" to transform hashes of + // events=>function references/names to a hash of events=>function references + normalizeMethods: normalizeMethods$1, - target[actionName](entity, evt, method); - }); -} + _setOptions: function _setOptions(options, classOptions) { + this.options = _.extend({}, _.result(this, 'options'), options); + this.mergeOptions(options, classOptions); + }, -function bindEvents(entity, bindings) { - if (!entity || !bindings) { - return this; - } - iterateEvents(this, entity, bindings, 'listenTo'); - return this; -} + // A handy way to merge passed-in options onto the instance + mergeOptions: mergeOptions$1, -function unbindEvents(entity, bindings) { - if (!entity) { - return this; - } + // Enable getting options from this or this.options by name. + getOption: getOption$1, - if (!bindings) { - this.stopListening(entity); - return this; - } + // Enable binding view's events from another entity. + bindEvents: bindEvents$1, - iterateEvents(this, entity, bindings, 'stopListening'); - return this; -} + // Enable unbinding view's events from another entity. + unbindEvents: unbindEvents$1, -// Bind/Unbind Radio Requests -// ----------------------------------------- -// -// These methods are used to bind/unbind a backbone.radio request -// to methods on a target object. -// -// The first parameter, `target`, will set the context of the reply method -// -// The second parameter is the `Radio.channel` to bind the reply to. -// -// The third parameter is a hash of { "request:name": "replyHandler" } -// configuration. A function can be supplied instead of a string handler name. + // Enable binding view's requests. + bindRequests: bindRequests$1, -function iterateReplies(target, channel, bindings, actionName) { - // type-check bindings - if (!_.isObject(bindings)) { - throw new MarionetteError({ - message: 'Bindings must be an object.', - url: 'marionette.functions.html#marionettebindrequests' - }); - } + // Enable unbinding view's requests. + unbindRequests: unbindRequests$1, - var normalizedRadioRequests = normalizeMethods.call(target, bindings); + triggerMethod: triggerMethod$1 +}; - channel[actionName](normalizedRadioRequests, target); -} +_.extend(CommonMixin, Backbone.Events); -function bindRequests(channel, bindings) { - if (!channel || !bindings) { - return this; - } +var DestroyMixin = { + _isDestroyed: false, - iterateReplies(this, channel, bindings, 'reply'); - return this; -} + isDestroyed: function isDestroyed() { + return this._isDestroyed; + }, + destroy: function destroy(options) { + if (this._isDestroyed) { + return this; + } -function unbindRequests(channel, bindings) { - if (!channel) { - return this; - } + this.triggerMethod('before:destroy', this, options); + this._isDestroyed = true; + this.triggerMethod('destroy', this, options); + this.stopListening(); - if (!bindings) { - channel.stopReplying(null, null, this); return this; } - - iterateReplies(this, channel, bindings, 'stopReplying'); - return this; -} - -// Internal utility for setting options consistently across Mn -var setOptions = function setOptions(options) { - this.options = _.extend({}, _.result(this, 'options'), options); -}; - -var CommonMixin = { - - // Imports the "normalizeMethods" to transform hashes of - // events=>function references/names to a hash of events=>function references - normalizeMethods: normalizeMethods, - - _setOptions: setOptions, - - // A handy way to merge passed-in options onto the instance - mergeOptions: mergeOptions, - - // Enable getting options from this or this.options by name. - getOption: getOption, - - // Enable binding view's events from another entity. - bindEvents: bindEvents, - - // Enable unbinding view's events from another entity. - unbindEvents: unbindEvents }; // MixinOptions @@ -485,8 +437,8 @@ var RadioMixin = { /* istanbul ignore next */ if (!Radio) { throw new MarionetteError({ - name: 'BackboneRadioMissing', - message: 'The dependency "backbone.radio" is missing.' + message: 'The dependency "backbone.radio" is missing.', + url: 'backbone.radio.html#marionette-integration' }); } @@ -505,21 +457,7 @@ var RadioMixin = { }, getChannel: function getChannel() { return this._channel; - }, - - - // Proxy `bindEvents` - bindEvents: bindEvents, - - // Proxy `unbindEvents` - unbindEvents: unbindEvents, - - // Proxy `bindRequests` - bindRequests: bindRequests, - - // Proxy `unbindRequests` - unbindRequests: unbindRequests - + } }; // Object @@ -527,14 +465,10 @@ var RadioMixin = { var ClassOptions = ['channelName', 'radioEvents', 'radioRequests']; -// A Base Class that other Classes should descend from. // Object borrows many conventions and utilities from Backbone. var MarionetteObject = function MarionetteObject(options) { - if (!this.hasOwnProperty('options')) { - this._setOptions(options); - } - this.mergeOptions(options, ClassOptions); - this._setCid(); + this._setOptions(options, ClassOptions); + this.cid = _.uniqueId(this.cidPrefix); this._initRadio(); this.initialize.apply(this, arguments); }; @@ -544,150 +478,11 @@ MarionetteObject.extend = extend; // Object Methods // -------------- -// Ensure it can trigger events with Backbone.Events -_.extend(MarionetteObject.prototype, Backbone.Events, CommonMixin, RadioMixin, { +_.extend(MarionetteObject.prototype, CommonMixin, DestroyMixin, RadioMixin, { cidPrefix: 'mno', - // for parity with Marionette.AbstractView lifecyle - _isDestroyed: false, - - isDestroyed: function isDestroyed() { - return this._isDestroyed; - }, - - - //this is a noop method intended to be overridden by classes that extend from this base - initialize: function initialize() {}, - _setCid: function _setCid() { - if (this.cid) { - return; - } - this.cid = _.uniqueId(this.cidPrefix); - }, - destroy: function destroy() { - if (this._isDestroyed) { - return this; - } - - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - this.triggerMethod.apply(this, ['before:destroy', this].concat(args)); - - this._isDestroyed = true; - this.triggerMethod.apply(this, ['destroy', this].concat(args)); - this.stopListening(); - - return this; - }, - - - triggerMethod: triggerMethod -}); - -// Template Cache -// -------------- - -// Manage templates stored in `