Using this.sendAction() in components? - ember.js

I am using Ember 2.2.0
When writing components I used to propagate events from the component to the route (or whatever embeds the component) with this.sendAction(…). I checked out the docs lately and discovered that they advise another approach.
https://guides.emberjs.com/v2.2.0/components/triggering-changes-with-actions/
this.get('action')();
Since Ember is known to be very opinionated I want to stick as much to the best practices as possible. But I am not sure if the docs might be out of date or the tutorials using sendActions are.
So I would like what is the way to do this?

When you are using this.sendAction('actionName') you're bubbling up an action which you'll have to catch on the component/controller with actions
//controller/route/component.js
actions: {
actionName: function() {
//Do something
}
}
If you want to send that up the chain, you'll have to call sendAction('') again on the component/controller and catch it again on the parent (and so on).
The other approach this.get('action')() uses closure actions, which are regular javascript functions. These are the preferred way to invoke actions in Ember 1.13.X as far as I know. One neat thing that closure actions have is that you can have return values. Meaning that you can have something like this:
//a controller
actions: {
saveResult() {
return this.get('model').save(); //notice the return (which returns a promise)
}
}
//some template that uses the controller above
{{a-component save=(action 'saveResult')}} // Passes the saveResult action to the component
//a-component.js
actions: {
someAction: function() {
this.attrs.save().then(() => {
//Do something with the return value
});
}
}
A lot can be written about closure actions, but others have written far better than I could, so I recommend the following articles:
Ember Closure Actions #1
Ember Closure Actions #2
Ember Closure Actions #3
And if you're new to the whole DDAU (Data Down Actions Up) concept, I really recommend Sam's article about the concept in general.
Update: There's also an addon (linked in the comments by #locks) which allows closure actions to bubble to routes. Note that if you look to upgrade Ember to more recent versions (3.8 and up), route-actions will not be compatible.

For those using Ember version >=3.4:
Use closure actions instead of sendAction
sendAction has been deprecated in 3.4. You can find an example of how to define closure actions under the relevant deprecation text:
https://deprecations.emberjs.com/v3.x/#toc_ember-component-send-action

Related

Closure actions in Ember

I'm rewriting some old Ember code to use closure actions, but I'm new to Ember and I can't seem to figure out how to change a sendAction that hasn't got parameters.
This is the code in the component row.js:
click: function() {
this.sendAction();
}
The row.hbs as well as the parent templates are just
{{yield}}
so I can't add anything there it seems. Does anyone know how to solve this?
If it's just {{yield}} all the way up is it possible that the click didn't actually do anything?
Usually the calling template would have something like {{row action="doSomething"}} Ember Docs
It's also possible there was some behavior here that kept sending the action up when you have a {{yield}} in which case you would want to just keep going up the tree until you found (somewhere) an action="doSomething" declaration. That is where you will start sending the action back down.
e.g.
{{top action="doSomething"}}
{{row}}
{{/top}}
Would become
{{top}}
{{row doSomething=(action "doSomething")}}
{{/top}}
Then row.js
click() {
this.doSomething();
}

In an Ember route how can I check if an action exists?

In a component it is really easy to have an optional action provided to the component. In the JS of the component I can write:
if (this.get('someAction')) {
this.sendAction('someAction');
}
In my application route I have a 'generic action' that saves me providing widget components with long lists of actions, it looks like this:
genericAction: function(customActionName, customActionParams) {
this.send(customActionName, customActionParams);
}
For various reasons (including using genericAction in some components to fire an action a test could subscribe to, but the app not necessarily use in some hard to test async/pretender workflows) I would prefer to check the action exists, i.e:
genericAction: function(customActionName, customActionParams) {
if (this.get(customActionName)) {
this.send(customActionName, customActionParams);
}
}
Similar to how you can in a component, however this does not work, nor does this.controller.get(customActionName).
Other than keeping a hard coded list of actions, how can I achieve this?
If you keep your actions in the routes/application.js file itself then the code would be
In Ember 2.0 or later:
if(Em.get(this.actions, actionName)) {
this.send(actionName);
}
In Ember 1.13
this.actions is undefined in Ember 1.13, you have to use this._actions
if(Em.get(this._actions, actionName)) {
this.send(actionName);
}
If you need to support both Ember 1.x and 2.x then use something like:
let actions = this.actions || this._actions;
if(Em.get(actions, actionName)) {
this.send(actionName);
}
If you keep your actions in the application controller (controllers/application.js) then siva - abc's answer works great.
You can check for the actions in controller.actions. In your case, you have to check as
if(Em.get(this.controller.actions, actionName)) {
this.get('controller').send(actionName);
}
Here is a demo
if you are in a component you can use
if (this.get('yourActionName')) { }

