OK, so this answer is basically modification of previous routing answer allowing this time multiple levels (question is about 3 levels but I'll provide general answer for any number of levels).
Model
As in my previous answer, I'll not use Durandal child routers (reason stated in description of linked earlier answer). Instead will provide my own way of defining "embedded" routes.
Let's assume having following model
var model = [
{
route: '',
moduleId: 'viewmodels/home',
title: 'Validation test',
nav: true,
hash: ''
},
{
route: 'samples',
moduleId: 'viewmodels/samples',
moduleRootId: 'viewmodels', // Custom property to make child routes easier
title: 'Samples',
nav: true,
hash: 'samples',
childRoutes: [
{ route: 'simpleList', moduleId: 'simpleList', title: 'SimpleList', nav: true, hash : 'simpleList', childRoutes: [
{ route: 'simpleListA', moduleId: 'simpleListA', title: 'SimpleListA', nav: true, hash : 'simpleListA' },
{ route: 'simpleListB', moduleId: 'simpleListB', title: 'SimpleListB', nav: true, hash : 'simpleListB' } ] },
{ route: 'clickCounter', moduleId: 'clickCounter', title: 'Click Counter', nav: true, hash : 'clickCounter' }
]
}
];
We have two 0-level (top level) routes (Home and Samples), two 1-level (childs of Sample - SimpleList and ClickCounter) and two 2-level (childs of SimpleList - SimpleListA and SimpleListB).
Transformation
What Durandal expects to have is just a list of routes. So we need to transform our nice model structure into list of routes (with some changes to hash, moduleId [I assume that structure of viewModel folder is same as our routes i.e. SimpleList and ClickCounter viewmodels are in subfolder, same for SimpleListA and SimpleListB])
Algorithm is basically tree traverse and flatting.
function flatRoutes (routes, level, path, parent) {
var output = []
$.each(routes, function(index, route) {
route.route = path + route.route;
route.moduleId = path + route.moduleId;
route.hash = '#' + path + route.hash;
route.parent = parent;
route.level = level;
output = output.concat(route)
if (route.childRoutes !== undefined) output = output.concat(flatRoutes(route.childRoutes, level + 1, route.route + '/', route.route))
})
return output;
}
function createRoutes (routes) {
return flatRoutes(routes, 0, '', '')
}
As always routes need to be registered and router activated:
var routes = createRoutes(model);
return router.map(routes)
.buildNavigationModel()
.activate();
Rendering
Sorry, this part is not tested as I'm not using Durandal / Knockout stack anymore.
In the ShellVM (or somewhere else You want to bind router with Knockout) you will need function finding child routes for given parent.
function findRoutes (parent) {
var output = []
$.each(router.navigationModel, function(index, route) {
if(route.paent === parent) output = output.concat(route)
}
return output;
}
And in Knockout / HTML part You will need to use recursive templates for Knockout.
<script id="treeElement" type="text/html">
<li>
<span data-bind="text: title"></span>
<ul data-bind="template: { name: 'treeElement', foreach: $root.vm.findRoutes($data.moduleId) }">
</ul>
</li>
</script>
<ul data-bind="template: { name: 'treeElement', foreach: $root.vm.findRoutes('') }"></ul>
Of course it's just rendering router as simple mulitlevel list - You will need to change it according to what You want to achieve and what CSS framework are You using. But that should be easy.