In this tutorial I am going to step through some very basic set up of creating your own drop down navigation using jQuery. The point of this tutorial is to show how it is done so that you can create a menu that suits your needs, rather than relying on often bloated plugins full of features you don’t need from other developers.

By the end you should be able to do the following:

  • Create a jQuery plugin
  • Give it a few options
  • Iterate over the selected elements
  • Find relevant child elements
  • Display and correctly position menus

To see my full functioning code, as used in this site, visit the Codex and see the Simple Menu page.

Firstly I’m going to define the structures that will be used by this plugin. Generally speaking, this is the format that is produced by all current CMS platforms so it is a good place to start, this will give us a place to start in considering how we construct our plugin. You should also consider grabbing the CSS for how we will set up the menu below, if you don’t already know how to set it up so you can more effectively test and trouble shoot your plugin.

<ul id="navigation">
    <li><a href="#">Home</a></li>
    <li><a href="#">Menu</a>
        <ul>
            <li><a href="#">Sub menu 1</a></li>
            <li><a href="#">Sub menu 2</a></li>
        </ul>
    </li>
    <li><a href="#">Page</a></li>
</ul>

Now, I’ve simplified this so that it only shows one sub menu, but when we’re done we should be able to handle infinite numbers of sub menus. All that we need to note of the structure is that in order to create a sub-menu, we only need to embed another un-ordered list in a similar style to another list tag.

Making Your Plugin

Making a plugin in jQuery is wonderfully simple and one of its many great features. jQuery has its own great tutorials on what everything that you are about to see means, and I invite you to explore that codex, for now we’ll start with this block of code.

/**
 * Protect namespace, etc
 */
(function ( $ ) {
    /**
     * create plugin for main nav stuff
     */
    $.fn.mainNav = function( options ) {
        
        // handle settings, if i had any
        var settings = $.extend({
            activator: 'hover', // how will the menu be triggered
            callbackIn: null,   // what to do when the menu is shown
            callbackout: null   // what to do when the menu is hidden
        }, options );

        // continue your plugin here
    };
})(jQuery);

You now have a plugin name ‘mainNav’ attached to the jQuery object which you may call at your leisure. How neat.

