Condition menu in Ember.js - ember.js

In my Ember app I have a couple different menus or nabbers that I need to display depending on the section of the app the user is in. What is the recommended way of doing this? What I was attempting to do was have view in my template:
{{view App.NavbarView controllerBinding="App.CurrentNavbarController.nav"}}
And in my view I'm checking the path:
App.NavbarView = Ember.View.extend({
templateName: function() {
path = App.getPath('router.currentState.path');
//change navbar
}
});
That's where I'm stuck. How do I dynamically switch the views?

Another way would be to use the router to to connect some outlets to a Nav view.
In your application.handlebars put a named outlet:
{{outlet nav}}
In your router, connect the outlet when you enter a route:
userRoute:
connectOutlets: (router, model)->
# normal stuff here
router.get('applicationController').connectOutlet('navbar', 'userNav', model)
exit: (router)->
router.get('applicationController').disconnectOutlet 'navbar'
subRoute:
# still has the navbar outlet connected in a subroute

One way would be to use the router to set it up.
someRoute:
connectOutlets: (router, model)->
# ... normal stuff here
App.set 'section', 'theNameOfThisSection'
exit: (router)->
App.set 'section', ''
subRoute:
# ... in the sub route, App.section will still be set

Related

ember.js, ember-cli: Outlets not nesting properly

