2
\$\begingroup\$

I need to make sure the correct menu item is highlighted according to which URL is currently being viewed.

Now the problem with our site is that each menu item can represent several pages. So the Client menu will show the client list - but should still be highlighted when you go to the edit client page. So I can't parse the current URL and match it against the menu item to determine if it should be highlighted.

Instead I have decided to add a custom field - menu - to the $routeProvider setup. So when setting up the routes :

$routeProvider
            .when(
                '/SecurityGroup/Index',
                {
                    templateUrl: '/SecurityGroup/IndexPartial',
                    controller: 'GroupListController',
                    menu: 'group'
                }
            )
            .when(
                '/SecurityGroup/MemberClients/:groupId&:groupName',
                {
                    templateUrl: '/SecurityGroup/MemberClientsPartial',
                    controller: 'MemberClientsController',
                    menu: 'group'
                }
            )

Then in the HTML, the menus are specified as (the tr-menu attribute matches against the name field in the route) :

<li menuitem tr-menu="group" href='/SecurityGroup/IndexPartial'>Groups</li>
<li menuitem tr-menu="client" href='/Clients/IndexPartial'>Clients</li>

Then I have a directive that manages the menu highlighting - essentially adding and removing the Active class if the current route contains the correct name :

(function() {
    'use strict';
    angular.module('Administration')
        .directive('menuitem', ['$location', function($location) {
            return {
                restrict: 'A',
                scope: false,
                transclude: true,
                replace: false,
                template: '<a ng-transclude></a>',
                link: function (scope, element, attrs) {
                    var href = attrs.href;
                    var name = attrs.trMenu;
                    var link = element.find('a');
                    link.attr('href', href);

                    scope.$on('$routeChangeSuccess', function (event, currentRoute, previousRoute) {
                        var menu = currentRoute.$$route.menu;
                        if (menu === name)
                            element.addClass('active');
                        else
                            element.removeClass('active');
                    });

                }
            };

        }]);
}());

Is this a clever solution, or a terrible abuse of javascripts dynamic abilities? Is there a more Angular way of doing things rather than relying on adding my own fields to the existing Angular data structures?

\$\endgroup\$
3
\$\begingroup\$

I'm not sure if my solution is relevant to your problem, but I had a similar problem with keeping the correct page selected on page refresh as refresh was resetting defaults and home would be selected on what ever page you refreshed from.

This is how I solved it:

page controllers

app.controller('aboutCtrl', ['$rootScope', function($rootScope){
    $rootScope.$broadcast('pageSelected', "about");
}]);

app.controller('stuffCtrl', ['$rootScope', function($rootScope){
    $rootScope.$broadcast('pageSelected', "stuff");
}]);

app.controller('homeCtrl', ['$rootScope', function($rootScope){
    $rootScope.$broadcast('pageSelected', "home");
}]); 

When the page controller is initiated, it broadcasts pageSelected with the name of the page as the value (important to use $rootScope to avoid parent/child cases).

navigation menu controller

app.controller('headerCtrl', ['$scope', '$rootScope', function($scope, $rootScope){
    $scope.$on('pageSelected', function(e, val){
        $scope.selected = val;
    });
}]);

The menu controller is listening for the pageSelected broadcast and will set $scope.selected to the value received.

navigation menu html

<a ui-sref="home"><div ng-class="{selected: selected=='home'}">Home</div></a>
<a ui-sref="toDo"><div ng-class="{selected: selected=='todo'}">To Do</div></a>
<a ui-sref="stuff"><div ng-class="{selected: selected=='stuff'}">Stuff</div></a>
<a ui-sref="about"><div ng-class="{selected: selected=='about'}">About</div></a>

Finally, the menu will update which item has the selected class dynamically based on $scope.selected.

In your case, you could maybe have the page controllers that need the same menu item selected to broadcast the same value?

Although not necessary, I also recommend using ui-router. It is state-based instead of URL-based; that's why I have ui-sref="home" ect.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.