Ember js: Render Nested Navigation Sidebar together with a main view - ember.js

I have a page whichs layout will look like the following:
There is a nested navigation on the left side. In this case it displays:
a list of libraries (l1, l2, l3). currently l1 is selected
a list of books (b1, b2) present in the library (1)
a list of pages (p1, p2, p3) contained in the book
In case a library is selected, the column for displaying the books in the library would automatically be updated. When selecting a book, the pages column would automatically be updated with all the pages in that book etc...
In addition to that there is a main view, which displays additional details for the currently selected entity. In this case page 2 (p2):
________________________________
|*l1 | b1 | p1 | |
|----|----|----| |
| l2 |*b2 |*p2 | Main View |
|----|----|----| (p2 details) |
| l3 | | p3 | |
--------------------------------
Here is an additional view when just a library is selected. It shows the selected library, all the books in that library in the sidebar and the details about the library in the main view.
________________________________
|*l1 | b1 | |
|----|----| |
| l2 | b2 | Main View |
|----|----| (l1 details) |
| l3 | | |
--------------------------------
or when just a book is selected:
________________________________
|*l1 | b1 | p1 | |
|----|----|----| |
| l2 |*b2 | p2 | Main View |
|----|----|----| (b2 details) |
| l3 | | p3 | |
--------------------------------
So, there are mainly two jobs when for instance a library is selected:
Display all the books in the library (navigation)
Display details about the selected library (main)
The navigation on its own works fine when using nested routes the following way:
Router.map(function() {
this.resource('libraries', { path: '/libraries' }, function() {
this.resource('books', { path: ':library'}, function() {
this.resource('pages', { path: ':book'});
});
});
});
Within the pages route I just get all the books for the selected library and render it into the sidebar outlet:
export default Ember.Route.extend({
model: function (params) {
return this.store.find('book', {song: params.library});
},
renderTemplate: function() {
this.render({ outlet: 'sidebar-books' });
}
});
However, by now I am stuck when trying to render the details about the currently selected library, book or page to the main view AND to the sidebar view.
I found that I can add an additional render call to the renderTemplate function, which lets me render additional content to the main view, however I can't figure out how to retrieve the entity I want to display from the store, as any additional routes won't get called.
What is the recommended why to do something like this?

