Friday night I pulled up an old site I developed that was in great need of refresh on its navigation. As far back as months ago I'd wanted to use a two level horizontal menu to provide site navigation and give some awareness of the current page the user was on. The web site is actually a web application desktop app replacement and pretty much every page is a data entry page and can be neatly grouped under a category.
Months back I attempted to implement a two level menu by the usual method of searching for an example of exactly what I wanted and copy-pasting. After a few hours of fruitless searching I gave up and shelved the mini project for a while. Friday night I pulled it off the shelf and dusted it off. I started again with investigating the 'net for something that would already do the job. Why re-invent the wheel. By 1am I was getting to that point of losing interest until I had a thought. "I'm a software developer. Why don't I just make my own". With my enthusiasm building, I went to bed, promising to attack the problem in the morning.
My Web Application has the following basic structure:
- Contracts
-- New Contract
-- Find Contract
- Invoicing
-- Invoice Run
-- Invoicing History
- Maintenance
-- Agents
-- Accounts
-- Companies
-- Contracts
-- Networks
-- Plans
- Control Panel
-- Users
-- System Settings
I had the following requirements for my two level menu:
- Categories along the top level.
- Sub-categories on the second level as per the above hierarchy.
- Menu indicates current page
- Current Category is highlighted
- Current Page is highlighted.
Saturday morning and a few hours later I had the layout and the stying done.
HTML
<ul id="menu">
<li><a href="#" id="contracts">Contracts</a>
<span>
<a href="/contracts/submitcontract.aspx">New Contract</a>
<a href="/contracts/search.aspx">Find Contract</a>
</span>
</li>
<li><a href="#" id="invoicing">Invoices</a>
<span>
<a href="/invoicing/invoicerun.aspx">Invoices Run</a>
<a href="/invoicing/invoicerun.aspx">Invoicing History</a>
</span>
</li>
<li><a href="#" id="maintenance">Maintenance</a>
<span>
<a href="/maintenance/account.aspx">Accounts</a>
<a href="/maintenance/agent/search.aspx">Agents</a>
<a href="/maintenance/company.aspx">Companies</a>
<a href="/maintenance/contact.aspx">Contacts</a>
<a href="/maintenance/networks.aspx">Networks</a>
<a href="/maintenance/plans.aspx">Plans</a>
</span>
</li>
<li><a href="#" id="admin">Control Panel</a>
<span>
<a href="/admin/userlist.aspx">Users</a>
<a href="/admin/systemsettings.aspx">System Settings</a>
</span>
</li>
</ul>
Using an unordered list is the way to go with menus and while I initially tried implementing the second level as another unordered list, I had more luck (and less styling changes) using a span.
Styling
#menu
{
margin:0;
padding:0;
list-style:none;
position:relative;
background:url(../images/menubg.gif) repeat-x;
height:32px;
text-transform:uppercase;
font-family: Arial, Helvetica, sans-serif;
}
#menu li
{
float:left;
padding:0;
margin:0;
}
#menu a
{
text-decoration:none;
padding:10px 15px;
display:block;
color:#ffffff;
}
#menu li:hover a, a.selected {
background:#ffffff;
color: #666666 !important;
}
#menu li span
{
float:left;
padding: 5px 0;
position:absolute;
display:none;
left:0;
top:31px;
background:#ffffff;
color: #000;
width: 100%;
}
#menu li:hover span, .selectedSubMenu { display:block; }
#menu li span a { display: inline; padding: 5px 15px; }
#menu li span a:hover {text-decoration: underline;}
.active { font-weight: bold; }
The above worked correctly for the menu but it lacked indicating where I was in the navigation. Here's where I needed to write some JavaScript (jquery) so I could detect the current url and use some logic to select the appropriate menu item.
My physical site hierarchy reflects the categories (this is an ASP.NET Web Forms application) making it easy to determine categories, by using the id attribute of the anchor element. As long as the case is correct, it'll work.
jquery (using jquery 1.2.6)
function markActiveLink() {
var $path = location.pathname.substring(1);
if ($path) {
$('#menu li > a').each(function() {
var $category = $(this).attr('id');
if ($path.indexOf($category) > -1) {
$(this).addClass('selected');
$(this).siblings('span:first').addClass('selectedSubMenu');
$('#menu li span a[@href="/' + path + '"]').addClass('active');
}
});
}
}
$(document).ready(markActiveLink);
The above jquery iterates over every top level menu item (category) and if it's the appropriate category for my url, I add the selected class category and displayed the span (selectedSubMenu class). I then needed to find the appropriate link for the current url
$('#menu li span a[@href="/' + path + '"]').addClass('active');
Unfortunately it didn't work as desired. The second level menu was not visible when I navigated to the page. I spent a good two hours trying to figure it out, sure that it was something simple. Finally I had to take my daughter to soccer so I had to let it go for the time being. Returning with fresh eyes, and with my brother on IM, we both looked at it and turns out it was a CSS specificity problem (doh! I knew it something simple. Hopefully won't forget that anytime soon). With my updated CSS everything worked as desired, and it only took about 12 hour. /sigh.
Updated CSS.
#menu li span.selectedSubMenu { display:block; }
#menu li span.selectedSubMenu a { color: #666666; }
The final product, navigated to the Contact Maintenance page here