I'm trying to build an Ember app and I'm running into some difficulty.
I have an index route, which I want to render the following page:
+--------------------------+
| Welcome, foo#bar.com |
+--------------------------+
| |
| New Posts |
| --------- |
| *foo |
| *bar |
| *baz |
| |
+--------------------------+
So I have an Account model, and a Post model. (which don't have any related fields)
I'm having conceptual difficulty with the following:
Ember routes have a model method which returns the model for the route. But what if I want multiple models to be associated with my route? In this case I have Account and Post. How do I make them both available to my page?
One thing I've tried is using setupController and manually setting account and posts on the controller so they can be accessed by the template. But if I can do this, what's the point/significance of the model method!?
Why does Ember want to associate a route with only a single model?
Appreciate any advice,
Thanks,
Daniel
One thing I've tried is using setupController and manually setting account and posts on the controller so they can be accessed by the template.
What you are doing is correct, this is what the setupController hook is for.
But if I can do this, what's the point/significance of the model method!? Why does Ember want to associate a route with only a single model?
In RC3, preventing the default behavior was impossible. In RC4, implementing the setupController hook prevents the default behavior from happening. This is a potentially breaking change for dev's migrating to newer versions.
Also note that if your route implements the setupController hook and you want to preserve the default behavior (the model from also being invoked) make sure you call this._super() from within the setupController hook. You can read more here in the announcing blog post.
Hope it helps.
Using model callback has some advantages:
It automatically generate model callback when you define router.
In template, when you use link-to "someRoute" model, then when you click that link and transition to another route, the route will use the model you pass in for it's model (without calling model callback to get data).
See http://emberjs.com/api/classes/Ember.Route.html#method_model
But the biggest advantage I think, is promise support.
A route uses model callback to get data it needs, then in setupController, it pass the data to the second argument.
Notice what I mean setupController gets data from model callback. If the model callback returns a promise, the route will wait until this promise is resolved, then get the data from promise. If the promise is processing, the setupController is not called, and the template is not rendered.
App.PostsRoute = Em.Route.extend({
model: function() {
// The server response an array of posts like [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]
return $.get('/api/posts.json');
},
setupController: function(controller, model) {
console.log(model); // it's array, not promise object
controller.set('model', model);
}
});
When you can use setupController to do the same thing, it's like this:
App.PostsRoute = Em.Route.extend({
setupController: function(controller, model) {
$.get('/api/posts.json').then(function(data) {
controller.set('model', data);
});
}
});
BUT, setupController does not support promise, that means if you get data from the server in the setupController, Ember will render template before the data is prepared. In many case it's not what we want.
BUT, the model seems not a good place to get multiple data from server. You can do that using Ember.RSVP.all with multiple promises. but you also need to consider link-to. I'm not sure what is the best practice
to get multiple data in a route and wait them all prepared before rendering template. If anyone has a good solution, Please tell me!
For your situation, I think you can use different route to do this. After all I think something like "Welcome, foo#example.com" is a top bar for all of the pages, right? Then you can put it in ApplicationRoute, and render account info in application.hbs template.
Related
For CompaniesRoute I render links for each item:
{{#link-to 'company' id}}The name should change here:{{name}}{{/link-to}}
I have a (CompanyRoute) route that renders a template to edit the object.
The "find" method (AJAX request) on the model hook is called.
However, if I edit the object in CompanyController (ObjectController) it does not reflect back to the item in the CompaniesRoute (CompaniesController).
What I understand on the route is that the model hook is called only when it does not already know about the model.
So when I do this instead:
{{#link-to 'company' this}}The name should change here:{{name}}{{/link-to}}
Now it works, now the name (and any other related properties) changes when I get into edit mode.
However, now the problem is that the "find" method (AJAX request) on the model hook for the CompanyRoute is not being called. It assumes that the model is already there so that it just uses the existing model with the limited properties used when calling "all" method on the CompaniesRoute model hook.
My question is how to get around this so that it's called (find method) when I use the latter ({{#link-to 'company' this}}) link-to method?
Note: also I'm using nested resources on the Router.map. ie:
this.resource('companies', function() {
this.resource('company', {path:':company_id'});
}
Note2: I'm not using Ember Data
After playing around found I have to add setupController on the CompanyRoute:
setupController: function(controller, model) {
controller.set('model', App.CompanyAdapter.find(model.id));
this._super(controller, model);
}
This will now fire request each time and reflect changes accordingly based on link-to helper with this attribute.
I'm trying to build a Tweetdeck-like UI to arrange items from a central library into categories. I really need help wrapping my head around the canonical way of using Ember's router.
Essentially, I have a search UI, which allows the user to open zero or more categories simultaneously. The categories show a list of items, which the user can add to from a central library on the right. By completely ignoring the router and the URL, I have managed to hack together a semi-working proof of concept. Now I want to go back and try to do it the Ember way. Below is a high level sketch of what I am trying to accomplish:
If I understand correctly, the desired URL scheme would be a comma-separate list of model IDs that are currently open. I got a good idea of how to approach that from another question, How to design a router so URLs can load multiple models?.
Unfortunately, there are a few concepts I do not understand:
How do I construct my templates and router, such that the library is displayed with its own model and controller? I assume a named {{outlet}} is the way to go, but I am completely lost when it comes to the renderTemplate configuration. Or perhaps I should use {{render}} instead? In either case, I do not understand the router's role in this situation.
EDIT 1/28: I've added an updated fiddle that includes a standalone library route/template and documents my attempts to render it into the categories template. How does Ember expect me to give the library template its model when I try to embed it into another route? I've tried both {{outlet}} with renderTemplate and {{render}}, but in both cases, I am stuck when it comes to specifying the model.
Using renderTemplate:
App.CategoriesRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('categories');
this.render("library", {
into: "categories",
outlet: "library",
controller: "library",
});
},
});
When my controller receives a request to open a category, how do I communicate that to the router? How is the hash path updated? Who is responsible for loading the appropriate model(s)? I assume I should start with transitionTo or transitionToRoute, but I do not understand the router's role here either. Specific questions:
How do I de-serialize multiple, comma-separated models from the URL? Do I just split on the comma or is there a better way?
Once I get the IDs from the URL, how do I make my model hook return multiple records? Do I just shove them all into an Ember array?
When the controller gets the ID of a new record to open, how do I communicate that to the router?
I've tried to work this out on my own and have read the Ember documentation many times, but I am afraid it is simply over my head. I put together a minimal (currently non-functional) fiddle to outline my thoughts and point out where I am stuck. I would appreciate any help anyone could offer.
this.render does not accept a model parameter, but you could pass the model through the controller property instead, this makes sense to do since the Controller is really a proxy for the model at any rate
App.IndexRoute = Ember.Route.extend({
var self = this,
notesController = self.controllerFor('notes').set('content', self.store.find('notes'));
renderTemplate: function() {
this.render('notes', {
controller: notesController,
into: 'index',
outlet: 'notes'
});
}
});
You could also try something like this from this link.
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
books: this.store.findAll('book'),
genres: this.store.findAll('genre')
});
},
setupController: function(controller, model) {
controller.set('books', model.books);
controller.set('genres', model.genres);
}
});
Here, they load multiple models into one route using Ember.RSVP.hash and then using setupController they set each model (Rails: instance variable?) individually.
I'm assuming using this method that you could load as many models as you needed.
I have a route that loads all my models, and a nested route that allows the user to add a new model.
App.Router.map(function() {
this.resource("foo", {path: "/foo"}, function() {
this.route("add", {path: "/add"});
});
});
My add template looks like this (very basic)
{{input value=wat}}
Here is the linkTo from my index template
{{#linkTo 'foo.add'}}Add A New Model{{/linkTo}}
When I click the add button I simply create the model using $.ajax and transition back to the list route. All works great, until I click the "add" link again.
When the add route loads up the template from above the 2nd time it still shows the "wat" value I entered previously. I was hoping it would not persist any state as each time I "add" a new model it should be unaware of any previous model data.
How can I achieve this with ember 1.1.2+
Update
The approach I took was to reset each element in the setupController method of the route (as this is invoked each time you load the controller).
App.FooAddRoute = Ember.Route.extend({
model: function(params) {
var parentId = 1;
return Ember.Object.create({'bar': parentId});
},
setupController: function(controller, model) {
this._super(controller, model);
controller.set('bazz', '');
}
});
The quick and dirty answer is you want to use a model on the route. If you didn't, you'd have to manually blank out the values on the controller. Ember builds up singleton controllers. This generally is super convenient and very performant.
Singleton controllers keep state. The best way to keep them stateless is to have them backed by a model (return an empty object from the model hook, and don't have the values defined on the controller). By returning something from the model hook it will use an ObjectController (or you'll need to update your code to use an ObjectController on your controller). Then all values will be proxied to the model instead of being stored on the controller.
http://emberjs.jsbin.com/OPaguRU/1/edit
According to the docs:
If you're using Ember Data, you only need to override the model hook
if you need to return a model different from the record with the
provided ID
But this does not work for me, ember data gives me wrong data.
App.UsersEditRoute = Ember.Route.extend
model: (params) ->
return ['Just', 'Some', 'Random']
setupController: (controller, model) ->
controller.set('model', model) # Returns #get('store').find 'user', params.user_id
This should return ['Just', 'Some', Random], but instead it gives me the original #get('store').find 'user', params.user_id
Why and how do I get the data I want?
Btw, If I do like below, everything works, but I want to know why my model function never is called.
setupController: (controller, model) ->
controller.set('model', ['Just', 'Some', 'Random']) # returns ['Just', 'Some', 'Random']
Thank you, I'm using ember-data 0.14 and ember 1.0.0
The model hook, for routes with a dynamic segment, is only called when the page is (re)loaded, here's what the ember guide says (the note at the end):
Note: A route with a dynamic segment will only have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), then a model context is already provided and the hook is not executed. Routes without dynamic segments will always execute the model hook.
I had a similar problem when I wanted to override the model hook. The answer from Simon gave me the right direction. In addition, it should be noted, also from the ember guide but in the Links section, that the {{link-to}} helper takes:
At most one model for each dynamic segment. By default, Ember.js will
replace each segment with the value of the corresponding object's id
property. If there is no model to pass to the helper, you can provide
an explicit identifier value instead. The value will be filled into
the dynamic segment of the route, and will make sure that the model
hook is triggered.
So the bottom line is that by replacing the model in the {{link-to}} helper (in my case 'product') by the object id (in my case 'product.id'), my model hook is now called every time.
As I understad, a template in emberjs gets it's data from controller. So, it's a controller's job to get hold of model data and present it to the template.
The docs here associate a model with a route like this:
App.FavoritesRoute = Ember.Route.extend({
model: function() {
// the model is an Array of all of the posts
return App.Post.find();
}
});
In this case and ArrayController is automatically generated.
However, there's also the setupController function. So, can we also do this :
App.FavoritesRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('model', App.Post.find());
}
});
as the first example given here do?
Do the two ways do the same thing?
Do the two ways do the same thing?
Almost. In both cases the controller's content property will be set to the result of App.Post.find(). And both will work.
That said, using the model hook is the preferred way to do this. If your model hook returns a promise, the router will wait for it to resolve before moving on. That is not the case with the setupController hook. generally you will want to avoid anything async from the setupController hook.