Multiple Action Helpers on single element


#1

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)


#2

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!


#3

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.


#4

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?


#5

@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>

#6

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


#7

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


#8

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


#9

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


#10

Ember.JS has now HTMLBars.

Is it now possible?

@lukemelia any idea?


#11

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


#12

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?


#13

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>

#14

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