Using the Aurelia router, how do I define two routes to the same view? - aurelia

I have a sub-router. Let's say it's for fancy fruits. In that sub-router, I want to define routes to specific fruits that will show up in the navigation using RouteConfig nav: true as such:
config.map([
{ route: [":fruit"], name: "AppleFruit", moduleId: PLATFORM.moduleName("./fruity-fruit-fruit"), nav: true, title: "Apples are yum!", layoutModel: { fruit: "Apple" } },
{ route: [":fruit"], name: "LemonFruit", moduleId: PLATFORM.moduleName("./fruity-fruit-fruit"), nav: true, title: "Lemons are for booze!!", layoutModel: { fruit: "Lemon" } }
]);
Specifically, I'm wanting to do it this way because there are navigation elements on the page that rely on other routes in this sub-router, and I would like these to appear in the navigation with the others. My thought was that the layoutModel would get passed into the activate params in fruity-fruit-fruit, but it just blows up before making it to that point.
Is this possible, or what am I doing wrong?

As often happens, I have found the answer. My problem was that I was using layoutModel instead of just specifying the href. It should have been:
config.map([
{ route: [":fruit"], name: "AppleFruit", moduleId: PLATFORM.moduleName("./fruity-fruit-fruit"), nav: true, title: "Apples are yum!", href: "/fruits/Apple" },
{ route: [":fruit"], name: "LemonFruit", moduleId: PLATFORM.moduleName("./fruity-fruit-fruit"), nav: true, title: "Lemons are for booze!!", href: "/fruits/Lemon" }
]);

Related

What's the correct aurelia router config for routes #/items/ AND #/items/:id/summary?

I'm trying to get a parent/child router working for this basic hierarchy:
#/items (show options to select a specific item)
#/items/:id/summary (specific item summary)
#/items/:id/detail (specific item detail)
Detail
I have the following routes defined in the parent router:
[{
route: '', redirect: 'items'
}, {
route: ['items'],
name: 'Items',
viewPorts: {
itemNavigation: {moduleId: '....'},
currentItem: { moduleId: '......' }
},
nav: true,
title: 'Items'
},
{
route: ['items/:id'],
viewPorts: {
itemNavigation: {moduleId: '......'},
currentItem: { moduleId: '......' }
},
nav: false
}...
I have the following routes defined in the 'currentItem' module child router:
[{
route: '',
moduleId: '....',
name: 'noRouteSelected',
nav: false
}, {
route: 'summary',
moduleId: '....',
name: 'summary',
nav: true,
title: 'Summary'
}, {
route: 'detail',
moduleId: '....',
name: 'detail',
nav: true,
title: 'Detail'
}...
This looks ok but if I navigate to a child route (e.g. '#/items/123/summary'), because nav is false, there's nothing shown as 'active' in the main nav menu (the red circle in the image). That's fair enough but if I merge the 2 parent configs along the lines of:
route: ['items', 'items/:id']
I get a shed load of exceptions being thrown:
Error: A value is required for route parameter 'id' in route 'investments'.
If I user the optional config:
route: ['items/:id?']
then I need to specify the href parameter in the config, but I've no idea what that should be in this scenario. Should I instead be using a Navigation Strategy? (examples would be great!)
Can anyone help? The documentation is somewhat lacking on this front.
Here's how I would structure your app:
In the top-level page:
{
route: '',
redirect: 'items'
},
{
route: 'items',
name: 'items',
moduleId: './items',
title: 'Items',
nav: true
}
In the Items page:
{
route: '',
name: 'no-item-selected',
moduleId: './some-empty-view',
nav: false
},
{
route: ':id',
name: 'item',
moduleId: './item',
nav: false
}
In the Item page:
{
route: '',
redirect: 'summary'
},
{
route: 'summary',
moduleId: './item-summary',
name: 'item-summary',
title: 'Summary',
nav: true
},
{
route: 'detail',
moduleId: './item-detail',
name: 'item-detail',
title: 'Detail',
nav: true
}
In the Item view model itself you could store the Router you get during configureRouter, and then during activate() set the title accordingly if you want to, since "Items | Item | Detail" is probably a bit silly (so I left the title out for the Item page)
You could also completely omit the nested child router in item page and just have both summary and detail in there, showing/hiding them according to some other logic. Though it can be neat to have the back/forward navigation capabilities and be able to directly link to something.
Original answer (this was addressing what turned out to be a typo, but i'll just leave it for context)
Change your route from items:id to items/:id. Basically every "segment" of a route (be it static, optional, dynamic) needs to be separated by a slash or route-recognizer doesn't map them correctly.
You don't need to make the route optional with a setup like this. You won't be selecting an item without an id anyway (and if you create a new one, you'd typically just pass new or something similar as the id so your view can distinguish)

# in paramertized route breaks Aurelia routes

We have sample app with router configuration defined as follows. "id" parameter in the user details route can have # in its value, such as /users/#abc. We can navigate to the user details view from users view whose "id" is #abc with no problem. However, when refreshing the details page, it goes back to the users view. Is there a way to escape character "#"?
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true },
{ route: 'users/:id', name: 'userDetail', moduleId: 'users/detail' }
]);
}
}
Since template parameters are not being encoded/decoded, they are very restrictive. Basically, this means [a-zA-Z0-9_]. Note, that this is not from documentation, but my observation.
If you need to pass any arbitrary data, just use query parameters. To do that, set up router without defining template parameter.
export class App {
configureRouter(config, router) {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true },
{ route: 'user', name: 'userDetail', moduleId: 'users/detail' }
]);
}
}
You don't have to change anything else (navigation, parameters in activate, etc.) In this way, upon navigation, it'll be turned into query parameter:
router.navigateToRoute('userDetail', { id: '#user123' });
This will result in url: http://myserver/#user?id=%23user123.

