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?
Related
I have two controllers: postsController and postController. the post route is NOT nested under posts ( I do not want to do so because I want the posts view to be replaced by the post view, not added to it.)
This is what my router looks like.
this.resource('posts', {path: '/'}, function(){
// this.route('index', {path: '/'});
});
this.resource('post', { path: 'posts/:id' }, function(){
this.route('show');
}
Now, the postsController:
App.PostsRoute = Ember.Route.extend({
model: function(){
return this.store.find('post');
},
})
App.PostsController = Ember.ArrayController.extend({
testProperty: "This is a test"
})
And the PostController:
App.PostRoute = Ember.Route.extend({
model: function(params){
return this.store.find('post', params.id);
},
})
App.PostController = Ember.ObjectController.extend({
needs: ['posts'],
percentValue: 100,
progressBarWidth: null,
advancePost: function(delta) {
var that = this;
var posts = that.get('controllers.posts');
// debugger <-- This is where all my questions are concerned with.
...
},
actions: {
nextPost: function() {
this.advancePost(1);
},
previousPost: function() {
this.advancePost(-1);
},
}
})
So. PostController 'needs' PostsController, and on action advancePost, needs to access PostsController's model (which is supposed to contain an array of Post Objects.) At the point where my debugger is, the variable posts accessed postsController. At this point, if I run
posts.get('testProperty') // => gives "This is a test", which is correct.
on the chrome console, I get "This is a test". So I know that I have gained access to the Posts Controller itself. However, any of the following attempts:
posts.get('model') // gives []??
posts.get('content') // gives []??
returns an empty array ( [] ).
I fail to understand why. If I have access to the Posts arrayController, why do I not have access to its data? Consequently, how would I gain access to its data?
I would greatly appreciate clarity on this.
First, if I understand your context, I think you should rather nest post under posts by doing this :
this.resource('posts', {path: '/'}, function(){
this.route('post', {path: '/:id'});
});
And then, since posts is your "parent" route, you no longer need to declare the needs dependency in your controller.
To access get your posts, apply the following in your Route :
setupController: function(controller, model) {
this._super(controller, model);
controller.set('posts', this.modelFor("posts").get("content"));
}
You should think about the interaction patterns you want to support. Is it typical for a user to go back to the list of items they were just looking at? Reloading posts whilst keeping the scroll position in the list they were at will be problematic. Also re-fetching from the server and tearing down and setting up the DOM has a lot more overhead than just leaving it there.
Are you intending to use animation on your transitions? Using something like liquid-fire will need both outlets rendered to perform an animation so it makes no sense to tear down the list in that case.
CSS can be used to have the nested outlet fill whatever container it is put in allowing the approach provided by Pascal Boutin to be used.
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 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 am trying to reproduce example for learning sake part by part from here Original
and my jsbin is MY JSBIN
My Post routes are
App.PostsIndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('posts');
}
});
App.PostsRoute = Em.Route.extend({
model: function(){
return this.store.findAll('post');
}
});
and my tags route are exactly same.
App.TagsIndexRoute = Em.Route.extend({ model:function(){
return this.modelFor('tags'); } });
App.TagsRoute = Em.Route.extend({ model: function(){
return this.store.findAll('tag'); } });
while I can display data if my data template name is data-template-name="tags"
I cannot display data with data-template-name="tags/index"
my router map looks like this
App.Router.map(function() {
// put your routes here
this.route('about');
this.resource('posts',{path:'/posts'},function(){
this.route('post',{path:':id'});
});
this.resource('tags',{path:'/tags'});
});
It just silently fails no error message. If in IndexRoute I change from transitionTo('tags') to transitionTo('posts') things work fine no clue where I am going wrong.
There is a documented bug/feature where if your resource has no function argument passed it won't create the index route
this.resource('tags',{path:'/tags'}, function(){});
https://github.com/emberjs/ember.js/issues/3995#issuecomment-31200805
NOTE: If you define a resource using this.resource and do not supply a function, then the implicit resource.index route is not created
http://emberjs.com/guides/routing/defining-your-routes/