I am learning Ember, and I have an app that has nested routes. The outer route renders an index of products that acts as a sidebar, and the inner route renders the selected product.
So I have a setup like this in my coffeescript router:
#resource 'products', ->
#resource 'product'
And then I have a template like so for product_types.js.emblem:
each product in controller
.product<NEED_SOMETHING_HERE>
I want NEED_SOMETHING_HERE to give me an extra attribute that will allow me to add a style to the product which will be rendered in the inner route. A class "active", or something similar, is the convention I would use elsewhere. What is the Ember way to do this?
Ember actually does this automatically with the link-to helper!
Ember adds an "active" class when the link matches the current route
http://emberjs.com/guides/templates/links/#toc_the-code-link-to-code-helper
Related
I currently trying use LinkTo to render another nested route by using {{outlet}} tag to another new page.
I have a forum route and nested forum details route
In the forum template:
<LinkTo #route="main.forum.forum-details" #model={{post}}>{{post.title}}</LinkTo>
{{outlet}}
As the image above. The nested route will be render at the bottom instead of to the new page. How am i going to render it to new page? I was thinking LinkTo will actually link it to the new page right? And the forum details should be render at the {{outlet}} tag, so where should I place the {{outlet}} tag in order to let it render to new page?
You problem is that you need to understand nested routes.
If your route is main.forum.forum-details this means your router looks like this:
this.route('main', function() {
this.route('forum', function() {
this.route('forum-details');
});
});
So the forum route is a parent route of the forum-details route.
And its important to understand that parent routes are always visible when visiting a child route.
So for the main.forum.forum-details ember will render your application route and inside its {{outlet}} it will render the forum route and inside this {{outlet}} it will render the forum-details route.
So if you want either the forum or the forum-details route you can restructure your routes:
this.route('main', function() {
this.route('forum');
this.route('forum-details');
});
or you can move what you currently have in your forum route to your forum.index route. If a route has subroutes and none of the subroutes is active there will always be an index route active that you can use.
On Ember 3.15 (Octane)
I'm trying to create the following routes in my application
/cars
/cars/:car_id
/cars/:car_id/:model_id
The first two are fine and I've been able to create them using ember-cli
ember g route cars
ember g route cars/car
The problem with the third one is that I need the model template to be shown under the cars template, ie. replace the car template. So if I put an {{outlet}} on the car template, the model template is rendered in it fine but obviously with that {{outlet}} nothing happens when I navigate to /cars/5/2
My router looks like this
Router.map(function() {
this.route('cars', function(){
this.route('car', { path: ':car_id'}, function() {
this.route('model', { path: ':model_id' });
});
});
});
I've tried using an index route ember g route cars/car/index but the problem with this that the params are not available in the index route and can only be accessed on the car route.
As plan b I've created the car and model routes as top level routes (based on this answer), but I'd like to achieve the above because of a few reasons
it seems more logical ie, structuring the app based on the nesting of the routes
I have many nested routes and creating all of them as first level routes will become hard to maintain
ember doesn't apply the active class correctly with this configuration. For example if I have a navbar with Cars as an link, I'd want it to have the active styling on all three pages. But this doesn't work anymore because the second route will be called something like car and the third one something like model.
there are some issues with the urls that are created by default using <LinkTo />.
So if I have something like this in my car template
<ul>
{{#each #model.models as |model|}}
<li><LinkTo #route="model" #model={{model}}> {{model.title}} </LinkTo></li>
{{/each}}
</ul>
The actual link works properly in that it takes me to the correct model page, however, the url is shown as /cars/undefined/undefined. (Although this is fixable by passing #models={{array #model.id model.id}}, it seems like a workaround at this point)
The solution is indeed to use the index route.
So have cars.car.index and cars.car.model. However you should use the cars.car route to load the common car model. Then you can access it in the cars.car.index route with this.modelFor('cars.car').
Then you can access the params.car_id in the cars.car route, use it to load the car, and then access this car from the cars.car.index and cars.car.model routes with modelFor.
In Ember.js, given these routes:
this.route('chefs', { path: ":country/chefs"});
this.route('recipes', { path: ":country/recipes"});
I am required to put the positional params in the link-to component, like:
{{#link-to 'chefs' 'mx'}}Mexico Chefs{{/link-to}}
How can I avoid specifying some params in all my link-to components?
I'm thinking the 'chefs' route could use a default parameter coming from a service. But how? Any other ideas?
Here is a Twiddle:
https://ember-twiddle.com/fe34f195723fffa88869558e94f3fabc
Edit
Another example for this need is when using nested routes. Imagine a menu component with links to "chefs" and "recipes". When placed inside a parent route named 'country', say "/ca", the links don't need params.
{{#link-to 'chefs'}}Chefs{{/link-to}}
{{#link-to 'recipes'}}Recipes{{/link-to}}
These will navigate between '/ca/chefs' and '/ca/recipes'. But when this hypothetical menu is put on a root-level route, say '/login', the menu will error out "Assertion Failed: You attempted to define a {{link-to "chefs"}} but did not pass the parameters required for generating its dynamic segments..."
I would like to give the route a default "country" parameter when one is not passed by the link.
One alternative I see, which doesn't seem too elegant, is to create a service and inject the "country" param in every link.
I have this on my router.js:
this.resource('campaigns', {path:'/campaigns'}, function() {
this.route('index', {path: '/'});
this.route('group', {path: '/*campaign_group_id'});
});
So it's a route called campaigns, and 2 nested routes: index and group.
When the browser is on the group route, I need to access the model from group route, on the campaigns route/controller.
But I can't access it. I always get the model that it's on campaigns.js route.
If you set the model from the campaign.group route on its controller, you should be able to use controllerFor to from the campaigns route, something like
// routes/campaigns.js
this.controllerFor('campaigns.group').get('your-prop')
You could also send an action from your group route and handle it in the parent campaigns route, sending along the model. This is perhaps more idiomatic with Ember 2.0 conventions.
Btw if you're using Ember CLI (which you should be) you should try getting rid of this.resource and putting all your routes/templates/controllers in pods.
How can I set a link to route with a dynamic segment. According to guide I start with this
window.App = Ember.Application.create()
App.Router.map ->
#resource 'products'
#resource 'product', path: '/product/:product_id'
in my template:
{{#linkTo "product.1"}}products{{/linkTo}}
Unfortunately this gives me the follwing error:
Uncaught Error: assertion failed: The attempt to linkTo route 'product.1' failed.
The router did not find 'product.1' in its possible routes: 'products', 'product', 'index'
{{linkTo}} expects the route defined in the Router.map, so in according to your mapping it should be simply product.
As for the dynamic segment, you also have to pass an object which will be serialized in the ProductRoute. The serialization in almost all scenarios occur without the developer having to do anything since Ember relies on conventions. In rare cases, one must implement serialize a little differently, but for most cases you don't have to touch it.
If you're using {{linkTo}} inside an {{each}} loop you can do it like this:
{{#each product in controller}}
{{#linkTo product product}}Details{{/linkTo}}
{{/each}}
or
{{#each controller}}
{{#linkTo product this}}Details{{/linkTo}}
{{/each}}
Where the first argument is the route name and the second is your model object. In the first code the object has been also named as product, while in the second it's simply being passed as this, which is the product of the iteration.
If you have an unusual scenario where you have to link to a dynamic route while not using the {{each}} loop, you have to expose the object in the controller (preferred) or view. Then you'd have to do something similar to the following:
App.SomeController = Em.Controller.extend
product: null
App.SomeRoute = Em.Route.extend
###
controller is actually `SomeController` here
model is not being used, and is null, while the actual model being
supplied to the controller is `product`, retrieved from store
###
setupController: (controller, model) ->
product = App.Product.find 1
controller.set 'product', product
return
While your template would be similar to this:
{{#linkTo product controller.product}}Product{{/linkTo}}
How does the route know the id?
Conventions. The route will serialize the object you pass, and expose an object with a single property, which has the name of the model for that route, followed be "_id", which in this case would be product_id, so when you click that link, the app activates the ProductRoute, runs the serialize method creating that id property, which will subsequently be used as the argument of the model hook. That's where you call find passing params.product_id as argument. Then the model returns a promise of that model which will be used by the setupController, exposing the object to the view layer as controller.content or simply controller.