Aurelia: Child Router Navigation: Route not found

Below are my views:
1. app - standard
2. home - Has a list of items on left, on selection of any, will display some content on the right side in router-view (contract-view to be loaded).
3. contract-view
app.ts: route Config:
configureRouter(config: RouterConfiguration, router: Router) {
config.title = 'Contracts Portal';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home', nav: true, title: 'Home' },
{ route: 'resources', name: 'resources', moduleId: 'resources', nav: true, title: 'Resources' },
{ route: 'tools', name: 'tools', moduleId: 'tools', nav: true, title: 'Tools' }
]);
this.router = router;
}
Home.ts Router Config:
configureRouter(config: RouterConfiguration, router: Router) {
config.title = "test";
config.map([
{ route: ['', 'contract-view/:id'], name: 'contract-view', moduleId: 'contract-view', nav: true, title: 'Test' }
]);
this.router = router;
}
on selection of a item in home page list, I am trying to navigate as below to load content in the right pane's router-view, in home.ts:
this.router.navigateToRoute("contract-view", { id: 4090 });
However it throws the error: Route not found: /contract-view/4090
At this point, it's still home page and default route, hence the url reads: http://localhost:9000/#/
and so it fails.
But, if I manually change the url to http://localhost:9000/#/home and then select a list item, navigation to contract-view works.
What I am I missing here?
I am looking for absolute path navigation. Tried navigating to home/contract-view but fails with error:
A route with name 'home/contract-view' could not be found. Check that name: home/contract-view was specified in the route's config.
The default route of Home.ts has a parameter:
config.map([
{ route: ['', 'contract-view/:id'], name: 'contract-view', moduleId: 'contract-view', nav: true, title: 'Test' }
]);
This might be a problem because the parameter :id is not referenced in the first name. So, I suggest you change the route as follow:
config.map([
//create another route with no parameters,
//this route will represent an empty selection of contract
{ route: [ '', 'contract-view' ], name: 'contract-view-empty', moduleId: 'contract-view-empty', Title: 'Select a Contract' }
{ route: 'contract-view/:id', name: 'contract-view', moduleId: 'contract-view', nav: true, title: 'Test' }
]);
Then, to generate a navigation link you can use route-href attr. Like this:
<a route-href="route: contract-view; params.bind: { id: 4090 }">Navigate</a>
Hope it helps!
It is an issue with Aurelia Router framework. Discussion and workaround here:
https://github.com/aurelia/skeleton-navigation/issues/230

Durandal activating to specific route not working