I'm having an issue where I'm unable to get nested outlets to appear properly in my Ember CLI app. The view tree I want is as follows:
application (list of all resources, of which client_availability is one)
- client_availabilities.index (list of client_availabilities)
-- client_availability (individual client_availability)
This is very similar to the "application > posts.index > post" hierarchy in the Ember Starter Kit. My desired behavior is for a list of client_availabilities to appear in "mainoutlet" when I navigate to client_availabilities.index, then persist when I bring up an individual client_availability in "suboutlet".
Easy, right? This is the default behavior & why we all love Ember. However, I can't seem to get it working. When I explicitly target my named suboutlet in client_availabilities.index and click on an individual client_availability, nothing shows up in either outlet:
Scenario 1: Render suboutlet inside client_availabilities
/app/template/application.hbs:
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet 'mainoutlet'}}
/app/template/client-availabilities/index.hbs:
{{outlet 'suboutlet'}}
/app/routes/client-availabilities/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render({
into: "application",
outlet: "mainoutlet"
});
},
model: function() {
return this.store.find('client_availability');
}
});
/app/routes/client-availability.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render('client_availability', {
into: "client_availabilities",
outlet: "suboutlet"
});
},
model: function(params) {
return this.store.find('client_availability', params.client_availability_id);
}
});
Alternately, when I target my mainoutlet in application, client_availability appears in "suboutlet" client_availabilities.index disappears from "mainoutlet":
Scenario 2: Render suboutlet inside application
/app/template/application.hbs:
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet 'mainoutlet'}}
{{outlet 'suboutlet'}}
/app/template/client-availabilities/index.hbs: (empty)
/app/routes/client-availabilities/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render({
into: "application",
outlet: "mainoutlet"
});
},
model: function() {
return this.store.find('client_availability');
}
});
/app/routes/client-availability.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render('client_availability', {
into: "application",
outlet: "suboutlet"
});
},
model: function(params) {
return this.store.find('client_availability', params.client_availability_id);
}
});
And here's my router, the same in both cases:
/app/router.js:
import Ember from 'ember';
var Router = Ember.Router.extend({
location: 'auto'
});
Router.map(function() {
this.resource('client_availabilities', function() {
this.resource('client_availability', { path: ':client_availability_id' });
});
});
export default Router;
I'm happy to share more code, but the application is split into several files and unfortunately not something I can post in its entirety. Can anyone see what I'm doing wrong? The rest of the app is working fine, I just can't seem to get this basic behavior to work.
Do you have an /app/templates/client-availibilities.hbs template with only {{outlet}} inside of it? Without this, the app is going to lose its place in the outlet tree. Ember-CLI and the Ember Starter Kit are very, very different from each other in structure, so I can see where the confusion comes from.
How I like to think of Ember's rendering style is that each handlebars file inside the templates folder (i.e. /templates/users.hbs) represents a change the overall state of the application from one subject to another (example: from newsfeed to users).
The corresponding subfolders inside the templates folder change the state of the subject itself.
For example:
Required Templates
Users container OR the only users page you need app-wide is at /templates/users.hbs
Optional Templates
Users Index would be at /templates/users/index.hbs
Users Show would be at /templates/users/show.hbs
Users New would be at /templates/users/new.hbs
You can have [ /templates/users.hbs ] without having [ /templates/users/*.hbs ] and still keep track of your data; however, you cannot have [ templates/users/index.hbs ] without [ /templates/users.hbs ] and still keep track of your data. Why? Imagine if you navigate to somesite.com/users. There is currently no top-level template with an outlet into which Ember can render the [ users/index.hbs ] template. The [ /templates/users.hbs ] template bridges that gap and also serves as a container for all other pages inside the /templates/users folder as well.
For example, in the terms of your app, in order to render [ /app/templates/client-availibilities/index.hbs ] when a user visits http://www.yourwebsite.com/client-availibilities, your app will need these templates defined so that ember can drill down into them.
application.hbs // and in its outlet, it will render...
--client-availibilities.hbs // and in its outlet, it will render by default...
----client-availibilities/index.hbs // then, for the client-availability (singular), you can have ember render it in
----client-availibilities/show.hbs // will render also in the client-availabilites as it is a separate state of the subject. Can also be nested inside the index route within the router so that it renders inside the index template.
As it is, I would structure your app as such...
/app/router.js
... // previous code
Router.map(function() {
this.resource('client_availabilities', function() {
this.route('show', { path: '/:client_availability_id' });
// this.route('new'); ! if needed !
// this.route('edit', { path: '/:client_availability_id/edit' ); ! if needed !
});
});
... // code
/app/templates/application.hbs
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet}}
/app/templates/client-availabilities.hbs
{{outlet}}
/app/templates/client-availabilities/index.hbs
<ul>
{{#each}}
{{#if available}}
<li>
{{#link-to #link-to 'client-availabilities.show' this}}
{{firstName}} {{lastName}}
{{/link-to}}
</li>
{{/if}}
{{else}} <!-- we want this to only render if the each loop returns nothing, which is why it's outside the if statement -->
<li>Nobody is available</li>
{{/each}}
</ul>
<!-- Note: you don't need to put an outlet here because you're at the end of the tree -->
/app/templates/client-availabilities/show.hbs
<!-- Everything you want to show about each availability -->>
<!-- Note: you don't need to put an outlet here because you're at the end of the tree -->
/app/routes/client-availabilities/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('client_availability');
}
});
/app/routes/client-availabilities/show.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.store.find('client-availability', params.client_availability_id);
}
});
/app/models/client-availability.js
import DS from 'ember-data';
var client-availability = DS.Model.extend({
firstName: DS.attr('string'),
lastname: DS.attr('string'),
available: DS.attr('boolean'),
available_on: DS.attr('date')
});
export default client-availability;
However, are you sure you want to structure your app by the availability of each client? Wouldn't it make more sense to structure it by each client and then just filter each client to show if they were available or not? Resources are supposed to be nouns, and routes are supposed to be adjectives. Therefore, it would be best to use a client as your model instead of their availability and have a either an isAvailable property on the model (as used in the example above) or a one-to-many association with an additional availability model if you want to show clients who have several availabilities (as shown below).
For example,
/app/models/client.js
import DS from 'ember-data';
var Client = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
availabilities: DS.hasMany('availability')
});
export default Client;
/app/models/availability.js
import DS from 'ember-data';
var Availability = DS.Model.extend({
date: DS.attr('date'),
client: DS.belongsTo('client')
});
export default Availability;
In the long run, this latter approach would set up your app to show all availabilities at once and allow the user to filter by the client, plus it would allow the user to view a client and see all their availabilities. With the original approach (the isAvailable property on the client model), the user can only get the availabilities from the client model itself, but what if the user wants to see all clients who are available on, say, March 3rd at noon? Well, without an availability model associated with the client model, you are going to have to put a lot of code into your client controller that ember would give you by default if you go down the one-to-many path.
If you need more advice on where to go from here, let me know. I'm more than happy to add more examples of the templates, controllers, and routes that you'll need in order to pull this off.

Emberjs Modals on a route

So I am trying to figure out how best to put modals on a route, such that you can navigate to them via the url.
application.hbs
{{outlet}}
{{outlet modal}}
There is a discussion here and emberjs cookbook provides another example but nothing covers how you can have modals on a specific route.
The closest thing I have seen is this Stack Overflow question but it suffers from two problems:
When the modal route is visited, the view in the main outlet gets destroyed. So in your UI, things underneath the modal get wiped out.
history.back() is that you essentially revisit that route causing that view to be redrawn and feels very hackish.
This is where I feel a solution would exist but not sure what exactly:
App.MyModalRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
/**
* When my modal route is visited, render it in the outlet
* called 'modal' but somehow also persist the default outlet.
**/
this.render({ outlet: 'modal' });
}
});
Here is how we handle it:
In the route you use a render piece similar to what you described:
App.UserDeleteRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({
into: 'application',
outlet: 'modal'
});
},
actions: {
closeModel: function() {
this.transitionTo('users.index');
}
},
deactivate: function() {
this.render('empty', {
into: 'application',
outlet: 'modal'
});
}
}
Clearing out outlet on the "deactivate" hook is an important step.
For the main template all you need is:
{{outlet}}
{{outlet "modal"}}
This does not override the default outlet.
One thing you may want to do for all your modals is implement a layout:
App.UserDeleteView = Ember.View.extend({
layoutName: 'settings/modals/layout',
templateName: 'settings/modals/user_delete'
});
Another gotcha is that there needs to be a parent route rendered into the main outlet. If whatever is in that outlet is not the parent of your modal, then the act of moving away from that route will remove it from the outlet.
working JS bin
You've got several options here:
make modal route child of route you want to persist - than either render modal nested in application template, like in example you mentioned, or put it inside its own template id="myModal"
add modal outlet inside persisted route's outlet and render it in renderTemplate method
renderTemplate: function(controller, model) {
this.render(); //render default template
this.render('myModalTemplate', { //render modal to its own outlet
outlet: 'modal',
into: 'myModal', //parent view name. Probably same as route name
controller : controller
});
}
Besides, you can render template with modal in named outlet any moment(on action f.e.), by just calling render method with proper arguments