After quite some try and error, loading two models within the same route and rendering both views with that models, seems to do the trick.
This is the books route.
routes/books.js:
export default Ember.Route.extend({
model: function (params) {
return Ember.RSVP.hash({
books: this.store.find('book', {library: params.library}),
library: this.store.find('library', params.library)
})
},
renderTemplate: function(controller, model) {
this.render('books',{
outlet:'sidebar-books'
});
this.render('library');
}
});
In the handlebar template I can now access the library via:
{{library.name}}
and iterate over the books via:
{{#each books}}
<div class="book">
<div>{{title}}</div>
</div>
{{/each}}

side navigation with dynamic menu
Example of rendering a side menu navigation with nested routes closer to what codeySmurf described, although the main issue of loading multiple models has not been tuckled, it has been solved by codeySmurf.
http://emberjs.jsbin.com/begewoza/1/edit
side navigation with static menu
An approach for rendering a sidebar menu along with the content could be something like the following. It is assumed that it is not required to render the parent models along with the children in main view area (eg show the lib1 on top underneath book1 and below that page1 at the same time) and the menu can be defined in template so no nested resources have been used. The menu has been rendered by using the render helper, although named outlets could also be used (named outlets example http://emberjs.jsbin.com/nibikufa/1/edit) .
http://emberjs.jsbin.com/hituxado/1/edit
js
App = Ember.Application.create();
App.Router.map(function() {
this.route("allLibraries",{path:"/libraries"});
this.resource("library",{path:"/libraries/:library"});
this.resource("book",{path:"/libraries/:library/books/:book"});
this.resource("page",{path:"/libraries/:library/books/:book/pages/:page"});
});
App.IndexRoute = Ember.Route.extend({
redirect:function(){this.transitionTo("allLibraries");}
});
App.LibraryRoute = Ember.Route.extend({
model:function(params){
return {libId:params.library};
}
});
App.BookRoute = Ember.Route.extend({
model:function(params){
return {libId:params.library,bookId:params.book};
}
});
App.PageRoute = Ember.Route.extend({
model:function(params){
return {libId:params.library,bookId:params.book,pageId:params.page};
}
});
App.MenuView = Ember.View.extend({
});
hbs
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
<div style="float:left">{{render "menu"}}</div>
<div style="float:left;margin-left:30%">{{outlet}}</div>
</script>
<script type="text/x-handlebars" data-template-name="allLibraries">
all libs
</script>
<script type="text/x-handlebars" data-template-name="library">
the library {{libId}}
</script>
<script type="text/x-handlebars" data-template-name="book">
the book {{libId}} - {{bookId}}
</script>
<script type="text/x-handlebars" data-template-name="page">
the page {{libId}} - {{bookId}} - {{pageId}}
</script>
<script type="text/x-handlebars" data-template-name="menu">
{{#link-to "allLibraries"}}all libs{{/link-to}}
<br/>
{{#link-to "library" 1}}lib 1
<br/>
{{#link-to "book" 1 1}}book 1
<br/>
{{#link-to "page" 1 1 1}}page1{{/link-to}}
{{/link-to}}
<br/>
{{#link-to "book" 1 2}}book 2
<br/>
{{#link-to "page" 1 2 2}}page2{{/link-to}}
{{/link-to}}
{{/link-to}}
<br/><br/>
{{#link-to "library" 2}}lib 2
<br/>
{{#link-to "book" 2 2}}book 2
<br/>
{{#link-to "page" 2 2 2}}page2 2{{/link-to}}
{{/link-to}}
{{/link-to}}
</script>
css
a.active{
background-color:lightgrey;
border-radius:4px;
margin:8px;
padding:4px;
}

Related

Ember Nested Views in Master Detail UI

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

Ember Routing: Is it possible to nest inside a "verb" route, rather than just a "noun"?

I'm trying to display a list of "tracks" on the "edit" page of an "album".
My router.js.coffee looks like this:
#resource 'albums', ->
#resource 'album', path: '/:album_id', ->
#route 'edit', ->
#resource 'tracks'
If I visit /albums/1/edit, the 'edit.handlebars' template is showing correctly. However, if I put an {{outlet}} tag into that template, it's not triggering my TracksRoute.
Looking in the Ember inspector, it looks like the 'albums.edit.index' route is being used, and my 'tracks' route is nested at the same level as that, rather than below it:
albums (AlbumsRoute)
|
|
|-> album (AlbumRoute)
|
|
|-> album.edit (AlbumEditRoute)
|
|
|-> tracks (TrackRoute)
|
|
|-> album.edit.index (AlbumEdit.IndexRoute)
I think I want the TrackRoute below the AlbumEdit.IndexRoute, but I'll settle for any other way to list the child model records on the "edit" page of a parent (Ideally they'd be listed in a way that would mean I could do CRUD actions in-place)
You would need to use more than one controller in the same route for this:
Router.map(function() {
this.resource('albums', function() {
this.resource('album', {path: '/:album_id'}, function() {
this.route('edit');
});
});
});
App.AlbumEditRoute = Em.Route.extend({
setupController: function(editController, model) {
var tracksController = this.controllerFor('tracks');
this._super(editController, model);
tracksController.set('model', model.get('tracks'));
}
});
And in your template: album/edit/index.hbs
<div class="edit-form">
{{!-- anything related to edit --}}
</div>
<div class="edit-tracks">
{{render "tracks"}}
</div>

ember.js route Transitioned, but Template not render?

about this reps demo.
route:
this.resource('index',{path:'/'}, function(){
this.route('login',{path:'/login'});
this.route('signup',{path: '/signup'});
})
index
- login
- signup
index -- render index.hbs -> index_login.hbs
index.login --render index.hbs -> index_login.hbs
index.signup --render index.hbs -> index_signup.hbs
I have no idea! I just want to reuse index.hbs, but I don't how to control.
Based on your code:
<div class="well">
<h1>index</h1>
{{outlet}}
Welcome Ember.js! {{#link-to 'index.signup'}}signup{{/link-to}}
</div>
By default (not overriding route.renderTemplate), the {{outlet}} will be automatically updated when the content of index/login.hbs or index/signup.hbs when you enter on each specific route.
<script type="text/x-handlebars" data-template-name="index/login">
<script type="text/x-handlebars" data-template-name="index/singup">
To show Login when you transition to 'index' (IndexRoute), you could define your IndexRoute or IndexIndexRoute to redirect to IndexLoginRoute.
Yodemo.IndexIndexRoute = Ember.Route.extend({
beforeModel: function(transition) {
this.transitionTo('index.login');
}
});
http://emberjs.jsbin.com/titabaxe/3/edit

Accessing clicked model in an Ember.ArrayController

I have a small Ember.js app that shows the current month as a list filled with day models. Think a calendar where you can click certain days to highlight them.
App.HolidaysNewRoute = Ember.Route.extend({
actions: {
toggleSelected: function() {
console.log(this.day); # --------> day is undefined here
}
});
App.HolidaysNewController = Ember.ArrayController.extend({
days: function() {
return [
this.store.createRecord('day'),
...
];
}.property()
});
<script type="text/x-handlebars" id="holidays/new">
<ul>
{{#each day in days}}
<li {{action "toggleSelected"}}>{{day}}</li>
{{/each}}
</ul>
</script>
How can I know which day model was clicked in the action handler? I know there is a currentModel but this is an ArrayController with multiple models.
When I create an Ember component in the template and pass it the day model from the loop it works but this means I am handling the behavior in the component and I think I am supposed to leave that up the the controller.
Thanks!
change your toggleSelected function to allow another argument
App.HolidaysNewRoute = Ember.Route.extend({
actions: {
toggleSelected: function(obj) {
console.log(obj);
}
});
and then in the action send the object
{{#each day in days}}
<li {{action "toggleSelected" day}}>{{day}}</li>
{{/each}}

rendering one item from a list in Ember

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.