I developed a general solution by reading this post. With this solution, you can dynamically add any routes to the drop-down menu on the navigator.
In main.js, where I specify my routes, I added a function to map the submenu object to the configuration object of the main navigation route.
function mapSubNav(parentRouteInfo) { var subroutes = []; var length = arguments.length; for (var i = 0; i < length; i++) { subroutes.push(arguments[i]); } parentRouteInfo.settings.subroutes = subroutes; } var page = router.mapNav('page', null, 'Page'), sub1 = router.mapRoute('page/sub1', null, 'Sub1'), sub2 = router.mapRoute('page/sub2', null, 'Sub2'); mapSubNav(nav, sub1, sub2);
Explanation:
The mapNav router mapNav returns a route definition that looks like this:
{ url:'flickr', //you provided this name: 'Flickr', //derived moduleId: 'flickr', //derived caption: 'Flickr', //derived (uses to set the document title) settings: {}, //default, hash: '#/flickr', //calculated visible: true, //from calling mapNav instead of mapRoute isActive: ko.computed //only present on visible routes to track if they are active in the nav }
The mapSubNav helper function mapSubNav put a list of links to the routes that you want to display in the drop-down menu in the settings object. In this example, the result would be:
{ url:'page', name: 'Page', moduleId: 'page', caption: 'Page', settings: { subroutes: [nav, sub1, sub2] }, hash: '#/page', visible: true, isActive: ko.computed }
I expanded my viewmodel to look like this:
define(function (require) { var router = require('durandal/plugins/router'); var app = require('durandal/app'); var ViewModel = function () { var self = this; self.router = router; self.dataToggle = function (route) { return !!route.settings.subroutes ? 'dropdown' : ''; }; self.html = function (route) { return !!route.settings.subroutes ? route.name + ' <b class="caret"></b>' : route.name; }; self.hash = function (route) { return !!route.settings.subroutes ? '#' : route.hash; }; self.divider = function (route, parent) { system.log('Adding', route, 'to dropdown', 'Parent', parent); return route.hash === parent.hash; }; self.activate = function () { return router.activate('welcome'); } }; return new ViewModel(); });
Note:
Additional functions in shell.js will determine which attributes should be added to the DOM elements in the navigator.
And finally, mine I edited the shell view to look like this:
<div class="nav-collapse collapse"> <ul class="nav navbar-nav" data-bind="foreach: router.visibleRoutes()"> <li data-bind="css: { active: isActive, dropdown: !!settings.subroutes }"> <a data-bind="css: { 'dropdown-toggle': !!settings.subroutes }, attr: { href: $root.hash($data), 'data-toggle': $root.dataToggle($data) }, html: $root.html($data)"></a> <ul data-bind="foreach: settings.subroutes" class="dropdown-menu"> <li><a data-bind="attr: { href: hash }, html: name"></a></li> <li data-bind="css: { divider: $root.divider($data, $parent) }"></li> </ul> </li> </ul> </div>
Result:
The end result is a menu item with a drop-down switch. The drop-down menu will contain a link to the parent and one link per subroutine. Something like that:
--------------------------------------------- Menu items... | Page v | More menu items... --------------------------------------------- | Page | ---------- | Sub 1 | | Sub 2 | ----------
jQuery does not allow you to map a route to a drop-down menu button, so I made a parent route by storing the link to myself in the list of routines. There will be a link to the parent route on nav.