2

I am trying to make multilevel menu. Menu should contain both one level, in some section two levels, and three levels. Sections can not be placed in a row. Example menu: This is example menu, which I need

I wrote to the Google's group DurandalJS, but nobody explained what am I doing wrong. I found this example but I does not work.

Appeal to people who understand such things: 1. How should I organize the logic of this menu. 2. How to bind data to a view 3. Do I need to use a ChildRouter for this?

I hope for the help.

Community
  • 1
  • 1
Paweł Groński
  • 567
  • 1
  • 6
  • 17

1 Answers1

1

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.

Community
  • 1
  • 1
Krzysztof Cieslak
  • 1,695
  • 13
  • 14
  • Sorry, but can you give me example project? I have a lot of error in project. Bindings value: template: { name: 'treeElement', foreach: $root.vm.findRoutes('') } Maybe you can give url in GitHub or similar server? Unable to parse bindings. Message: $root.vm is undefined; – Paweł Groński Aug 25 '15 at 13:15