From eea78083674b110d7c8f01da9f7ba109091337be Mon Sep 17 00:00:00 2001 From: David Seveloff Date: Tue, 28 May 2024 18:20:49 +0300 Subject: [PATCH 1/9] Internal: Improve Nested Elements Dynamic Tags - Title [ED-13824] --- assets/dev/js/editor/elements/views/base.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/assets/dev/js/editor/elements/views/base.js b/assets/dev/js/editor/elements/views/base.js index 87c297b61c5d..9fe99df33f03 100644 --- a/assets/dev/js/editor/elements/views/base.js +++ b/assets/dev/js/editor/elements/views/base.js @@ -341,7 +341,23 @@ BaseElementView = BaseContainer.extend( { }, attachElContent( html ) { - this.$el.empty().append( this.getHandlesOverlay(), html ); + if ( elementor.previewView.isBuffering || ! elementorCommon.config.experimentalFeatures.e_nested_atomic_repeaters || 'nested-accordion' !== this?.model?.config?.name ) { + this.$el.empty().append( this.getHandlesOverlay(), html ); + return; + } + + console.log({html}); + const detailsRegex = /[\s\S]*?<\/details>/gi; + const titleElementRegex = /
([\s\S]*?)<\/div>/gi; + const activeIndex = +this?.model?.changed?.editSettings?.changed - 1 || 0; + const activeDetailsElement = html.matchAll( detailsRegex ).toArray()[ activeIndex ]; + const newTitle = titleElementRegex.exec( activeDetailsElement ).toArray()[ 1 ].replace( /<\/?[^>]+(>|$)/g , '' ).trim(); + + this.$el.find( 'details' )[ activeIndex ].find( '.e-n-accordion-item-title-text' ).text( newTitle ); + + // this.$el.html().matchAll(/[\s\S]*?<\/details>/gi).toArray() + // _this$model.changed.editSettings.attributes.activeItemIndex + // this.$el.empty().append( this.getHandlesOverlay(), html ); }, isStyleTransferControl( control ) { From 3e8899397c733bdf0614443e250e1ad95b26679a Mon Sep 17 00:00:00 2001 From: David Seveloff Date: Wed, 29 May 2024 10:44:48 +0300 Subject: [PATCH 2/9] wip --- assets/dev/js/editor/elements/views/base.js | 32 +- assets/lib/backbone/backbone.marionette.js | 1328 +++++++++---------- 2 files changed, 687 insertions(+), 673 deletions(-) diff --git a/assets/dev/js/editor/elements/views/base.js b/assets/dev/js/editor/elements/views/base.js index 9fe99df33f03..c43676c9f058 100644 --- a/assets/dev/js/editor/elements/views/base.js +++ b/assets/dev/js/editor/elements/views/base.js @@ -340,26 +340,40 @@ BaseElementView = BaseContainer.extend( { return $handlesOverlay; }, - attachElContent( html ) { + attachElContent( html, data ) { if ( elementor.previewView.isBuffering || ! elementorCommon.config.experimentalFeatures.e_nested_atomic_repeaters || 'nested-accordion' !== this?.model?.config?.name ) { this.$el.empty().append( this.getHandlesOverlay(), html ); return; } - console.log({html}); - const detailsRegex = /[\s\S]*?<\/details>/gi; - const titleElementRegex = /
([\s\S]*?)<\/div>/gi; - const activeIndex = +this?.model?.changed?.editSettings?.changed - 1 || 0; - const activeDetailsElement = html.matchAll( detailsRegex ).toArray()[ activeIndex ]; - const newTitle = titleElementRegex.exec( activeDetailsElement ).toArray()[ 1 ].replace( /<\/?[^>]+(>|$)/g , '' ).trim(); + console.log( { html, data } ); + const activeIndex = +this?.model?.changed?.editSettings?.changed?.activeItemIndex - 1 || +this?.model?.changed?.editSettings?.attributes?.activeItemIndex - 1; + if ( activeIndex ) { + const newTitle = this.takeDataFromRepeater( activeIndex ), + element = data.view.$el[ 0 ].querySelectorAll( '.e-n-accordion-item-title-text' )[ activeIndex ]; + element.innerText = newTitle; + } + // Const detailsRegex = /[\s\S]*?<\/details>/gi; + // const titleElementRegex = /
([\s\S]*?)<\/div>/gi; + // const activeIndex = +this?.model?.changed?.editSettings?.changed?.activeItemIndex - 1; + // + // const activeDetailsElement = html.matchAll( detailsRegex ).toArray()[ activeIndex ]; + // const newTitle = titleElementRegex.exec( activeDetailsElement['input'] ).toArray()[ 1 ].replace( /<\/?[^>]+(>|$)/g , '' ).trim(); - this.$el.find( 'details' )[ activeIndex ].find( '.e-n-accordion-item-title-text' ).text( newTitle ); + // this.$el.find( 'details' )[ activeIndex ].find( '.e-n-accordion-item-title-text' ).text( newTitle ); - // this.$el.html().matchAll(/[\s\S]*?<\/details>/gi).toArray() + // This.$el.html().matchAll(/[\s\S]*?<\/details>/gi).toArray() // _this$model.changed.editSettings.attributes.activeItemIndex // this.$el.empty().append( this.getHandlesOverlay(), html ); }, + takeDataFromRepeater( replacedIndex ) { + const repeater = 'elementor-control-type-repeater', + repeaterItemClass = 'elementor-repeater-row-item-title'; + + return document.querySelectorAll( `.${ repeater } .${ repeaterItemClass }` )[ replacedIndex ].innerText; + }, + isStyleTransferControl( control ) { if ( undefined !== control.style_transfer ) { return control.style_transfer; diff --git a/assets/lib/backbone/backbone.marionette.js b/assets/lib/backbone/backbone.marionette.js index 2ee209ca293c..2dea31d3071d 100644 --- a/assets/lib/backbone/backbone.marionette.js +++ b/assets/lib/backbone/backbone.marionette.js @@ -506,33 +506,33 @@ Marionette.Deferred = Backbone.$.Deferred; /* jshint unused: false *//* global console */ - + // Helpers // ------- - + // Marionette.extend // ----------------- - + // Borrow the Backbone `extend` method so we can use it as needed Marionette.extend = Backbone.Model.extend; - + // Marionette.isNodeAttached // ------------------------- - + // Determine if `el` is a child of the document Marionette.isNodeAttached = function(el) { return Backbone.$.contains(document.documentElement, el); }; - + // Merge `keys` from `options` onto `this` Marionette.mergeOptions = function(options, keys) { if (!options) { return; } _.extend(this, _.pick(options, keys)); }; - + // Marionette.getOption // -------------------- - + // Retrieve an object, function or other value from a target // object or its `options`, with `options` taking precedence. Marionette.getOption = function(target, optionName) { @@ -543,12 +543,12 @@ return target[optionName]; } }; - + // Proxy `Marionette.getOption` Marionette.proxyGetOption = function(optionName) { return Marionette.getOption(this, optionName); }; - + // Similar to `_.result`, this is a simple helper // If a function is provided we call it with context // otherwise just return the value. If the value is @@ -559,10 +559,10 @@ } return value; }; - + // Marionette.normalizeMethods // ---------------------- - + // Pass in a mapping of events => functions or function names // and return a mapping of events => functions Marionette.normalizeMethods = function(hash) { @@ -576,7 +576,7 @@ return normalizedHash; }, {}, this); }; - + // utility method for parsing @ui. syntax strings // into associated selector Marionette.normalizeUIString = function(uiString, ui) { @@ -584,7 +584,7 @@ return ui[r.slice(4)]; }); }; - + // allows for the use of the @ui. syntax within // a given key for triggers and events // swaps the @ui with the associated selector. @@ -596,7 +596,7 @@ return memo; }, {}); }; - + // allows for the use of the @ui. syntax within // a given value for regions // swaps the @ui with the associated selector @@ -617,7 +617,7 @@ }); return hash; }; - + // Mix in methods from Underscore, for iteration, and other // collection related features. // Borrowing this code from Backbone.Collection: @@ -627,7 +627,7 @@ 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest', 'last', 'without', 'isEmpty', 'pluck']; - + _.each(methods, function(method) { object[method] = function() { var list = _.values(_.result(this, listProperty)); @@ -636,7 +636,7 @@ }; }); }; - + var deprecate = Marionette.deprecate = function(message, test) { if (_.isObject(message)) { message = ( @@ -645,53 +645,53 @@ (message.url ? ' See: ' + message.url : '') ); } - + if ((test === undefined || !test) && !deprecate._cache[message]) { deprecate._warn('Deprecation warning: ' + message); deprecate._cache[message] = true; } }; - + deprecate._console = typeof console !== 'undefined' ? console : {}; deprecate._warn = function() { var warn = deprecate._console.warn || deprecate._console.log || function() {}; return warn.apply(deprecate._console, arguments); }; deprecate._cache = {}; - + /* jshint maxstatements: 14, maxcomplexity: 7 */ - + // Trigger Method // -------------- - + Marionette._triggerMethod = (function() { // split the event name on the ":" var splitter = /(^|:)(\w)/gi; - + // take the event section ("section1:section2:section3") // and turn it in to uppercase name function getEventName(match, prefix, eventName) { return eventName.toUpperCase(); } - + return function(context, event, args) { var noEventArg = arguments.length < 3; if (noEventArg) { args = event; event = args[0]; } - + // get the method name from the event name var methodName = 'on' + event.replace(splitter, getEventName); var method = context[methodName]; var result; - + // call the onMethodName if it exists if (_.isFunction(method)) { // pass all args, except the event name result = method.apply(context, noEventArg ? _.rest(args) : args); } - + // trigger the event, if a trigger method exists if (_.isFunction(context.trigger)) { if (noEventArg + args.length > 1) { @@ -700,11 +700,11 @@ context.trigger(event); } } - + return result; }; })(); - + // Trigger an event and/or a corresponding method name. Examples: // // `this.triggerMethod("foo")` will trigger the "foo" event and @@ -715,7 +715,7 @@ Marionette.triggerMethod = function(event) { return Marionette._triggerMethod(this, arguments); }; - + // triggerMethodOn invokes triggerMethod on a specific context // // e.g. `Marionette.triggerMethodOn(view, 'show')` @@ -724,49 +724,49 @@ var fnc = _.isFunction(context.triggerMethod) ? context.triggerMethod : Marionette.triggerMethod; - + return fnc.apply(context, _.rest(arguments)); }; - + // DOM Refresh // ----------- - + // Monitor a view's state, and after it has been rendered and shown // in the DOM, trigger a "dom:refresh" event every time it is // re-rendered. - + Marionette.MonitorDOMRefresh = function(view) { if (view._isDomRefreshMonitored) { return; } view._isDomRefreshMonitored = true; - + // track when the view has been shown in the DOM, // using a Marionette.Region (or by other means of triggering "show") function handleShow() { view._isShown = true; triggerDOMRefresh(); } - + // track when the view has been rendered function handleRender() { view._isRendered = true; triggerDOMRefresh(); } - + // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method function triggerDOMRefresh() { if (view._isShown && view._isRendered && Marionette.isNodeAttached(view.el)) { Marionette.triggerMethodOn(view, 'dom:refresh', view); } } - + view.on({ show: handleShow, render: handleRender }); }; - + /* jshint maxparams: 5 */ - + // Bind Entity Events & Unbind Entity Events // ----------------------------------------- // @@ -781,52 +781,52 @@ // The third parameter is a hash of { "event:name": "eventHandler" } // configuration. Multiple handlers can be separated by a space. A // function can be supplied instead of a string handler name. - + (function(Marionette) { 'use strict'; - + // Bind the event to handlers specified as a string of // handler names on the target object function bindFromStrings(target, entity, evt, methods) { var methodNames = methods.split(/\s+/); - + _.each(methodNames, function(methodName) { - + var method = target[methodName]; if (!method) { throw new Marionette.Error('Method "' + methodName + '" was configured as an event handler, but does not exist.'); } - + target.listenTo(entity, evt, method); }); } - + // Bind the event to a supplied callback function function bindToFunction(target, entity, evt, method) { target.listenTo(entity, evt, method); } - + // Bind the event to handlers specified as a string of // handler names on the target object function unbindFromStrings(target, entity, evt, methods) { var methodNames = methods.split(/\s+/); - + _.each(methodNames, function(methodName) { var method = target[methodName]; target.stopListening(entity, evt, method); }); } - + // Bind the event to a supplied callback function function unbindToFunction(target, entity, evt, method) { target.stopListening(entity, evt, method); } - + // generic looping function function iterateEvents(target, entity, bindings, functionCallback, stringCallback) { if (!entity || !bindings) { return; } - + // type-check bindings if (!_.isObject(bindings)) { throw new Marionette.Error({ @@ -834,13 +834,13 @@ url: 'marionette.functions.html#marionettebindentityevents' }); } - + // allow the bindings to be a function bindings = Marionette._getValue(bindings, target); - + // iterate the bindings and bind them _.each(bindings, function(methods, evt) { - + // allow for a function as the handler, // or a list of event names as a string if (_.isFunction(methods)) { @@ -848,39 +848,39 @@ } else { stringCallback(target, entity, evt, methods); } - + }); } - + // Export Public API Marionette.bindEntityEvents = function(target, entity, bindings) { iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings); }; - + Marionette.unbindEntityEvents = function(target, entity, bindings) { iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings); }; - + // Proxy `bindEntityEvents` Marionette.proxyBindEntityEvents = function(entity, bindings) { return Marionette.bindEntityEvents(this, entity, bindings); }; - + // Proxy `unbindEntityEvents` Marionette.proxyUnbindEntityEvents = function(entity, bindings) { return Marionette.unbindEntityEvents(this, entity, bindings); }; })(Marionette); - + // Error // ----- - + var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number']; - + Marionette.Error = Marionette.extend.call(Error, { urlRoot: 'http://marionettejs.com/docs/v' + Marionette.VERSION + '/', - + constructor: function(message, options) { if (_.isObject(message)) { options = message; @@ -888,33 +888,33 @@ } else if (!options) { options = {}; } - + var error = Error.call(this, message); _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps)); - + this.captureStackTrace(); - + if (options.url) { this.url = this.urlRoot + options.url; } }, - + captureStackTrace: function() { if (Error.captureStackTrace) { Error.captureStackTrace(this, Marionette.Error); } }, - + toString: function() { return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : ''); } }); - + Marionette.Error.extend = Marionette.extend; - + // Callbacks // --------- - + // A simple way of managing a collection of callbacks // and executing them at a later point in time, using jQuery's // `Deferred` object. @@ -922,23 +922,23 @@ this._deferred = Marionette.Deferred(); this._callbacks = []; }; - + _.extend(Marionette.Callbacks.prototype, { - + // Add a callback to be executed. Callbacks added here are // guaranteed to execute, even if they are added after the // `run` method is called. add: function(callback, contextOverride) { var promise = _.result(this._deferred, 'promise'); - + this._callbacks.push({cb: callback, ctx: contextOverride}); - + promise.then(function(args) { if (contextOverride) { args.context = contextOverride; } callback.call(args.context, args.options); }); }, - + // Run all registered callbacks with the context specified. // Additional callbacks can be added after this has been run // and they will still be executed. @@ -948,141 +948,141 @@ context: context }); }, - + // Resets the list of callbacks to be run, allowing the same list // to be run multiple times - whenever the `run` method is called. reset: function() { var callbacks = this._callbacks; this._deferred = Marionette.Deferred(); this._callbacks = []; - + _.each(callbacks, function(cb) { this.add(cb.cb, cb.ctx); }, this); } }); - + // Controller // ---------- - + // A multi-purpose object to use as a controller for // modules and routers, and as a mediator for workflow // and coordination of other objects, views, and more. Marionette.Controller = function(options) { this.options = options || {}; - + if (_.isFunction(this.initialize)) { this.initialize(this.options); } }; - + Marionette.Controller.extend = Marionette.extend; - + // Controller Methods // -------------- - + // Ensure it can trigger events with Backbone.Events _.extend(Marionette.Controller.prototype, Backbone.Events, { destroy: function() { Marionette._triggerMethod(this, 'before:destroy', arguments); Marionette._triggerMethod(this, 'destroy', arguments); - + this.stopListening(); this.off(); return this; }, - + // import the `triggerMethod` to trigger events with corresponding // methods if the method exists triggerMethod: Marionette.triggerMethod, - + // A handy way to merge options onto the instance mergeOptions: Marionette.mergeOptions, - + // Proxy `getOption` to enable getting options from this or this.options by name. getOption: Marionette.proxyGetOption - + }); - + // Object // ------ - + // A Base Class that other Classes should descend from. // Object borrows many conventions and utilities from Backbone. Marionette.Object = function(options) { this.options = _.extend({}, _.result(this, 'options'), options); - + this.initialize.apply(this, arguments); }; - + Marionette.Object.extend = Marionette.extend; - + // Object Methods // -------------- - + // Ensure it can trigger events with Backbone.Events _.extend(Marionette.Object.prototype, Backbone.Events, { - + //this is a noop method intended to be overridden by classes that extend from this base initialize: function() {}, - + destroy: function(options) { options = options || {}; - + this.triggerMethod('before:destroy', options); this.triggerMethod('destroy', options); this.stopListening(); - + return this; }, - + // Import the `triggerMethod` to trigger events with corresponding // methods if the method exists triggerMethod: Marionette.triggerMethod, - + // A handy way to merge options onto the instance mergeOptions: Marionette.mergeOptions, - + // Proxy `getOption` to enable getting options from this or this.options by name. getOption: Marionette.proxyGetOption, - + // Proxy `bindEntityEvents` to enable binding view's events from another entity. bindEntityEvents: Marionette.proxyBindEntityEvents, - + // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity. unbindEntityEvents: Marionette.proxyUnbindEntityEvents }); - + /* jshint maxcomplexity: 16, maxstatements: 45, maxlen: 120 */ - + // Region // ------ - + // Manage the visual regions of your composite application. See // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/ - + Marionette.Region = Marionette.Object.extend({ constructor: function(options) { - + // set options temporarily so that we can get `el`. // options will be overriden by Object.constructor this.options = options || {}; this.el = this.getOption('el'); - + // Handle when this.el is passed in as a $ wrapped element. this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el; - + if (!this.el) { throw new Marionette.Error({ name: 'NoElError', message: 'An "el" must be specified for a region.' }); } - + this.$el = this.getEl(this.el); Marionette.Object.call(this, options); }, - + // Displays a backbone view instance inside of the region. // Handles calling the `render` method for you. Reads content // directly from the `el` attribute. Also calls an optional @@ -1096,125 +1096,125 @@ if (!this._ensureElement()) { return; } - + this._ensureViewIsIntact(view); Marionette.MonitorDOMRefresh(view); - + var showOptions = options || {}; var isDifferentView = view !== this.currentView; var preventDestroy = !!showOptions.preventDestroy; var forceShow = !!showOptions.forceShow; - + // We are only changing the view if there is a current view to change to begin with var isChangingView = !!this.currentView; - + // Only destroy the current view if we don't want to `preventDestroy` and if // the view given in the first argument is different than `currentView` var _shouldDestroyView = isDifferentView && !preventDestroy; - + // Only show the view given in the first argument if it is different than // the current view or if we want to re-show the view. Note that if // `_shouldDestroyView` is true, then `_shouldShowView` is also necessarily true. var _shouldShowView = isDifferentView || forceShow; - + if (isChangingView) { this.triggerMethod('before:swapOut', this.currentView, this, options); } - + if (this.currentView && isDifferentView) { delete this.currentView._parent; } - + if (_shouldDestroyView) { this.empty(); - + // A `destroy` event is attached to the clean up manually removed views. // We need to detach this event when a new view is going to be shown as it // is no longer relevant. } else if (isChangingView && _shouldShowView) { this.currentView.off('destroy', this.empty, this); } - + if (_shouldShowView) { - + // We need to listen for if a view is destroyed // in a way other than through the region. // If this happens we need to remove the reference // to the currentView since once a view has been destroyed // we can not reuse it. view.once('destroy', this.empty, this); - + // make this region the view's parent, // It's important that this parent binding happens before rendering // so that any events the child may trigger during render can also be // triggered on the child's ancestor views view._parent = this; this._renderView(view); - + if (isChangingView) { this.triggerMethod('before:swap', view, this, options); } - + this.triggerMethod('before:show', view, this, options); Marionette.triggerMethodOn(view, 'before:show', view, this, options); - + if (isChangingView) { this.triggerMethod('swapOut', this.currentView, this, options); } - + // An array of views that we're about to display var attachedRegion = Marionette.isNodeAttached(this.el); - + // The views that we're about to attach to the document // It's important that we prevent _getNestedViews from being executed unnecessarily // as it's a potentially-slow method var displayedViews = []; - + var attachOptions = _.extend({ triggerBeforeAttach: this.triggerBeforeAttach, triggerAttach: this.triggerAttach }, showOptions); - + if (attachedRegion && attachOptions.triggerBeforeAttach) { displayedViews = this._displayedViews(view); this._triggerAttach(displayedViews, 'before:'); } - + this.attachHtml(view); this.currentView = view; - + if (attachedRegion && attachOptions.triggerAttach) { displayedViews = this._displayedViews(view); this._triggerAttach(displayedViews); } - + if (isChangingView) { this.triggerMethod('swap', view, this, options); } - + this.triggerMethod('show', view, this, options); Marionette.triggerMethodOn(view, 'show', view, this, options); - + return this; } - + return this; }, - + triggerBeforeAttach: true, triggerAttach: true, - + _triggerAttach: function(views, prefix) { var eventName = (prefix || '') + 'attach'; _.each(views, function(view) { Marionette.triggerMethodOn(view, eventName, view, this); }, this); }, - + _displayedViews: function(view) { return _.union([view], _.result(view, '_getNestedViews') || []); }, - + _renderView: function(view) { if (!view.supportsRenderLifecycle) { Marionette.triggerMethodOn(view, 'before:render', view); @@ -1224,13 +1224,13 @@ Marionette.triggerMethodOn(view, 'render', view); } }, - + _ensureElement: function() { if (!_.isObject(this.el)) { this.$el = this.getEl(this.el); this.el = this.$el[0]; } - + if (!this.$el || this.$el.length === 0) { if (this.getOption('allowMissingEl')) { return false; @@ -1240,7 +1240,7 @@ } return true; }, - + _ensureViewIsIntact: function(view) { if (!view) { throw new Marionette.Error({ @@ -1248,7 +1248,7 @@ message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.' }); } - + if (view.isDestroyed) { throw new Marionette.Error({ name: 'ViewDestroyedError', @@ -1256,56 +1256,56 @@ }); } }, - + // Override this method to change how the region finds the DOM // element that it manages. Return a jQuery selector object scoped // to a provided parent el or the document if none exists. getEl: function(el) { return Backbone.$(el, Marionette._getValue(this.options.parentEl, this)); }, - + // Override this method to change how the new view is // appended to the `$el` that the region is managing attachHtml: function(view) { this.$el.contents().detach(); - + this.el.appendChild(view.el); }, - + // Destroy the current view, if there is one. If there is no // current view, it does nothing and returns immediately. empty: function(options) { var view = this.currentView; - + var emptyOptions = options || {}; var preventDestroy = !!emptyOptions.preventDestroy; // If there is no view in the region // we should not remove anything if (!view) { return this; } - + view.off('destroy', this.empty, this); this.triggerMethod('before:empty', view); if (!preventDestroy) { this._destroyView(); } this.triggerMethod('empty', view); - + // Remove region pointer to the currentView delete this.currentView; - + if (preventDestroy) { this.$el.contents().detach(); } - + return this; }, - + // call 'destroy' or 'remove', depending on which is found // on the view (if showing a raw Backbone view or a Marionette View) _destroyView: function() { var view = this.currentView; if (view.isDestroyed) { return; } - + if (!view.supportsDestroyLifecycle) { Marionette.triggerMethodOn(view, 'before:destroy', view); } @@ -1313,7 +1313,7 @@ view.destroy(); } else { view.remove(); - + // appending isDestroyed to raw Backbone View allows regions // to throw a ViewDestroyedError for this view view.isDestroyed = true; @@ -1322,7 +1322,7 @@ Marionette.triggerMethodOn(view, 'destroy', view); } }, - + // Attach an existing view to the region. This // will not call `render` or `onShow` for the new view, // and will not replace the current HTML for the `el` @@ -1335,35 +1335,35 @@ this.currentView = view; return this; }, - + // Checks whether a view is currently present within // the region. Returns `true` if there is and `false` if // no view is present. hasView: function() { return !!this.currentView; }, - + // Reset the region by destroying any existing view and // clearing out the cached `$el`. The next time a view // is shown via this region, the region will re-query the // DOM for the region's `el`. reset: function() { this.empty(); - + if (this.$el) { // 2020-12-20 Changed for compatibility with jQuery 3. this.el = this.options.el; } - + delete this.$el; return this; } - + }, - + // Static Methods { - + // Build an instance of a region by passing in a configuration object // and a default region class to use if none is specified in the config. // @@ -1383,26 +1383,26 @@ if (_.isString(regionConfig)) { return this._buildRegionFromSelector(regionConfig, DefaultRegionClass); } - + if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) { return this._buildRegionFromObject(regionConfig, DefaultRegionClass); } - + if (_.isFunction(regionConfig)) { return this._buildRegionFromRegionClass(regionConfig); } - + throw new Marionette.Error({ message: 'Improper region configuration type.', url: 'marionette.region.html#region-configuration-types' }); }, - + // Build the region from a string selector like '#foo-region' _buildRegionFromSelector: function(selector, DefaultRegionClass) { return new DefaultRegionClass({el: selector}); }, - + // Build the region from a configuration object // ```js // { selector: '#foo', regionClass: FooRegion, allowMissingEl: false } @@ -1410,41 +1410,41 @@ _buildRegionFromObject: function(regionConfig, DefaultRegionClass) { var RegionClass = regionConfig.regionClass || DefaultRegionClass; var options = _.omit(regionConfig, 'selector', 'regionClass'); - + if (regionConfig.selector && !options.el) { options.el = regionConfig.selector; } - + return new RegionClass(options); }, - + // Build the region directly from a given `RegionClass` _buildRegionFromRegionClass: function(RegionClass) { return new RegionClass(); } }); - + // Region Manager // -------------- - + // Manage one or more related `Marionette.Region` objects. Marionette.RegionManager = Marionette.Controller.extend({ constructor: function(options) { this._regions = {}; this.length = 0; - + Marionette.Controller.call(this, options); - + this.addRegions(this.getOption('regions')); }, - + // Add multiple regions using an object literal or a // function that returns an object literal, where // each key becomes the region name, and each value is // the region definition. addRegions: function(regionDefinitions, defaults) { regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments); - + return _.reduce(regionDefinitions, function(regions, definition, name) { if (_.isString(definition)) { definition = {selector: definition}; @@ -1452,51 +1452,51 @@ if (definition.selector) { definition = _.defaults({}, definition, defaults); } - + regions[name] = this.addRegion(name, definition); return regions; }, {}, this); }, - + // Add an individual region to the region manager, // and return the region instance addRegion: function(name, definition) { var region; - + if (definition instanceof Marionette.Region) { region = definition; } else { region = Marionette.Region.buildRegion(definition, Marionette.Region); } - + this.triggerMethod('before:add:region', name, region); - + region._parent = this; this._store(name, region); - + this.triggerMethod('add:region', name, region); return region; }, - + // Get a region by name get: function(name) { return this._regions[name]; }, - + // Gets all the regions contained within // the `regionManager` instance. getRegions: function() { return _.clone(this._regions); }, - + // Remove a region by name removeRegion: function(name) { var region = this._regions[name]; this._remove(name, region); - + return region; }, - + // Empty all regions in the region manager, and // remove them removeRegions: function() { @@ -1504,10 +1504,10 @@ _.each(this._regions, function(region, name) { this._remove(name, region); }, this); - + return regions; }, - + // Empty all regions in the region manager, but // leave them attached emptyRegions: function() { @@ -1515,68 +1515,68 @@ _.invoke(regions, 'empty'); return regions; }, - + // Destroy all regions and shut down the region // manager entirely destroy: function() { this.removeRegions(); return Marionette.Controller.prototype.destroy.apply(this, arguments); }, - + // internal method to store regions _store: function(name, region) { if (!this._regions[name]) { this.length++; } - + this._regions[name] = region; }, - + // internal method to remove a region _remove: function(name, region) { this.triggerMethod('before:remove:region', name, region); region.empty(); region.stopListening(); - + delete region._parent; delete this._regions[name]; this.length--; this.triggerMethod('remove:region', name, region); } }); - + Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions'); - + // Template Cache // -------------- - + // Manage templates stored in `