call another controller method from an action method in emberjs - ember.js

I am trying to call a second controller method from an action method.
I am able to call second controller method using
this.controller.get('controllers.secondController').method();
How can i call method of another controller from action method?
There is work around i know of like call the controller of the current action method which in turn calls the second controller method.
this.callFirstControllerMethod();
and then use
this.controller.get('controllers.secondController').method();
But is there a way to call the second controller method directly?

Generally this kind of logic should not be on the controller, a controller should really only have actions that relate to the UI or the display of your model.
I would suggest to add an action in the route, that can access the controllers and invoke any methods you need.
// route.js
actions: {
doSomething: function() {
var controllerA = this.controllerFor('firstController');
var controllerB = this.controllerFor('secondController');
controllerA.doSomething();
controllerB.doSomethingElse();
}
}

Related

How to trigger didReceiveAttrs in Ember component

Using version 2.17. I have an Ember component inside an /edit route with a controller:
// edit.hbs
{{ingredient-table recipe=model ingredients=model.ingredients}}
Inside my component, I am using a didRecieveAttrs hook to loop through ingredients on render, create proxy objects based off of each, and then build an ingredient table using those proxy objects.
// ingredient-table.js
didReceiveAttrs() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
this.set('recipeIngredients', Object.values(uniqueIngredients));
}
I also have a delete action, which I invoke when a user wishes to delete a row in the ingredient table. My delete action looks like this:
// ingredient-table.js
deleteIngredient(ingredient) {
ingredient.deleteRecord();
ingredient.save().then(() => {
// yay! deleted!
})
}
Everything mentioned above is working fine. The problem is that the deleted ingredient row remains in the table until the page refreshes. It should disappear immediately after the user deletes it, without page refresh. I need to trigger the didReceiveAttrs hook again. If I manually call that hook, all my problems are solved. But I don't think I should be manually calling it.
Based on the docs, it is my understanding that this hook will fire again on page load, and on re-renders (not initiated internally). I'm having some trouble figuring out what this means, I guess. Here's what I've tried:
1) calling ingredients.reload() in the promise handler of my save in ingredient-table.js (I also tried recipe.reload() here).
2) creating a controller function that calls model.ingredients.reload(), and passing that through to my component, then calling it in the promise handler. (I also tried model.reload() here).
Neither worked. Am I even using the right hook?
I suppose recipeIngredients is the items listed in the table. If that is the case; please remove the code within didReceiveAttrs hook and make recipeIngredients a computed property within the component. Let the code talk:
// ingredient-table.js
recipeIngredients: Ember.computed('ingredients.[]', function() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
return Object.values(uniqueIngredients)
})
My guess is didReceiveAttrs hook is not triggered again; because the array ingredients passed to the component is not changed; so attrs are not changed. By the way; do your best to rely on Ember's computed properties whenever possible; they are in the hearth of Ember design.

Ember observer / run schedule for each time a route is loaded

I have a property in an Ember controller which has an observer. This is called when I first visit the page / route and also when I change the a dropdown that the value is bound to (as expected).
What isn't happening, is the observer being fired when I re-visit the page and the value is re-set (to the same value it was when I left the page).
The reason I need this, is that the value is used as a filter for another function and that function is called inside the observer.
Is there a way to make an observer fire even if the value is the same as it was before?
Or alternatively, a sensible way to fire a function (perhaps via the run loop) from a controller, each time the controller is loaded / route visited?
In setupController route hook, you will get controller reference so you can use that to call observer function.
setupController(controller,model){
this._super(...arguments);
controller.get('testObserver')();
}
I prefer below style to observe a property.
setupController(controller, model){
if (this.get('somelogic')) {
controller.addObserver('yourProperty', controller, this.get('callback'));
}
}
resetController(controller, isExisting, transition) {
if (isExisting) {
controller.removeObserver('yourProperty', controller, this.get('callback'));
}
}

Ember.JS A BEFORE() function for every controller action

