I have struggled all day unable to figure out how to properly create a child router using Durandal in my hot towel-based (MVC4 SPA) project. This is what I have at the moment:
shell.js
var routes = [
{ route: '', moduleId: 'home', title: 'Home', nav: 1 },
{ route: 'inventory/index', moduleId: 'inventory/inventory', title: 'Inventory', nav: 2 }];
return router.makeRelative({ moduleId: 'viewmodels' }) // router will look here for viewmodels by convention
.map(routes) // Map the routes
.buildNavigationModel() // Finds all nav routes and readies them
.activate(); // Activate the router
inventory.js
define(['services/logger', 'plugins/router'], function (logger, router) {
var title = 'Details';
var childRouter = router.createChildRouter()
.makeRelative({
moduleId: 'viewmodels/inventory',
fromParent: true
}).map([
{ route: '', moduleId: 'inventory', title: 'Inventory', nav: 3 },
{ route: 'items', moduleId: 'items', title: 'Items', nav: 4 }])
.buildNavigationModel();
var vm = {
router: childRouter,
activate: activate,
title: title
};
return vm;
//#region Internal Methods
function activate() {
logger.log(title + ' View Activated', null, title, true);
}
//#endregion
});
The problems I currently have:
- Inventory view is activated twice when I click on it
- No further navigation options become visible, although some empty 3D rounded rectangle appears in the upper right... I'm not sure if that's a progress bar that failed to go away or some failed navigation element.
My project files are organized like this:
My main problem was that I was expecting child navigation menus to show up without having implemented child navigation in my view or viewmodel. I wasn't aware that child navigation wasn't intrinsic to the existing navigation. Here are the changes I had to make.
Change my inventory.html file to act as the child navigation container by adding an element with a data-bind
on router.navigationModel
, which picks up and displays the child navigation menu; also include an element with a data-bind
on router
which will pick up the leaf node (page content) at the lowest navigation level:
<section>
<header>
<h2 class="page-title" data-bind="text: title"></h2>
<nav class="pull-left">
<ul class="nav nav-pills nav-stacked" data-bind="foreach: router.navigationModel()">
<li><a data-bind=" css: { active: isActive }, attr: { href: hash }, text: title"
href="#"></a></li>
</ul>
</nav>
</header>
<section id="inventoryPane" data-bind="router: { transition: 'entrance', cacheViews: true }"></section>
</section>
Note There are actually 3 modules involved in a fully navigated view here. shell.js is the root with the top level navigation, then inventory.js (for example) would be another container with child navigation buttons, and finally items.js (for example) would represent content displayed in the inventory.js container.
Next, I needed to update my inventory.js to act as a container and specify the fully qualified path for the module in my call to makeRelative (which I had removed after posting the code in my question as one of many desperate experiments); if you don't get the moduleId right, fatal errors will occur when trying to access the router property of a non-existent object, and the message given by the debugger is not at all helpful if you're running IE:
define(['services/logger', 'plugins/router'], function (logger, router) {
var title = 'Inventory';
var childRouter = router.createChildRouter()
.makeRelative({
moduleId: 'viewmodels/inventory',
fromParent: true
}).map([
{ route: 'items', moduleId: 'items', title: 'Items', nav: 3 }])
.buildNavigationModel();
var vm = {
router: childRouter,
activate: activate,
title: title
};
return vm;
//#region Internal Methods
function activate() {
logger.log(title + ' View Activated', null, title, true);
}
//#endregion
});
Next it seems I had to update the hash on my root level routing to avoid an error about the "*inventory" route not being found. I don't know why splat routes aren't handled better automatically for me; somehow the knockout samples got around this problem, but I could not without overriding the hash:
var routes = [
{ route: '', moduleId: 'home', title: 'Home', nav: 1 },
{ route: 'inventory*inventory', moduleId: 'inventory', title: 'Inventory', hash:"#inventory", nav: 2 }];
And finally, I had to restructure my tree to move the inventory files up one level: