I'm using the render controller extensively in my app and am having trouble understanding the logic around Controller creation.
The code in question uses this (cut-down) template (using emblem.js):
.span4
render "learningNeeds" # models loaded in learningNeeds controller
.span8
render "notices" student.room.notices # student is defined on the top-level controller
render "observations" # models loaded in observations controller
and the setupController for the template's Route:
App.ParentRoute = Ember.Route.extend
setupController: (controller, model) ->
console.log "ParentRoute setupController"
controller.set('student', model.get('students').objectAt(0))
#set('controller.controllers.observations.showFilters', true) # this works
#set('controller.controllers.learning_needs.showFilters', true) # this works
#set('controller.controllers.notices.showAdd', true) # this doesn't work
App.currentUser = model
I'm setting the content of the learning_needs and observations controllers in observers within their respective controllers, so I'm not passing in any model to the render call in the template.
With the notices controller I'm passing in the student.room.notices as the 2nd parameter to the render call.
Now the issue is that the I'm seeing different notices controllers when I render the template to that in the Parent setupController method. That is, they have different ember ids. The controller at #set('controller.controllers.notices.showAdd', true) is different to that rendered by the template.
If I remove the student.room.notices model from the template and just use `render "notices" then the same controller is used and I can set the showAdd property and have it display in the template. The problem then is it doesn't contain any models.
The relevant docs say the render view helper will Get (or generate) the singleton instance of AuthorController but this doesn't seem the case for me.
Can anyone shed any light on this behaviour?
thanks,
Martin
I think you should use property bindings.
In NoticesController:
showAdd: Ember.computed.alias('parentController.noticesShowAdd'
In setupController:
controller.set('noticesShowAdd', true)
Then you don't have to worry about figuring out how to get a reference to the notices controller.
Note, if render is called inside an each, you may need to use target instead of parentController.
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 having a part of my applications split away from the main template in a sepparate handlebars template using {{render "header"}} to render that at the right place.
This sepparate template also has its own controller to display some data from the model (App.Notification).
What I want to do is display a limited amount of notifications and the number of new notifications in total.
Ember data loads 10 entries from the server when the controller's notifications property is called in an each loop but as soon as I try to limit the amount of shown notifications via splice, it won't return any data.
Basically, I'm having trouble handling model data if I can't set the controllers model in a route, I assume that's why the normal syntax with slice isn't working in this case.
I already searched Stackoverflow and the Ember docs but only found examples with normal Route -> Controller setup but nothing on this particular topic, so the question is, how to correctly handle model data in this setup?
Header Controller:
App.HeaderController = Ember.Controller.extend
notificationNew: (->
#get('notifications').filterBy('seen', false).get('length')
).property('notifications')
notifications: (->
#store.find('notification').slice(0, 2)
).property('#each.notification')
Template:
{{#each notifications}}
...
{{/each}}
To set properties on a controller not affiliated directly with a route by naming convention refer to the following jsbin.
http://emberjs.jsbin.com/gugajaki/3/edit
App = Ember.Application.create();
notifications = [
{seen: false, message: "tornado siren!!"}
{seen: true, message: "Omg lol security breach"}
{seen: false, message: "BFF Rite?"}
{seen: false, message: "steve kane 4 prez?"}
]
App.HeaderController = Ember.Controller.extend
newNotifications: Ember.computed.filterBy("notifications", "seen", false)
App.ApplicationRoute = Ember.Route.extend
setupController: (controller, model) ->
headerController = this.controllerFor("header")
#this can be a remote fetch via find. will work the same
headerController.set("notifications", notifications)
There are several issues in your posted code will are addressed in the link but I will enumerate here for clarity:
You can use the length property directly in the template
Use the Ember.computed.filterBy to get very efficient array observations
Configure your singleton instance of the header controller by using the application route's "controllerFor" method
I don't understand the splice but I wouldn't do it anyway
Hope this helps.
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.
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.
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.