# 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]]