How to define two ember routes with same name with different number of query params? - ember.js

I have defined two ember routes as follows.
export default Router.map(function () {
this.route('stock-overview', {path: '/:lan/stock-overview/:companyId'});
this.route('stock-overview', {path: '/:lan/stock-overview/:exchange/:symbol'});
same name but different number of params. but when i add link-to helper as follows it gives an error saying.
<li>{{#link-to 'stock-overview' 'en' '70'}}Stock Overview{{/link-to}}</li>
<li>{{#link-to 'stock-overview' 'en' 'exchange' 'someSymbol'}}Stock Overview{{/link-to}}</li>
Console Error:
Uncaught Error: More context objects were passed than there are dynamic segments for the route: stock-overview
How can I handle this situation.? My ember version is:1.11.1
Appreciate any help.

I'm not %100 sure, but if i'm not wrong, you can only have one model for a route.
IMO, it would be better for you to do this with nested route.
Update:
I found some Q-A's from SO. Maybe those can be helpful for you:
Is resource nesting the only way to enable multiple dynamic segments?
Multiple Dynamic Segments in a Single Resource in Ember.js (see also Kingpin2k's answer)

Related

Dynamic model in LinkTo component in Ember

I am using Ember 3.18, I am facing the below issue. Consider the following routes:
Router.map(function() {
this.route('author');
this.route('author' , {path:"/author/:author_id"});
});
Now, in my hbs file, I am trying to transition to the above routes using a single LinkTo. As you can see, only the second route requires model attribute. In simple terms, I want to combine the below 2 into a single line.
<LinkTo #route="author" />
<LinkTo #route="author" #model="2" />
As you can see, I require the model attribute to be gone in certain cases and availble in certain cases.
Please help.
I think the easiest way forward is to tweak your routing setup a bit. I know you want to combine the routes, but it's hard/confusing, imo, and would be "more standard" to do something more traditional like:
Router.map(function() {
this.route('author', function() {
this.route('view', {path:":author_id"});
});
});
and
<LinkTo #route="author.index" />
<LinkTo #route="author.view" #model="2" />
author.index would match /author
and author.view (with a #model) would match /author/2.
Note that index is an implicit convention of the web, and not needed in the router.js file

rendering templates with #link-to and dynamic segments

I have a simple app to show a list of clients, and a detail page for each specific client.
So basically, two URLs:
/clients/
/clients/:slug/
I've been having some trouble rendering the results from my Client model into a template, specifically when using the #link-to helper with dynamic segments.
I've found two methods for doing so, and while I can get them both to work, I don't know which one is the best and why.
Here is some (simplified) code from my app to explain.
// app/models/client.js
export default DS.Model.extend({
name: DS.attr('string'),
slug: DS.attr('string')
});
(my actual model is larger, but these are the only relevant fields)
and here are my routes:
// app/router.js
Router.map(function() {
this.route('clients');
this.route('client', { path: 'clients/:slug' });
});
NOTE: I didn't nest the routes, because I didn't want to use the {{outlet}} nested template feature.
Here is the route for Clients, where I retrieve my list of clients
// app/routes/clients.js
export default Route.extend({
model: function() {
return this.get('store').findAll('client'); // fetch all Clients
}
});
Finally, here is the route to fetch info for a single Client:
// app/routes/client.js
export default Route.extend({
model: function(params) {
return this.store.query('client', {
filter: { slug: params.slug } // fetch one specific client, by slug
});
}
});
Everything works fine up to here, but my issue starts when displaying the model data in the templates.
There are two "ways", which one is correct??
OPTION A:
//app/templates/clients.hbs
// list all clients using #each
{{#each model as |client|}}
{{#link-to "client" client}} // pass the "client" object to generate my dynamic routes
{{client.name}}
{{/link-to}}
{{/each}}
Clicking on any of the generated links will render the client detail template, client.hbs
//app/templates/client.hbs
<h1>Client - {{model.name}}</h1>
In this example, I can use model.name to render my model object. That's fine, until I refresh the page! Then the info returned by model.name is obliterated. Same thing if I try to visit the URL directly. I have to go back to /clients and click on the link again to see my client's name.
I then looked for another way to display my data, where the model information would survive a page reload.
OPTION B:
After much reading I found a suggestion to use the specific client slug/id as param for #link-to
// app/templates/clients.hbs
// list all clients using #each
{{#each model as |client|}}
{{#link-to "client" client.slug}} // specifically use the actual slug/id, and not the object
{{client.name}}
{{/link-to}}
{{/each}}
But... by passing client.slug instead of client as parameter to #link-to, I can no longer use model.name to display my data. It simply returns nothing!
//app/templates/client.hbs
<h1>Client - {{model.name}}</h1> <-- model.name now returns nothing D:
However, using a loop DOES work for some reason:
//app/templates/client.hbs
{{#each model as |client|}}
<h1>Client - {{client.name}}</h1> <-- using loop even though I only have one record :(
{{/each}}
So option B works, and the information is displayed correctly after a page reload, or a direct visit from the URL.
But I'm now using a loop to display a single record! And I can't find an alternative solution that actually works.
To summarize:
option A feels like the correct way, but reloading the page obliterates the data.
option B actually works and returns the data, but I have to use a loop to iterate through a single record, which doesn't feel idiomatic / efficient.
I'm extremely confused, any clarification would be greatly appreciated.
Thanks in advance!
This is one of those fun 'magic' things that is supposed to simplify onboarding and make Ember seem easy - but really just caused the most confusion for what should be the most common use-case for the router.
At least it's in the guides now.
https://guides.emberjs.com/v2.12.0/routing/specifying-a-routes-model/#toc_dynamic-models
"Note: A route with a dynamic segment will always have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), and a model context is provided (second argument to link-to), then the hook is not executed. If an identifier (such as an id or slug) is provided instead then the model hook will be executed."
So, that's one thing...
But the other thing - is that you are getting back an array - because you are using query + filter - to create an array of records.
So, if you use queryRecord() - which is meant to get 1 record - then you'll get what you want. : )
I'd just add that {{outlets}} are cool. Here's how I normally do it - but t I can see that my route always loads all of the data this way... / which I normally want - but could see how in many cases - it wouldn't be desired.
And also - if you run into any more whacky params issues... investigate the underscore in the dynamic segment - because it's probably trolling you.

Multiple layouts in Ember 2

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}}

How to make route parameters optional in Ember.js?

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.

How show component only in a specific route?

I have a componente called hero (in application.hbs) and I wish display this componente only in home page.
I researched about how do this but without any success. Thanks!
After a few minutes and some searches on GitHub...
Just install ember install ember-truth-helpers and check the route name:
{{#if (eq currentRouteName 'index')}}
{{hero}}
{{/if}}
Glad to help!
I need more specifics, however, I am going to make the assumption that your home route is the '/' route.
The '/' route is actually your index route, so if you create an index.hbs file it will act as the template for your index route. And then your should just move the hero component to your index.hbs file.
I can't be sure your reasons, but I suspect that this could be a solution.
There is an invisible 'application' route... there is also an implicit 'index' route, but you can skip the confusion of that and just create a 'home' route and give it a path to the root. The application template will house the outlet - and then you can place your component just in the 'home' template;
(don't write an application route like this, but just for visualization)
Router.map(function() {
// overarching 'application' route
this.route('application', function() {
this.route('home', { path: '/' });
this.route('other');
});
});
Here is a twiddle with the full example in place. If this doesn't do what you want, then refer to the conditional suggestions. : )
Router.map(function() {
// here's an example of skipping of skipping the mysterious 'index' in another situation
this.route('books', function() {
this.route('books-list', { path: '/' });
this.route('book');
});
});
You can also render a component dynamically using component helper which save you a conditional statement inside your template.
The first parameter of the helper is the name of a component to render, as a string. So {{component 'blog-post'}} is just the same as using {{blog-post}}.
When the parameter passed to {{component}} evaluates to null or undefined, the helper renders nothing. When the parameter changes, the currently rendered component is destroyed and the new component is created and brought in.
So you can safely pass in anything to the component helper, in your case you can make the component name dynamically without worry an error will raised.
https://guides.emberjs.com/v2.1.0/components/defining-a-component/#toc_dynamically-rendering-a-component