Finishing the Navigation with AngularJS and WordPress

Table of Contents:

  1. Introduction
  2. Active State
  3. Drop Down Navigation
  4. Drop Down Indicator
  5. Fixing Responsive
  6. Summary
  7. Other Posts in this Series

Introduction

In part 1 of this AngularJS and WordPress series we wired up a Yeoman AnguarJS app to a WordPress back-end with WP REST API. In part 2 we set up a catch-all route, controller and view that could fetch our WordPress pages dynamically. In part 3 we did a lot of work to create the start of dynamic navigation.

In this post I am going to finish what we started in part 3 by putting the finishing touches on our navigation with active state, drop-down items and more. It won’t be a long post, but after the epic that was part 3, I am sure you’ll be fine with that.

Active State

It can be most useful to you visitor to know what page they are on by seeing the navigation item that reflects the the current page, highlighted somehow in another color or state. Unfortunately, with a single page application and dynamic content, knowing what page you’re actually on can be challenging.

For this we’ll use a bit of logic and our current location to determine our active state.

  1. Open app/scripts/directives/navigation.js
  2. Inject $location to the callback argument list:
    .directive('navigation', function (sitemapService, $location) {
    
  3. In our postLink function add a simple function called isActive, and add it to scope:
    link: function postLink(scope, element, attrs) {
        scope.sitemap = sitemapService.get();
        scope.$on('sitemap:updated', function (event, data) {
            scope.sitemap = data;
        });
    
        // NEW CODE
        scope.isActive = isActive;
    
        function isActive(viewLocation) {
            if ($location.path()) {
                return viewLocation === $location.path();
            }
        }
    
    }
    

    What this function does is take an argument, viewLocation, that we’ll test against $location.path() and return true or false. For instance, if viewLocation equals /test-page and $location.path() equals /test-page, then isActive will be true.

  4. Now we need to set the active state in our view. Open app/views/navigation.html and change the original list item to this:

    <li ng-repeat="nav in sitemap" ng-if="nav.navigation" ng-class="{ 'active' : isActive(nav.path) }"><a ng-href="{{ nav.path }}">{{ nav.name }}</a></li>
    

    What we’ve added is ng-class="{ 'active' : isActive(nav.path) }". This will dynamically set the class of our list item to active if nav.path is equal to the current page. It should look like this as you navigate around:

    yo-angular-active-state

Drop Down Navigation

If you recall in part 3 our sitemap-json contained a children property which held an array of additional navigation objects. Now we are going to make use of those objects much the same way we did with the top level.

  1. In app/views/navigation.html, prepare the top level link to accept a sub navigation system by adding a few bootstrap toggle classes and attributes:
    <a ng-href="{{ nav.path }}" ng-class="{'dropdown-toggle' : nav.children }" ng-attr-data-toggle="{{ nav.children ? 'dropdown' : '' }}">{{ nav.name }}</a>
    

    Here we added ng-class="{'dropdown-toggle' : nav.children }" ng-attr-data-toggle="{{ nav.children ? 'dropdown' : '' }}" which adds a dropdown-toggle class and dropdown data-toggle attribute value if the navigation object has children.

  2. Now we can nest another unordered list, much like the original, but as a child of our top-level list item. Just below our first link, add the new unordered list:

    <ul ng-show="nav.children" class="dropdown-menu" role="menu">
        <li ng-repeat="child in nav.children" ng-if="child.navigation === true" ng-class="{ 'active' : isActive(child.path) }">
            <a ng-href="{{ child.path }}">{{child.name}}</a>
        </li>
    </ul>
    

    This is much like the first list from part 3, except we only show this list if our current object has .children and we now run ng-repeat against the nav.children array. Already you should see the sub navigation toggle when you click the the Test Page 1 top nav item.

    yo-angular-drop-down

Drop Down Indicator

A drop down navigation is useless if your visitors don’t know it’s there, so let’s put a simple disclosure triangle to the right of any top level page that contains children:

  1. In app/views/navigation.html break your top-level link into a couple lines to make it easier to read:
    <a ng-href="{{ nav.path }}" ng-class="{'dropdown-toggle' : nav.children }" ng-attr-data-toggle="{{ nav.children ? 'dropdown' : '' }}">
        {{ nav.name }}
    </a>
    
  2. Underneath our binding expression, add a span with a simple unicode down triangle (9660) (you can get fancy with font glyphs like FontAwesome if you like).
    <a ng-href="{{ nav.path }}" ng-class="{'dropdown-toggle' : nav.children }" ng-attr-data-toggle="{{ nav.children ? 'dropdown' : '' }}">
        {{ nav.name }}
        <span ng-if="nav.children"> &#9660;</span>
    </a>
    

    This span only renders if there are children of the page object. You should now see your navigation look like this, clearly indicating there is more to be found under that heading:

    yo-angular-drop-down-indicator

    yo-angular-drop-down-indicator-open

Fixing Responsive

Throughout part 3 and part 4 I’ve been mainly focused on the desktop behavior of the navigation without giving the responsive view a thought. Now let’s have a look at what the menu is doing in mobile view. The first thing you will notice is that the menu doesn’t collapse when an item is clicked.

You will read on StackOverflow that you can’t use Twitter Bootstrap’s js, and that you need to use Angular UI Bootstrap to achieve all the JavaScript goodness that is Bootstrap. Hogwash.

While there is actually a lot of simplicity to be gained if you were to start using Angular UI Bootstrap, it is certainly not a requirement. JavaScript is JavaScript after all. So we will proceed simply using what’s built in already. Feel free to use Angular UI Bootstrap at a later date.

  1. Open app/views/navigation.html and edit the following:

    Change this…

    <a ng-href="{{ nav.path }}" ng-class="{'dropdown-toggle' : nav.children }" ng-attr-data-toggle="{{ nav.children ? 'dropdown' : '' }}">
    

    To this…

    <a ng-href="{{ nav.path }}" ng-class="{'dropdown-toggle' : nav.children }" ng-attr-data-toggle="{{ nav.children ? 'dropdown' : 'collapse' }}" ng-attr-data-target="{{nav.children ? '.dropdown' : '#js-navbar-collapse'}}">
    

    What we’ve added here is a few simple evaluations.

    1. We’ve extended our ng-class to add collapse if there are no children, instead of empty string.
    2. We’ve added an evaluation for data-target, using ng-attr to indicate that we need angular to evaluate the expression.
    3. If the item has children, our data-target is .dropdown (this isn’t necessary but harmless just the same).
    4. Otherwise, the data-target is #js-navbar-collapse

    So if there is a child item, we’ll expand the drop down navigation, otherwise we’ll collapse the menu.

  2. Now we need to do the same thing with the child items as well:

    Change this…

    <a ng-href="{{ child.path }}">{{child.name}}</a>
    

    To this…

    <a ng-href="{{ child.path }}" data-toggle="collapse" data-target="#js-navbar-collapse">{{child.name}}</a>
    

    Since nothing has to be evaluated here, we can simply use the same data-toggle and data-target used in our hamburger menu button.

Now when you click on any given item in the responsive navigation, the navigation should collapse when the page changes.

yo-angular-fixing-responsive

Summary

In this article we put the finishing touches on our navigation. We added active state, enabled drop-down navigation, added drop-down navigation indicators and enabled responsive menu collapse. There is so much more you can do but I’ll let you explore those possibilities in your own time.

In future posts I want to show you how to use the same sitemap-json data to automatically generate a sitemap page that’s always up to date. Then I want to cover page specific configuration, site deployment and more.

Other Posts in this Series

 

Adam Merrifield

 

Leave a Reply