Pete as a Newspaper

jQuery makes you smarter: Dynamic site navigation menu based on JSON object

Posted Aug 25, 02:02 PM in by Pete Karl, comments closed.

This was a fun one.

Our content management vendor doesn’t have an out-of-the-box solution to web site navigation. When it came time for a site navigation, we were told that a javascript-based solution was all that they would allow.

My co-worker (Nick Sergeant) has elaborated on the shortcomings of this solution in the article Website usability, performance, and SEO with JavaScript navigation strategies..

The part I played in this adventure was to build the code that would spit out a navigation that changed depending on where we were in the site. Pretty simple, eh? Well, with jQuery, it was.

FYI, I’ve trimmed off some site-specific functionality from this code to make it more accessible. Feel free to ask questions about your specific navigational needs :)

Overview

In a nutshell, I needed to accept a JSON obj that contained site navigation information, turn that into a navigation, then hide or show the appropriate nav. items as appropriate for that page.

Essentially starting with this:

..and ending with this:

See how we have an active element (in this case ‘pro_columns’), and the appropriate parents & children are displayed? See

Give me code now plz, kthx

As usual, this will require the latest jQuery


<script type="text/javascript" src="jquery-1.2.6.pack.js"></script>

We’ll also need a scrap of HTML to place the nav once it’s complete. I’m using this:


<ul id="main-menu" class='level_1'></ul>

Here’s the bulk of the JavaScript. I’ll step through it after the code.


var active = ‘pro_columns’;

$(function() { var nav = $(’#main-menu’);

$.getJSON(“json.js”, function(json) { nav.html(jsonToLists(json, 1)); $(’.level_1 li’).css(‘display’,‘none’); $(’.active’).parents(‘li’).each(function(i,o) { $(this).attr(‘class’,‘active’); }); $(’.active’).each(function() { $(this).css(‘display’,‘block’); $(this).siblings(‘li’).css(‘display’,‘block’); $(this).children().children(‘li’).css(‘display’,‘block’); $(this).children(‘ul’).css(‘display’,‘block’); }) }); });

function jsonToLists(json, depth) { var html = ‘’; for(i in json) { var c = ‘’; if(json[i].id == active) { c = ‘ class=“active”’} else { c = ‘’} if(json[i].subsections.length > 0) { html += ‘‘ + json[i].id + ‘‘ + ‘

    ‘ + jsonToLists(json[i].subsections, depth + 1) + ‘
‘; } else { html += ‘‘ + json[i].id + ‘‘; } } return html;
}

Here we go:

Set an active element

We need an active element that exists within the JSON. In this case, I’m assigning a random site id to variable active. Also, we need a handle on our HTML. Seen here:


var active = ‘pro_columns’;

$(function() { var nav = $(’#main-menu’); …

Process that pesky JSON

The JSON in this case lives in another file, so I’m using jQuery’s $.getJSON() helper. jQuery performs a request to grab json.js, and passes the contents of the file into the json argument:


$.getJSON("json.js", function(json) { }

When I generate the HTML, I assign each level of the navigation a horizontal id based on how deep it is. When you see level_1, it’s exactly that. Level 1.

Once we have a handle on the JSON, we step through it and recursively build a set of nested HTML lists. For that, we have jsonToLists(). I pass two arguments to jsonToLists(), json which contains our JSON, and depth which helps us establish classes that contain UL depth while we’re nesting. It’s a good thing.


function jsonToLists(json, depth) {
    var html = '';
    for(i in json) {
        var c = '';
        if(json[i].id == active) { c = ' class="active"'} else { c = ''}
        if(json[i].subsections.length > 0) {
            html += '<li' + c + '><a href="' + json[i].path + '">' + json[i].id + '</a><ul class="level_' + String(depth) + '">' + jsonToLists(json[i].subsections, depth + 1) + '</ul></li>';
        } else {
            html += '<li' + c + '><a href="' + json[i].path + '">' + json[i].id + '</a></li>';
        }
    }
    return html;
}

Whip the HTML into shape

In this case, we’re building a horizontal nav, but based on your CSS expertise, I think you could pull just about anything out of the structure that this produces.

After placing the newly-formed HTML into the nav, we need to manipulate the nested list structure according to the following constraints:

  • Show elements that are either active
  • Show elements that are siblings of active elements (if any)
  • Show elements that are children of active elemtnts (if any)

This is cool and all, except we only have a single item in the entire menu that’s labeled as active. We need more descriptive HTML if we’re to manipulate this properly.

Thanks to jQuery, assigning active roles to parents of our active element is a snap:


$('.active').parents('li').each(function(i,o) {
    $(this).attr('class','active');
});

Nice. Now, let’s hide everything below the top level (because who wants to hide the top-level nav?)


$('.level_1 li').css('display','none');

Now it gets sticky. We have to take each of the active elements and manipulate it based on the constraints listed above. This means showing active elements, showing siblings of active elements, and showing children of active elements


$('.active').each(function() {
    $(this).css('display','block');
    $(this).siblings('li').css('display','block');
    $(this).children('ul').css('display','block').children('li').css('display','block');
})

You’ll notice that the last line is quite long. It’s a jQuery feature called chaining that allows you to manipulate sets of objects that are related one-after-another. Very cool.

Finished

That’s really it. Really. To use this code for yourself, you’ll probably have to do the following:

  • Change the jsonToLists() output code to match your HTML needs & JSON structure
  • If you don’t have an external JSON file, just remove the $.getJSON() function and pass JSON to jsonToLists() like you would any variable.
  • Ask questions! I’d really be happy to help :)

Comments

Comments closed for this article.