How can I have a bit of code executed whenever / route is visited?
I have this now:
App.indexController = Ember.Controller.extend({
showFront: function () {
alert("zzz");
}
});
But I am stuck. How can I make it actually work?
You can use beforeModel and setupController hooks to execute code when a route is loaded.
App.Router.map(function(){
this.resource('posts', { path: '/posts' }, function() {});
});
App.PostsRoute = Ember.Route.extend({
// http://emberjs.com/api/classes/Ember.Route.html#method_beforeModel
beforeModel: function() {
console.log("beforeModel fired");
},
// http://emberjs.com/api/classes/Ember.Route.html#method_setupController
setupController: function(controller, model){
this._super(controller, model);
console.log("setupController fired");
},
model: function(){
// resolve the promise after a short delay
return Ember.RSVP.Promise(function(resolve, reject){
setTimeout(function(){
resolve(true);
}, 2000);
});
}
});
beforeModel will fire, as the name suggests, before the model is loaded and setupController will fire after the model has loaded. The example in the JSBin uses a delayed loading model to demonstrate the difference.
This example shows the hooks being used for App.Post route, but you can use this on App.ApplicationRoute if you want to have code execute when loading the default route.
JSBin example
You first need to define a route, and then call a function on it.
Read how here:
http://emberjs.com/guides/routing/defining-your-routes/
Related
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
}
});
I've noticed that if i use the same controller for different routes it does not get reset so i can keep data shared between routes which is really helpful for me.
But i wonder... when does the controller reloads in ember? (runs the init and cleans all of his properties)?
And can i manually tell the controller to reload itself?
Thanks for the help guys :)
The controllers are generally singleton instances (excluding itemController instances), they live the life of the page.
If you need to reset some properties you can do it during setupController of the route in need.
App.FooRoute = Ember.Route.extend({
model: function(){
//return something...
},
setupController: function(controller, model){
this._super(controller, model);
controller.setProperties({foo:'asdf', bar: 'ewaf'});
}
});
or you can define some method on the controller that resets it all, and call it during the setupController. Computed properties are all marked dirty and recalculated automatically when the model behind the controller is swapped out.
App.FooRoute = Ember.Route.extend({
model: function(){
//return something...
},
setupController: function(controller, model){
this._super(controller, model);
controller.reset();
}
});
App.FooController = Ember.ObjectController.extend({
foo: 'asdf',
bar: 'wert',
reset: function(){
this.setProperties({foo:'asdf', bar: 'ewaf'});
}// if you want it to happen on init tack on .on('init') right here
});
on init
App.FooController = Ember.ObjectController.extend({
foo: 'asdf',
bar: 'wert',
reset: function(){
this.setProperties({foo:'asdf', bar: 'ewaf'});
}.on('init')
});
this route sends server request on every transition to the post.index. Including page reload, navigation and link-to 'post.index'.
I really need to load all the posts once, when that route is transitioned to the first time. What is the best way to cache the result set?
App.PostIndexRoute = Em.Route.extend({
model: function() {
return this.get("store").findAll("post")
}
});
I am using Ember 1.0 and Ember Data 1.0.beta3
In your application route's model/setupController you can prefetch:
ApplicationRoute = Em.Route.extend({
setupController: function(controller, model){
this._super(controller, model);
this.store.find("post");
}
});
And in your post index route you could do:
App.PostIndexRoute = Em.Route.extend({
model: function() {
return this.store.all("post")
}
});
I have the following route:
var UserRoute = Ember.Route.extend({
afterModel: function(model) {
// model.ensureAllData();
},
redirect: function (model) {
this.transitionTo('user.followers', model);
}
});
After adding the afterModel hook, the redirection don't work anymore, even the code in the hook commented out.
I guess you should put the transitionTo into the afterModel hook and remove the redirect at all, if I'm not mistaken it was deprecated in favor to the afterModel:
var UserRoute = Ember.Route.extend({
afterModel: function(model) {
//model.ensureAllData();
this.transitionTo('user.followers', model);
}
});
Hope it helps.
I'm fairly new to ember.js and I'm doing some experiements. I recently hit a bit of a wall when trying to delete records. Here is my editing route (from which I call delete)
App.PostsEditRoute = Ember.Route.extend({
model: function(params){
return App.Post.find(params.id);
},
exit: function() {
this._super();
tx = this.get('currentModel.transaction');
if(tx)
tx.rollback();
},
setupController: function(controller, model){
controller.set('content', model);
},
events: {
save: function(post){
post.one('didUpdate', this, function(){
this.transitionTo('posts.show', post);
});
post.get('transaction').commit();
},
cancel: function(){
this.transitionTo('posts.show', post);
},
destroyPost: function(context) {
var post = context.get('content');
post.deleteRecord();
post.get('store').commit();
this.transitionTo('posts.index');
}
}
});
So I have a link through which I trigger destroyPost. The record is successfully deleted, and I start to transition to the index route, but an error occurs...
Uncaught Error: Attempted to handle event rollback on while in state rootState.deleted.inFlight. Called with undefined
After this, loading the models for the index page stops and I get an empty page. I can provide any additional code required. Here is the index route.
App.PostsIndexRoute = Em.Route.extend({
model: function(){
return App.Post.find();
},
setupController: function(controller, model){
controller.set('content', model);
}
});
I should note that both of these routes load correctly by themselves. It's only in transition that I get failure.
You need to bind to didDelete like you did with didUpdate in save method.
destroyPost: function(context) {
var post = context.get('content');
post.one('didDelete', this, function () {
this.transitionTo('posts.index');
});
post.deleteRecord();
post.get('transaction').commit();
}
Also, your code seems a bit inconsistent: in the save method you're committing a separate transaction, while in destroyPost you are committing the whole store.