Understanding Ember routes

Assume I have routemap like this:
App.Router.map ->
#resource 'posts', ->
#resource 'post', {path: '/:post_id'}
So I have list of posts on /posts route and a single post on posts/2. By default posts template will be rendered to {{outlet}} of a parent template (application) which is OK and post template to {{outlet}} of posts template which is not what I want. I want to replace list of posts with a single post.
So I came with that solution to set desired template to render to it's {{outlet}}:
App.PostRoute = Em.Route.extend
renderTemplate: ->
this.render(into: 'application')
But as a result I have list of posts and a single post rendered into one {{outlet}}. I can emtpy {{outlet}} each new route but then it breaks back button, cuz Ember wont render previous template again, assuming that is already rendered.
So questions is how to use single {{outlet}} for different routes and don't break ember's logic. Of course I can avoid nested routes but they a really usefull when you need semantic links like this post/2/comment/12/edit.
If your templates are not nested, you shouldn't nest your routes, or you'll end up fighting Ember. Acting as Ember's state manager, routes describe your app structure. Nesting should follow your UI, not how you want your URLs to look like.
If your concern is URLs, you can just modify the path to fit your needs:
App.Router.map ->
#resource 'posts'
#resource 'post', { path: 'posts/:post_id' }
The approach I have used for handling nested and non nested templates even when my routes are nested is to use named outlets. Ember makes it really easy.
I have a top level menu outlet that always holds a menu relevant to the route being visited.
Example Application Template:
<div id="nav">
{{partial "nav"}}
</div>
<div id="menu">
{{outlet "menu"}}
</div>
<div id="main">
{{outlet}}
</div>
{{outlet "modal"}}
I have two named outlets, "menu" and "modal", which are used to hold content which is not nested but used by nested routes or any route to be precise. Any route can render a modal in response to actions into the global "modal" outlet, and similarly any route can render a menu into the "menu" outlet.
My examples use coffeescript.
Router:
App.Router.map ->
#resource "posts", ->
#route "create"
#resource "post", {path: ':id'}, ->
#resource "comments", {path: ':id'}, ->
#route "create"
#resource "comment", {path: ':id'}, ->
Routes which render a menu into a named outlet that is not nested:
App.PostsIndexRoute = Em.Route.extend
renderTemplate: (controller, model)->
# default render
#_super arguments...
# render posts menu into the application menu outlet
#render "posts.menu",
outlet: "menu"
into: "application"
App.CommentsIndexRoute = Em.Route.extend
renderTemplate: (controller, model)->
# default render
#_super arguments...
# render comments menu into the application menu outlet
#render "comments.menu",
outlet: "menu"
into: "application"
You don't have to do the default nested rendering as above, you could just define a route type that always renders into a named outlet such as "content" (just don't call it "main" because Ember uses that).
App.ContentRoute = Em.Route.extend
renderTemplate: (controller, model)->
#render outlet: "content", into: "application"
And then subclass from it for any routes that should always render into the application 'content' outlet:
App.PostsIndexRoute = App.ContentRoute.extend()
App.CommentsIndexRoute = App.ContentRoute.extend()
But it would be better to do it with a mixin so you can easily include whatever concerns you wish into any route (eg authenticated route concerns).
App.RenderIntoContent = Em.Mixin.create
renderTemplate: (controller, model)->
#render outlet: "content", into: "application"
App.PostsIndexRoute = Em.Route.extend App.RenderIntoContent,
...
App.CommentsIndexRoute = Em.Route.extend App.RenderIntoContent,
...

