user1740381
user1740381

Reputation: 2199

How to handle nested menu in knockout viewmodel

I am using knockoutjs in my project. I have a scenario where I have to create a nested menu in my viewmodel, which I did like this:

self.menu = [
    { 
         name: 'Services', 
         sub: [{ name: 'Service-A' }, { name: 'Service-B' }] 
    },
    // etc
];

self.chosenMenu = ko.observable();

self.goToMenu = function (main, sub) {

    var selectedMenu = {
        main: main,
        sub: sub
    };

    self.chosenMenu(selectedMenu);
};

My View:

<ul class="nav navbar-nav menuitems col-md-8" data-bind="foreach: menu">
    <li class="dropdown">
        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
          <span data-bind="text: name"></span></a>
        <ul class="dropdown-menu" role="menu" data-bind="foreach: sub">
            <li>
                <a href="javascript:void(0);" data-bind="text: name, 
                   click: function() { $root.goToMenu($parent, $data); }">
                </a> 
            </li>
        </ul>
    </li>
</ul>

However, I feel that this approach of creating nested menu is not good, because suppose if I want to go on any menu item programmatically then with his approach it is not possible?

Can anybody please suggest me the good approach to handle this kind of scenario?

Upvotes: 0

Views: 335

Answers (2)

Jan Christenson
Jan Christenson

Reputation: 11

I ran into a similar scenario yesterday. You can have a look at my solution on Is there any knockout plugin for showing a nested context menu?. The main point is that I've used the template binding to be able to construct a hierarchical menu of any depth.

Upvotes: 0

Jeroen
Jeroen

Reputation: 63830

Make sure each menu item has a unique identifier. For the example data you've given name will suffice, but you may have to add a fullPath property to menu item view models.

Your goToMenu function can now take just one parameter: uniqueMenuIdentifier, and recurse through all menu items to find the correct one, like this:

function findMenuItem(menuList, uniqueMenuIdentifier) {
    for (var i = 0; i < menuList.length; i++) {
        if (menuList[i].name === uniqueMenuIdentifier) {
            return menuList[i];
        }
        if (!!menuList[i].sub) {
            var subItem = findMenuItem(menuList[i].sub, uniqueMenuIdentifier);
            if (!!subItem) {
                return subItem;
            }
        }
    }
    return null;
}

self.goToMenu = function (menuItem) {
    var uniqueMenuIdentifier = menuItem.name;
    var item = findMenuItem(self.menu, uniqueMenuIdentifier);
    self.chosenMenu(item);
}

This allows for a much simpler binding in the anchor tag:

<a href="javascript:void(0);" data-bind="text: name, click: $root.goToMenu">

See this fiddle for a demo.

From this you can also guess that it's even possible to set the chosenMenu directly:

// No longer needed:
//function findMenuItem(menuList, uniqueMenuIdentifier) { }

self.goToMenu = function (menuItem) {
    self.chosenMenu(menuItem);
}

See this fiddle for a demo.

Upvotes: 0

Related Questions