From ec2f4562bb5fa6553017e40caa687bf314c2e6ba Mon Sep 17 00:00:00 2001 From: Paul Falgout Date: Thu, 2 Aug 2018 01:48:20 +0900 Subject: [PATCH] Bump and build v4 With changelog and other v4 doc updates. --- bower.json | 2 +- changelog.md | 15 + docs/dom.prerendered.md | 1 - docs/marionette.region.md | 6 +- lib/backbone.marionette.esm.js | 3343 ++++++++------------------- lib/backbone.marionette.esm.js.map | 2 +- lib/backbone.marionette.js | 3376 +++++++++------------------- lib/backbone.marionette.js.map | 2 +- lib/backbone.marionette.min.js | 6 +- lib/backbone.marionette.min.js.map | 2 +- package.json | 2 +- readme.md | 14 +- upgradeGuide.md | 7 +- 13 files changed, 2026 insertions(+), 4752 deletions(-) diff --git a/bower.json b/bower.json index 780eaf8e4e..70f8691afb 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", "license": "MIT", "keywords": [ "backbone", diff --git a/changelog.md b/changelog.md index b88b9e2b41..6069cb556a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,18 @@ +### v4.0.0 [view commit logs](https://github.com/marionettejs/backbone.marionette/compare/v3.5.1...v4.0.0) + +#### Breaking Changes +The breaking changes are documented in the [upgrade guide](https://marionettejs.com/docs/v4.0.0/upgrade-v3-v4.html). + +#### Features +* `CollectionView` can now render a template in the same fashion of the removed `CompositeView`. +* `View#triggers` now passes the originating DOM event object as the final argument of the triggered Mn event. +* View classes now have the `bindRequests` and `unbindRequests` API. +* The ES6 package was exposed in `package.json` on `jsnext:main` +* The underscore dependency was updated to include 1.8.3 - 1.9.x. + +#### Documentation +The documentation structure was overhauled to provide a flow to reading through the docs. + ### v3.5.1 [view commit logs](https://github.com/marionettejs/backbone.marionette/compare/v3.5.0...v3.5.1) #### Fixes diff --git a/docs/dom.prerendered.md b/docs/dom.prerendered.md index a5af7467c0..01e8021ad9 100644 --- a/docs/dom.prerendered.md +++ b/docs/dom.prerendered.md @@ -9,7 +9,6 @@ import { View } from 'backbone.marionette'; const myView = new View({ el: $('#foo-selector') }); -const myView = new MyView(); myView.isRendered(); // true if '#foo-selector` exists and has content myView.isAttached(); // true if '#foo-selector` is in the DOM ``` diff --git a/docs/marionette.region.md b/docs/marionette.region.md index 1b7911c55f..2af89fcbe8 100644 --- a/docs/marionette.region.md +++ b/docs/marionette.region.md @@ -514,7 +514,7 @@ const EmptyMsgRegion = Region.extend({ } }); ``` -[Live example](https://jsfiddle.net/marionettejs/c1nacq0c/1/) +[Live example](https://jsfiddle.net/marionettejs/c1nacq0c/) ## Set How View's `el` Is Attached and Detached @@ -615,8 +615,8 @@ const MyView = View.extend({ }); ``` -[Live example](https://jsfiddle.net/marionettejs/c1nacq0c/3/) +[Live example](https://jsfiddle.net/marionettejs/qtvjLu70/) Using a similar approach is possible to create a region animated with CSS: -[Live example](https://jsfiddle.net/marionettejs/9ys4d57x/2/) +[Live example](https://jsfiddle.net/marionettejs/8uoabg7c/) diff --git a/lib/backbone.marionette.esm.js b/lib/backbone.marionette.esm.js index 73812e1999..062d196055 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 * -* 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"; //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 `