Running Code in active Controller after transitionToRoute - ember.js

I'm getting to know emberjs by building a simple pomodoro app. My problem is running code in the newly active controller after a transitionToRoute has occured.
Here's where I create a new Pomodoro record:
App.PomodorosNewController = Ember.ObjectController.extend({
createPomodoro: function() {
this.get('model.transaction').commit();
this.transitionToRoute('pomodoros.pomodoro', this.get('model'));
},
});
As you can see I create the record, then transition to the newly created record's view. Which is using this controller:
App.PomodorosPomodoroController = Ember.ObjectController.extend({});
My question is how do I run code in this controller after the transition has occured? This there a way I can detect a transitionToRoute in the receiving controller?

You can implement the setupController function in your PomodorosPomodoroRoute
This will be invoked every time you transition to that route and can be used to set up your controller and anything you need for the view.
App.PomodorosPomodoroRoute = Ember.Route.extend({
setupController: function(controller, model) {
this._super.apply(this, arguments);
//implement your code here
}
});

Related

Ember.js action bubbling but controller undefined in action

I have a route that has an afterModel hook.
afterModel: function(model, transition){
transition.send('doInAppRoute');
}
I have an action in my application route:
doInAppRoute: function(){
var controller = this.get('controller');
controller.set('someProp', true);
}
When I allow the action to bubble from the route with the afterModel hook, I get the following error.
Error while processing route: embed Cannot read property 'set' of undefined TypeError: Cannot read property 'set' of undefined
If I put an action call to doInAppRoute in the application template, everything runs as expected.
If the action call to doInAppRoute bubbles, this.get('controller') in my application route is undefined. Why?
And how can this be changed so the bubbled action updates the application controller property?
Thanks to #torazaburo for leading me in the right direction.
This is what I did.
Set someProp to an initial value in application route.
someProp: false,
Then in the the application route action do something like:
doInAppRoute: function(){
this.set('someProp', true);
}
And then in the application route setupController do:
setupController: function(controller, model){
controller.set('someProp', this.get('someProp'));
controller.set('model', model);
}
Then everything should work.

Ember.js how to get a model inside beforeModel/afterModel hooks and pass it to the controller?

