A lot of people are using Chris Coyier’s example, which relies on jQuery and doesn’t work so great if you’ve got multiple levels of nested menus. The CSS-Tricks example ignores their hierarchical value and simply appends the lower menu items to the select navigation as another option. From a usability perspective this isn’t the best approach as all items appear to have the same priority.
Tip: The demo has no CSS styling on the navigation to show it’s structure.
Let’s look at the current, demo, navigation structure, which you’ll see has three levels of hierarchy, Top Level, Sub Item, Sub-sub Item. I’ve merely named them as these for the demo. The task for our script is to then take these levels of hierarchy, and dynamically append each item to a new inside a element.
Creating the Select element
We then need to give our new element an ID of ‘mobile’, so we know it’s the mobile navigation, and for later CSS styling purposes. We’ll need to create a variable to add the ID attribute, let’s call our variable ‘select’ and set the mobile ID attribute.
This would dynamically give us this markup:
‘Navigation’ first option
The first option in our menu will be called ‘Navigation’. You can of course call it whatever you like. It’s best to create a ‘dummy’ first option that doesn’t do anything, one for readability and secondly for letting the user know what it is.
Now we’ll create the first item:
We’ll now need to add our first item to the select menu:
The full script for this portion looks as follows:
Dynamically creating the options
Let’s target the existing markup, and create an option element for each item. We create a nav variable which targets our element by ID, which is in our earlier markup. Using a recursive function, we then use a for loop to loop through the child elements of our nav ID.
Inside the loop, we’ve got two different sections, one that creates an option for all ‘A’ elements, and the other which looks for the ‘UL’ element. Each child element is then passed through the loop, creating an for each element found.
A special check is then passed if the element (nodeName check) is strictly ‘UL’ - a tag. I’ve then setup a hierarchy detection piece, which detects the level of the ‘UL’. If the ‘UL’ is detected under a specific level, it won’t append a hyphen. For nested tags, past the first level, it will append a hyphen.
This script uses a function which is recursive, and is setup so it will append one more hyphen for each level of hierarchy, so you always know how deep you are inside the navigation when using the nav. This has the added bonus in which you can pretty much have as many nested tags as you wish, so you don’t need to worry about how nested your navigation is.
Cross-browser ‘onchange’ solution
Appending the links to the element is the first section, now we need to make the menu work when an is selected. jQuery makes this easy, but not everything fun is easy ;-)
A cross-browser solution to this uses a few different one-line feature detections:
First we find our newly created and assign it to a variable mobile.
addEventListener is supported by pretty much all browsers, apart from IE.
attachEvent is IE’s version of addEventListener.
Should all else fail, a default onchange has been added. These have been setup as if, else if, else statements for our browsers to run through and check which suits them.
Putting everything together
Now that we’ve created the main pieces of our script, we need to put it all together. I’ve wrapped it all inside a function named selectnav() which you need to call after the DOM structure has been rendered. You can either remove the function ‘wrap’ and add the script to the bottom of your page, or include it inside a DOM ready function - but you knew that already.
Inside this script you’ll notice this piece:
This adds our newly created menu inside our element. This keeps them inside the same element, which is great from a styling perspective as everything will be inside the same element.
Include the script inside your tag, and call the function just before the closing tag. For performance purposes you could include the script at the bottom of the page, and you wouldn’t need to call the function. We merely call the function because it needs to be executed after the DOM elements have loaded.
If you are using a DOM ready function handler or putting scripts before the closing body tag, then you can of course remove the script entirely from its function wrap, and add it like this:
I’ve tested this in IE6, IE7, IE8, IE9, Chrome, iOS Safari & Chrome, Safari, FireFox, Opera. If you do run into any browser compatibility issues, drop a comment or pull/issue request on GitHub.