Can I force 'active' class on a {{link-to}} helper? - ember.js

Here is possibly an edge case for how ember adds the 'active' class on a link to helper.
I have my current router set up like so:
import Ember from 'ember';
var Router = Ember.Router.extend({
location: PortalDevENV.locationType
});
Router.map(function() {
this.resource('portal', function() {
this.route('admin');
this.resource('placements', function() {
this.route('import-debtors');
this.resource('add-debtor', function() {
this.route('debtor-form');
});
this.route('view-debtors');
});
this.resource('debtor', {path: 'placements/view-debtors/debtor/:debtor_id'}, function() {
this.route('agent-notes');
this.route('transactions');
});
});
});
export default Router;
notice how I have a resource called "debtor" that- while it is being rendering into the portal template- i still need it to appear (in terms of the URL) to be a child of the "view-debtors" route... which, in reality, is nested deeper within a separate set of templates.
This structure seems to be working fine, but it is breaking my breadcrumb-style navigation.
When moving into the "debtor" page.. i still want "view-debtors" {{link-to}} helper to get the 'active' class from ember... along with the {{link-to}}'s that lead up to the "view-debtors".
Is this possible to do by calling some functions in my routes... or some other way?
It doesn't seem to be a common ember convention... but then again perhaps Ember actually does work in this way and I did something else that broke it? Take a look and see if my set up is correct.

You should be able to bind the active class to a computed property. Assuming the {{link-to}} you are referring to is in your application.hbs template, you could do something like this:
// templates/applictaion.hbs
{{#link-to "view-debtors" class="isDebtorsRoute:active"}}View Debtors{{/link-to}}
// controllers/application.js
export default Ember.Controller.extend({
isDebtorsRoute: function() {
// use this.get('currentRouteName') or this.get('currentPath')
}.property('currentPath')
})
EDIT: Here is a jsbin example http://emberjs.jsbin.com/wuhor/1/edit?html,css,js,output

Related

Ember: Reuse nested route in different parents

I wanted to know how to reuse a nested route in different parent route.
For example:
Template routeA:
{{outlet}} -> routeX
Template routeB:
{{outlet}} -> routeX
Router.map(function() {
this.route('routeA', function(){
this.route('routeX', {resetNamespace:true});
});
this.route('routeB', function(){
this.route('routeX', {resetNamespace:true});
});
})
I need to use {{link-to 'routeX'}} to show routeX inside the current parent.
In the example always shows the routeB when I use {{link-to 'routeX'}}
I have 2 aproax to solution:
1) Put 1 child route in routeA and other in routeB:
Router.map(function() {
this.route('routeA', function(){
this.route('routeX');
});
this.route('routeB', function(){
this.route('routeX');
});
})
and use {{link-to 'routeA.routeX'}} and {{link-to 'routeB.routeX'}}
but in this aproax I have a duplicate code.
2)
router.map(function() {
this.route('*wildcard', function(){
this.route('routeX', {resetNamespace:true});
});
})
this option works but in the url appear "undefined" http://...undefined/routeX
Any idea?
Thanks
If possible, I recommend using either a component or mixin to share whatever code or functionality you need between those two similar routes.
That would look something like this
export default Ember.Mixin.create({
model() {
return Ember.Object.create({title: 'Awesome'})
}
});
and then use it in your route like so,
import ReUseMixin from '../../mixins/re-use'
export default Ember.Route.extend(ReUseMixin,{
or by defining your component, as lets say re-use.hbs and re-use.js
you can just use that in routeA/routeX and routeB/routeX as
{{re-use}}
Here is a link to an ember twiddle, that demonstrates what I would recommend, hopefully that can provide you with a solution.

Call Router.generate from a helper

I need to generate the url for given route from a helper.
How to generate url for a route in Ember.js pointed me to use the generate function. And yes it works as i need (Checked the functionality by making application route global). But i am not sure how to call it from inside a helper.
You were in a good direction, so you mainly solved this problem. :) There are two type of helper in Ember a simple function helper and the Class Based Helpers. We will use a Class Based Helper in this case.
As you have seen in your linked example, we need access to the main Router. We can do this with Ember.getOwner(this).lookup('router:main'). (Ember.getOwner() exists from v2.3, before v2.3 use this.container.lookup('router:main'))
For example, you have this map in your router.js:
Router.map(function() {
this.route('about');
this.route('posts', function() {
this.route('post', {path: '/:post_id'});
});
});
And if you create a helper for example with the name of url-for your template could contain these lines:
{{url-for 'about'}}
{{url-for 'posts'}}
{{url-for 'posts.post' 2}}
And your Class Based Helper could be the following:
// app/helpers/url-for.js
import Ember from 'ember';
export default Ember.Helper.extend({
router: Ember.computed(function() {
return Ember.getOwner(this).lookup('router:main');
}),
compute([routeName, ...routeParams]) {
let router = this.get('router');
return Ember.isEmpty(routeParams) ?
router.generate(routeName) : router.generate(routeName, routeParams[0]);
}
});
Demo on Ember Twiddle

How can I transitionTo a route that includes a model id, from having the id stored in a variable?

Here is my router.js:
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('category', {path: '/category/:category_id'});
this.route('listing', {path: '/listing/:listing_id'});
});
export default Router;
I'm trying to make it so that when the user selects an option from the navbar component, they will be redirected to the appropriate categories/category_id page. I am able to get the right ID into a variable in the navbar component but my this.transitionTo statement does not work. Here is the navbar-header.js:
import Ember from 'ember';
export default Ember.Component.extend({
model() {
return this.store.findAll('category');
},
actions: {
categorySelected: function(){
debugger;
var e = document.getElementById("categories");
var catId = e.options[e.selectedIndex].value;
//I have verified that catId contains the appropriate ID at this point.
//Where the error happens:
this.transitionTo('/category/' + catId);
}
}
});
What am I doing wrong?
EDIT: Heads up everyone, you can't do a transitionTo from a component, you have to use an action and send it back to the route. And as joshfarrent said it is supposed to be this.transitionTo('category', catId);
You need to pass the Id as a separate param in the transitionTo, and remove the slashes, like this:
this.transitionTo('category', catId);
See the transitionTo section of the Ember Route docs here.
I'd also recommend against using the HTML element's value to figure out which item has been selected, and rather do something like this on each action helper in your template:
{{action "categorySelected" VALUE}}
Just replace VALUE with the same numerical value that you were setting on the HTML element. This way, the value of the element will be passed to your categorySelected function as follows:
categorySelected: function(value) {
debugger;
this.transitionTo('category', value);
}
Finally, is there a reason you're not just using a {{link-to}} helper to achieve the same effect?

