Multiple layouts in Ember 2 - ember.js

I need implement next application structure with 3 routes:
localhost/users
localhost/posts
localhost/settings
'users' and 'posts' routes should have basic layout1 with main navbar.
'settings' route should have another layout2 with second navbar.
How can I implement multiple layouts approach with Ember > 2.12?
Can I set a layout name/path for each route or group of routes?

I can think of two possible recommended approaches to this problem. I've used both in different scenarios. You can either:
Use a component to encapsulate each navbar and then present them accordingly in each template
Set the templateName attribute of each route to use the correct template.
The component approach seems to be the easiest/most used in my experience. It also allows you to have differences within your base route template. e.g.:
users.hbs:
{{layout1}}
<h1>Users<h1>
...
posts.hbs:
{{layout1}}
<h1>Posts</h1>
...
While if you use the templateName approach, you are locked into using the same exact template. So, if you need any customization between any page that uses the same layout, you must use a subroute. e.g.:
routes/users.js:
import Ember from 'ember';
export default Ember.Route.extend({
templateName: 'layout1'
});
routes/posts.js:
import Ember from 'ember';
export default Ember.Route.extend({
templateName: 'layout1'
});
templates/layout1.hbs:
<nav>...</nav>
{{outlet}}
A third possible approach, though I don't necessarily recommend it, is to dynamically replace the navbars in the application.hbs template. The application controller/template have access to a special attribute called currentPath, which you can used to create two computed properties (isLayout1 and isLayout2).
With that, this becomes a viable, though like I said, not necessarily recommended solution:
application.hbs:
{{#if isLayout1}}
<nav>layout 1</nav>
{{else}}
<nav>layout 2</nav>
{{/if}}
{{outlet}}

Related

How do I set a class on a parent element when a route is the first one loaded?

I have an Ember demo app that works fine if the first route loaded is 'index', 'list' or 'list/index', but not if the first route loaded is 'list/show'. Code is at https://github.com/DougReeder/beta-list , demo is running at https://ember-demo.surge.sh To see the problem, set your window narrower than 640px and surf to https://ember-demo.surge.sh/list/5 You'll see the list panel, rather than the detail panel.
The underlying problem is that, when the route is 'list/show', the divs with class 'panelList' and 'panelDetail' should also have the class 'right'.
I can't set this in the template, because panelList and panelDetail are created by the parent 'list' template. If I move panelList and panelDetail to the child templates 'list/index' and 'list/show', then the list gets re-rendered when going from 'list/index' to 'list/show' which would be a terrible performance hit.
Currently, I use the 'didTransition' action to toggle the class 'right'. This is called both then transitioning from 'list/index' to 'list/show', and when 'list/show' is the initial route. Unfortunately, if 'list/show' is the first route, none of the DOM elements exist when 'didTransition' is called.
I can envision two routes to a solution, but don't know how to implement either:
Toggle the class 'right' on some action which happens after DOM elements exist.
Insert conditional code in the 'list' template, which sets the class 'right' on 'panelList' and 'panelDetail' if the actual route is 'list/show'.
Suggestions?
Answer current as of Ember v2.12.0
You can use the link-to helper to render elements other than links, with styles that change based on the route. Utilizing the activeClass, current-when, and tagName properties, you can basically have that element be styled however you want depending on which route you are on. For example, to render your panelList div:
{{#link-to tagName='div' classNames='panelList' activeClass='right' current-when='list/show'}}
More markup
{{/link-to}}
I love a trick with empty component. In didInsertElement and willDestroyElement hooks you can add and remove a css class from parent element or (I like it better) body. Here is a code:
import Ember from 'ember';
export default Ember.Component.extend({
bodyClass: '',
didInsertElement() {
const bodyClass = this.get('bodyClass');
if (bodyClass) {
Ember.$('body').addClass(bodyClass);
}
},
willDestroyElement() {
const bodyClass = this.get('bodyClass');
if (bodyClass) {
Ember.$('body').removeClass(bodyClass);
}
}
});
I use it in template (in my example it's a template of player route) like this
{{body-class bodyClass='player-page-active'}}
To apply classes to parent element, you can use this.$().parent(), but using body is more reliable. Note that this component will create an empty div, but it shouldn't be a problem (can be in rare cases, fix it with classNames and css if needed).
Sukima suggested looking at currentRouteName, and I thus found hschillig's solution, which I simplified for my case. In the controller, I created an isShow function:
export default Ember.Controller.extend({
routing: Ember.inject.service('-routing'),
isShow: function() {
var currentRouteName = this.get('routing').get('currentRouteName');
return currentRouteName === 'list.show';
}.property('routing.currentRouteName'),
In the template, I now use the if helper:
<div class="panelList {{if isShow 'right'}}">
RustyToms's answer eliminates the need for adding a function to the Controller, at the expense of being less semantic.

Getting element by ID in Ember

I am running two ember applications. One has the following component:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
click: function() {
Ember.$('#wrapper').toggleClass('toggled');
}
});
and the other one, has this one:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
click: function() {
this.$('#wrapper').toggleClass('toggled');
}
});
What I can't understand here is why in one application I select an element by ID using Ember.$('#wrapper') and in the other using this.$('#wrapper').
What is this about? Ember version?
UPDATE
I'm very puzzled, since both components are the same:
{{#show-menu}}
<i class="fa fa-bars"></i>`
{{/show-menu}}`
They are both hamburger menus used to hide a sidebar div, and the #wrapper is an external element.
Since in both cases the #wrapper are external elements, shouldn't just the first case work #Gaurav and #Kevin Jhangiani?
The difference is in the context of the jquery selector.
Ember.$()
is scoped to the entire document, ie, you can access any element on the page.
In contrast,
this.$()
is scoped to the current component or view, and thus you can only access dom elements that are children.
Generally, you should be using this.$ as it will be more performant (since the search space is only child elements). Ember.$ should be reserved for times when you absolutely need to access an element outside of the current context.
Ember.$('#wrapper') will find an element in the page with the id of wrapper.
this.$('#wrapper') will find an elment within the component with the id of wrapper.
If there is any chance that the component you are defining will ever occur more than once in the page, then you should use neither. Edit the appropriate template so that wrapper is a class, not an id. Then use:
this.$('.wrapper')
Since you are essentially just toggling a class, the more "Ember" way of doing this is having a conditional class on your wrapper and toggle a property on your component:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
classToggle: false,
click: function() {
this.toggleProperty('classToggle');
}
});
Then, on your DOM element you can have a conditional class:
<div id="wrapper" class="{{if toggleClass "toggled"}}">...</div>
or, if you are using an older version of Ember:
<div id="wrapper" {{bind-attr class="toggleClass:toggled"}}>...</div>
This is a bit more reusable as your component doesn't rely on a DOM element (which can get messy if you want to reuse this component ever).

In Ember, why does my template want {{model.key}}, not just {{key}}?

I am trying out Ember, and finding a discrepancy with the docs. I used the Ember CLI to ember generate template index and ember generate route index. Then I set up a trivial model in index.js:
model: function () {
return {name: "Joe"};
}
From my reading of the docs and examples, I expected to be able to access this value simply with {{name}} in my index.hbs template, but instead I only get the value with {{model.name}}. Why?
Before Ember 1.11 you could use ObjectController, that works like a proxy to corresponding route model, and you could write {{name}} for model.name.
ObjectController was deprecated in Ember 1.11, details here:
http://emberjs.com/deprecations/v1.x/#toc_objectcontroller. So in last Ember versions you should use Controller class instead ObjectController, that doesn't work as proxy of model. You could think of it as of Ember Object with model property from corresponding route. So {{name}} means property of Controller, {{model.name}} - property of model.
For example:
//route
model: function () {
return {name: "Joe"};
}
//controller
import Ember from 'ember';
export default Ember.Controller.extend({
name: 'Marry'
});
//template
{{name}} //=> Marry
{{model.name}} //=> Joe
I think this might be a thing about explicitness but I'm not 100% sure - you can also have data sent to the template on a property other than model so it might be about allowing that to be more easily understood - model is a poor property name IMO anyway
You could use the with helper if the syntax is too verbose for you:
{{#with story}}
<div class="intro">{{{intro}}}</div>
<div class="body">{{{body}}}</div>
{{/with}}

When to use controllers when using components with Ember

In the latest (1.10+) versions of Ember there seems to be a trend to make use of components instead of controllers and views.
The wisdom seems to be:
"Only use controllers at the top-level for receiving data from the route, and use Ember.Controller instead of Ember.ArrayController or Ember.ObjectController"
This makes some sense to me as it's cool to create reusable components, but there are some scenarios where it is unclear to me what the appropriate approach is.
Say we have a component which represents a row in a table, something like this:
# entry-row.js component
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'tr',
actions: {
toggleResolving: function() {
this.toggleProperty('resolving');
}
},
isChecked: function() {
return this.get('resolving');
}.property('resolving')
});
# entry-row.hbs template
<td {{action "toggleResolving"}}>{{entry.name}}</td>
<td>{{entry.currency.symbol}}{{entry.amount}}</td>
<td>{{entry.date}}</td>
<td class="bs-checkbox">{{input type="checkbox" checked=isChecked class="toggle"}}</td>
To form a table, many of the same component are used. That's great. If there is some non-persistent state which applies only to that row then that can belong to the component and be changed there. Something like:
actions: {
toggleResolving: function() {
this.toggleProperty('resolving');
}
}
But say that my resolving property is also useful outside the component. In this case, I wish to be able to mark several rows for resolution and then perform some action on them collectively. I believe that before the switch to components, I would have defined the resolving property on the Controller and made use of that controller wherever I needed the model + state.
Should I still be putting this on the controller?
Besides using an outer component, another option is to put properties on the model. You can also share common actions with mixins.

{{outlet}}, {{view}}, {{render}}, and {{control}} helpers

I am trying to put together a simple master-details Ember app. Directory tree on one side and file list on another.
Ember offers few helpers to render context into a view. Which of them I can use for:
Subtrees of the directory tree.
Details list.
In fact, would be very helpful if someone can point me to any docs I can read about the difference between {{render view}}, {{view view}} and {{control view}} helpers and how to use them properly.
Thanks a lot!
{{view "directory"}} renders the view within the context of the current controller.
{{render "directory"}} renders the view App.DirectoryView with template directory within the context of the singleton App.DirectoryController
{{control directory}} behaves the same way as render only it creates a new instance of App.DirectoryController every time it renders (unlike render which uses the same controller instance every time).
Update 18 Feb 2014: {{control}} has been removed.
The last two helpers are relatively new, so there isn't much documentation about them. You can find {{view}} documentation here.
Now looking at your use case, I don't think you need any of these helpers. Just use nested routes and the {{outlet}} helper and it should just work.
App.Router.map(function(){
this.resource('directories', function() {
this.resource('directory', { path: '/:directory_id'}, function() {
this.route('files');
});
});
});
You can build on that following this guide.
UPDATE: {{render}} now creates a new instance every time if you pass a model.
For a very good explanation of the helpers render, partial, outlet and template have a look at this question.
Just as a rough a summary, how one might use those helpers:
{{render "navigation"}} -> Renders the NavigationController and NavigationView at this place. This is helper is good for places, where the Controller and View do not change, e.g. a navigation.
{{outlet "detailsOutlet"}} -> This will provide a stub/hook/point into which you can render Components(Controller + View). One would use this with the render method of routes. In your case you will likely have a details route which could look like this. This would render the DetailsController with DetailsView into the outlet 'detailsOutlet' of the index template.
App.DetailsRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('details', { // the template/view to render -> results in App.DetailsView
into: 'index', // the template to render into -> where the outlet is defined
outlet: 'detailsOutlet', // the name of the outlet in that template -> see above
});
}
});
{{view App.DetailsView}} -> This will render the given view, while preserving the current context/controller. One might change the context, e.g. using your master entity and pass its details to a view like this:
{{view App.DetailsView contextBinding="masterEntity.details"}}
This is helper is useful, when you want to encapsulate certain parts of a component in subviews, that have own custom logic like handling of actions/events.
{{control}} I know that control instantiates a new controller every time it is used, but I cannot see a good fit for your, nor have i a good example for using it.
To Understand the difference between ember {{render}},{{template}},{{view}},{{control}}
you can refer this article
http://darthdeus.github.io/blog/2013/02/10/render-control-partial-view/