From dfb9d22aed4350ba4bfdc1f17d358cebb67d47f3 Mon Sep 17 00:00:00 2001 From: Wesley Workman Date: Mon, 27 Mar 2017 19:04:06 -0400 Subject: [PATCH] [BUGFIX release] Fixing IE and Edge issue (#15076) which caused action handlers to be fired twice. --- .../helpers/element-action-test.js | 34 +++++++++++++++++++ .../lib/system/event_dispatcher.js | 7 +++- 2 files changed, 40 insertions(+), 1 deletion(-) 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 ac3b0e68038..1c3cc0be79d 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 28d86a03162..06ecb730ce2 100644 --- a/packages/ember-views/lib/system/event_dispatcher.js +++ b/packages/ember-views/lib/system/event_dispatcher.js @@ -220,6 +220,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); @@ -231,8 +232,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.includes(action)) { 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.pushObject(action); } } }