David C. Dalton
Web Application & Database Development, Responsive Website Design, Programming & SEO Services
I’m starting to sound like a broken record to myself about this stuff but I keep getting nonsensical flack from so called designers who insist on over blown, graphic laden, Javascript driven menu systems on clients websites. If I stopped hearing this crap from so-called designers I might actually shut up about it!
One PHP Class + Standard List Items + CSS = GREAT MENUS!
I could whine and cry here about the latest designer who irritated me with their 1999 'slice n dice' mentality and how I then had to give the same standards, accessibility and 'other device support' speech to the client but this time I thought I’d try to show some examples of the right way to do it, along with a PHP menu building class to make matters even easier for most developers. Granted it’s not cutting edge stuff and no I haven’t made any earth shattering breakthroughs in coding, just some really REALLY simple practices put into use to build menus the proper way! Maybe, just MAYBE someone will get the hint and finally understand this tableless stuff isn’t complete voodoo!
First off let’s go through the PHP class. If you aren’t into PHP you can skip this section and go directly to Part Two of this and just build your menus in a normal html file. I have two files that build our menus and I have also built in some functionality that I can see I will need down the road. The first file is just an associative array of the menu items along with a variable (called homelinkposition) to tell me where in the array the home link is (so I can shut it off on the home page) and another variable called homepagename that tells me how I reference the home page in my code. Maybe I should explain that last one a bit.
In my controllers on any given website I use a page variable called (or something to that effect). By testing that variable at the beginning of my script I can either grab the proper text file (for a static text page) or do something else, all dependent on that variable. Since I can’t set a variable on an incoming hit from outside the site and since I like to use <a href='/'> for my internal links to the home page I need a way to know I am on the main page of the site. So at the top of my controller I will have a snippet of code something like this:
$pageName = (isset($_REQUEST['pageName']) && strlen(trim($_REQUEST['pageName'])) != 0) ? trim($_REQUEST['pageName']) : 'main';
Anyway, that is why I have the variable homepagename referenced in the file (boy was that the long way around!)
Last, but not least I have my associative array of menu items. I choose to do it this way because I am a NUT about not hitting the database if I don’t need to and storing menu items in a database table just really rubs me the wrong way. So here is my array of menu items:
$menus = array(
'about us' => array(
'about our company' => 'about-our-company',
'our mission' => 'our-mission-in-life',
'press releases' => 'some-great-press-releases',
'testimonials' => 'testimonials-about-us',
'newsletter' => 'another-newletter-signup',
'our staff' => 'staff-profiles'),
'for developers' => array(
'another geek link' => 'another-geek-link',
'advance designs' => 'advance-designs',
'script-pricing' => 'its-free-silly',
'addition services' => 'addition-services',
'FAQ' => 'more-useless-faq'),
'for site owners' => array(
'tell your developer' => 'no-more-crappy-javascript-menus',
'other device support' => 'pda-cellphone-and-more-oh-my',
'FAQ' => 'sam-faqs-as-before'),
'our services' => 'about-our-services',
'contact us' => 'contact-us',
'home' => '/'
);
If you look at the array you will notice it contains a 1st level item that MAY associate to a sub array. The 1st level item is the main menu display item. The sub arrays contain the drop down items, also in an associative manner: display words => hyperlink. If the main level item does not reference an array it has no drop down item and will contain the link to that page.
So that’s file number one, literally nothing more than reference items. Now let’s look at the 'meat and potatoes' of this thing, this MenuBuilder class. (feel free to copy and paste this into your favorite editor)
<?php
class MenuBuilder {
function buildMenu($pageName, $pathtofile) {
require($pathtofile . '/menus.ini.php');
$inner = "<div id='menu'>";
$inner .= "<ul id='navigation'>";
/* we get each main element out of the array */
$position = 0;
foreach ($menus as $key => $menuitem) {
if (is_array($menuitem)) { // a main menu item that has a sub menu
/* the top level item */
$inner .= "<li><a class='main' href='#'>{$key}</a>";
$inner .= "<ul class='inner'>";
$maxwidth = 0;
/* get the max width of the longest string in the sub array */
foreach ($menuitem as $inkey => $inner) {
$maxwidth = (strlen($inkey) > $maxwidth) ? strlen($inkey) : $maxwidth;
}
/* set up the proper style width from the maxwidth variable so the drop down items dont wrap */
$width = round($maxwidth * .7);
/* loop through the drop downs */
foreach ($menuitem as $innerkey => $inneritem) {
if ($pageName == $inneritem) {
$inner .= "<li id='innerat' style='width: {$width}em;'>{$innerkey}</li>";
}
else {
$inner .= "<li style='width: {$width}em;'><a href='{$inneritem}' title='" . str_replace("-", " ", $inneritem) . "'>" . $innerkey . "</a></li>";
}
}
$inner .= "</ul>";
$inner .= "</li>";
}
else { // a menu item without drop downs
if ($pageName == $homepagename && $position == $homelinkposition) { // the home page link, dont show it on the home page!
continue;
}
else if ($pageName == $menuitem) {
$inner .= "<li id='outerat'>{$key}</li>";
}
else {
$titletag = (($menuitem == "/") ? "return to the main page" : str_replace("-", " ", $menuitem));
$inner .= "<li><a class='main' href='{$menuitem}' title='{$titletag}'>{$key}</a></li>";
}
}
$position++;
} // end outer foreach
$inner .= "</ul>";
$inner .= "</div>";
return $inner;
} // end function
}
Now I’ve simplified the code down some from my real class to standard if / else checks just to make it easier for us to go through (I’m a ternary operator nut too) and I’ve removed some of my output formatting for easier reading. You will notice the function buildMenu takes two parameters, $pageName and $pathtofile. The first is the page we are on passed in from our controller (the page the user is requesting). The 2nd variable is just so the menus.ini.php file can be put wherever I want it. The function starts with a simple echo of our outer element, namely the <div id='menu'>. Many people might just skip the div and only use the ul tag but I have found some sites where I wanted to do some extra styling and this div can really help out. The 2nd addition is nothing more than our outer ul tag with an id of navigation. I also set a quick variable to test my current position in the array. Now the fun begins!
We start looping through the outer elements of our menus array in a foreach loop, pulling both the key and element (remember the key is the name we will display in the menu, the element is the link). Our first check is to see if the element is an array or not. If it’s an array we have a menu item that has a drop down, if not it has no drop down. Let’s deal with a menu item that has a drop down first.
Once we have determined the top level menu item will have a drop down we our outer list item (the main menu item) to the $inner variable (our return output) and then start the inner ul tag, outputting our top level menu words with this code:
/* the top level item */
$inner .= "<li><a class='main' href='#'>" . $key . "</a>";
$inner .= "<ul class='inner'>";
Notice I have set a class on the top level li hyperlink and (of course) the href = #. I have also set a class on the inner ul tag ... all of these for styling later. Now comes an interesting part. Since we can never be 100% sure how long any one menu item may be and since they can change at any time we loop through the $menuitem array to find out the longest string in the group. We simply match up the length of each inner menu item against our $maxwidth variable and if it’s larger we assign it to that variable. Once we are done with that we do some simple math to come up with the proper width (in ems) for our list items with this line of code:
$width = round($maxwidth * .7);
Where did I come up with that .7 as a multiplier? Simple, I played with it for a while. It seems to work fine but may need to be adjusted when the font size you are using on your site is bigger than .9em. So what will happen if the user has their font size adjusted up, another simple answer the text wraps.
Next we start looping through the inner menu items with this code:
foreach ($menuitem as $innerkey => $inneritem) {
if ($pageName == $inneritem) {
$inner .= "<li id='innerat' style='width: {$width}em;'>{$innerkey}</li>";
}
else {
$inner .= "<li style='width: {$width}em;'><a href='{$inneritem}' title='" . str_replace("-", " ", $inneritem) . "'>{$innerkey}</a></li>";
}
}
Because I HATE having an active link to the page I’m already on (and you should too) I have some simple logic that checks to see if the $pageName variable matches the current url element in the array. If it does we simply remove the hyperlink and set a new class to the list item (just in case we want to do some styling). Also notice I am using our width variable and applying it to each list item. By doing this we don’t have to play games with the width of the unordered list or have menu items wrap. Lastly you will notice I am using the page’s url as a title tag by removing the - between the words (I exclusively use hyphenated page names and a mod_rewrite). Once that is done we close our inner unordered list and our top level list item and continue the looping.
Now lets tackle the menu items that don’t have drop down items (the else statement in our main loop). The else handles a few different situations that could happen. The first is an if statement that checks to see if the request is for the home page of the site and if we are at the home page link position in our array. This is the code block:
if ($pageName == $homepagename && $position == $homelinkposition) {
continue;
}
Nothing too exciting here, if we drop into this if block we simply use the continue to move to the next iteration of the loop (because it’s stupid to have a link to the home page on the home page!) The else if after this if block handles displaying a top level menu item when the request is for that page, removing the hyperlink and setting an id to that list item:
else if ($pageName == $menuitem) {
$inner .= "<li id='outerat'>{$key}</li>";
}
And finally the else handles a normal top level menu link (yes I’m playing with the links title too):
else {
$titletag = (($menuitem == "/") ? "return to the main page" : str_replace("-", " ", $menuitem));
$inner .= "<li><a class='main' href='{$menuitem}' title='{$titletag}'>{$key}</a></li>";
}
Last in our main foreach loop we increment our position variable. Once the loop has run we close the outer unordered list and the div tag and we are done! The code this class produces outputs perfectly semantic html code that can now be styled as you wish. Using CSS you can now create horizontal or vertical menus, with or without drop downs as we need. But that’s for part two!
Part Two - Let's Style These Puppies