I have the following:
<div class="page-host" data-bind="router: { transition:'entrance' }"></div>
And with the standard router configuration, I call:
return router.activate('browse');
however, I get "hello" routed every time. I need to conditionally select which route to navigate to from within shell - how can this be done?
My routeConfig:
router.map([
{ route: ['', 'hello'], title: 'hello', moduleId: 'hello/hello', nav: true, allowedUsers: ['authenticated', 'unauthenticated'] },
{ route: 'browse', moduleId: 'browse/browse', nav: true, allowedUsers: ['authenticated'] },
{ route: 'resetpassword/:id', moduleId: 'forgot/reset', nav: false, allowedUsers: ['unauthenticated'] }
]).buildNavigationModel();
I ended up extracting the routes array and manipulating it to set the default route before calling router.map - though I'll leave this open in case there's a better way.
You can specify which route to use on activate with the startRoute argument, as follows:
return router.activate({
startRoute: "myStartRoute"
});

Define the starting module in durandal 2.0 and navigate to it

It seems to me... that this way the initial route is defined via:
{ route: '', moduleId: 'viewmodels/customers', title: 'customers', nav: true },
When the application is loaded with the route '' which must be oddly set to empty then this route is loaded initially.
When I navigate now to mysite/#/customers nothing is loaded.
How can I give my route a starting module which I can use to navigate to it?
In the old router I used startModule but I can not find it in durandal 2.0.
You probably need to setup a second route with the same moduleId. Here's a live example for child routes that uses this http://dfiddle.github.io/dFiddle-2.0/#hello and http://dfiddle.github.io/dFiddle-2.0
define(['plugins/router', 'durandal/system', 'global', 'knockout'], function( router, system, global, ko ) {
var childRouter = router.createChildRouter()
.makeRelative({
moduleId: 'hello',
route: 'hello'
}).map([
{route: '', moduleId: 'default/index', title: 'Hello World', type: 'intro'},
{route: 'default', moduleId: 'default/index', title: 'Hello World', type: 'intro', nav: true},
{route: 'dFiddle', moduleId: 'dFiddle/index', title: 'Hello World', type: 'fiddle', nav: true}
]).buildNavigationModel();
// .on is mixed in an not meant to be chainable
childRouter.on('router:navigation:complete').then(global.createSampleLink);
return {
global: global,
router: childRouter,
getItemsByCategoryId: function( categoryId ) {
return ko.utils.arrayFilter(childRouter.navigationModel(), function( route ) {
return route.type === categoryId;
});
},
binding: function() {
system.log('Lifecycle : binding : hello/index');
return { cacheViews: false }; //cancels view caching for this module, allowing the triggering of the detached callback
}
};
});
This specific setup that has child routes on all top routes use router.guardRoute in shell.js to handle the empty root case. There's an open ticket https://github.com/BlueSpire/Durandal/issues/240 that discusses better handling of these kind of edge cases.
define(['plugins/router'], function (router) {
// Redirecting from / to first route
router.guardRoute = function(routeInfo, params, instance){
if (params.fragment === ''){
return routeInfo.router.routes[0].hash;
}
return true;
};
return {
router: router,
activate: function () {
router.map([
{ route: '', moduleId: 'hello/index', title: 'Hello World' },
{ route: 'hello*details', hash: '#hello', moduleId: 'hello/index', title: 'Hello World', nav: true },
...
]).buildNavigationModel();
return router.activate();
}
};
});
You can declare your routes like this:
routes = [
{ route: ['welcome', ''], title: 'Welcome', moduleId: 'welcome', nav: true },
{ route: 'Visits', title: 'Visits', moduleId: 'Visits', nav: true },
{ route: 'VisitAddEdit', title: 'Add New Vsit', moduleId: 'VisitAddEdit', nav: true }
];
Notice how the welcome route is declared as an array (['welcome', '']) that includes an empty string in addition to a name.
This may not be the best long-term solution, but my fix was to pass in a startUrl in the options object of router.activate() and then change one line of history.js (line 163, the end of the activate function):
return history.loadUrl(options.startUrl);
I call it like this after I set up the router mapping and buildNavigationModel:
router.activate({startUrl: 'hello'});
Hope that helps as a quick fix.
EDIT
To get the URL to update as well, I changed it to history.navigate(options.startUrl) instead of loadUrl.