If I define an action in a view:
{{action handleClick}}
The method that handles the action is no longer provided a reference to an event. In the past an the event was the first argument and the context was accessible in the event object. Now only the context is passed as the first argument. Although this is great in many situations, access to the original event is required in some situations.
For example, in a list of items, it is often useful to respond only to clicks on certain elements in each listing (without having to create a rumbling herd of sub-views) or stop propagation of an event.
+1
context as the first argument and event as the second.
+1
One example where this is useful is when wrapping 3rd party UI widgets with Ember. For example twitter bootstrap has several methods that rely on the event object.
$('a[data-toggle="tab"]').on('shown', function (e) {
e.target // activated tab
e.relatedTarget // previous tab
})
Although there are ways to work around some of these issues, it becomes increasingly difficult to simply grab a widget and add it to the application. There are also cases where we have to re-trigger an even on a parent element because an action stops the event bubbling, which again some widgets rely on. For example, with twitter bootstrap a drop down menu would normally close on its own, but now we have to add custom code to manually handle that situation.
Again there are work arounds, and in most cases dealing with the event directly is not the best idea, but it can be useful and removing access completely can really impact development.
@cloke why are you trying to do this via the action helper and not via an Ember.View that listens on the appropriate events?
@realityendshere @darthdeus @lfridael if you want to filter by elements in each listing, you can get access to anything in the current scope (by {{action 'edit' post}} for example). Why do you need access to DOM?
@wycats Previously, my app contained a listing of items where clicking on certain elements within the listing would 1) perform a specific action (i.e. "select" the item), 2) prevent the event from bubbling up to a containing element and causing an opposing action (i.e. deselect all listings), and 3) determine which modifier keys were pressed at the time of the click to modify the action (i.e. select a range or toggle the selection state of a given item). The method for managing the selected items was mixed in at the collection view level. Each list item used the action helper to call a "selectListItem" method in the "selectable" mixin I created for my app.
To address the removal of the event object from the action callback method signature, I stopped using the action helper and placed a "click" method in the item view to intercept click events, identify the pressed modifier keys, and call an updated version of my mixin's "selectListItem" method. This is a satisfactory (and likely better) resolution to my specific problem.
That said, unless I am missing something, action helpers can now only address needs 1 and 2 listed in the first paragraph. The original event does contain data --- such as pointer position and active modifier keys --- that could be useful shorthand when communicating between related views (e.g. a list item and its parent collection view). While I certainly understand the desire to deter developers from sending DOM events into controllers, the original event data can still be used between views, as an intermediate iteration between a simple action and a more complex event handling method, or to send events along to non-Ember widgets.
@realityendshere you are correct. If you want to do DOM event handling, you should do that via a view's event handlers, not via {{action}}.
Remember that events bubble up through the view hierarchy (via normal DOM bubbling), so you can simply register your event handlers on the parent view and things will work as expected.
In my case I want to access (+have been accessing) pageX and pageY targeting a view, which in turn was generating a higher-level event. As @realityendshere is suggesting, this seems like a reasonable pattern?
I _can_ handle this by creating a sub-view for every location where I track where the user clicks, but I'd rather not. I _can_ add it only to the parent view, and filter by the generating element..... but again, this is a lot less terse than having my one view with several different click methods (one of which tracks the location).
I assume there's a significant performance gain to be had from not passing on evt, because otherwise, this seems sort of arbitrary. Its useful to be able to access familiar javascript event information.
What about HTML5 drop events ? For example, in my case, a drop on the UI is supposed to result in a record creation. First, I used a View to handle the drop, which was calling the controller, passing the event.dataTransfer content. But it seems that controllers are not made to handle data persistency, more data manipulation for display. The routes are supposed to do so (if there is any, or a specific internal component). If I let the drop event bubble from the view to the route, I should have a 'drop' handler in the route (which in my opinion is bad, cause too close to the DOM). Is there any best practice for this ?
@salper I think you should just send an action from the drop method in the view, just like {{action}} does. Then, the view do not care if this is the controller or the route, or even nobody that handles this action. This is perhaps not a "best practice", but this is how I use it :).
Anyway, I'll be glad to hear better solution, even if I do not think mine is so bad!
@lcoq Well, it seems good enough, thanks.
What do we do now the Ember.View is being deprecated?
@caffinatedmonkey Not sure what you are looking to do. This section on the view documentation:
Still applies to components. You can just handle the event with a method.
@caffinatedmonkey @mixonic I may be able to explain a use case
In a component I have a dataset and use an {{#each}} to loop through it. When I click on one of the datum I want to send an action to the component but I need that specific element clicked on to perform some logic (say hiding / masking the rest of the dataset)
Inside of a standard template I am able to do something like this (this is from an older app that is still on 1.08) there may be a new way of doing this. But this is what I expected to do inside the component I'm writing (on a 1.13 app)
{{#each link in theLinks}}
<div {{action 'clickedLink' this link}}>Some content</div>
{{/each}}
where this is the actual DOM element and link is the item being iterated through. However in the component I'm writing today (1.13) this is referring to the component itself. Am I not doing this the ember way? Should I have nested components for the things being clicked on?
Update
I was able to get it to work by making the element that I want to click on a sub component. Inside that components controller I implemented the click event like so
click: function(evt, view){
this.get('parentView').send('clickedLink', this.get('link'), evt);
}
This still doesn't seem like the ember way but I was having issues with sendAction. Hopefully this helps someone googling in the future
If you want the event passed to your handler in Angular you just add 'event' to the parameter list. The Ember equivalent would be something like:
{{action 'param1' 'param2' event}}
Would have been nice.
I think another way to get the event is to use the action helper as a parameter for the bare javascript handler.
<div onclick={{action 'name' 'param'}}>Click here</div>
Now the action name will get 2 parameters. The first being 'param' and the second the event.
But you still have to handle event.stopPropagation() and event.preventDefault() which are normally handled by the action helper.
Could we not have a named argument passEventAsLastArgument=true that will add the event to the arguments for the action if it is set? Seems like overkill to have to use the above or make another component each time you would just like the javascript event.
Most helpful comment
I think another way to get the event is to use the action helper as a parameter for the bare javascript handler.
Now the action
namewill get 2 parameters. The first being 'param' and the second the event.But you still have to handle
event.stopPropagation()andevent.preventDefault()which are normally handled by the action helper.Could we not have a named argument
passEventAsLastArgument=truethat will add the event to the arguments for the action if it is set? Seems like overkill to have to use the above or make another component each time you would just like the javascript event.