diff --git a/docs/marionette.region.md b/docs/marionette.region.md index 5f9389d645..e803888eda 100644 --- a/docs/marionette.region.md +++ b/docs/marionette.region.md @@ -18,6 +18,7 @@ managing regions throughout your application. * [Showing a View](#showing-a-view) * [Hiding a View](#hiding-a-view) * [Preserving Existing Views](#preserving-existing-views) + * [Detaching Existing Views](#detaching-existing-views) * [Checking whether a region is showing a view](#checking-whether-a-region-is-showing-a-view) * [`reset` A Region](#reset-a-region) @@ -219,6 +220,23 @@ mainRegion.empty({preventDestroy: true}); **NOTE** When using `preventDestroy: true` you must be careful to cleanup your old views manually to prevent memory leaks. +### Detaching Existing Views +If you want to detach an existing view from a region, use `detachView`. + +```javascript +var myView = new MyView(); + +var myOtherView = new MyView(); + +var childView = new MyChildView(); + +// render and display the view +myView.showChildView('main', childView); + +// ... somewhere down the line +myOtherView.showChildView('main', myView.getRegion('main').detachView()); +``` + ### Checking whether a region is showing a view If you wish to check whether a region has a view, you can use the `hasView` diff --git a/docs/marionette.view.md b/docs/marionette.view.md index 27815add19..e3628989a8 100644 --- a/docs/marionette.view.md +++ b/docs/marionette.view.md @@ -20,6 +20,7 @@ multiple views through the `regions` attribute. * [Managing Sub-views](#managing-sub-views) * [Showing a view](#showing-a-view) * [Accessing a child view](#accessing-a-child-view) + * [Detaching a child view](#detaching-a-child-view) * [Organizing your View](#organizing-your-view) * [Events](#events) * [onEvent Listeners](#onevent-listeners) @@ -198,6 +199,34 @@ var MyView = Mn.View.extend({ If no view is available, `getChildView` returns `null`. +#### Detaching a child view +You can detach a child view from a region through `detachChildView(region)` + +```javascript + +var Mn = require('backbone.marionette'); +var SubView = require('./subview'); + +var MyView = Mn.View.extend({ + template: '#tpl-view-with-regions', + + regions: { + firstRegion: '#first-region', + secondRegion: '#second-region' + }, + + onRender: function() { + this.showChildView('firstRegion', new SubView()); + }, + + onMoveView: function() { + var view = this.detachChildView('firstRegion'); + this.showChildView('secondRegion', view); + } +}); +``` +This is a proxy for [region.detachView()](./marionette.region.md#detaching-existing-views) + ### Region availability Any defined regions within a `View` will be available to the `View` or any calling code immediately after instantiating the `View`. This allows a View to diff --git a/src/mixins/regions.js b/src/mixins/regions.js index 5342e06d46..e000234362 100644 --- a/src/mixins/regions.js +++ b/src/mixins/regions.js @@ -180,6 +180,10 @@ export default { return region.show(view, ...args); }, + detachChildView(name) { + return this.getRegion(name).detachView(); + }, + getChildView(name) { return this.getRegion(name).currentView; } diff --git a/src/region.js b/src/region.js index 42c0652e49..45e1b847ec 100644 --- a/src/region.js +++ b/src/region.js @@ -245,6 +245,19 @@ const Region = MarionetteObject.extend({ } }, + detachView() { + const view = this.currentView; + + if (!view) { + return; + } + + delete this.currentView; + this._detachView(view); + delete view._parent; + return view; + }, + _detachView(view) { const shouldTriggerDetach = !!view._isAttached; if (shouldTriggerDetach) { diff --git a/test/unit/region.spec.js b/test/unit/region.spec.js index 8898ff9083..b8cb519aec 100644 --- a/test/unit/region.spec.js +++ b/test/unit/region.spec.js @@ -449,6 +449,20 @@ describe('region', function() { }); }); + describe('and the view is detached', function() { + beforeEach(function() { + this.detachedView = this.region.detachView(); + this.noDetachedView = this.region.detachView(); + }); + + it('should return the childView it was given', function() { + expect(this.detachedView).to.equal(this.view); + }); + + it('should not return a childView if it was already detached', function() { + expect(this.noDetachedView).to.be.undefined; + }); + }); }); describe('when showing nested views', function() { diff --git a/test/unit/view.child-views.spec.js b/test/unit/view.child-views.spec.js index 88ff7beaf5..e7bdf68aa2 100644 --- a/test/unit/view.child-views.spec.js +++ b/test/unit/view.child-views.spec.js @@ -315,6 +315,21 @@ describe('layoutView', function() { it('childViewEvents are triggered', function() { expect(this.childEventsHandler).to.have.been.calledOnce; }); + + describe('and the view is detached', function() { + beforeEach(function() { + this.detachedView = this.layoutView.detachChildView('regionOne'); + this.noDetachedView = this.layoutView.detachChildView('regionOne'); + }); + + it('should return the childView it was given', function() { + expect(this.detachedView).to.equal(this.childView); + }); + + it('should not return a childView if it was already detached', function() { + expect(this.noDetachedView).to.be.undefined; + }); + }); }); describe('when showing a layoutView via a region', function() {