sliding menu

Make sliding menu by AngularJS

In this lesson, I will show you the way to make a sliding menu by Angular. The sliding menu always appears on mobile devices. When you click/tap on the menu button, the sliding menu will appear from left/right side of your page.

This lessonwill be broken down into three separate sections: the HTML, the JavaScript, and the CSS. The HTML shows how I’ve got the menu directive laid out, and also provides example usage. The JavaScript is the meat of the article, and will explain how to create the sliding menu directive, as well as how to tie the Angular JS controller into the directive itself. The CSS is a little complicated, as I’m adding fancy animations to do the actual sliding, but outside of that, it’s just plain old styling.

The html page

You can put the following html code inside the body tag:

<menu visible="visible" alignment="left">
    <menu-item hash="first-page">First Page></menu-item>
    <menu-item hash="second-page">Second Page></menu-item>
    <menu-item hash="third-page">Third Page></menu-item>
</menu>

Directive

Here’s the HTML for the directive template. It’s relatively straightforward, but I’d still recommend you put this in a separate file for ease of use. The only interesting thing going on here is the use of the ng-class directive, which indicates that a CSS class be added to the indicated element based on the truthiness of the specified variable. In this case, if visible is truthy (meaning not null or undefined), the show class will be added to the parent div. I’m also setting the alignment class here, too, with left assigning the left class, and right the right, respectively. The ng-transclude directive is used to render child elements into the directive. For example, the sample usage above denotes three menu-items; those three nodes would be placed inside the rendered HTML for the menu, as desecribed below.

<div ng-class='{ show: visible, left: alignment === \"left\", right: alignment === \"right\" }' ng-transclude></div>

JavaScript

The JavaScript is broken down into a couple of different sections. First, there’s the initialization of the Angular JS application. Second, we’ll take a look at the controller that initializes the directive. And finally, we’ll take a look at the two directives used to render the menu: the menu itself, and the nested menu items.

Application

Here, we’re initializing the Angular JS application, and then hooking up some events that we can use to hide the menus once they’re revealed. When the user hits escape or clicks anywhere on the document, the menus should hide. Angular JS has event emitters built into its scope objects, so we’re making use of those on the $rootScope variable, which is the overarching scope that is the eventual parent of all of the other scopes in your application.

Note: You’ll have to stop the propagation of the event used to trigger the showing of the menus, because if you don’t, this document-wide click handler will immediately hide the menu after it’s shown. Take a look at the test page for an example of how to do this.

var app = angular.module("demo", []);

app.run(function($rootScope) {
    document.addEventListener("keyup", function(e) {
        if (e.keyCode === 27)
            $rootScope.$broadcast("escapePressed", e.target);
    });

    document.addEventListener("click", function(e) {
        $rootScope.$broadcast("documentClicked", e.target);
    });
});

Controller

Here’s an example as to how your controller would manipulate the menus so as to show and hide them. The showLeft and showRight functions would show the left and right menus, respectively, and we’re tying into the aforementioned event emiiter on the $rootScope variable via the $on methods to close the menus when appropriate.

app.controller("modalDemo", function($scope, $rootScope) {
    $scope.leftVisible = false;
    $scope.rightVisible = false;

    $scope.close = function() {
        $scope.leftVisible = false;
        $scope.rightVisible = false;
    };

    $scope.showLeft = function(e) {
        $scope.leftVisible = true;
        e.stopPropagation();
    };

    $scope.showRight = function(e) {
        $scope.rightVisible = true;
        e.stopPropagation();
    }

    $rootScope.$on("documentClicked", _close);
    $rootScope.$on("escapePressed", _close);

    function _close() {
        $scope.$apply(function() {
            $scope.close(); 
        });
    }
});

Directives

Here’s the code to control the two directives in the menu. Both are restricted to elements (via the restrict: “E” option), and we’re transcluding the inner content of both into each directive’s content. This means that the inner contents of the menu directive in the example above get written into the menu’s immediate child div. The inner content of each menu-item tag (namely the text we want to show in the menu items) get rendered within each menu item, too. Angular JS knows where to put the transcluded contents via the ng-transclude directive, which you see in both templates below. For the menu directive, we’ve created an isolated scope containing the visible two-way binding and the alignment one-way binding. This allows us to read the visible variable’s value and the alignment’s string value. We’re doing similarly with the hash variable in the menu item’s isolated scope. Finally, the menu item has a method added to its scope via the link option’s function, navigate, which is fired when the user clicks on the menu item, as seen by the ng-click directive.

app.directive("menu", function() {
    return {
        restrict: "E",
        template: "<div ng-class='{ show: visible, left: alignment === \"left\", right: alignment === \"right\" }' ng-transclude></div>",
        transclude: true,
        scope: {
            visible: "=",
            alignment: "@"
        }
    };
});

app.directive("menuItem", function() {
     return {
         restrict: "E",
         template: "<div ng-click='navigate()' ng-transclude></div>",
         transclude: true,
         scope: {
             hash: "@"
         },
         link: function($scope) {
             $scope.navigate = function() {
                 window.location.hash = $scope.hash;
             }
         }
     }
});

CSS

Finally, here’s the CSS. The first three lines describe some LESS CSS mixins I’ve defined to help ease the vendor prefix nightmare. The first is for defining transitions (or animations); the second is for CSS transformations; and the third is to set the border-box value for box-sizing. Below that is the CSS for the menu, and within that, each menu item. I’m making liberal use of the & LESS CSS command, which allows me to specify styles for various conditions, which in this case are additional classes, like left, show, etc. Those are almost exclusively used to define positioning of the menus depending on the specified alignment.

.transition (@value1,@value2:X,...) { @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; -webkit-transition: @value; -moz-transition: @value; -ms-transition: @value; -o-transition: @value; transition: @value; }
.transform (@value1,@value2:X,...) { @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; transform:@value; -ms-transform:@value; -webkit-transform:@value; -o-transform:@value; -moz-transform:@value; }
.border-box { box-sizing:border-box; -moz-box-sizing:border-box; }

menu { display:block;
    @menu-width:250px;
    >div { position:absolute; z-index:2; top:0;  width:@menu-width; height:100%; .border-box; .transition(-webkit-transform ease 250ms); .transition(transform ease 250ms);
        &.left { background:#273D7A; left:@menu-width*-1; }
        &.show.left { .transform(translate3d(@menu-width, 0, 0)); }

        &.right { background:#6B1919; right:@menu-width*-1; }
        &.show.right { .transform(translate3d(@menu-width*-1, 0, 0)); }

        >menu-item { display:block;
            >div { float:left; width:100%; margin:0; padding:10px 15px; border-bottom:solid 1px #555; cursor:pointer; .border-box; color:#B0B0B0;
                &:hover { color:#F0F0F0; }
                >span { float:left; color:inherit; }
            }
        }
    }
}

You can test the result at GitHub
Thank you Chris Harrington for nice article.