From 4e7d3bebce85414d08ae22ab4d5f5a05e3b5bbb8 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Mon, 8 Jun 2015 05:51:13 -0700 Subject: [PATCH] [BUGFIX beta] Fix issue with multiple actions in a single element. In prior versions of Ember multiple `{{action}}` helpers were not able to be attached to a single element. This was primarily due to the way that the actions were wired (via `data-ember-action` attribute). That original limitation seems like a bug, and this commit enables usage of multiple actions in a single DOM element by having the private `ActionManager` keep its internal representation of "actions for a given action ID" as an array and iterating that array when an event occurs. --- .../lib/keywords/element-action.js | 17 +++++--- .../tests/helpers/element_action_test.js | 42 ++++++++++++++++++- .../tests/helpers/render_test.js | 2 +- .../lib/system/event_dispatcher.js | 16 ++++--- packages/ember/tests/routing/basic_test.js | 14 +++---- 5 files changed, 71 insertions(+), 20 deletions(-) diff --git a/packages/ember-routing-htmlbars/lib/keywords/element-action.js b/packages/ember-routing-htmlbars/lib/keywords/element-action.js index a8111171c7f..7d276dda5c2 100644 --- a/packages/ember-routing-htmlbars/lib/keywords/element-action.js +++ b/packages/ember-routing-htmlbars/lib/keywords/element-action.js @@ -54,7 +54,10 @@ export default { }, render: function(node, env, scope, params, hash, template, inverse, visitor) { - var actionId = ActionHelper.registerAction({ + let actionId = env.dom.getAttribute(node.element, 'data-ember-action') || uuid(); + + ActionHelper.registerAction({ + actionId, node: node, eventName: hash.on || "click", bubbles: hash.bubbles, @@ -77,10 +80,14 @@ export var ActionHelper = {}; // that were using this undocumented API. ActionHelper.registeredActions = ActionManager.registeredActions; -ActionHelper.registerAction = function({ node, eventName, preventDefault, bubbles, allowedKeys }) { - var actionId = uuid(); +ActionHelper.registerAction = function({ actionId, node, eventName, preventDefault, bubbles, allowedKeys }) { + var actions = ActionManager.registeredActions[actionId]; + + if (!actions) { + actions = ActionManager.registeredActions[actionId] = []; + } - ActionManager.registeredActions[actionId] = { + actions.push({ eventName, handler(event) { if (!isAllowedEvent(event, allowedKeys)) { @@ -116,7 +123,7 @@ ActionHelper.registerAction = function({ node, eventName, preventDefault, bubble } }); } - }; + }); return actionId; }; diff --git a/packages/ember-routing-htmlbars/tests/helpers/element_action_test.js b/packages/ember-routing-htmlbars/tests/helpers/element_action_test.js index 5dba678bc5e..54bb4989b6f 100644 --- a/packages/ember-routing-htmlbars/tests/helpers/element_action_test.js +++ b/packages/ember-routing-htmlbars/tests/helpers/element_action_test.js @@ -633,7 +633,6 @@ QUnit.test("should allow 'send' as action name (#594)", function() { ok(eventHandlerWasCalled, "The view's send method was called"); }); - QUnit.test("should send the view, event and current context to the action", function() { var passedTarget; var passedContext; @@ -1053,6 +1052,47 @@ QUnit.test("a quoteless parameter that does not resolve to a value asserts", fun "Perhaps you meant to use a quoted actionName? (e.g. {{action 'save'}})."); }); +QUnit.test('allows multiple actions on a single element', function() { + var clickActionWasCalled = false; + var doubleClickActionWasCalled = false; + + var controller = EmberController.extend({ + actions: { + clicked() { + clickActionWasCalled = true; + }, + + doubleClicked() { + doubleClickActionWasCalled = true; + } + } + }).create(); + + view = EmberView.create({ + controller: controller, + template: compile(` + click me + `) + }); + + runAppend(view); + + var actionId = view.$('a[data-ember-action]').attr('data-ember-action'); + + ok(ActionManager.registeredActions[actionId], "The action was registered"); + + view.$('a').trigger('click'); + + ok(clickActionWasCalled, "The clicked action was called"); + + view.$('a').trigger('dblclick'); + + ok(doubleClickActionWasCalled, "The double click handler was called"); +}); + QUnit.module("ember-routing-htmlbars: action helper - deprecated invoking directly on target", { setup() { dispatcher = EventDispatcher.create(); diff --git a/packages/ember-routing-htmlbars/tests/helpers/render_test.js b/packages/ember-routing-htmlbars/tests/helpers/render_test.js index 482304a6152..cef3ed4ea13 100644 --- a/packages/ember-routing-htmlbars/tests/helpers/render_test.js +++ b/packages/ember-routing-htmlbars/tests/helpers/render_test.js @@ -461,7 +461,7 @@ QUnit.test("{{render}} helper should link child controllers to the parent contro var button = jQuery("#parent-action"); var actionId = button.data('ember-action'); - var action = ActionManager.registeredActions[actionId]; + var [ action ] = ActionManager.registeredActions[actionId]; var handler = action.handler; equal(button.text(), "Go to Mom", "The parentController property is set on the child controller"); diff --git a/packages/ember-views/lib/system/event_dispatcher.js b/packages/ember-views/lib/system/event_dispatcher.js index d8309a3f40a..7692d1e4be5 100644 --- a/packages/ember-views/lib/system/event_dispatcher.js +++ b/packages/ember-views/lib/system/event_dispatcher.js @@ -190,13 +190,17 @@ export default EmberObject.extend({ rootElement.on(event + '.ember', '[data-ember-action]', function(evt) { var actionId = jQuery(evt.currentTarget).attr('data-ember-action'); - var action = ActionManager.registeredActions[actionId]; + var actions = ActionManager.registeredActions[actionId]; - // 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) { - return action.handler(evt); + for (let index = 0, length = actions.length; index < length; index++) { + let action = actions[index]; + + // 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) { + return action.handler(evt); + } } }); }, diff --git a/packages/ember/tests/routing/basic_test.js b/packages/ember/tests/routing/basic_test.js index cb24d6dd8e3..d0d88d699cc 100644 --- a/packages/ember/tests/routing/basic_test.js +++ b/packages/ember/tests/routing/basic_test.js @@ -1164,7 +1164,7 @@ QUnit.asyncTest("Events are triggered on the controller if a matching action nam bootApplication(); var actionId = Ember.$("#qunit-fixture a").data("ember-action"); - var action = ActionManager.registeredActions[actionId]; + var [ action ] = ActionManager.registeredActions[actionId]; var event = new Ember.$.Event("click"); action.handler(event); }); @@ -1198,7 +1198,7 @@ QUnit.asyncTest("Events are triggered on the current state when defined in `acti bootApplication(); var actionId = Ember.$("#qunit-fixture a").data("ember-action"); - var action = ActionManager.registeredActions[actionId]; + var [ action ] = ActionManager.registeredActions[actionId]; var event = new Ember.$.Event("click"); action.handler(event); }); @@ -1236,7 +1236,7 @@ QUnit.asyncTest("Events defined in `actions` object are triggered on the current bootApplication(); var actionId = Ember.$("#qunit-fixture a").data("ember-action"); - var action = ActionManager.registeredActions[actionId]; + var [ action ] = ActionManager.registeredActions[actionId]; var event = new Ember.$.Event("click"); action.handler(event); }); @@ -1271,7 +1271,7 @@ QUnit.asyncTest("Events are triggered on the current state when defined in `even bootApplication(); var actionId = Ember.$("#qunit-fixture a").data("ember-action"); - var action = ActionManager.registeredActions[actionId]; + var [ action ] = ActionManager.registeredActions[actionId]; var event = new Ember.$.Event("click"); action.handler(event); }); @@ -1310,7 +1310,7 @@ QUnit.asyncTest("Events defined in `events` object are triggered on the current bootApplication(); var actionId = Ember.$("#qunit-fixture a").data("ember-action"); - var action = ActionManager.registeredActions[actionId]; + var [ action ] = ActionManager.registeredActions[actionId]; var event = new Ember.$.Event("click"); action.handler(event); }); @@ -1392,7 +1392,7 @@ QUnit.asyncTest("Actions are not triggered on the controller if a matching actio bootApplication(); var actionId = Ember.$("#qunit-fixture a").data("ember-action"); - var action = ActionManager.registeredActions[actionId]; + var [ action ] = ActionManager.registeredActions[actionId]; var event = new Ember.$.Event("click"); action.handler(event); }); @@ -1431,7 +1431,7 @@ QUnit.asyncTest("actions can be triggered with multiple arguments", function() { bootApplication(); var actionId = Ember.$("#qunit-fixture a").data("ember-action"); - var action = ActionManager.registeredActions[actionId]; + var [ action ] = ActionManager.registeredActions[actionId]; var event = new Ember.$.Event("click"); action.handler(event); });