Is it possible to create some sort of before() function for every action inside a controller?
I know I can do an init() for every action, but what if I have a some code I want to run for every thing? It would be way too redundant to create the same init() function for each action.
No, there is no way to run code before every action without explicitly calling it yourself. At one point there was an idea to lookup actions using get(), which would have allowed this, but it never got implemented. Not sure what your use case is, but if you really do need to call functionality before every action call, just call it yourself. It'll be much more readable than any clever hacks we could come up with.
You could possibly do something like this:
First make sure your controller extends the `Ember.TargetActionSupport' mixin.
Ember.Controller.extend(Ember.TargetActionSupport,{
...
});
This will allow you to trigger actions programmatically. See more about this: Ember.TargetActionSupport
Then you would make an entry point action that contains your common code and a call to trigger the proper action:
Ember.Controller.extend(Ember.TargetActionSupport,{
actions: {
common: function (actionName) {
//Do common logic here
this.set('foo', 'bar');
//And then trigger the intended action
this.triggerAction({
action: actionName,
target: this
});
},
otherAction: function () {
//Do some more logic here
this.set('bar', 'baz');
}
}
});
You would call this action from your template like this:
<button {{action 'common' 'otherAction'}}>Press Me</button>

Make a programmatically Created Controller Delegate (Bubble) Events

I have an app with many similar views which I instantiate programmatically to "DRY-up" my app.
The problem is that controllers instantiated programmatically do not delegate actions in the actions hash further. This is clear because there is nothing from which the controller can derive the hierarchy. There should be a way, however, to tell a controller which parent controller it has to use for event bubbling. Does anyone know it?
You shouldn't be initializing controller's on your own. All controller initialization should be handled by Ember itself. Another interesting note, controller's are intended to be singletons in the application. The only exception to this being the itemController when looping over an ArrayController. You can read more about it in the guides. Quote from the guides:
In Ember.js applications, you will always specify your controllers as
classes, and the framework is responsible for instantiating them and
providing them to your templates.
This makes it super-simple to test your controllers, and ensures that
your entire application shares a single instance of each controller.
Update 1:
An example of how to do routing for a wizard:
App.Router.map(function() {
this.resource('wizard', function() {
this.route('step1');
this.route('step2');
this.route('step3');
});
});
This way, you can have a separate controller/view/template per step of the wizard. If you have logic around how much of each step should be completed prior to transitioning to the next one, you can handle that in the individual routes.
Update 2:
In the event that the number of steps aren't predetermined, but are based on the data being fed to the app, you can make a WizardController that is an ArrayController where each item in the array is a step in the wizard. Then, use the lookupItemController hook on the ArrayController, kind of like this:
App.WizardRoute = Ember.Route.extend({
model: function() {
return [
{controllerName: 'step1', templateName: 'step1'},
{controllerName: 'step2', templateName: 'step2'},
{controllerName: 'step3', templateName: 'step3'}
];
}
});
App.WizardController = Ember.ArrayController.extend({
lookupItemController: function(modelObject) {
return modelObject.controllerName;
}
});
{{#each step in controller}}
{{view Ember.View templateName=step.templateName}}
{{/each}}
As another, probably better, alternative, you can override the renderTemplate hook in the route where you're pulling down the model for the next step in the wizard and pass in the appropriate templateName and controller in the render call, kind of like you see here.
Point being, I think it should be possible to do this without having to instantiate controllers yourself.

Using Ember.js, how do I run some JS after a view is rendered?

How do I run a function after an Ember View is inserted into the DOM?
Here's my use-case: I'd like to use jQuery UI sortable to allow sorting.
You need to override didInsertElement as it's "Called when the element of the view has been inserted into the DOM. Override this function to do any set up that requires an element in the document body."
Inside the didInsertElement callback, you can use this.$() to get a jQuery object for the view's element.
Reference: https://github.com/emberjs/ember.js/blob/master/packages/ember-views/lib/views/view.js
You can also use afterRender method
didInsertElement: function () {
Ember.run.scheduleOnce('afterRender', this, function () {
//Put your code here what you want to do after render of a view
});
}
Ember 2.x: View is deprecated, use component instead
You have to understand the component's lifecycle to know when does certain things happen.
As components are rendered, re-rendered and finally removed, Ember provides lifecycle hooks that allow you to run code at specific times in a component's life.
https://guides.emberjs.com/v2.6.0/components/the-component-lifecycle/
Generally, didInsertElement is a great place to integrate with 3rd-party libraries.
This hook guarantees two (2) things,
The component's element has been both created and inserted into the DOM.
The component's element is accessible via the component's $() method.
In you need JavaScript to run whenever the attributes change
Run your code inside didRender hook.
Once again, please read the lifecycle documentation above for more information
Starting with Ember 3.13, you can use components that inherit from Glimmer, and this example below shows what that could look like:
import Component from '#glimmer/component';
import { action } from '#ember/object';
/* global jQuery */
export default class MyOctaneComponent extends Component {
#action configureSorting(element) {
jQuery(element).sortable();
}
}
<div {{did-insert this.configureSorting}}>
<span>1</span>
<span>2</span>
<span>3</span>
</div>
These view style components don't have lifecycle hooks directly, instead, you can use render-modifiers to attach a function. Unofficial introduction to modifiers can be found here
The benefit of this is that, it's clearer what the responsibilities of the template are and become.
Here is a runnable codesandbox if you want to play around with this:
https://codesandbox.io/s/octane-starter-ftt8s
You need to fire whatever you want in the didInsertElement callback in your View:
MyEmberApp.PostsIndexView = Ember.View.extend({
didInsertElement: function(){
// 'this' refers to the view's template element.
this.$('table.has-datatable').DataTable();
}
});