Call Router.generate from a helper - ember.js

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

Related

How to hand off model data to component in Ember 2.x

I have a route in ember which looks like
//fish.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
fishPrices: this.store.query('fish-price', {filter: {type: params.type}}),
type: params.type
});
}
});
My fish.hbs uses model.type to change text on the page. However, I need to hand model.fishPrices off to a component which plots the price of the fish as a function of time:
//fish-price-plot.js
import Ember from 'ember';
/* global Plotly */
export default Ember.Component.extend({
didInsertElement() {
console.log(this.get('model'));
Plotly.plot( 'fish-price-plot', [{
// ...
//Need to access model.fishPrices here
}
});
How do I access the model in this component? I see a lot of information that suggests that I should be able to do something like
var fishPrices = this.get('model.fishPrices');
//do something with fishPrices
but this always ends up undefined.
One way is directly passing it to the component props like this:
// route template in which you want to use your component & model
{{fishprice-plot model=model}}
Have a look at the following twiddle that demo's the first use case.
The other is injecting a service into the component with the required data.
// components/fishprice-plot.js
export default Ember.Component.extend({
fishData: Ember.inject.service()
});
Have a look at this twiddle that demonstrates passing data to a component more comprehensively, and also this part of the guides, as pointed out by #locks in comments.
You can also have a look at this SO link regarding passing properties to your component.

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.

Ember transitionToRoute cleanly in a component without sendAction

How can transitionToRoute be called cleanly from within an Ember component?
It works with injecting a controller into the component and calling the controller's transitionToRoute function, however I'd like something a little more elegant if possible.
What it currently looks like inside the component's javascript:
// this.controller is injected in an initializer
this.controller.transitionToRoute("some.target.route.name");
What would be nicer in the component's javascript:
transitionToRoute("some.target.route.name");
One goal is do this without using sendAction as this particular component has a single purpose and should always transition to the same route. There's no need for any other Ember artifacts to be aware of the route this component always transitions to, there's no need for the associated indirection. The responsibility for the target route is owned by this component.
UPDATE Please see the other more recent answers for how to achieve this with less code in newer Ember versions, and vote those up if they work for you - Thanks!
Inject the router into the components and call this.get('router').transitionTo('some.target.route.name').
To inject the router into all components, write an initializer at app/initializers/component-router-injector.js with the following contents:
// app/initializers/component-router-injector.js
export function initialize(application) {
// Injects all Ember components with a router object:
application.inject('component', 'router', 'router:main');
}
export default {
name: 'component-router-injector',
initialize: initialize
};
Sample usage in a component:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
submit: function() {
this.get('router').transitionTo('some.target.route.name');
}
}
});
Jan 22, 2018 update
As of Ember 2.15, phase 1 of the public router service is implemented.
Transition to a route from inside a component:
import { inject as service } from '#ember/service';
export default Ember.Component.extend({
router: service(),
actions: {
someAction() {
this.get('router').transitionTo('index');
}
}
});
Use
router: service()
instead of
router: service('-routing')
import Component from '#ember/component';
import {inject as service} from '#ember/service';
export default Component.extend({
router: service(),
actions: {
onClick(params) {
let route = this.getMyRoute(params);
this.get('router').transitionTo(route);
}
}
});
If you want to use the router only in a specific component or service or controller, you may try this:
Initialize an attribute with the private service -routing. The - because it's not a public API yet.
router: service('-routing'),
And then inside any action method or other function inside the service or component:
this.get('router').transitionTo(routeName, optionalParams);
Note: It'll be transitionToRoute in a controller.
You can use container to get access to any needed part of application. To get application controller :
this.container.lookup('controller:application')
But what about structure of application - components should generate events - so my opinion it's better to use sendAction. Cause in future you can get situation, when you need to filter such behavior ( for example ) or other application-specific logic before transition

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')
});

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

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