diff --git a/packages/ember-glimmer/tests/integration/helpers/element-action-test.js b/packages/ember-glimmer/tests/integration/helpers/element-action-test.js index 97f1aa8f7b1..32e02a3c48d 100644 --- a/packages/ember-glimmer/tests/integration/helpers/element-action-test.js +++ b/packages/ember-glimmer/tests/integration/helpers/element-action-test.js @@ -1423,4 +1423,38 @@ moduleFor('Helpers test: element action', class extends RenderingTest { this.assertText('Click me'); } + + ['@test action handler that shifts element attributes doesn\'t trigger multiple invocations']() { + let actionCount = 0; + let ExampleComponent = Component.extend({ + selected: false, + actions: { + toggleSelected() { + actionCount++; + this.toggleProperty('selected'); + } + } + }); + + this.registerComponent('example-component', { + ComponentClass: ExampleComponent, + template: '' + }); + + this.render('{{example-component}}'); + + this.runTask(() => { + this.$('button').click(); + }); + + this.assert.equal(actionCount, 1, 'Click action only fired once.'); + this.assert.ok(this.$('button').hasClass('selected'), 'Element with action handler has properly updated it\'s conditional class'); + + this.runTask(() => { + this.$('button').click(); + }); + + this.assert.equal(actionCount, 2, 'Second click action only fired once.'); + this.assert.ok(!this.$('button').hasClass('selected'), 'Element with action handler has properly updated it\'s conditional class'); + } }); diff --git a/packages/ember-views/lib/system/event_dispatcher.js b/packages/ember-views/lib/system/event_dispatcher.js index 753c3b6d974..69baa9d95ab 100644 --- a/packages/ember-views/lib/system/event_dispatcher.js +++ b/packages/ember-views/lib/system/event_dispatcher.js @@ -219,6 +219,7 @@ export default EmberObject.extend({ rootElement.on(`${event}.ember`, '[data-ember-action]', evt => { let attributes = evt.currentTarget.attributes; + let handledActions = []; for (let i = 0; i < attributes.length; i++) { let attr = attributes.item(i); @@ -230,8 +231,12 @@ export default EmberObject.extend({ // We have to check for action here since in some cases, jQuery will trigger // an event on `removeChild` (i.e. focusout) after we've already torn down the // action handlers for the view. - if (action && action.eventName === eventName) { + if (action && action.eventName === eventName && handledActions.indexOf(action) === -1) { action.handler(evt); + // Action handlers can mutate state which in turn creates new attributes on the element. + // This effect could cause the `data-ember-action` attribute to shift down and be invoked twice. + // To avoid this, we keep track of which actions have been handled. + handledActions.push(action); } } }