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.
Related
I am developing a website using Ember JS.
I have created a nested route like this:
//router
this.resource('store/checkout', {path: '/store/checkout/:order_id'}, function(){
this.resource('store/checkout-lines', {path: ''});
});
This results in the route /store/checkout/:order_id calling both routes and corresponding tempaltes.
The template for store/checkout has an {{outlet}} for the template store/checkout-lines.
In the routes I have this code:
//store/chekout
export default Ember.Route.extend({
model: function(params) {
return this.store.find('order', params.order_id);
}
});
//store/checkout-lines
export default Ember.Route.extend({
model: function(params) {
var order_id = params.order_id; //this does not work
return this.store.find('order-item', {orderId: order_id});
}
});
But my problem is that in the route for store/checkout-lines, I cannot get the orderId.
How can I achieve this? Or am I at the wrong track and should be doing this in another way?
My goal is that the route /store/checkout/:order_id should call the server to fetch both order and orderItems.
What some people seem to miss is that even if you are visiting a nested route, the model for the parent route is loaded. In your nested route, you can easily fetch the model from the parent route using modelFor(type)and then get your information from there. In your case it would be like this.
//store/checkout-lines
export default Ember.Route.extend({
model: function(params) {
var order_id = this.modelFor('checkout').get('id');
return this.store.find('order-item', { orderId: order_id });
}
});
This might seem like an extra step but when you get around to it it really makes a lot of sense and works very well.
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 would like to create a route for / that loads another route, say 'posts'. It seems that the only two solutions are to configure Ember's IndexRoute:
App.IndexRoute = Ember.Route.extend({
redirect: function() {
return this.transitionTo('posts');
}
});
OR
Map our 'posts' resource to the / path:
App.Router.map(function() {
return this.resource('posts', { path: '/' });
});
The first solution does not seem reasonable because it always sends visitors to /posts instead of having an actual base path of /. The second solution does not seem reasonable because it only allows posts to be viewed from / and not /posts. The second solution inherently creates strange nested URLs like /new for a new post instead of /posts/new.
What is the most idiomatic way to configure / to load another route instead of redirecting, while still making the target resource available from its normal URL? In other words, I would like the / path to access posts, and still have posts available via /posts.
Another way to go is to have your IndexController needs the PostsController, and then you can use render in your index template to render the posts.
App.IndexController = Ember.Controller.extend({
needs : ["posts"]
});
And then your index template might just be
{{render 'posts'}}
I think what you want to do is the following:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.get('store').findAll('post');
},
setupController: function(controller, model) {
this.controllerFor('posts').set('content', model);
}
});
That way the controller for this route will be an ArrayController filled with all your posts. And you can still use your /posts route whichever way you like. By default this would be App.IndexController (which you can override to implement custom functionality).
Alternatively, if you wanted to use a different controller (say App.PostsController), you could specify that in the routes renderTemplate hook. So if you wanted to use your posts template and your App.PostsController used in your App.IndexRoute, you would include:
renderTemplate: function() {
this.render('posts', { controller: 'posts' });
}
For more details have a look at the routing section of the Ember.js guides.
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 have a list of accounts and then i have a view link to view an account in detail and this is the account route. When i click the view link the (guid) doesnt update when going through the router, it only updates in the URL but it doesnt seem to be carrying through to the code.
When i do a browser refresh then the (guid) gets carried through to the router... Its not resolving for some reason.
Im not using ember-data but will use it in the future.
Here is my "Accounts" template code with the "View" link:
{{#each accountdata in controller}}
<tr>
<td>{{accountdata.accountnumber}}</td>
<td>{{accountdata.accountname}}</td>
<td>{{accountdata.accounttypestatus}}</td>
<td>{{accountdata.accountuser}}</td>
<td>{{#linkTo account accountdata}}View{{/linkTo}}</td>
</tr>
{{/each}}
accountdata is the context with the "accountguid" which is my id.
Here is my router:
App.Router.map(function() {
this.resource("accounts", { path: '/accounts' });
this.resource("account", { path: "/accounts/:accountguid" });
});
App.AccountsRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('searchfilter','ALL');
controller.search();
}
});
App.AccountRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.show(controller);
},
model: function(params) {
this.controllerFor('account').set('accountguid',params.accountguid);
},
serialize: function(model) {
return {accountguid: Em.get(model, 'accountguid')}
}
});
My controller.show is where i send the context to call a script to display the account details.
So i just need view to carry through the correct accountguid each time which it isnt and then to call the show(context) method.
Thanks
This is my old router code which worked 100%. When i clicked a link it resolved the :accountguid and when i did a browser refresh it did the same thing. i had no problems, everything just worked.
// //Accounts
// accounts: Ember.Route.extend({
// route: '/accounts',
// index: Ember.Route.extend({
// route: '/',
// connectOutlets: function (router) {
// router.get('applicationController').connectOutlet('accounts');
// router.get('accountsController').set('searchfilter','ALL');
// router.get('accountsController').search();
// }
// }),
// view: Ember.Route.extend({
// route: '/:accountguid',
// connectOutlets: function (router, account) {
// router.get('applicationController').connectOutlet('account', account);
// router.get('accountController').show(account);
// //router.get('accountController').connectOutlet('eventloghistory','eventloghistory');
// }
// })
// }),
I managed to solve my problem with the following code. I am now able to refresh the browser and i am able to click the link and it will carry through the current :accountguid in use to the show() method.
App.AccountRoute = Ember.Route.extend({
model: function(params) {
return {accountguid: params.accountguid};
},
setupController: function(controller, model) {
controller.show(model);
},
serialize: function(model) {
return {accountguid: Em.get(model, 'accountguid')}
}
});
Change your router map to the following and it should work
App.Router.map(function() {
this.resource("accounts", function(){
this.resource('account',{path:':account_id'});
});
});
Please, show us the code behind your AccountsController and AccountController. It would be most useful if you provide a jsfiddle with the whole construction.
In general, you may be unaware of the new flow of things. Here is what happens in the two scenarios:
1. You navigate to the AccountRoute by setting the URL (/account/5 for example).
1.1. the 'model' hook of the AccountRoute is called
model: function(params) {
return your model here...
}
with params = { accountguid: 5 }. Because you are not using ember-data, you should implement this hook and initialise and return the model there.
1.2. the setupController hook is called with the AccountController and the model returned by the model hook. Without the code behind
controller.show(controller);
It is not quite clear what its purpose is, but you should probably do something like
setupController: function(controller, model) {
this._super(controller, model);
controller.set('content', model);
controller.show(model);
}
As you can see, by not implementing the model hook, you URL stays correct, but the route does not know how to build the needed model resource.
You transition to the route via a linkTo call
{{#linkTo account accountdata}}View{{/linkTo}}
Here, linkTo expects accountdata to be the full account model for the route. Meaning it may not carry only partial data like id for example (read this for clearance: In latest Ember, how do you link to a route with just the id/name of a model, rather than providing all of its attributes in the linking page?).
2.1. The model hook of the AccountRoute is NOT called. The AccountRoute model property is set to the object that is passed to linkTo instead (in our case 'accountdata').
2.2. setupController is called with AccountController and the accountdata object.
If you accountdata object is not complete, it would be wise to create a complete instance here and set it to the controller.
As you can imagine, if you accountdata is like { id: 5, accountname: "John", accounttypestatus: "A", ...}, then after a click on linkTo, the URL will update correctly to /account/5, but the account template will receive accountdata, rather than an actual account.
P.P. If none of the above is any help, this might be your issue: https://github.com/emberjs/ember.js/issues/1709