Multiple Action Helpers on single element

Cross post from: https://github.com/emberjs/ember.js/issues/3204

Use case:

define one action to occur on click and another action to occur on double-click.

Related:

ember.js#569: Have our views on this changed from one year ago? A custom view for this purpose seems heavy-handed to this embereño.

(cc @lukemelia)

Here’s a before and after for the current state of affairs:

Before:

        {{#each this}}
          <li {{bindAttr class=":template-page selected"}} {{action selectTemplatePage this}}>
            <div {{bindAttr class=":page-icon iconClassName"}}></div>
            <label>{{defaultTitle}}</label>
          </li>

We then have a new requirement: double-clicking the list item should immediately choose the template page instead of just selecting it. My first wish was to do:

   ...
   <li {{bindAttr class=":template-page selected"}} {{action selectTemplatePage this}}
         {{action chooseTemplatePage this on="doubleClick"}}>
  ...

Instead, I need to change things up fairly dramatically and do:

        {{#each this itemViewClass=Editor.TemplatePageListItemView}}
          <div {{bindAttr class=":page-icon iconClassName"}}></div>
          <label>{{defaultTitle}}</label>
        {{/each}}
Editor.TemplatePageListItemView = Ember.View.extend(Ember.ViewTargetActionSupport,
  tagName: 'li'
  classNames: ['template-page']
  classNameBindings: ['content.selected']
  click: ->
    @triggerAction
      action: 'selectTemplatePage'
      actionContext: this.get('content')
  doubleClick: ->
    @triggerAction
      action: 'chooseTemplatePage'
      actionContext: this.get('content')
)

To be clear, it’s not that I find the resulting code very problematic, it’s just that I think it is awkward to be forced into this particular refactoring in order to handle a doubleClick in addition to a click.

Your thoughts welcome!

1 Like

I should add that despite @pwagenet’s quick response “I can’t think of any clean way to handle this with Handlebars helper”, I see no serious technical obstacle preventing multiple action helpers from working as desired. If you know of an issue I’m not considering, let me know.

In starting to investigate this, it looks like the limiting factor to supporting multiple action helpers is that the way that Handlebars helpers work is to replace the {{...}} with something. In the case of an action helper, that something is an html attribute data-action-id="42" or whatever ID is returned when the action is registered.

Given this, perhaps the the best path would be to identify a syntax allowing multiple actions to registered in one {{action ... }} helper.

Thoughts? Maybe @wycats has an opinion?

@lukemelia I agree with your findings on the current state of affairs; it is awkward. When I needed to handle two different interactions on one element, I immediately jumped to the conclusion that all I had to do was add another helper. It was the intuitive option, but cost a fair bit of time debugging to find it didn’t work, hence my push for better documentation on the subject.

This is just a shot-in-the-dark idea as I haven’t given the code a thorough perusing yet, but would it be possible for the {{action}} helper to check for a pre-existing data-action-id attribute? Or maybe something similar to the functionality of jQuery’s addClass would be handy? Would space delimiting the IDs of the registered actions be a good choice?

Inital template:

<div {{action selectPage this}} {{action choosePage this on="doubleClick"}}>
  click me
</div>

First helper gets processed, which adds a new data-action-id:

<div data-action-id="42" {{action choosePage this on="doubleClick"}}>
  click me
</div>

Second helper gets processed, which updates the existing data-action-id:

<div data-action-id="42 43">
  click me
</div>
1 Like

HTMLBars, currently under development by @ebryn / @krisselden / @wycats / @machty will let us offer a solution to this.

FYI for search engine warriors: as of today, this thread still reflects the current state of Ember on this topic.

1 Like

Considering we will have an Ember + HTMLBars release shortly, is this something that will now be possible?

Did this ever progress? I really want a mouseDown and mouseUp action on a single element.

Ember.JS has now HTMLBars.

Is it now possible?

@lukemelia any idea?

It appears this is implemented in Ember.js 1.13. The relevant PR is: https://github.com/emberjs/ember.js/pull/11373

1 Like

You can bind different actions to different handlers. You can’t bind multiple actions to the same handler.

<button {{action foo}} {{action bar}}>
   Bar is ignored
</button>

Twiddle here.

Create static composite action definitions for every combination of actions you may want to trigger at once?

If you’re like me and you found this thread in a Google search, I’d recommend using DockYard’s ember-composable-helpers:

<button {{action (pipe (action 'yellLeeroyJenkins') (action 'getDestroyed'))}}>
  Let's do this
</button>
2 Likes

Hello, Folks. I support old (1.7) pre-1.13 version of Ember and stunned with same issue to add multiple actions. Upgrading is overkill for this cause this is eak-based (not ember-cli) project.

So the only solution for me is to use component. But with which I stunned is, how to connect this component with addToCart action from my views. Please help.

export default Em.Component.extend({
  classNames: ['addToCart2'],
  tagName:'button', 
  click: function(e) {
    return this.triggerAction({
      action: 'addToCart',
      actionContext: this.get('content')
    });
    console.log('click');
  },
  doubleClick: function(e) {
    this.get('content').incrementProperty('selected', 10);
    console.log('dclick');
  }
});

External action situated in app/views/food-list’ :

import SortableList from 'app/views/sortable-list'
import ItemView from 'app/views/sortable-list-item'

export default SortableList.extend({
  positionField: 'position',
  includedModel: 'food',
  sortingArray: 'menuFoods',
  handle: '.drag-handler',
  itemViewClass: ItemView.extend({
    templateName: 'menu/food_item', 
    isAdmin: Em.computed.alias('controller.isAdmin'),

    isVisibleObserver: function() {
      if (this.get('content.isDirty')) {
        this.get('content').save();
      }
    }.observes('content.isVisible'),    
    
    isEmpty: Em.computed.lt('content.selected', 1),
    actions: {
      addToCart: function() {
          this.get('content').incrementProperty('selected');
      }

...

Errors:

EmberError {description: undefined, fileName: undefined, lineNumber: undefined, message: "Nothing handled the action 'addToCard'. If you did…er in a controller, causing the action to bubble.", name: "Error", …}description: undefinedfileName: undefinedlineNumber: undefinedmessage: "Nothing handled the action 'addToCard'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble."name: "Error"number: undefinedstack: "Error: Nothing handled the action 'addToCard'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.↵ at new EmberError

Uncaught TypeError: Cannot read property 'incrementProperty' of undefined

@stefan @lukemelia @nate @Kilowhisky @Charles_Bourasseau @Gaurav0 @barneycarroll @lvl99