Ember.js: Inserting child resource's view into the main application's outlet

By default Ember inserts the view of a child resource into an {{outlet}} defined by a view of a parent resource. How do I override that ? i.e. insert the child view in the {{outlet}} defined by the application view. Why is this the default?
Usecase: There is a users resource, with a new route inside it. I want the new to show in the applications {{outlet}} rather than the parent resource's {{outlet}}.
App.Router.map(function(){
this.resource('users', function(){
this.route('new');
});
});
For each route we have a renderTemplate method that we can overload. This gives us full control over the rendering of the views.
For example, we can specify into which {{outlet}} the view will render with into:
(I assume this is your use case, but I'm a little absent-minded today.)
var UsersRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('users', {
// Render the UsersView into the outlet found in application.hbs
into: 'application'
});
}
});
We can also specify the name out of outlet to render into using the outlet property:
var UsersRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('users', {
// Render the UsersView into the outlet named "sidebar"
outlet: 'sidebar'
});
}
});
And of course we can use a combination of both to specify both the outlet's name, as well as where that outlet is found using the into property.

Ember template displayed twice

There is a simple ember.js app with one view displayed in a particular place on the webpage. Have a look at this jsfiddle: http://jsfiddle.net/jkkK3/9/
App = Ember.Application.create({
ready: function(){
this._super();
this.ApplicationView.create().appendTo(".content");
},
ApplicationController: Ember.Controller.extend({
}),
ApplicationView: Ember.View.extend({
templateName: 'application'
}),
Router: Ember.Router.extend({
root: Ember.Route.extend({
})
})
});
My question is: Why is the "some content here" element displayed twice? It works when I remove the router, but that's exactly what I cannot do, since I try to add Router to my Ember app. Could you please help me to display application view only once, inside the red box?
When using router, applicationController/view are used by default. In your ready method you append it explicitly. So 'application' template is appended twice. Remove appending it in ready method and it will be appended only once.
By default it's appended to body but if you want to override use rootElement property of Ember.Application
Ember.Application.create( {
rootElement : '.content',
....
})