Not so long ago I was in a big search for a solution like this and my search didn’t lead to any positive results. I was looking for a dynamic PHP menu that will allow me to play with lots of options and situations.
My application required some special code because it’s based on member groups and so, my menu was supposed to reflect the permissions attached to each group in part. I was left with some horror solutions and this one explained in this article. Why horror? Well, imagine building a special menu for each group in part. Worst, my software will be sold to people, what if they create 100 groups with various combinations…what’s the menu now huh?!
The following tutorial is based on my finished work which I’m very pleased with. I’m not just going to paste the code and explain but I will start with a basic example and going up the road of “dynamic”.
Let’s imagine a menu array:
$menu = array ( 1 => array ( 'text' => 'Manage articles', 'link' => '#' ), 2 => array ( 'text' => 'Manage articles', 'link' => '#' ), 3 => array ( 'text' => 'Manage articles', 'link' => '#' ), 4 => array ( 'text' => 'Manage articles', 'link' => '#' ) );
It’s not that hard to imagine how would this example look in our browsers after writing some small lines of code to output it:
$out = ''; $out .= "\n".'<ul id="navmenu-h">' . "\n"; for ( $i = 1; $i <= count ( $menu ); $i++ ) { if ( is_array ( $menu [ $i ] ) ) { $out .= '<li class="' . $menu [ $i ] [ 'class' ] . '"><a href="' . $menu [ $i ] [ 'link' ] . '">'; $out .= $menu [ $i ] [ 'text' ]; $out .= '</a>'; $out .= '</li>'; } else { die ( sprintf ( 'menu nr %s must be an array', $i ) ); } } $out .= '</ul>'."\n"; echo $out;
The above code will create an unordered list with 4 elements. Simple and very basic I would say. At this point, our first question arrives: “How do we display each element based on permissions?”. For example, I want my 4th element to be shown only to users that are also customers. Tricky enough but still very easy. Let’s add another field to our arrays. One that will hold the condition which has to return boolean (TRUE/FALSE) in order for our application to decide if the element can be shown or not. Here’s how will our menu elements look after “upgrading” them to support this new feature that we need:
1 => array ( 'text' => 'Manage articles', 'link' => '#', 'show_condition'=> is_customer (), )
The “show_condition” field will be our new field. You may notice the function “is_customer ()” which needs to return TRUE or FALSE. Any function or condition can be used, just follow your needs. This change will also be reflected in the code that is supposed to display the menu. Before outputting each menu element, our code will have to check if the “show_condition” is set to TRUE, otherwise keep it hidden:
if ( is_array ( $menu [ $i ] ) ) { if ( $menu [ $i ] [ 'show_condition' ] ) {//are we allowed to see this menu? $out .= '<li><a href="' . $menu [ $i ] [ 'link' ] . '">'; $out .= $menu [ $i ] [ 'text' ]; $out .= '</a>'; $out .= '</li>'; } } else { die ( sprintf ( 'menu nr %s must be an array', $key ) ); }
Nice I would say. We’re starting to put a brain on our menu. It takes decisions. Let’s make it smarter! I want drop downs to group my elements and not fill my page and break my nice design with your menu. Ok, we’ll have to figure out a way to group them. Let’s try adding a new field into the arrays in order to specify parents. What’s a parent? Each menu that is a submenu will have in it’s declaration the key index of the main element that he’s a part of. The array now looks like this:
1 => array ( 'text' => 'Manage articles', 'link' => '#', 'show_condition'=> is_customer (), 'parent' => 0 )
In our example, the element is itself a parent because he links to no existing key index so this makes him a top level element. The array can be very flexible and every submenu can be a parent for another submenu and so on. There’s no limitation besides your CSS knowledge in order to make a menu that can also support multi-level drop downs.
The example that I’ve attached at the bottom of this tutorial is based on one of the great examples from CSSplay (you can find hundreds of them there, very good examples) and supports 3 levels which should be enough to most of us.
The next question that comes into our minds is how the heck to we display them? How do we put the parent on top of it’s childs and parent-childs on top of….you know the rest. It’s not hard but kept me busy for a little while. A lot of “fun” with arrays and loops.
The principle is simple in theory: we will make a function that displays the top elements (parent = 0) and inside every element we will insert another function that loops through childs until all categories and subcategories will find their end.
function build_menu ( $menu ) { $out = "\n".'<ul>' . "\n"; for ( $i = 1; $i <= count ( $menu ); $i++ ) { if ( is_array ( $menu [ $i ] ) ) {//must be by construction but let's keep the errors home if ( $menu [ $i ] [ 'show_condition' ] && $menu [ $i ] [ 'parent' ] == 0 ) {//are we allowed to see this menu? $out .= '<li><a href="' . $menu [ $i ] [ 'link' ] . '">'; $out .= $menu [ $i ] [ 'text' ]; $out .= '</a>'; $out .= get_childs ( $menu, $i );//loop through childs $out .= '</li>' . "\n"; } } else { die ( sprintf ( 'menu nr %s must be an array', $i ) ); } } $out .= '</ul>'."\n"; return $out; }
The above function picks the top elements exactly how we needed ( $menu [ $i ] [ ‘parent’ ] == 0 ) and also allows our second function to loop through the childs of every top menu ( get_childs ( $menu, $i ) ). This is just basic stuff, no complicated things, 2 parameters that need to be met (show_condition must be true otherwise we’re not allowed to view this menu and parent must be 0 because we want only top elements in this loop ), and the top elements are displayed. More interesting is how we build the 2nd function that will have to go down the category tree until no child of parent is left orphan.
The skeleton is somewhat the same. The 2nd function will loop through the array and, foreach element that has in it’s parent the id that is passed it grabs it and loops again through the new id until it’s finished. Simpler, the function calls itself passing by the parents:
function get_childs ( $menu, $el_id ) { $has_subcats = FALSE; $out = ''; $out .= "\n".' <ul>' . "\n"; for ( $i = 1; $i <= count ( $menu ); $i++ ) { if ( $menu [ $i ] [ 'show_condition' ] && $menu [ $i ] [ 'parent' ] == $el_id ) {//are we allowed to see this menu? $has_subcats = TRUE; $out .= '<li><a href="' . $menu [ $i ] [ 'link' ] . '">'; $out .= $menu [ $i ] [ 'text' ]; $out .= '</a>'; $out .= get_childs ( $menu, $i ); $out .= '</li>' . "\n"; } } $out .= ' </ul>'."\n"; return ( $has_subcats ) ? $out : FALSE; }
This example that we learned can also work with databases. Just set up the table with the array fields ( text, link etc… ) and you’re good to go. The array can be extended hugely. Classes can be passed, images, hover effects, targets…many, many options and many levels can be added. Our handy functions will find and show them all. The flexibility of our menu will make plastic man jealous.
Demo. Download link below the ads.