I have a problem getting the models inside a controller of a route accessed using {{link-to}}
From my understanding (after reading http://emberjs.com/guides/routing/asynchronous-routing/) the model hook of a route doesn't get called when the route is accessed from a {{link-to 'route' model}}. The model is passed directly to the controller. This is a way that Ember ensures that no AJAX called will be made unnecessarily.
For example if I go to {{link-to 'post-review' post}} and I need to pass more than a post model to the PostReviewController.
App.Router.map(function () {
...
this.resource('post-review' , {path: '/post-review/:id'});
...
});
PostReviewRoute = Ember.Route.extend({
//method doesn't get called
model: function(params){
return Em.RSVP.hash({
post: this.store.find('post', params.id),
reviewTypes: this.store.find('reviewType')
});
}
});
The ReviewTypeis a model which has no relationship with Post so that I can access it directly using post.reviewType. A post has several reviews. A review has a reviewType. But I must show all the reviewTypes inside of a combobox.
Anyways, the model hook doesn't get called and I cannot access the this.get('reviewTypes') from PostReviewController. I understand that the beforeModel or afterModel hooks are used for this purpose: to pass additional models to a controller when the route it's accessed from a link-to and not directly from the browser URL. The documentation doesn't show how you can do that! Please enlighten me if you know how!
Thanks!
setupController to the rescue, in your route use the setupController hook to set the reviewTypes like so:
PostReviewRoute = Ember.Route.extend({
setupController: function(controller, model){
var postId = model.get('id');
this._super.apply(this, arguments);
this.store.find('reviewType').then((records)=> {
controller.set('reviewTypes', records);
});
this.store.find('post', postId).then(....)
},
// your code
});
You should now be able to call this.get('reviewTypes').
Let me know if this does it for you.

Loading Routes in nested route Hierachy

I am working on a mobile application with Ember. I want to make the user experience as good as possible and try to take into account that on mobile the connection is not always as good, that is why I want to utilize the loading routes with a loading spinner. Unfortunately in one case it is not behaving as I would expect:
In my Nested route Setup:
UserRoute:
UserIndexRoute (=Profile)
UserFriendsRoute
On the UserRoute I only load a small version (=different model) of the user. In 95% of the cases this model is already loaded when I want to navigate there. And in the Subroutes (e.g. UserIndexRoute and UserFriendsRoute I only need the full user.
What I want to achieve is that the UserRoute with its template is directly rendered when navigating to e.g. UserIndexRoute and then in the outlet for the Index part I want the UserLoadingView to be rendered. But the rendering always waits for all promises to be resolved and the UserLoadingView is never shown.
How can I force Ember to render the UserRoute and then the UserLoadingView in the outlet until the UserIndexRoute Model is resolved?
How I implemented it:
afterModel: function(model, transition){
var _this = this,
params = Ember.get(transition, 'params.user');
this.get('store').find('user', params.user_id).then(function(user){
_this.transitionTo('user.profile', user);
});
}
Don't use the index route for fetching the full model, just use it as a means for redirection.
Do something like this:
UserRoute:
UserIndexRoute
UserFooIndexRoute (=Profile) (Naming is up to you)
UserFriendsRoute
Then hook up your index route to fetch the full model and transition to FooIndex when it's completed getting the model, this depends on it being a route with a dynamic segment (:id).
App.UserIndexRoute = Em.Route.extend({
redirect: function(){
var self = this;
fetchTheFullModel.then(function(model){
self.transitionTo('user.fooIndex', model);
}
}
});
If it isn't like that you can do just transition to the other route after the transition and page has finished rendering.
App.UserIndexRoute = Em.Route.extend({
redirect: function(model, transition) {
var self = this;
transition.then(function(){
Ember.run.scheduleOnce('afterRender', function(){
self.transitionTo('user.fooIndex');
});
});
}
});
http://emberjs.jsbin.com/zohav/1/edit
You can read more about the transition promise, and afterRender here Ember transition & rendering complete event

`needs` not waiting for data to be returned before rendering template

I am trying to implement a controller needing another (CampaignsNew needing AppsIndex), which looks like
App.CampaignsNewController = Ember.Controller.extend({
needs: ['appsIndex']
});
And in my CampaignsNew template I am showing it via
{{#if controllers.appsIndex.content.isUpdating}}
{{view App.SpinnerView}}
{{else}}
{{#each controllers.appsIndex.content}}
{{name}}
{{/each}}
{{/if}}
However controllers.appsIndex.content.isUpdating is never true. I.e. it attempts to show the data before it has been loaded.
My AppsIndex route has the model overridden:
App.AppsIndexRoute = Ember.Route.extend({
model: function(controller) {
var store = this.get('store').findAll('app');
}
...
});
I can get it to work if I put the same code within my CampaignsNew route and modify the template to each through controller.content. Which says to me that needs is not using the route? It also works if I go to the /apps page and it loads the data, and then navigate to the /campaigns/new page.
How do I get this to work? Thanks!
Edit:
As requested, the relevant parts of my router:
App.Router.map(function() {
this.resource('apps', function() {
...
});
this.resource('campaigns', function() {
this.route('new');
});
});
And the AppsIndex is accessed at /apps and CampaignsNew is at /campaigns/new
Edit2:
After implementing the suggestion by #kingpin2k, I've found that Ember is throwing an error. Below are the updated files and the error received.
App.CampaignsNewController = Ember.ObjectController.extend({
pageTitle: 'New Campaign'
});
App.CampaignsNewRoute = Ember.Route.extend({
model: function(controller) {
return Ember.RSVP.hash({
campaign: this.store.createRecord('campaign'),
apps: this.store.find('app')
});
// return this.store.createRecord('campaign');
},
setupController: function(controller, model){
controller.set('apps', model.apps);
this._super(controller, model.campaign);
}
});
Ember throws this error:
Error while loading route: Error: Assertion Failed: Cannot delegate set('apps', <DS.RecordArray:ember689>) to the 'content' property of object proxy <App.CampaignsNewController:ember756>: its 'content' is undefined.
I read online that this is because the content object doesn't exist. If I set it like so:
App.CampaignsNewController = Ember.ObjectController.extend({
content: Ember.Object.create(),
...
});
Then the page loads without error, and when inspecting the Ember Chrome extension, I can see the data has loaded. But it doesn't show on the page. Which I suppose happened because the content object existed and so Ember didn't wait for the model's promise to fulfill before rendering the template. Seems odd that you should have to define content in such a way though. Any insight on how to handle this?
Edit3: Question answered for me in another thread
Based on your router, apps isn't a parent of campaigns/new.
This means someone could hit #/campaigns/new and Ember would hit ApplicationRoute, CampaignsRoute, and CampaignsNewRoute to populate the necessary information for the url requested. Using needs as a way of communicating between controllers really only makes sense in an ancestral pattern (aka communicating with your parents, grandparents etc).
Just as another quick note, AppsIndex is a route of Apps, it won't be hit when your url includes a child. e.g.
Router
this.resource('apps', function() {
this.resource('chocolate', function(){
.....
});
});
Url being hit
#/apps/chocolate
Routes that will be hit
ApplicationRoute
AppsRoute
ChocolateRoute
ChocolateIndexRoute
The index route is only hit when you don't specify a route of a resource, and you are hitting that exact resource (aka nothing past that resource).
Update
You can return multiple models from a particular hook:
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
}
});
If you want the main model to still be cows, you could switch this up at the setupController level.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
},
setupController: function(controller, model){
controller.set('dogs', model.dogs); // there is a property on the controller called dogs with the dogs
this._super(controller, model.cows); // the model backing the controller is cows
}
});
Check out the second answer here, EmberJS: How to load multiple models on the same route? (the first is correct as well, just doesn't mention the gotchas of returning multiple models from the model hook).
You can also just set the property during the setupController, though this means it won't be available when the page has loaded, but asynchronously later.
Which controller?
Use Controller if you aren't going to back your controller with a model.
App.FooRoute = Em.Route.extend({
model: function(){
return undefined;
}
});
Use ObjectController, if you are going to set the model of the controller as something, that isn't a collection.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
}
});
Use ArrayController if that something is going to be a collection of some sort.
App.FooRoute = Em.Route.extend({
model: function(){
return ['asdf','fdsasfd'];
}
});
Note
If you override the setupController, it won't set the model of the controller unless you explicitly tell it to, or use this._super.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
},
setupController: function(controller, model){
controller.set('cows', model.cows);
controller.set('dogs', model.dogs);
// uh oh, model isn't set on the controller, it should just be Controller
// or you should define one of them as the model
// controller.set('model', model.cows); or
// this._super(controller, model.cows); this does the default setupController method
// in this particular case, ArrayController
}
});

