Aurelia nested routers and router-views - aurelia

I want to have a list of courses on the screen(contrived example). When you click a course, it expands(still showing the list of courses) into a list of students that take the course. When I click a student it must expand into a view of the students details(still showing the list of students AND the list of courses). etc.
So basically I want to have nested routers, I think?
Example, app.js
config.map([
{route: "", moduleId: 'no-selection', title: 'Select'},
{route: "course/:course-id", moduleId: 'course', name: 'course'}
]);
course.js(inject the router):
this.router.configure(config => {
config.map([
{route: "course/:course-id/student/:student-id", name: "student", moduleId: "student"}
]);
})
student.html has its own router-view element
The above works, BUT when i click a top level link(a course), the content gets put in the inner router-view. Even worse, there is now a router-view in every student that gets expanded, how do I manage this?
Basically I can't find any documentation on how to do this - the child router stuff doesn't seem to be exactly what I need? Any help will be appreciated.

Let's look at this and how we can implement this with a single router. Here is the route structure I might use.
[
{ route: "", moduleId: 'no-selection', title: 'Select'},
{ route: "course/:courseId/:studentId?", moduleId: 'course', name: 'course'}
]);
The studentId parameter is optional.
Then I would look at how to use this course page to do everything you want based solely on the parameters it receives in the activate callback.
I don't have time to give a full answer right now, but hopefully this will help lead you down the right path!

Related

Browser back button not working properly on router query param change

I have a simple route definition:
{
path: 'customers',
name: 'customers',
component: Customers,
props: true
}
To start with, I am on the general /customers route.
But I can use query params on this route, and react on changes to load the corresponding data:
this.$router.push({ name: 'customers', query: { customerId: '123' } });
#Watch('$route.query.customerId')
customerIdChanged() {
this.loadCustomer($route.query.customerId);
}
This works as intended, but let´s say i push three different customerId´s to this router, and now want to go back in browser history and load the previous customers one by one. I can´t because a query change is not considered a "real" url change. So when I push back, I get routed back to the initial /customers route.
How to make these query changes count as real url changes so I can use the back button?
I could maintain my own browser history stack, but I would rather not, and think there is a more "official" solution?
Thanks!
---- UPDATE ----
I actually had an error in my code. Before i pushed to the route, i replaced the route with /customer. And then pushed the customer query route. This was why the back navigation did not work.
What about dynamic matching? You could use:
{
path: 'customers/:id',
name: 'customers',
component: Customers,
props: true
}
this.$router.push({ name: 'customers', params: { id: '123' } });

Only change one viewport in Aurelia router

