diff --git a/docs/marionette.abstractview.md b/docs/marionette.abstractview.md index d35348aa5b..ee3d48c0c7 100644 --- a/docs/marionette.abstractview.md +++ b/docs/marionette.abstractview.md @@ -331,9 +331,9 @@ bound at the time of instantiation, and an exception will be thrown if the handlers on the view do not exist. The `modelEvents` and `collectionEvents` will be bound and -unbound with the Backbone.View `delegateEvents` and `undelegateEvents` -method calls. This allows the view to be re-used and have -the model and collection events re-bound. +unbound with the Backbone.View `delegateEntityEvents` and `undelegateEntityEvents` +method calls. `delegateEntityEvents` is called in the View's `constructor` and +entity events are unbound during the View's `destroy`. ### Multiple Callbacks diff --git a/src/abstract-view.js b/src/abstract-view.js index 2044334103..3db61553af 100644 --- a/src/abstract-view.js +++ b/src/abstract-view.js @@ -25,6 +25,8 @@ Marionette.AbstractView = Backbone.View.extend({ Backbone.View.apply(this, arguments); + this.delegateEntityEvents(); + Marionette.MonitorDOMRefresh(this); this.on('show', this.onShowCalled); }, @@ -89,23 +91,9 @@ Marionette.AbstractView = Backbone.View.extend({ }, {}, this); }, - // Overriding Backbone.View's delegateEvents to handle - // the `triggers`, `modelEvents`, and `collectionEvents` configuration - delegateEvents: function(events) { - this._delegateDOMEvents(events); - this.bindEntityEvents(this.model, this.getOption('modelEvents')); - this.bindEntityEvents(this.collection, this.getOption('collectionEvents')); - - _.each(this._behaviors, function(behavior) { - behavior.bindEntityEvents(this.model, behavior.getOption('modelEvents')); - behavior.bindEntityEvents(this.collection, behavior.getOption('collectionEvents')); - }, this); - - return this; - }, - - // internal method to delegate DOM events and triggers - _delegateDOMEvents: function(eventsArg) { + // Overriding Backbone.View's `delegateEvents` to handle + // `events` and `triggers` + delegateEvents: function(eventsArg) { var events = Marionette._getValue(eventsArg || this.events, this); // normalize ui keys @@ -123,13 +111,27 @@ Marionette.AbstractView = Backbone.View.extend({ _.extend(combinedEvents, behaviorEvents, events, triggers, behaviorTriggers); Backbone.View.prototype.delegateEvents.call(this, combinedEvents); + + return this; }, - // Overriding Backbone.View's undelegateEvents to handle unbinding - // the `triggers`, `modelEvents`, and `collectionEvents` config - undelegateEvents: function() { - Backbone.View.prototype.undelegateEvents.apply(this, arguments); + // Handle `modelEvents`, and `collectionEvents` configuration + delegateEntityEvents: function() { + this.undelegateEntityEvents(); + + this.bindEntityEvents(this.model, this.getOption('modelEvents')); + this.bindEntityEvents(this.collection, this.getOption('collectionEvents')); + + _.each(this._behaviors, function(behavior) { + behavior.bindEntityEvents(this.model, behavior.getOption('modelEvents')); + behavior.bindEntityEvents(this.collection, behavior.getOption('collectionEvents')); + }, this); + + return this; + }, + // Handle unbinding `modelEvents`, and `collectionEvents` configuration + undelegateEntityEvents: function() { this.unbindEntityEvents(this.model, this.getOption('modelEvents')); this.unbindEntityEvents(this.collection, this.getOption('collectionEvents')); diff --git a/test/unit/behaviors.spec.js b/test/unit/behaviors.spec.js index 1ed41d6820..771a884989 100644 --- a/test/unit/behaviors.spec.js +++ b/test/unit/behaviors.spec.js @@ -520,16 +520,16 @@ describe('Behaviors', function() { expect(this.handleCollectionResetStub).to.have.been.calledOnce.and.calledOn(this.fooBehavior); }); - it('should unbind model events on view undelegate', function() { + it('should unbind model events on view undelegateEntityEvents', function() { this.view = new this.ItemView({ model: this.model }); - this.view.undelegateEvents(); + this.view.undelegateEntityEvents(); this.model.set('foo', 'doge'); expect(this.handleModelFooChangeStub).not.to.have.been.called; }); - it('should unbind collection events on view undelegate', function() { + it('should unbind collection events on view undelegateEntityEvents', function() { this.view = new this.CollectionView({ collection: this.collection }); - this.view.undelegateEvents(); + this.view.undelegateEntityEvents(); this.collection.reset(); expect(this.handleCollectionResetStub).not.to.have.been.called; }); @@ -678,6 +678,7 @@ describe('Behaviors', function() { }); this.sinon.spy(this.view, 'undelegateEvents'); + this.sinon.spy(this.view, 'undelegateEntityEvents'); }); it('should call initialize on grouped behaviors', function() { @@ -703,6 +704,11 @@ describe('Behaviors', function() { expect(this.view.undelegateEvents).to.have.been.calledOnce; }); + it('should call undelegateEntityEvents once', function() { + this.view.undelegateEntityEvents(); + expect(this.view.undelegateEntityEvents).to.have.been.calledOnce; + }); + it('should proxy modelEvents to grouped behaviors', function() { this.model.trigger('change'); expect(this.barModelChangeStub).to.have.been.calledOnce.and.calledOn(this.barBehavior); @@ -767,6 +773,18 @@ describe('Behaviors', function() { this.view.undelegateEvents({}); expect(this.view.undelegateEvents).to.have.returned(this.view); }); + + it('delegateEntityEvents should return the view', function() { + this.sinon.spy(this.view, 'delegateEntityEvents'); + this.view.delegateEntityEvents(); + expect(this.view.delegateEntityEvents).to.have.returned(this.view); + }); + + it('undelegateEntityEvents should return the view', function() { + this.sinon.spy(this.view, 'undelegateEntityEvents'); + this.view.undelegateEntityEvents({}); + expect(this.view.undelegateEntityEvents).to.have.returned(this.view); + }); }); describe('.destroy', function() { diff --git a/test/unit/view.entity-events.spec.js b/test/unit/view.entity-events.spec.js index 64cfe47be3..1bf23853e2 100644 --- a/test/unit/view.entity-events.spec.js +++ b/test/unit/view.entity-events.spec.js @@ -114,7 +114,7 @@ describe('view entity events', function() { }); }); - describe('when undelegating events on a view', function() { + describe('when undelegating entity events on a view', function() { beforeEach(function() { this.View = Marionette.AbstractView.extend({ modelEvents : {'foo': 'foo'}, @@ -128,8 +128,8 @@ describe('view entity events', function() { collection : this.collection }); - this.sinon.spy(this.view, 'undelegateEvents'); - this.view.undelegateEvents(); + this.sinon.spy(this.view, 'undelegateEntityEvents'); + this.view.undelegateEntityEvents(); this.model.trigger('foo'); this.collection.trigger('bar'); @@ -144,7 +144,7 @@ describe('view entity events', function() { }); it('should return the view', function() { - expect(this.view.undelegateEvents).to.have.returned(this.view); + expect(this.view.undelegateEntityEvents).to.have.returned(this.view); }); }); @@ -162,9 +162,9 @@ describe('view entity events', function() { collection : this.collection }); - this.view.undelegateEvents(); - this.sinon.spy(this.view, 'delegateEvents'); - this.view.delegateEvents(); + this.view.undelegateEntityEvents(); + this.sinon.spy(this.view, 'delegateEntityEvents'); + this.view.delegateEntityEvents(); }); it('should fire the model event once', function() { @@ -177,8 +177,8 @@ describe('view entity events', function() { expect(this.barStub).to.have.been.calledOnce; }); - it('should return the view from delegateEvents', function() { - expect(this.view.delegateEvents).to.have.returned(this.view); + it('should return the view from delegateEntityEvents', function() { + expect(this.view.delegateEntityEvents).to.have.returned(this.view); }); });