diff --git a/src/component/EventsCollector.js b/src/component/EventsCollector.js index 137f750a..60a346e0 100644 --- a/src/component/EventsCollector.js +++ b/src/component/EventsCollector.js @@ -56,7 +56,7 @@ class EventsCollector extends Disposable { if (!this.eventHandles_[selector]) { var fn = this.getListenerFn_(fnName); - this.eventHandles_[selector] = this.component_.delegate(eventType, selector, fn); + this.eventHandles_[selector] = this.component_.delegate(eventType, selector, this.onEvent_.bind(this, fn)); } } @@ -157,6 +157,24 @@ class EventsCollector extends Disposable { fnComponent = fnComponent || this.component_; return fnComponent[fnName].bind(fnComponent); } + + /** + * Fires when an event that was registered by this collector is triggered. Makes + * sure that the event was meant for this component and calls the appropriate + * listener function for it. + * @param {!function(!Object)} fn + * @param {!Object} event + * @return {*} The return value of the call to the listener function, or undefined + * if no function was called. + * @protected + */ + onEvent_(fn, event) { + // This check prevents parent components from handling their child inline listeners. + if (!event.handledByComponent || event.handledByComponent === this.component_) { + event.handledByComponent = this.component_; + return fn(event); + } + } } export default EventsCollector; diff --git a/test/src/component/EventsCollector.js b/test/src/component/EventsCollector.js index ab86291f..92b9a620 100644 --- a/test/src/component/EventsCollector.js +++ b/test/src/component/EventsCollector.js @@ -144,6 +144,33 @@ describe('EventsCollector', function() { assert.strictEqual(2, custom.handleClick.callCount); }); + it('should not trigger sub component listeners on parent components', function() { + var SubComponent = createCustomComponent('
'); + SubComponent.prototype.handleClick = sinon.stub(); + + var child; + var CustomComponent = createCustomComponent(); + CustomComponent.prototype.renderInternal = function() { + dom.append(this.element, '
'); + child = new SubComponent().render(this.element); + }; + CustomComponent.prototype.handleClick = sinon.stub(); + + var custom = new CustomComponent().render(); + var collector = new EventsCollector(custom); + collector.attachListeners(custom.element.innerHTML, 'group'); + var childCollector = new EventsCollector(child); + childCollector.attachListeners(child.element.innerHTML, 'group'); + + dom.triggerEvent(child.element.childNodes[0], 'click'); + assert.strictEqual(0, custom.handleClick.callCount); + assert.strictEqual(1, child.handleClick.callCount); + + dom.triggerEvent(custom.element.childNodes[0], 'click'); + assert.strictEqual(1, custom.handleClick.callCount); + assert.strictEqual(1, child.handleClick.callCount); + }); + it('should detach listeners that are unused', function() { var CustomComponent = createCustomComponent( '
'