I'm trying to create a master/detail UI with Ember that is a little different from the typical scenario shown in most guides/demos I have viewed. In my case, I want to have a list of users (loaded from /patients) where each user element has its own dedicated detail panel that is populated (with additional details loaded from /patients/:id) and rendered when an element/button is clicked in the master view. The typical master/detail demo online renders the detail for any element in a list of elements into a single detail element somewhere on the page.
Router:
App.Router.map(function() {
this.resource('patients', function() {
this.resource('patient', { path: ':patient_id' });
});
});
Models:
App.PatientsRoute = Ember.Route.extend({
model: function() {
return this.store.find('patient');
}
});
App.PatientRoute = Ember.Route.extend({
model: function() {
return this.store.find('patient', 1);
}
});
Templates:
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" id="patients">
{{#each item in model}}
{{#link-to "patient" item}}show detail{{/link-to}}
...info....
{{outlet}} <--where "patient" detail panel goes
{{/each}}
<script type="text/x-handlebars" id="patient">
....detail info....
</script>
This retrieves and renders the list of patients. But, when any detail link is clicked, all of the child detail panels render for every patient in the list. How do I just have the child 'patient' detail show/render for the clicked parent? Would it be better to use an action vs. #link-to?
Your {{outlet}} is inside the each loop which means that you will have as many outlets on your page as there are patients... Try
{{#each item in model}}
{{#link-to "patient" item}}show detail{{/link-to}}
{{/each}}
{{outlet}} <--where "patient" detail panel goes
Related
I want to render the users/userDetail template inside the users/index template outlet just to remain in the same page.
With nested resource works well but I think the route structure is more correct like in the code: http://emberjs.jsbin.com/jijar/1
Ditch the index route and just use the resource route. Index is a landing page that's useful for information you want to show, when you aren't in a deeper route (aka, show this when I'm just on /users, but if I go somewhere else, don't show said info).
App.UsersRoute = Ember.Route.extend({
model: function() {
return this.store.find('user');
}
});
App.UsersController = Ember.ArrayController.extend({});
<script type="text/x-handlebars" id="users">
<ul>
{{#each user in model}}
<li>
{{#link-to 'users.userDetail' user}}{{user.fullName}}{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</script>
http://jsbin.com/vuhorala/1/edit
You can call the template in the users/userDetail route via the docs
renderTemplate: function(){
this.render('userDetails', {
into: 'users.index',
outlet: 'your_outlet_name',
controller: 'your_controller_name'
}
}
I am trying to render a set of tabs for a set of objects (conversations) using the render helper for each. This is not part of a route as it is a persistent part of the interface. I have run into a problem where only the view with the same name as the model gets the intended controller (i.e. the panel contents and not the tab headers).
I have a Chat model, object controller and array controller (deliberately simplified here):
App.Chat = DS.Model.extend({ });
App.ChatsController = Ember.ArrayController.extend({
needs: 'application',
content: Ember.computed.alias('controllers.application.currentChats'),
});
App.ChatController = Ember.ObjectController.extend({ });
The ArrayController needed the needs/content properties because the chats are loaded in the application controller. I used the currentChats name as other routes may load non-current chats.
App.ApplicationController = Ember.Controller.extend({
init: function(){
this.store.find('chat', {"current": true});
this.set('currentChats', this.store.all('chat'));
}
});
I have no difficulty rendering the chat contents with the appropriate controller (into the 'chat' template). However, the chat tabs are given the default ObjectController, and therefore can't fire actions.
<script type="text/x-handlebars" id="application">
<!--application template-->
{{outlet chats}}
</script>
<script type="text/x-handlebars" id="chats">
<div id="chats">
<ul id="chat-tabs">
{{#each}}
{{render 'chatTab' this}}
{{/each}}
</ul>
{{#each}}
{{render 'chat' this}}
{{/each}}
</div>
</script>
<script type="text/x-handlebars" id="chatTab">
<!--tab template-->
</script>
<script type="text/x-handlebars" id="chat">
<!--chat template-->
</script>
The application router is as follows:
App.ApplicationRoute = Ember.Route.extend({
model: function(){ },
renderTemplate: function(){
this.render('application', { });
this.render('chats', {
into: 'application',
outlet: 'chats',
controller: 'chats'
});
}
});
This seems to come solely down to naming of the templates. The template called 'chat' inherits the correct controller, but chatTab doesn't despite receiving a chat as the model. Is there any way to force the view to inherit the correct controller? Or am I going about this in an idiosyncratic way.
Many thanks for your help to this Ember novice.
Andrew
It goes solely off the name provided to the render. The easiest way is to just create the other controller and extend the chat controller.
App.ChatTabController = App.ChatController.extend();
We have a scenario along these lines:
Quote
--->Create
So route names quote and quote.create.
The issue is that we need to render the templates into the main outlet. So in our main route (that all other are inherited from) we have this:
renderTemplate: function() {
this.render({ into: 'application' });
}
When I navigate to quote it renders the quote view. From there I navigate to quote.create and it renders the create view. However, going back to quote from quote.create renders nothing.
How can I get around this?
When I go back to the \quote url route 'quote.index' is sought. Since it is defined 'automagically' nothing happens. When I define the route explicitly ember tries to find the quote.index template and view and these do not exist.
A workaround I tried is to have this:
App.QuoteIndex{Route|Controller|View} = App.Quote{Route|Controller|View}.extend()
EDIT:
Hey diddle-diddle, here is my fiddle :) http://jsfiddle.net/EbenRoux/Mf5Dj/2/
Ember.js does not rerender a parent view when transitioning to a parent route, so using into with a parent view template is not recommended.
There is an easier way to create what you are trying to: use a quote/index route:
<script type="text/x-handlebars" data-template-name="application">
<h1>Rendering Issue</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="quote">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="quote/index">
<h2>Quote View</h2>
{{#linkTo 'quote.create'}}Create a new quote{{/linkTo}}
</script>
<script type="text/x-handlebars" data-template-name="quote/create">
<h2>Quote Create View</h2>
<p>Some controls would go here.</p>
{{#linkTo 'quote'}}Go back to quote view{{/linkTo}}
</script>
App = Ember.Application.create({});
App.ApplicationRoute = Ember.Route.extend({
activate: function () {
this.transitionTo('quote');
}
});
App.Router.map(function () {
this.resource('quote', function () {
this.route('create');
});
});
See http://jsfiddle.net/eYYnz/
In my Ember app, I get a list of all the restaurants using an ajax call copied from Discourse co-founder's blog post http://eviltrout.com/2013/02/27/adding-to-discourse-part-1.html
App.Restaurant.reopenClass({
findAll: function() {
return $.getJSON("restaurants").then(
function(response) {
var links = Em.A();
response.restaurants.map(function (attrs) {
links.pushObject(App.Restaurant.create(attrs));
});
return links;
}
);
},
I have a Restaurants route set up which calls the findAll shown above and renders it into the application template
App.RestaurantsRoute = Ember.Route.extend({
model: function(params) {
return(App.Restaurant.findAll(params));
},
renderTemplate: function() {
this.render('restaurants', {into: 'application'});
}
});
The restaurants are displayed as a restaurants template like this with a link to each individual restaurant. I've also included the restaurant template
<script type="text/x-handlebars" id="restaurants">
<div class='span4'>
{{#each item in model}}
<li> {{#link-to 'restaurant' item}}
{{ item.name }}
{{/link-to }}</li>
{{/each}}
</ul>
</div>
<div class="span4 offset4">
{{ outlet}}
</div>
</script>
In the Ember router, I have a parent/child route set up like this
this.resource("restaurants", function(){
this.resource("restaurant", { path: ':restaurant_id'});
});
Therefore, I'm hoping that when I click on the link to a particular restaurant in the restaurants list, it'll insert this restaurant template into the outlet defined in the restaurantS (plural) template
<script type="text/x-handlebars" id="restaurant">
this text is getting rendered
{{ item }} //item nor item.name are getting rendered
</script>
This restaurant template is getting rendered, however, the data for the item is not getting displayed.
When I click {{#link-to 'restaurant' item}} in the list, item represents that restaurant.
In this setup, does Ember need to make another ajax call to retrieve that particular item, even though it's already been loaded from the findAll call?
In the event that I do need to query for the individual restaurant (again) I created a new route for the individual restaurant
App.RestaurantRoute = Ember.Route.extend({
model: function(params) {
console.log(params);
console.log('resto');
return App.Restaurant.findItem(params);
}
});
and a findItem on the Restaurant model
App.Restaurant.reopenClass({
findItem: function(){
console.log('is this getting called? No...');
return 'blah'
}
but none of those console.logs are getting called.
In the Ember starter video https://www.youtube.com/watch?v=1QHrlFlaXdI, when Tom Dale clicks on a blog post from the list, the post appears in the template defined for it without him having to do anything more than set up the routes (as I did) and the {{outlet}} within the posts template to receive the post.
Can you see why the same is not working for me in this situation?
When you navigate to the restaurant route, the item will be the model to this route.
So in your template, if you try
<script type="text/x-handlebars" id="restaurant">
this text is getting rendered
{{ model.name }}
</script>
You'll be able to see the name of the restaurant
And also the model hook is not called, and the further console.logs are not working,
because
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.
Hope everything will be clear now.
I'm trying to understand how to use nested routes.
My code:
App.Router.map(function() {
this.route("site", { path: "/" });
this.route("about", { path: "/about" });
this.resource("team", {path:'/team'}, function(){
this.resource('bob',{path:'/bob'});
});
});
And I'm trying to get to the Bob page with:
{{#linkTo 'bob'}}bob{{/linkTo}}
What am I missing?
jsbin
Thanks.
try instead
{{#linkTo 'team.bob'}}bob{{/linkTo}}
Between you can simplify your router map this way - you only need to specify the path if it's different from the route name.
App.Router.map(function() {
this.route("site", { path: "/" });
this.route("about");
this.resource("team", function(){
this.route('bob');
});
});
UPDATE
See a working example here
In summary, You need to provide an implementation of the renderTemplate function of TeamBobRoute where you explicitly specify where you want to render your template bob. Using the render option into you can override the default behaviour, rendering to the parent outlet, and pick which parent template to render to
App.TeamBobRoute = Ember.Route.extend({
renderTemplate:function(){
this.render('bob',{
into:'application',
});
}
});
<script type="text/x-handlebars" data-template-name="site-template">
This is the site template
{{#linkTo 'about'}}about{{/linkTo}}
{{#linkTo 'team'}}team{{/linkTo}}
</script>
<script type="text/x-handlebars" data-template-name="about">
This is the about page
</script>
<script type="text/x-handlebars" data-template-name="team">
This is the team page
{{#linkTo 'team.bob'}}bob{{/linkTo}}
</script>
<script type="text/x-handlebars" data-template-name="bob">
This is the bob page
</script>
<script type="text/x-handlebars">
This is the application template
{{outlet}}
</script>
FYI the render method supports the following options: into, outlet and controller as described below.
The name of the PostRoute, as defined by the router, is post.
By default, render will:
render the post template
with the post view (PostView) for event handling, if one exists
and the post controller (PostController), if one exists
into the main outlet of the application template
You can override this behavior:
App.PostRoute = App.Route.extend({
renderTemplate: function() {
this.render('myPost', { // the template to render
into: 'index', // the template to render into
outlet: 'detail', // the name of the outlet in that template
controller: 'blogPost' // the controller to use for the template
});
}
});
If you had a named template inside your application template then you would target it this way
App.TeamBobRoute = Ember.Route.extend({
renderTemplate:function(){
this.render('bob',{
into:'application',
outlet:'team-member',
});
}
});
<script type="text/x-handlebars">
This is the application template
{{outlet 'team-member'}}
{{outlet}}
</script>
You're missing the outlet in the team page. The template should look like this.
<script type="text/x-handlebars" data-template-name="team">
This is the team page
{{#linkTo 'bob'}}bob{{/linkTo}}
{{outlet}}
</script>
Each route is rendered into it's parent's template's outlet.
so when you go into "team", then "team" is rendered into the "application" outlet.
When you go to "bob", the "bob" template is rendered into the "team" outlet.
This can be overridden, but is the default behavior.
Also, each parent resources gives you two model/controller/view/template sets. So when you define:
this.resource('team',{path:'/team'});
You get the "team" template and the "team-index" template.
the "team" template is where stuff that is shared between child routes goes (this is why it needs to have the outlet) and the "team-index" template is where stuff that is specific to your "team index" would go.