I am building my first EmberJS app.
Initially from index route, I will display an A. When user click A, it will route to /a. Component A will have a field url, after fill in and click a button, it will route to /b. Component B also has a field email. Fill in and click a button it will route to /summary which displays what I filled in A and B. How could I do it in best practice?
I am using ember-cli and this.transitionTo(/b) to go to route /b
It seems like nested routes would be good for this implementation.
/a - route
model(){
return {url:'default'};
}
a.hbs
{{comp-a model=model}}
/a/b - route
model(){
return {email:''};
}
a/b.hbs
{{comp-b model=model}}
/a/b/summary - route
model(){
return {amodel:this.modelFor('a'),bmodel:this.modelFor('b')};
}
a/b/summary.hbs
{{comp-a model=model.amodel }}
{{comp-b model=model.bmodel }}
Here is a tutorial that explains exactly how to make a multi-step form like you are asking for. http://romulomachado.github.io/2016/12/06/multi-step-form-with-ember.html
Using a nested route system, this is fairly straight forward.
Router.map(function() {
this.route('checkout', function() {
this.route('personal-info');
this.route('shipping-info');
this.route('payment-info');
this.route('confirmation');
});
});
and then you can use an 'order' or some 'form' model to keep the information alive.
I followed the example and built one out with the ability to edit and confirm at the end too. : )
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.
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.
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
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.