Create a dynamic list without the use of JavaScript

The creation of a dynamic nested unordered list without using any JavaScript code is sometimes a problem because the script needs to "remember" the values, sub values and also the query string from previous clicked links. In this tutorial we will learn how to create a dynamic navigation menu powered by PHP and MySQL only. While using standard HTML elements (unordered lists) it's easy to add different CSS styles later. Follow this link for an example or download the PHP example files here.

First we need a database source holding the information for our navigation menu:

CREATE TABLE `dyn_menu` (
  `id` int(11) NOT NULL auto_increment,
  `label` varchar(50) NOT NULL default '',
  `link_url` varchar(100) NOT NULL default '#',
  `parent_id` int(11) NOT NULL default '0',
  PRIMARY KEY  (`id`)
) TYPE=MyISAM;

Use PHP to create HTML from your MySQL data

Next we have to add some data for our menu: menu items and sub-menu items.
Example: our menu item "Products" will have several sub-menu items and the record id is a "2"
All submenu items need the number "2" as value for the parent_id. The parent_id for each main menu item is a "0" (zero).

In following code block we are building a database result and put the value into two multi-dimensional arrays:
$parent_menu and $sub_menu

The $parent_menu array is holding the label, the link (URL) and a count value that we need to decide if there is a sub-item or not. The $sub_menu is holding the label and link values and the value of the parent_id.

We are looping through the database record set for records where the parent_id is a "0" or not. If the value equal to "0" it's a parent record and otherwise it's a sub-item record.

$sql = "SELECT id, label, link_url, parent_id FROM dyn_menu ORDER BY parent_id, id ASC";
$items = mysql_query($sql);
while ($obj = mysql_fetch_object($items)) {
    if ($obj->parent_id == 0) {
        $parent_menu[$obj->id]['label'] = $obj->label;
        $parent_menu[$obj->id]['link'] = $obj->link_url;
    } else {
        $sub_menu[$obj->id]['parent'] = $obj->parent_id;
        $sub_menu[$obj->id]['label'] = $obj->label;
        $sub_menu[$obj->id]['link'] = $obj->link_url;
        if (empty($parent_menu[$obj->parent_id]['count'])) {
            $parent_menu[$obj->parent_id]['count'] = 0;
        }
        $parent_menu[$obj->parent_id]['count']++;
    }
}
mysql_free_result($items);

Put all the code into a single function

For the creation of the whole navigation bar we group the whole code into a function, this way it's more portable and we can accesses the code several times in one script without to repeat the code.

function dyn_menu($parent_array, $sub_array, $qs_val = 'menu', $main_id = 'nav', $sub_id = 'subnav', $extra_style = 'foldout') {
    $menu = '
    <ul id="'
.$main_id.'">';
    foreach ($parent_array as $pkey => $pval) {
        if (!empty($pval['count'])) {
            $menu .= '
        <li><a class="'
.$extra_style.'" href="'.$pval['link'].'?'.$qs_val.'='.$pkey.'">'.$pval['label'].'</a></li>';
        } else {
            $menu .= '
        <li><a href="'
.$pval['link'].'">'.$pval['label'].'</a></li>';
        }
        if (!empty($_REQUEST[$qs_val])) {
            $menu .= '
        <ul id="'
.$sub_id.'">';
            foreach ($sub_array as $sval) {
                if ($pkey == $_REQUEST[$qs_val] && $pkey == $sval['parent']) {
                    $menu .= '
            <li><a href="'
.rebuild_link($sval['link'], $qs_val, $sval['parent']).'">'.$sval['label'].'</a></li>';
                }
            }
            $menu .= '
        </ul>'
;
        }
    }
    $menu .= '
    </ul>'
;
    return $menu;
}

The arguments of the function are the both arrays, the name of the query string value that's used to fold out the menu, the two ids used to identify the unordered lists with the CSS style and an extra name for some CSS pseudo class to give a main link element with sub-items a different look. A nested unordered list is a list with list items ("li" elements) and several other lists ("ul" elements). A main can hold another lists or new "ul" elements.
To do this we start in our function with a "ul" element, to identify the element with CSS we add some id attribute here.

foreach ($parent_array as $pkey => $pval) {
    if (!empty($pval['count'])) {
        $menu .= '
        <li><a class="'
.$extra_style.'" href="'.$pval['link'].'?'.$qs_val.'='.$pkey.'">'.$pval['label'].'</a></li>';
    } else {
        $menu .= '
        <li><a href="'
.$pva['link'].'">'.$pval['label'].'</a></li>';
    }

Inside the loop there is a test for the count value from parent items, if the count is not null the menu item querystring value is added.

if (!empty($_REQUEST[$qs_val])) {
    $menu .= '
        <ul id="'
.$sub_id.'">;
    foreach ($sub_array as $sval) {
        if ($pkey == $_REQUEST[$qs_val] && $pkey == $sval['
parent']) {
            $menu .= '

            <li><a href="'.rebuild_link($sval['link'], $qs_val, $sval['parent']).'">'.$sval['label'].'</a></li>';
        }
    }
    $menu .= '

        </ul>';
    }

A PHP function is called to update the query string

Now the real code begins with this test for a request value send via the link from a parent menu item a new loop is opened to create a list with sub-items related to the parent item. Inside the statement the function "rebuild_link: is called, this function is used to handle the query string:

function rebuild_link($link, $parent_var, $parent_val) {
    $link_parts = explode('?', $link);
    $base_var = '?'.$parent_var.'='.$parent_val;
    if (!empty($link_parts[1])) {
        $link_parts[1] = str_replace('&amp;', '##', $link_parts[1]);
        $parts = explode('##', $link_parts[1]);
        $newParts = array();
        foreach ($parts as $val) {
            $val_parts = explode('=', $val);
            if ($val_parts[0] != $parent_var) {
                array_push($newParts, $val);
            }
        }
        if (count($newParts) != 0) {
            $qs = '&amp;'.implode('&amp;', $newParts);
        }
        return $link_parts[0].$base_var.$qs;
    } else {
        return $link_parts[0].$base_var;
    }
}

The arguments for this function are the link from the array, the variable name and value from the parent menu item (to keep the list open). Inside the function the query string will be exploded and is getting rebuilt without becoming in trouble with existing parts or new variables. At the end the function will return a modified link with all parts of the query string.

Example usage

How to use this function? It's easy just echo the function name like here:

echo dyn_menu($parent_menu, $sub_menu, 'menu', 'nav', 'subnav');

Don't forget to try our PHP demo for better understanding.