# IT:AD:Durandal.JS:HowTo:Navigation/Dynamic Menu Bars #
* [[../|(UP)]]
{{indexmenu>.#2|nsort tsort}}
## Process ##
### Routing Basics
See: [[IT/AD/Durandal.JS/HowTo/Navigation]] where we covered that.
### Creating Menus from the VisibleRoutes Array
We've already seen that `app/durandal/plugins/router.js` offeres the following `ko.observables`:
allRoutes = ko.observableArray([]),
visibleRoutes = ko.observableArray([]),
ready = ko.observable(false),
isNavigating = ko.observable(false),
...
activeItem = viewModel.activator(),
activeRoute = ko.observable(),
With the above `router` `ko.observable` arrays, the default Durandal demo shows a menu bar (built using `Bootstrap`) that binds to the `visibleRoutes` `ko.observableArray`:
>Notice how the first menu button is an icon, and it's link is pointing to the first member of the `visibleRoute` (the `Welcome` page) (same target as the first iteration of the `visibleRoutes` array, below it)?
### Better Control
That's all nice -- but not yet really useful. Some issues with the above are:
* There's duplication between the first menuitem/icon and first `li` (both going to same place). You might want to see one, but not both buttons.
* Secondly, there's no authenticated access control -- for showing routes available when signed in, versus not yet signed in.
* There's no role management (some links should be available only when the user is in in role xyz).
What we're going to have to do is:
* add some more custom values to the `settings` object of each routes -- to contain role names, etc.
* add a new `observable` property to the router, that in turn invokes the `visibleRoutes` array, but filters it according to the `settings` properties.
* point the menu to not the visible array -- but the new `observableArray` property.
First, the routes -- let's add two new custom `settings`properties: 'authenticated' and 'roles':
settings.routes = [
//Something visible to anybody even if not authenticated:
{ url: 'welcome', moduleId: 'viewmodels/welcome', name: 'Home', visible: false, settings: {authenticated:false} },
//Something that available to all authenticated
{ url: 'students/search', moduleId: 'viewmodels/students/studentSearch', name: 'Search', visible: true, settings: {authenticated:true, roles: []} },
// Something available to all authenticated users, restricted to special roles:
{ url: 'admin', moduleId: 'viewmodels/admin', name: 'Admin', visible: true, settings: {authenticated:true, roles: ['Special']} },
{ url: 'flickr', moduleId: 'viewmodels/flickr', name: 'flickr', visible: true, settings: {'one', authenticated:false, roles: []} },
];
The current menu view is bound to the menu's viewmodel's router property:
data-bind="foreach: router.visibleRoutes"
We're going to change that, to point to a new observable that we haven't written yet:
data-bind="foreach: router.visibleValidRoutes"
And then, in our view model, we're going to attach a new observable to the router, so that it is available in this menu -- and later anywhere else that needs it (as the router is a singleton). The property has to be an observable -- so that the menu can be dynamically updated if the user signs in or out -- and it has to update itself if any new routes are registered.
I'm going to suggest wrapping the already available `router.allRoutes()` -- and filter it for the following (is visible, and is open to everyone or the user is authenticated, and a member of any mentioned roles:
router.visibleValidRoutes = ko.computed(function() {
var routes =
ko.utils.arrayFilter(
router.allRoutes(),
function(routeInfo) {
if (!routeInfo.visible){return false;}
var routeInfoSettings = routeInfo.settings;
//Ensure routeInfo does not need to be authenticated
if (!routeInfoSettings.authenticated){return true;}
//or user is in role
if (user.isAuthenticated()){
//If no roles defined, considered open to authorised users:
if ((!routeInfoSettings.roles)||(routeInfoSettings.roles.length==0)) {return true;}
//iterate through requested roles, looking for presence in user's defined roles.
var userRoles = user.roles();
for (var i=0;i-1){
return true;
}
}
}
return false;
}
);
//Hack: isActive property was attached in routes.js to original observable array...
//but get's lost when rewrapped by this filter...so have to hack/reattach to each element
ko.utils.arrayForEach(
routes,
function(route){
route.isActive = ko.computed(
function () {
return router.ready() && router.activeItem() && router.activeItem().__moduleId__ == route.moduleId;
});
}
);
return routes;
}
,router);
Note:
* Yest, I'm using a singleton `user` that injected into the menu viewmodel. It' pretty simple:
define (
function(){
var model = function(){
this.roles = ko.observable(null);
this.imageUrl = ko.observable(null);
this.emailAddress = ko.observable(null);
this.displayName = ko.observable(null);
this.userName = ko.observable(null);
this.isAuthenticated = ko.observable(false);
this.clear();
}
model.prototype.clear=function(){
this.roles(null);
this.imageUrl();
this.emailAddress(null);
this.displayName(null);
this.userName(null);
this.isAuthenticated(false);
}
return new model();
}
);
Tada...done.
The menu will now update automatically when the user signs in or out, and show only menu items relevant to the user.
## Menus != Security
Just because a user can't see a link doesn't mean that they can't type the link in, and get to the page anyway.
That's not security.
See: [[IT/AD/Durandal.JS/HowTo/Navigation/Security]]