In Ember.js does setupController and model hooks work only for dynamic segments?

I am trying to understand the setupController and model hooks, will they be invoked only in case of dynamic segments?
This is my router configuration, I see the application is working fine,but I do not see these hooks getting executed:
// Router, this need to connect view and controller
App.Router.map(function(){
this.resource("login", {path : "/"});
this.resource("home" , {path : "home/:home_id"});
});
App.Router.IndexRoute = Ember.Route.extend({
setupController:function(controller,model){
console.log("in setupController hook for index route");
}
});
App.Router.LoginRoute = Ember.Route.extend({
setupController:function(controller,model){
console.log("in setupController hook for login route");
}
});
App.Router.HomeRoute = Ember.Route.extend({
setupController:function(controller,model){
console.log("in setupController hook for login route");
}
});
Assuming you are using the latest ember (1.0.0-RC.1) You should define your routes like this:
App.HomeRoute = Ember.Route.extend({
...
});
They are part of your App, and don't have the Router part.
Working JSBin example
As a note on model and setupController hook behavior:
In a route, model will only be called when navigating directly to a URL containing a dynamic segment. The parameters passed into model are used to retrieve the model for that route using the dynamic segment.
If the route is reached using a {{#linkTo route myObject}} or transitionTo(myObject) call then the passed object is used to call setupController directly and model is not called.
The setupController hook will be called every time the route enters the
Ember API docs for model
Ember API docs for setupController