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

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.

Related

ember data-binding from parent to child

i have just started learning ember 1 week ago, and i'm little confused about data-biding :
i have index controller that have a foor property,
test-component that have a its bar property comming from the index controller foo property
index
index.hbs // template
parent value : {{foo}}
{{test-component bar=foo }}
index.js // controller
import Ember from 'ember';
export default Ember.Controller.extend({
foo: "",
});
test-component // template
child value {{bar}}
test-component // component
import Ember from 'ember';
let TestComponent = Ember.Component.extend({
});
TestComponent.reopenClass({
positionalParams: ['bar'],
});
export default TestComponent;
what confuse is :
if write the component in my index template as {{test-component foo}} , i get only one-way data-binding, that mean if i change the bar property in the component, the foo property don't change.
if i use a {{input value=bar}} inside my component, i can see that both bar and foo get updated, so bar is binded to both foo and input value component ?? how its is working(PS : as i said in question 1, foo get updated only if i write in my index template {{test-component bar=foo}}
and thanks everyone.
It is best to avoid the two way binding and rely on separating the two actions. This is called Data Down; Actions Up. To illustrate your template might look like this:
{{test-component foo=bar update=(action (mut bar))}}
Then in your test-component component simply call the update action
this.get('update')(newValue);
In this way foo doesn't change until the parent does the change (from the (action (mut bar)) line). It separates the concern about who owns the truth.
Obviously this is a rule of thumb that can be broken (see the {{input}} helpers for an example of breaking the rule). But you should know when and why your straying from the happy path when doing so. If you aim at writing all your components to not change the data they are given but instead trigger actions then it will make your application easier to reason about and maintain.
Update: The behavior where positional parameters have a one-way binding and named parameters have a two-way binding was a bug in Ember versions < 2.9.0.
The reason your controller is updated when you pass in named parameters and not when you pass in positional parameters is that named parameters are bound and positional parameters are not.
I copied your example into an Ember Twiddle that illustrates the difference.

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).

How to use itemControllerClass to achieve a singleton like experience with ember.js

I've got a fairly simple form but it should never carry any state with it. I started reading an older discussion about how you can use itemControllerClass to get a "singleton" class created each time you enter the route.
If I want to use this how would I plug this into the template and wire it up from the parent controller?
Here is what I'm guessing you would do from the javascript side
App.FooController = Ember.Controller.extend({
itemControllerClass: 'someclass_name'
});
The only "must have" is that I need to have access to the parent route params from the child singleton controller.
Any guidance would be excellent -thank you in advance!
Update
Just to be clear about my use case -this is not an ArrayController. I actually just have a Controller (as shown above). I don't need to proxy a model or Array of models. I'm looking for a way to point at a url (with a few params) and generate a new instance when the route is loaded (or the parent controller is shown).
I'm doing this because the template is a simple "blank form" that doesn't and shouldn't carry state with it. when the user saves the form and I transition to the index route everything that happened in that form can die with it (as I've cached the data in ember-data or finished my $.ajax / etc)
Anyone know how to accomplish this stateless behavior with a controller?
I'm betting you're talking about this discussion. It's one of my personal favorite discoveries related to Ember. The outcome of it was the itemController property of an ArrayController; I use it all the time. The basic gist of it is, when you're iterating over an array controller, you can change the backing controller within the loop. So, each iterating of the loop, it will provide a new controller of the type you specify. You can specify the itemController as either a property on the ArrayController, or as an option on the {{#each}} handlebars helper. So, you could do it like this:
App.FooController = Ember.ArrayController.extend({
itemController: 'someclass'
});
App.SomeclassController = Ember.ObjectController.extend({});
Or, like this:
{{#each something in controller itemController='someclass'}}
...
{{/each}}
Within the itemController, you can access the parent controller (FooController, in this case), like:
this.get('parentController');
Or, you can specify the dependency using needs, like you ordinarily would in a controller. So, as long as the params are available to the parentController, you should be able to access them on the child controller as well.
Update
After hearing more about the use case, where a controller's state needs to reset every time a transition happens to a particular route, It sounds like the right approach is to have a backing model for the controller. Then, you can create a new instance of the model on one of the route's hooks; likely either model or setupController.
From http://emberjs.com/api/classes/Ember.ArrayController.html
Sometimes you want to display computed properties within the body of an #each helper that depend on the underlying items in content, but are not present on those items. To do this, set itemController to the name of a controller (probably an ObjectController) that will wrap each individual item.
For example:
{{#each post in controller}}
<li>{{title}} ({{titleLength}} characters)</li>
{{/each}}
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
In some cases it is helpful to return a different itemController depending on the particular item. Subclasses can do this by overriding lookupItemController.
For example:
App.MyArrayController = Ember.ArrayController.extend({
lookupItemController: function( object ) {
if (object.get('isSpecial')) {
return "special"; // use App.SpecialController
} else {
return "regular"; // use App.RegularController
}
}
});
The itemController instances will have a parentController property set to either the the parentController property of the ArrayController or to the ArrayController instance itself.

{{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/

How can I get My previous route?

How can I get my previous router in my current controller.
App.MyController = Em.ObjectController.extend({
next:function() { // This is my action helper in HBS
this.transitionTo('nextPage');
},
back:function() { // This is my action helper in HBS
// Here I need to dynamically identify my previous route.
// How can I get my previous route.
}
});
After having inspected the router object again, I don't see any property in there that will allow you to grab the last route. In pre 4 there was a property for the last route, but it was a difficult property to work with.
My solution is therefore the same as it was in pre 4: I'd create my own mixin to handle the routes that you navigate into, and from that list of routes, you can get whatever route you're after: the current one, the last one, et cetera...
jsFiddle here: http://jsfiddle.net/sMtNG/
Mixin
The first thing to do is create the mixin that will allow us to push the routes into a HistoryController. We can do this by creating a setupController method which of course gets invoked every time you move into a route.
App.HistoryMixin = Ember.Mixin.create({
setupController: function() {
this.controllerFor('history').pushObject(this.get('routeName'));
}
});
We are pushing the route into the HistoryController.
History Controller
Since we're currently pushing the routeName into a non-existent HistoryController, we'll need to go ahead and create that, which is absolutely nothing special.
App.HistoryController = Ember.ArrayController.extend();
Index Controller
Since the HistoryController stores the list of routes we've navigated into, we'll need it accessible on other controllers, such as the IndexController, we'll therefore use needs to specify in which controller it should be accessible.
App.ApplicationController = Ember.Controller.extend({
needs: ['history']
});
Implement Mixin
We now have everything we need to keep a track of the routes, and so we'll specify that our routes need to implement this mixin.
App.CatRoute = Ember.Route.extend(App.HistoryMixin);
Template
Last but not least, now that we have a HistoryController which our IndexController can access, and the mixin pushes each accessed route into the HistoryController, we can use our application view to output a list of the routes, and specify the last route. Of course in your case you'll need the last route minus one, but there's no sense in me doing everything!
<h1>Routes History ({{controllers.history.length}})</h1>
<ul>
<li>Last Route: {{controllers.history.lastObject}}</li>
{{#each controllers.history}}
<li>{{this}}</li>
{{/each}}
</ul>
I hope this gets you onto the straight and narrow.
Current solutions feel like the tail wagging the dog.
Simple solution is to do window.history.back()
Ember doesn't keep track of the router history, since it would be redundant. All browser already handle this by default.