didInsertElement in Ember 2.0+

Ember.View is deprecated, in favor of Components. That's great but I'm having trouble making sense of the 2.0 release.
Most often, I used the didInsertElement hook to run some jQuery code etc. But now that the Em.View class has been deprecated, how can I achieve the same thing? I don't want to create a component or anything like that. It doesn't make sense to create components for normal pages(routes). Simply because its not a re-usable thing plus component's scopes are isolated.
Say we have a about route, and when the template is rendered I just want to sun some jQuery code. How can I do this in Ember 2.0+?
You could take advantage of didTransition hook and Ember.run.next. Check my solution:
export default Ember.Route.extend({
actions: {
didTransition() {
Ember.run.next(this, 'initParticles');
}
},
initParticles() {
let ammount = (window.matchMedia('(max-width: 456px)').matches) ? 40 : 100;
particlesJS('particles-js', {
// my options
});
}
});
While it doesn't seem like the best approach, creating a component is probably your best option. Soon we'll have routeable components which will take over much of what controllers and views use to do. Creating a component and just inserting it into your template should put you on a good path to be ready for routeable components.

Catch All Action Handler

Now, this is really a question that's evolved from this. There is a whole lot more info there, but I guess, it's better to ask a direct specific question. So here it goes:
We typically define actions in a controller like so:
var FooController = Ember.Controller.extend({
actions: {
login: function() {
}
}
});
Is there a way to define a catch all action handler, like so (hypothetically):
actions: {
login: function() {
},
*: function(actionName, paramArray) {
}
}
This would be analogous to embers catch all route which I believe has been implemented, though I haven't tried it.
I need this because my Ember.Component renders a user supplied partial template using the {{partial}} helper. This partial might have {{action}}'s specified in them. These actions don't bubble up to the calling controller or route and are lost inside the component. This fact is mentioned in the docs in para 4.
If a catch all action was possible, my component could implement it and send the action back to the caller using something like this:
actions: {
*: function(actionName, paramArray) {
this.sendAction(actionName, paramArray)
}
}
The functionality around the actions hash on controllers, views and routes is provided by the action handler mixin [1]. As you can see, it injects the send(actionName) method and makes enables an object to receive actions. You can now easily override this function and catch all actions instead of looking for it in the actions hash -- which is what the implementation does.
[1] https://github.com/emberjs/ember.js/blob/v1.3.0/packages/ember-runtime/lib/mixins/action_handler.js#L8

Is there a way to get a callback when Ember.js has finished loading everything?

I am building an Ember.js app and I need to do some additional setup after everything is rendered/loaded.
Is there a way to get such a callback? Thanks!
There are also several functions defined on Views that can be overloaded and which will be called automatically. These include willInsertElement(), didInsertElement(), afterRender(), etc.
In particular I find didInsertElement() a useful time to run code that in a regular object-oriented system would be run in the constructor.
You can use the ready property of Ember.Application.
example from http://awardwinningfjords.com/2011/12/27/emberjs-collections.html:
// Setup a global namespace for our code.
Twitter = Em.Application.create({
// When everything is loaded.
ready: function() {
// Start polling Twitter
setInterval(function() {
Twitter.searchResults.refresh();
}, 2000);
// The default search is empty, let's find some cats.
Twitter.searchResults.set("query", "cats");
// Call the superclass's `ready` method.
this._super();
}
});
LazyBoy's answer is what you want to do, but it will work differently than you think. The phrasing of your question highlights an interesting point about Ember.
In your question you specified that you wanted a callback after the views were rendered. However, for good 'Ember' style, you should use the 'ready' callback which fires after the application is initialized, but before the views are rendered.
The important conceptual point is that after the callback updates the data-model you should then let Ember update the views.
Letting ember update the view is mostly straightforward. There are some edge cases where it's necessary to use 'didFoo' callbacks to avoid state-transition flickers in the view. (E.g., avoid showing "no items found" for 0.2 seconds.)
If that doesn't work for you, you might also investigate the 'onLoad' callback.
You can use jQuery ajax callbacks for this:
$(document).ajaxStart(function(){ console.log("ajax started")})
$(document).ajaxStop(function(){ console.log("ajax stopped")})
This will work for all ajax requests.
I simply put this into the Application Route
actions: {
loading: function(transition, route) {
this.controllerFor('application').set('isLoading', true);
this.router.on('didTransition', this, function(){
this.controllerFor('application').set('isLoading', false);
});
}
}
And then anywhere in my template I can enable and disable loading stuff using {{#if isLoading}} or I can add special jQuery events inside the actual loading action.
Very simple but effective.