durandal 3rd level child router - durandal

Once I declare 3rd level router, I am facing problem in navigation between modules defined in 2nd level router.
The issue is: when I navigate in between different modules at 2nd level router, activate is fired but attached is not fired for the module.
Code snippet for routers is as follow:
//1st level router
landing.router = router.map([
{ route: 'resourcebrowser*splat', title: 'Resource', moduleId: 'viewmodels/resourcebrowser', nav: true },
]).buildNavigationModel();
//2nd level router
var childRouter = parentView.router = landing.router.createChildRouter()
.makeRelative({
moduleId: 'viewmodels/tab/',
fromParent: true
}).map([
{ route: ':id/details', moduleId: 'details', title: 'Details', type: 'intro', nav: true },
{ route: ':id/docs*splat', moduleId: 'documents', title: 'Documents', type: 'intro', nav: true }
]).buildNavigationModel();
//3rd level router
var childRouter = parentView.router.createChildRouter()
.makeRelative({
moduleId: 'viewmodels/viewer/',
route: ':id/docs/'
})
.map([
{ route: 'documentviewer/:docid', moduleId: 'documentviewer', title: 'Viewer', type: 'intro', nav: true }
]).buildNavigationModel();
Kindly let me know where am I going wrong.

Check the documentation for canReuseForRoute : http://durandaljs.com/documentation/Using-The-Router.html
If you define the canReuseForRoute function and return false, I believe you will find that 2nd level router viewmodel is recreated completely as you intend.

Related

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

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" }
]);

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)

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 third-level subrouting

I'm trying to make child router of a child router in Durandal.js, but getting random dumb errors (for example 404 etc.). Is it Durandal's issue (I mean if it has not third-level subrouting support) or it's maybe my code problem?
Thanks
Anyways, I'm going to include my code here:
returning shell
return {
router: router,
activate: function () {
return router.map([
{
route: ['', 'Main'],
moduleId: 'Main/index',
nav: false
},
{
route: 'Main*details',
moduleId: 'Main/index',
hash: '#/Main/',
title: 'Main',
nav: false
}
]).buildNavigationModel()
.activate();
}
};
returning 2nd level child
var mainRouter = router.createChildRouter()
.makeRelative({
moduleId: 'Main',
fromParent: true
}).map([
{
route: ['', 'Dashboard'],
moduleId: 'Dashboard/index',
nav: false
},
{
route: 'Dashboard*details',
moduleId: 'Dashboard/index',
hash: '#/Main/Dashboard',
title: 'Dashboard',
nav: true
}
]).buildNavigationModel();
return {
router: mainRouter
}
returning 3rd level child
var dashboardRouter = router.createChildRouter()
.makeRelative({
moduleId:'Dashboard',
fromParent: true
}).map([
{
route: ['', 'Product'],
moduleId: 'Product/index',
hash: '#/Main/Dashboard/Product',
title: 'Product',
nav: true
}
]).buildNavigationModel();
return {
router: dashboardRouter
}
I believe it's currently a bug in child routers that are at the 3rd level or below. We have this tracked as an issue and are working on a solution. I apologize for the inconvenience.
hah... This is quite simple:
(var dashboardRouter = router.createChildRouter()
.makeRelative({
moduleId:'Dashboard',
fromParent: true
}).map([
{
route: ['', 'Product'],
moduleId: 'Product/index',
hash: '#/Main/Dashboard/Product',
title: 'Product',
nav: true
}
]).buildNavigationModel();
return {
router: dashboardRouter
}).makeRelative({
moduleId: 'Main',
fromParent: true
}).map([
{
route: ['', 'Dashboard'],
moduleId: 'Dashboard/index',
nav: false
},
{
route: 'Dashboard*details',
moduleId: 'Dashboard/index',
hash: '#/Main/Dashboard',
title: 'დეშბორდი',
tab: "dashboard",
nav: true
}
]).buildNavigationModel();
return {
router: mainRouter
}

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.