nickwoodward
nickwoodward

Reputation: 31

Why isn't my custom directives dropdown firing?

What's going on?

Everything works and populates correctly from what I can tell. The only thing that's misbehaving is the dropdown. For some reason it won't fire and display all of it's submenus/dividers.

app.directives.js

angular.module('mb.essentials', [])
.directive('mbAttribute', function ($log) {
    return {
        restrict: 'A',
        scope: {
            mbAttribute: '='
        },
        link: function(scope, element, attrs) {
            if (angular.isUndefined(scope.mbAttribute)) {
                $log.error(
                    'You must specify at least one attribute, condition pair!\n' +
                    'Eg. mb-attribute="{\'alert-warning\': alert.alertType}"');
            }

            $.each(scope.mbAttribute, function (key, value) {
                scope.$watch(function () {
                    return value;
                },
                function() {
                    if (value) {
                        element.attr(key, '');
                    } else {
                        element.removeAttr(key, '');
                    }
                });
            });
        }
    };
})
.directive('mbNavbar', function () {
    return {
        restrict: 'AE',
        require: '^dropdown',
        transclude: true,
        scope: {
            brand: '=',
            menus: '=',
            affixed: '=',
            inverse: '=',
            search: '=',
            searchFn: '&',
            navFn: '&'
        },
        controller: function ($scope, $element, $attrs, $sce) {
            $scope.isCollapsed = true;

            $scope.defaults = {
                brand: '<span class="glyphicon glyphicon-certificate"></span>',
                menus: [],
                search: {
                  show: false
                }
            }; // end defaults

            if (angular.isUndefined($attrs.navFn)) {
                $scope.navfn = function (action) {
                    if (angular.isObject(action))
                        $scope.$emit('nav.menu', action);
                    else
                        $scope.$emit('nav.menu', {'action': action});
                }
            };

            if (angular.isUndefined($attrs.searchFn)) {
                $scope.searchFn = function () {
                    $scope.$emit('nav.search.execute');
                }
            };

            $scope.trustedBrand = angular.isDefined($attrs.brand) ? $sce.trustAsHtml($scope.brand) : $sce.trustAsHtml($scope.defaults.brand);
            $scope.hasMenus = function () {
                return angular.isDefined($scope.menus);
            };

            $scope.hasDropdownMenu = function (menu) {
                return (angular.isDefined(menu.menu) && angular.isArray(menu.menu));
            };

            $scope.isDivider = function (item) {
                return (angular.isDefined(item.divider) && angular.equals(item.divider, true));
            }

            $scope.navAction = function (action) {
                $scope.navFn({'action': action});
            };
        },
        template: 
        '<nav class="navbar" ng-class="{\'navbar-inverse\': inverse,\'navbar-default\': !inverse,\'navbar-fixed-top\': affixed == \'top\',\'navbar-fixed-bottom\': affixed == \'bottom\'}" role="navigation">' +
            '<div class="container-fluid">' +
                '<div class="navbar-header">' +
                    '<button type="button" class="navbar-toggle" ng-click="isCollapsed = !isCollapsed">' +
                        '<span class="sr-only">Toggle Navigation</span>' +
                        '<span class="icon-bar"></span>' +
                        '<span class="icon-bar"></span>' +
                        '<span class="icon-bar"></span>' +
                    '</button>' +
                    '<a class="navbar-brand" ng-bind-html="trustedBrand"></a>' +
                '</div>' +
                '<div collapse="isCollapsed" class="collapse navbar-collapse">' +
                    '<ul class="nav navbar-nav" ng-if="hasMenus()">' +
                        '<li ng-repeat="menu in menus" mb-attribute="{\'dropdown\': hasDropdownMenu(menu)}">' +
                            '<a ng-if="!hasDropdownMenu(menu)" ng-click="navAction(menu.action)">{{menu.title}}</a>' +
                            '<a ng-if="hasDropdownMenu(menu)" class="dropdown-toggle" dropdown-toggle>' +
                                '{{menu.title}} <b class="caret"></b>' +
                            '</a>' +
                            '<ul ng-if="hasDropdownMenu(menu)" class="dropdown-menu">' +
                                '<li ng-repeat="item in menu.menu" ng-class="{\'nav-divider\': isDivider(item)}">' +
                                    '<a ng-if="!isDivider(item)" ng-click="navAction(item.action)">{{item.title}}</a>' +
                                '</li>' +
                            '</ul>' +
                        '</li>' +
                    '</ul>' +
                    '<form ng-if="search.show" class="navbar-form navbar-right" role="search">' +
                        '<div class="form-group">' +
                            '<div class="input-group">' +
                                '<input type="text" class="form-control" placeholder="Search" ng-model="search.terms" />' +
                                '<span class="input-group-btn">' +
                                    '<button class="btn btn-default" type="button">' +
                                        '<span class="glyphicon glyphicon-search"></span>' +
                                    '</button>' +
                                '</span>' +
                            '</div>' +
                        '</div>' +
                    '</form>' +
                '</div>' +
            '</div>' +
        '</nav>'
    };
});