The ‘(function ( $ ) {‘ section provides us with a nice way to encapsulate all our code we’ll be writing so that none of our variables accidentally leaks out into the rest of the page, which might cause errors. This is just good practice.

The line which reads: ‘$.fn.mainNav = function( options ) {‘ is the definition of your plugin. This is telling the $ (an alias for jQuery object) that its member object ‘fn’ now has an entry ‘mainNav’ with the value of this function. The function also accepts an argument for options.

All of that settings stuff will come in handy later, so don’t pay too much mind to it right now, just take a gander at its structure and understand that what is happening is we are taking into the plugin as an argument an ‘options’ structure which may have any or none of the key/values that we later see in the extend method. The extend methods job is to merge the ‘options’ argument with our default values defined in the first argument of the extend method and we save that product in a new ‘settings’ which we can later access for user provided settings.

Now I am going to identify some variables which I will be using as touch points for the rest of the script and do the very first step.

        // identify the nav item
        var self = $(this);
        
        // first hide children ul
       self.find("ul").css("visibility","hidden");
        
        
        // get all lis
        var lis = self.find("li");
        // get all top level lis
        var top = self.children("li");
        // get all sub level lis
        var tier = top.find("li");

What I have done here is defined several jquery objects as variables which i can use later. The variable ‘self’ now contains a jQuery object referencing ‘this’ which the jQuery will pass to the plugin as the actual DOM element we selected on the page. I use ‘self,’ our top-most ul element to find all list (li) elements then contained underneath its hierarchy with the ‘self.find(“li”)’ method. The variable ‘lis’ then contains a new jQuery object with every single ‘li’ element in that DOM tree. Now the next two steps are a little more subtle. 

I have two separate groups of menu styles I need to handle. Remember my first level menu is horizontal, while my subsequent level menus are vertical, which means I’ll needs some special logic applied distinctly to each. Therefor, I find only the very first level of menu by taking only the children ‘li’ elements “self.children(“li”)” and save them as top. Then I find all the layers of li elements below that by finding them in the ‘top’ object using ‘top.find(“li”).’ I now have all the pools I’ll need to iterate over to apply our dynamic menu styling.

The last line in there you may be looking at is the line where we set the visibility of all ‘ul’ elements found underneath the top level navigation item. We use the ‘.css()’ method to apply the CSS visibility style of ‘hidden’ to every single un-ordered list element that falls beneath our top level menu, this will hide all those otherwise visible sub-menus from view until we are ready to see them. Note that we

Now we are going to insert a lengthy block of code into our plugin which may not make much sense now in how it will be used but it will be a work horse for us later. 

        // fire first round positioning
        var pos = function( self, sub, w, sw, h ) {
            
            // do some edge detection
            var ww = $(window).width();
            
            
            // check for second tier
            if( top.filter(self).length == 0  ) {
                
                // detect edge collision and apply
                if( ww < $(self).offset().left + w + sw ) {
                    w=-sw;
                }
                
                // standard tier correction to h
                h = -h
                
            // otherwise first tier
            } else {
                
                
                // edge detect
                if( ww < $(self).offset().left + sw ){
                
                    // align right
                    w = w - sw;
                    
                } else {
                
                    w = 0;
                }
                
                // already at appropriate h
                h = 0;
            }
            
            sub.css("marginLeft",w);
            sub.css("marginTop",h);
        };

We define this function inside of our plugin so that we have access to all of the variables we defined before which makes some house keeping easier as well as protects from creeping outside of its scope.

I’m going to tell you to not worry to much about what each line does specifically, but instead walk you through what this function is actually doing.

For starters, the purpose of this function is to position a sub-menu on the screen right next to its parent element to achieve seamless menu transition. It even has a feature built in to detect the edge of the screen and bounce back the other way if it is going to slide past the right edge. The arguments it takes are this: ‘self’ is no longer the top level of our navigation but the top level of this sub-menu that we are navigating through, ‘sub’ is the ‘ul’ element of this sub-menu and is the object we are going to need to position as it encapsulates the rest of the items, ‘w’ is the width of the top menu item, ‘sw’ is the width of the sub-menu, and ‘h’ is the height of the sub-menu. We could have gotten nearly all of these inside the function itself, but I have written this as a utility function to be applicable to other cases down the road.

While this function does take a moment to identify the width of the window and store it in variable ‘ww,’ the real work begins with the if statement that first determines whether or not we are dealing with the first tier of sub-menus. The way the logic in this case is set up is that if it is NOT a top level sub-menu, we will execute the first logical block, otherwise we will execute the second logical block. 

Now, what takes place inside the logical blocks won’t make a lot of sense unless you’ve seen how your menus render when not hidden. What happens to all later tier menus is they appear under and aligned left on all their parent list elements, therefor in order to bump them in to position directly to the right and flush with the top, we need to move it upwards the same height as the parent element, and to the right the same as the width of the parent element. Easy enough, we’ve already provided those details to the function. But it also checks to see if we will extend passed the right side of the screen, if so we want to align against the left edge of our parent. Therefor we will need to move to the left the same width as our sub element. So we save the amount we want to move right (which may be negative if we want to go left) back into the ‘w’ variable and the amount we want to move up back into the ‘h’ variable, inverted to be able to use some CSS trickery later.

The second logical block is much the same, however, for our first tier of sub-menus we don’t need to move up since it is already positioned exactly below where we need it to be due to how browsers render absolute positioned items. Therefor we are going to apply no height correction and only check to see if we extend passed the right side of the screen. If we do we simply want to budge over so that the right side of our sub-menu is flush with the right side of our top-level menu. If you do the math, that means moving over just the difference in distance between our top level menu and sub-menu’s width. As before we save these adjustments in ‘w’ and ‘h.’

Finally we use the ‘.css()’ method again to apply some margins to our sub-menu. What this does simply bumps our absolutely positioned element (which by the nature of absolute positioning when not provided with a ‘top,’ ‘right,’ ‘bottom,’ or ‘left’ attribute will render right where it would be in its parent’s structure) and we simply bump it out of its parents position and into its new home.

Great! We can now position all of our menus on the screen in their proper homes. Lets give them each an initial place to live:

        // execute first round positioning
        lis.each(function(){
            var sub = $(this).children("ul");
        
            // gather dimensions
            var w = $(this).outerWidth();
            var sw = sub.outerWidth();
            var h = $(this).outerHeight();
            
            // fire positioning on this object
            pos( this, sub, w, sw, h );
        });

This is a nice and easy chunk of script which iterates over each list item in our navigation we previously selected and assigned to the ‘lis’ object and allows us to run some action on them. All we want to do here is put each sub menu in its initial position. I won’t go into to much detail on the lines of the script inside because I hope by this point that their purpose is self evident. In the end, we run our new positioning function on every sub-menu to put it in place.

        // show child ul for hovering
        lis.on('mouseover', function(){
            var sub = $(this).children("ul");
            
            sub.css("visibility","");
        
            // gather dimensions
            var w = $(this).outerWidth();
            var sw = sub.outerWidth();
            var h = $(this).outerHeight();
            
            // fire positioning on this object
            pos( this, sub, w, sw, h );
        
            // execute callback in 
            if( settings.callbackIn !== null ){
                settings.callbackIn(this);
            }
        
        }); // end of show function

This new code block is very similar to the last, only this time we’re adding some actual action to our script. About time, right? The ‘.on()’ function is going to register a new event handler for all of the elements we selected in and saved in the ‘lis’ object, we assign it a ‘mouseover’ event listener because we want our menu to pop open when we hover over the that list element, therefor whenever the mouse rolls over that list element, the function specified is called and the list element is passed along to it. We then do exactly what we did in the last step and update the position of the sub-menu, in case any higher level menu positions on screen have changed. Additionally we set any immediate ‘ul’ children to be visible, and finally do what we’ve been trying to achieve all along and displaying that drop down navigation. Whew.

You’ll notice a bit of extra code in there involving a callback function in the settings. I’m not going to wax on about that, but if you are interested it can be used to trigger events when menus are shown and hidden. It is, however, beyond the scope of this lesson.

We have only one more block of code remaining:

            lis.on("mouseout",function(){
            
                $(this).children("ul").css("visibility","hidden");
                
                
                // execute callback out 
                if( settings.callbackOut !== null ){
                    settings.callbackOut(this);
                }
            });

This is ultimately very simple. Like before we add a mouseout event listener and add a function to trigger when its happens. All we are doing is the opposite of what we did before: hiding the sub-menu. Easy enough, huh? There is again a little bit of extra callback fun in there if you want to play around.

And that is it. You have a menu plug in. Easy right? jQuery rocks. If you want to see my full script (it has just a bit extra to add click behaviors) head on over to see the Simple Menu entry in the Codex and grab the full thing. Read on for a bit of help with styling.

Styling the Menu

In order for your menu to look like anything usable you will need to set up some styling first. Mostly it is simple and not overly challenging to wrap your head around so it is a good idea to start by adding this to your page, so that you may more effectively test your menu plugin. I offer some explanation for the styles used below.

For the sake of producing a horizontal menu, the following CSS was sufficient to create the horizontal feel of my menu. It is styled to my needs, so definitely adjust to yours.

ul#navigation, ul#navigation>li {
    display: inline-block;
    margin: 0;
    padding: 0;
    position: relative;
}
ul#navigation li a, ul#navigation li a:visited, ul#navigation li a:active
{
    padding: 15px 10px;
    display: block;
    color: inherit;
}
ul#navigation li a:hover, ul#navigation li:hover {
    background: beige;
    color: #333;
    text-decoration: none;
}

This will define the top level navigation set up. What we see happening here is I set up the main navigation ul element, and its immediate children li tags to display in the inline-block mode. To better understand inline-block look up the ‘display’ property online but in short it allows the elements to display next to each other, inline as it were, and thus provide us with a nice horizontal menu. The next few styles are just to make our links nice and big and clickable, always a good strategy.

In order to make the second level behave the styles need to get a little more involved. Remember that with a drop down menu we no longer want it to be horizontal, so I’ll do a little house keeping to make sure it pops up in order and some position attributes as well.

/** Second tier **/
ul#navigation li ul {
    z-index: 99;
    background: beige;
    color: #333;
    position: absolute;
}
ul#navigation li ul, ul#navigation li ul li, ul#navigation li ul li a {
    display: block;
}
ul#navigation li ul li {
    position: relative;
}

I will admit I am a bit extra when it comes to CSS, not always a good practice but that’s me.

To briefly explain what is going on here, the first style is simply telling all un-ordered lists under other list elements in this structure will have absolute positioning applied to them. This will allow us to move them about the page at our whim and also escape them from affecting the positioning of other elements on the page, which is very important. The next step is to tell all the elements under this structure to behave as “block” elements, in that they should take up all space available to them which helps us keep the links, again, nice and clickable.

I then tell all the non-top level list tags to have relative positioning, which lets me bump them around a little bit without destroying the formatting of other elements–if needed of course. 

That should be all you need for styling outside of your script. Everything else should be nice and automated by your new plug in!