Good morning, I have my route setup as shown below within Aurelia CLI.
config.map([
{
route: [''],
viewPorts: {
'side': { moduleId: 'side' },
'main': { moduleId: 'main' }
},
title: 'Test',
nav: false,
name: 'Temp'
]);
What I would like to do is based on what I select on my side view, I just want to change the moduleId for main and load that view.
I don't think there's a way to dynamically change the moduleId of a view-port. I see 2 options to solve your proble:
1 - Create another route, changing the moduleId of one of the viewports
2 - Use the layout mechanism and change its content in run time. I recommend you to read http://aurelia.io/hub.html#/doc/article/aurelia/router/latest/router-configuration/10.
I know this is not the answer you were expecting but I hope it helps.

How to dynamically build navigation menu from routes linking to parent/child views/controllers

This question is a follow-up question for my original question Linking directly to both parent+child views/controllers from the main navigation menu
The accepted answer is great, but later when I added a 2nd "childRoute" dynamically, I noticed a problem. In order to build my navigation dynamically I had to add the multiple routes with the same "route" attribute. (see app.js in the example code below). The only difference were the "title" and "settings" attributes.
configureRouter(config, router){
config.title = 'Aurelia';
config.map([
{ route: 'shared-parent', moduleId: './shared-parent', settings: { childRoute: 'child-a' }, nav: true, title: 'Shared Parent - child-a' },
{ route: 'shared-parent', moduleId: './shared-parent', settings: { childRoute: 'child-b' }, nav: true, title: 'Shared Parent - child-b' },
...
]);
this.router = router;
}
The settings attribute I used in the view for doing this:
<a if.bind="row.settings.childRoute" href.bind="row.href + '/' + row.settings.childRoute">${row.title}</a>
I know it's not pretty but it does navigate to the right child route. The problem is that it's always the last of the 2 routes with duplicate "route" attributes that is marked as active.
The reason why I added the settings: {childRoute: 'child-a/b' } instead of giving them distinct "route" attributes like route: 'shared-parent/child-a' and route: 'shared-parent/child-b' was that the url would actually then match shared-parent/child-a/child-a and shared-parent/child-b/child-b since we're first linking to the shared-parent.
This live runnable gist should clearly display the problem (child-a route never activating): https://gist.run/?id=95469a9cb3a762d79da31e0b64248036
Ps.
If you have a better idea of what to call the title of this question please feel free to edit it.
So I took a stab at your problem using the EventAggregator in the Activate lifecycle hook in the child view models.
https://gist.run/?id=bfb5df5e39ac0bb73e9e1cae2d2496e2
in the child view models, I just published an event stating the child route was updated:
activate(params, routeConfig, navigationInstruction) {
let payload = navigationInstruction.parentInstruction.config.title;
payload = payload.substring(0, payload.length - 7);
this.aggregator.publish("child route updated", payload + "child-a");
}
In the app.js file, I updated the route titles, and added an activeChild property. Next, update the activeChild property when the event is captured:
constructor(aggregator) {
this.aggregator = aggregator;
this.aggregator.subscribe("child route updated", payload => {
this.activeChildRoute = payload;
console.log(this.activeChildRoute);
});
}
Finally, I updated the class expression on you list item to update based on that active child Flag:
<li repeat.for="row of router.navigation"
class="${row.title === activeChildRoute ? 'active' : ''}">

How can I show/hide navigation elements based on a user's state using Aurelia

I am quite new to Aurelia and I am loving every minute of the leaning curve. I am using Aurelia's Skeleton Navigation for es6 apps and I am currently working on a simple app where a user logs in and submits a story after providing certain details. The flow of my app is fairly simple:
Login Screen --> Screen where user provides phone number --> User submit's story
My navigation bar has "Home" and "Submit Story". However, I want to hide the "Submit Story" menu item until after the user has provided his phone number. So basically on phone number submit, if the function returns true I need the menu item to show up.
I think there are two ways to go about this. One is by binding the nav items to a true or false value depending on whether the phone number is submitted, and the other is to dynamically add the route on a successful submission of the phone number. Which one is better to use? And I would greatly appreciate a nudge in the right direction.
I tried assigning a boolean variable to the routes but it gave me mixed results. See the code below (I have included just the related code):
check_phone = false;
configureRouter(config, router) {
config.title = 'Aurelia';
config.map([
{route: ['', 'home'], name: 'home', moduleId: 'home', nav: check_phone, title: 'Home'},
{route: 'story', name: 'story', moduleId: 'story', nav: check_phone, title: 'Submit a Story'}
]);
config.mapUnknownRoutes('/');
this.router = router;
}
phone_submit() {
if(success) this.check_phone = true;
}
I also tried dynamically adding the route but it does not work:
configureRouter(config, router) {
config.title = 'Aurelia';
config.map([
{route: ['', 'home'], name: 'home', moduleId: 'home', nav: check_phone, title: 'Home'},
]);
config.mapUnknownRoutes('/');
this.router = router;
}
phone_submit() {
if(success)
this.router.addRoute({route: 'story', name: 'story', moduleId: 'story', nav: true, title: 'Submit a Story'})
this.router.refreshNavigation();
}
Please assist me in this. Thank you for your time!
There is actually a settings property on the route. You can set the settings property to an object in which you can manipulate. You can use this object when you render your route and have the nav bar hide it if it has a particular property.
e.g.
{route: user/:id',
name: user,
moduleId: './Aurelia/users,
nav: false,
title: 'User',
settings: {isShown: true}
}
Check my article on linked-in on the topic which explains using the settings property more thoroughly;
https://www.linkedin.com/pulse/routing-aurelia-framework-mike-gold?trk=mp-author-card

Durandal - how to route parametrized module at levels with a different no. of parameters?

While the direct mapping of route parameters to activate() parameters is convenient, it poses a problem for me for the reuse of parametrized modules.
Take a route like e.g.
{ route: '/user/:id/request(/:requestId)', moduleId: 'requestDetail', title: 'Request Details' }
The viewmodel will be called with
activate(id,requestId)
Now, the requestDetail module could be reused at different positions and levels in navigation, like so
parent router
{ route: '/company/:companyId*users', moduleId: 'someModule', title: 'Request Details' }
child router
{ route: '/user/:id/request(/:requestId)', moduleId: 'requestDetail', title: 'Request Details' }
which has the effect of the requestDetail vm being called with
activate(companyId,id,requestId)
which obviously is a problem.
Is there any way to restrict the passed parameters to those coming from the child router (which would solve the problem)?