Say I have two controllers, a CompaniesController and an IndexController. It turns out that all the data my Index route needs comes from the CompaniesController. So, I've specified my IndexController like this:
App.IndexController = Ember.ArrayController.extend({
needs: 'companies',
});
This works well if the CompaniesController is already initialized, but what about the first time I visit the site? CompaniesController is empty.
So, I need to initialize the data for CompaniesController from within the IndexController. How do I do this?
Use setupController and controllerFor within the IndexRoute:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('company');
},
setupController: function(controller, model) {
this.controllerFor('companies').set('model', model);
}
});
It sounds to me like you may want to reverse the dependency and have your CompaniesController depend on Application, like so:
App.CompaniesController = Ember.ArrayController.extend({
needs: 'application',
contentBinding: 'controllers.application.companies'
});
Then, just initialize your application as needed when it first loads the base route.
Related
I have an example route:
this.route('client', {path: ':id'});
I can access this in my route like this:
model: function(params) {
console.log(params.id);
}
How do I access the :id in my controller?
This is how I do it in my application. Not sure if this is the best approach.
App.IndexRoute = Em.Route.extend({
model: function(params) {
this.set('params', params);
},
setupController: function(controller, model) {
controller.set('params', this.get('params'));
this._super(controller, model);
}
});
Alternatively you can also do a lookup on the container inside your controller. But I dont really think this is a good approach. Here is an example.
this.get('container').lookup('router:main').router.currentHandlerInfos
.findBy('name','index').params
There's a serialize function within the Route that you can take advantage of. Here's the API Documentation for it. However, in your context, you can just do this:
App.IndexRoute = Em.Route.extend({
model: function(params) {
console.log(params.client_id);
},
serialize: function(model){
return { client_id : model.id };
}
});
Let me know if this works!
I also noticed an error in your route definition. It should presumably be
this.route('client', {path: '/:client_id'});
or
this.route('client', {path: '/client/:client_id'});
If your id happens to be part of your model you can retrieve from the model itself.
For example, if you have a route with an object Bill as a model, and the path bills/:billId, on your controller you can retrieve it this way:
this.get('model').id
I just came across this question since I too was wondering what was the best way to do this. I chose to go via the route of returning a hash from the model rather than setting parameters in the route.
So in your case I would do the following:
model() {
Ember.RSVP.hash({client: <get client>, id: id});
}
And then in the controller or template I can access the client by calling
model.client
and get the id by calling
model.id
I personally feel this is a cleaner way of accessing the id as compared to setting a param on the route. Of course I am assuming that the id is not already set on the model. Otherwise this entire exercise is pointless.
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')
});
You guys may need more details to answer this, but I figured it might be simple. I'm using Ember Data and the fixture adapter.
This correctly maps the URL to each item in my model.
App.Router.map(function() {
this.resource('quotes', function(){
this.resource('quote', {path: '/:quote_id' })
});
});
App.QuotesRoute = Ember.Route.extend ({
model: function(){
return App.Quote.find();
}
});
But this does not.
App.Router.map(function() {
this.resource('quotes', {path: '/:quote_id' });
});
App.QuotesRoute = Ember.Route.extend ({
model: function(){
return App.Quote.find();
}
});
Does Ember only know to return App.Quote.find(quote_id) if it's a nested resource?
The controller for your route in the second example will be generated as an Ember.ArrayController, which will mismatch with the controller generated for your '/:quote_id' path, as it's a singleton.
What happens if you pass a single model, by using return App.Quote.find(1); or something that will return one record.
I am still trying to understand what exactly you want to happen with that second code example. what kind of logic are you expecting?
I think I'm doing something wrong but I don't know what.
When my application loads it needs to retrieve all companies and when those arrive it needs to set a property activeCompany on my ApplicationController. But when I bind an observer on content.isLoaded on my CompaniesController is fires before the data is loaded.
Application
App = Ember.Application.create({
ApplicationController : Ember.Controller.extend({
needs: ['companies'],
activeCompany: null,
activateCompany: function(company) {
this.set('activeCompany',company);
}
})
});
Router
App.ApplicationRoute = Ember.Route.extend({
enableLogging : true,
setupController: function(controller, model) {
this.controllerFor('companies').set('content', App.Company.find());
}
});
CompaniesController
App.CompaniesController = Em.ArrayController.extend({
needs: ['application'],
activateCompany: function() {
console.log(this.get('content.length')); // 0
console.log(this.get('content.isLoaded')); // true
console.log(this.get('content.firstObject')); // undefined
this.get('controllers.application').activateCompany(this.get('content.firstObject'));
}.observes('content.isLoaded')
});
Why does content.isLoaded fire when my data is not loaded?
Maybe my concept is wrong but the rest of my application depends on the activeCompany to retrieve other data. I also have a 'company-switcher' which also sets the activeCompany property.
When I change my observer to content.#each it fires for all the items that are in the Array.
EDIT
I could work around it like this:
App.CompaniesController = Em.ArrayController.extend({
needs: ['application'],
activateCompany: function() {
if (this.get('content.length') > 0)
this.get('controllers.application').activateCompany(this.get('content.firstObject'));
}.observes('content.firstObject.isLoaded')
});
This only fires when my firstObject changes.
It turns out I should use findQuery. I tried it before like this: App.Store.findQuery(App.Company) but that didn't work. The right way of doing is like this:
this.controllerFor('companies').set('model', this.get('store').findQuery(App.Company));
I needed to get the store via this.get('store')