Plnkr

Upvotes: 1

Views: 374

Answers (1)

nickwoodward
nickwoodward

Reputation: 31

The Issue

Basically angular compiles the DOM then the mb-attribute directive adds the dropdown directive. Essentially Angular and UI Bootstrap have no idea the directive even exists.

The "Solution"

I put this in quotes because it isn't entirely a solution.

Rather then conditionally checking if the dropdown directive should be added. We just add it to every element, regardless of whether or not its a dropdown menu.

The reason this works in this case is because UI Bootstraps dropdown directive requires it to be paired with a dropdown-toggle as well as a dropdown-menu in order to operate properly.

angular.directives.js

angular.module('mb.essentials', [])
.directive('mbNavbar', function () {
    return {
        restrict: 'AE',
        require: '^dropdown',
        scope: {
            brand: '=',
            menus: '=',
            affixed: '=',
            inverse: '=',
            search: '=',
            searchFn: '&',
            navFn: '&'
        },
        controller: function ($scope, $element, $attrs, $sce) {
            $scope.isCollapsed = true;

            $scope.defaults = {
                brand: '<span class="glyphicon glyphicon-certificate"></span>',
                menus: [],
                search: {
                  show: false
                }
            } // end defaults

            if (angular.isUndefined($attrs.navFn)) {
                $scope.navfn = function (action) {
                    if (angular.isObject(action))
                        $scope.$emit('nav.menu', action);
                    else
                        $scope.$emit('nav.menu', {'action': action});
                }
            }

            if (angular.isUndefined($attrs.searchFn)) {
                $scope.searchFn = function () {
                    $scope.$emit('nav.search.execute');
                }
            }

            $scope.trustedBrand = angular.isDefined($attrs.brand) ? $sce.trustAsHtml($scope.brand) : $sce.trustAsHtml($scope.defaults.brand);
            $scope.hasMenus = function () {
                return angular.isDefined($scope.menus);
            }

            $scope.hasDropdownMenu = function (menu) {
                return (angular.isDefined(menu.menu) && angular.isArray(menu.menu));
            }

            $scope.isDivider = function (item) {
                return (angular.isDefined(item.divider) && angular.equals(item.divider, true));
            }

            $scope.navAction = function (action) {
                $scope.navFn({'action': action});
            }
        },
        template: 
        '<nav class="navbar" ng-class="{\'navbar-inverse\': inverse,\'navbar-default\': !inverse,\'navbar-fixed-top\': affixed == \'top\',\'navbar-fixed-bottom\': affixed == \'bottom\'}" role="navigation">' +
            '<div class="container-fluid">' +
                '<div class="navbar-header">' +
                    '<button type="button" class="navbar-toggle" ng-click="isCollapsed = !isCollapsed">' +
                        '<span class="sr-only">Toggle Navigation</span>' +
                        '<span class="icon-bar"></span>' +
                        '<span class="icon-bar"></span>' +
                        '<span class="icon-bar"></span>' +
                    '</button>' +
                    '<a class="navbar-brand" ng-bind-html="trustedBrand"></a>' +
                '</div>' +
                '<div collapse="isCollapsed" class="collapse navbar-collapse">' +
                    '<ul class="nav navbar-nav" ng-if="hasMenus()">' +
                        '<li ng-repeat="menu in menus" dropdown>' +
                            '<a ng-if="!hasDropdownMenu(menu)" ng-click="navAction(menu.action)">{{menu.title}}</a>' +
                            '<a ng-if="hasDropdownMenu(menu)" class="dropdown-toggle" dropdown-toggle>' +
                                '{{menu.title}} <b class="caret"></b>' +
                            '</a>' +
                            '<ul ng-if="hasDropdownMenu(menu)" class="dropdown-menu">' +
                                '<li ng-repeat="item in menu.menu" ng-class="{\'nav-divider\': isDivider(item)}">' +
                                    '<a ng-if="!isDivider(item)" ng-click="navAction(item.action)">{{item.title}}</a>' +
                                '</li>' +
                            '</ul>' +
                        '</li>' +
                    '</ul>' +
                    '<form ng-if="search.show" class="navbar-form navbar-right" role="search">' +
                        '<div class="form-group">' +
                            '<div class="input-group">' +
                                '<input type="text" class="form-control" placeholder="Search" ng-model="search.terms" />' +
                                '<span class="input-group-btn">' +
                                    '<button class="btn btn-default" type="button">' +
                                        '<span class="glyphicon glyphicon-search"></span>' +
                                    '</button>' +
                                '</span>' +
                            '</div>' +
                        '</div>' +
                    '</form>' +
                '</div>' +
            '</div>' +
        '</nav>'
    };
});

Plnkr

For the Future

I'm going to use this solution, but I'm not going to mark it as the solution. I figure if in the future someone comes up with a real solution then I can mark it as such.

Upvotes: 1

Related Questions