How do get a set of Ember Data models as a controller that can be injected to others?

In my Ember application, I wanted to have a controller wrapping a collection of models, that I could inject into other controllers.
I've set it up like this:
app/controllers/zones.js:
export default Ember.Controller.extend({
model: function () {
return this.store.find('zone');
}
});
app/controllers/zones/index.js:
export default Ember.Controller.extend({
needs: ['zones'],
zones: Ember.computed.alias('controllers.zones.model')
});
This seems like it ought to work, but unfortunately, it doesn't. I get this error in my JavaScript console (in the browser):
Error: Assertion Failed: The value that #each loops over must be an Array. You passed function () {
"use strict";
return this.store.find('zone');
}
I've tried moving stuff around, or using ArrayController rather than just Controller, but I still get this error.
This makes very little sense to me, any ideas?
Here is the thing, model is the function need to resolve the model for route not controller. That model then automatically injected to controllers model property.
Ember way
In ember way I would suggest move this model definition to the route for controller. Something like this.
export default Ember.Route.extend({
model: function (param) {
return this.store.find('zone');
}
});
This is the Ember way of doing thing. Resolve model in route then have controller to filter / decorate it.
I would also suggest using ArrayController instead of Controller since you are handling number of models.
The other way
Again if you want to have model resolved in controller. I warn you its not the Ember way but you can do it something like this -
export default Ember.ArrayController.extend({
//dont override the model property
mydata: function () {
return this.store.find('zone');
}.property('model'),
});
I figured out the problem – I just needed to set my overridden model implementation to be a property, like this:
app/controllers/zones.js (injected controller):
export default Ember.Controller.extend({
model: function () {
return this.store.find('zone');
}.property() // `.property()` turns the function into an iterable object for use in templates and the like.
});
The main controller is still the same.
app/controllers/zones/index.js (active route controller):
export default Ember.Controller.extend({
needs: ['zones'],
zones: Ember.computed.alias('controllers.zones.model')
});

Ember renderTemplate relay model

Working hard on my Ember app here, and it's going along fine. However, I've run into an issue of unexpected behaviour and I'm not sure regarding the best approach to this problem.
The problem is that in a specific route, I want to render another route into another outlet. However, the other route that I render into the other outlet doesn't retain it's own model.
If I do this:
App.TestRoute = Ember.Route.extend({
model: function() {
return {
heading: "Test",
testContent: "This is test."
}
}
});
App.IndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render("test", {
outlet: "left"
});
this.render({
outlet: "right"
});
},
model: function() {
return {
heading: "Index",
indexContent: "This is index."
}
}
});
... and access the IndexRoute, I would expect the TestRoute's model to be rendered into the TestRoute's template, but only the IndexRoute's model is relayed to both templates.
Fiddle here:
http://jsfiddle.net/3TtGD/1/
How do I allow Ember to use the default model for a route without having to expressively merge them? It seems tedious.
Also, having the same name of some model properties, like {{heading}} is desirable, but not necessary.
What's the best approach for solving this issue?
Thank you for your time.
Best regards,
dimhoLt
In the renderTemplate method you're telling Ember to render a template inside an outlet but it will just default the controller to the one managing the route. Given it's the controller handling the route it makes sense that it manages all the templates within that route.
Of course you can specify a different controller using:
this.render("test", {
outlet: "left",
controller: 'test'
});
it can in turn be a controller you already instantiated (and maybe set its content):
var testController = this.controllerFor('test');
testController.set(....)
this.render("test", {
outlet: "left",
controller: testController
});
About using the model: You can call this.modelFor('test') inside the route and it will return the model of the test route (it even knows if it has already been resolved). I usually do this when I need to access the model of one of the parent routes.
I believe it makes sense to access the model of a parent route, but not so much if you're accessing the model of an unrelated route. Why don't you want to merge both models?