How to build unlimited level of menu through PHP and mysql
Well, to build my menu my menu I use a db similar structure like this
2 Services 0 3 Photo Gallery 0 4 Home 0 5 Feedback 0 6 FAQs 0 7 News & Events 0 8 Testimonials 0 81 FACN 0 83 Organisation Structure 81 84 Constitution 81 85 Council 81 86 IFAWPCA 81 87 Services 81 88 Publications 81
To assign another submenu for existing submenu I simply assign its parent's id as its value of parent field. parent 0 means top menu
now there is not problem while creating submenu inside another submenu
now this is way I fetch the submenu for the top menu
<ul class="topmenu">
<? $list = $obj -> childmenu($parentid);
//this list contains the array of submenu under $parendid
foreach($list as $menu) {
extract($menu);
echo '<li><a href="#">'.$name.'</a></li>';
}
?>
</ul>
What I want to do is.
I want to check if a new menu has other child menu
and I want to keep on checking until it searches every child menu that is available
and I want to display its child menu inside its particular list item like this
<ul>
<li><a href="#">Home</a>
<ul class="submenu">
........ <!-- Its sub menu -->
</ul>
</li>
</ul>
Here is a "developer-friendly" version of the "one query, no recursion" solution for this problem.
SQL:
SELECT id, parent_id, title, link, position FROM menu_item ORDER BY parent_id, position;
PHP:
$html = '';
$parent = 0;
$parent_stack = array();
// $items contains the results of the SQL query
$children = array();
foreach ( $items as $item )
$children[$item['parent_id']][] = $item;
while ( ( $option = each( $children[$parent] ) ) || ( $parent > 0 ) )
{
if ( !empty( $option ) )
{
// 1) The item contains children:
// store current parent in the stack, and update current parent
if ( !empty( $children[$option['value']['id']] ) )
{
$html .= '<li>' . $option['value']['title'] . '</li>';
$html .= '<ul>';
array_push( $parent_stack, $parent );
$parent = $option['value']['id'];
}
// 2) The item does not contain children
else
$html .= '<li>' . $option['value']['title'] . '</li>';
}
// 3) Current parent has no more children:
// jump back to the previous menu level
else
{
$html .= '</ul>';
$parent = array_pop( $parent_stack );
}
}
// At this point, the HTML is already built
echo $html;
You just need to understand the usage of the $parent_stack variable.
It is a "LIFO" stack (Last In, First Out) - the image in the Wikipedia article worths a thousand words: http://en.wikipedia.org/wiki/LIFO_%28computing%29
When a menu option has sub-options, we store its parent ID in the stack:
array_push( $parent_stack, $parent );
And then, we immediately update $parent, making it be the current menu option ID:
$parent = $option['value']['id'];
After we looped all its sub-options, we can return back to the previous level:
$parent = array_pop( $parent_stack );
This is why we stored the parent ID in the stack!
My suggestion is: contemplate the code snippet above, and understand it.
Questions are welcome!
One of the advantages I see in this approach is that it eliminates the risk of entering into an infinite loop, which can happen when recursion is used.
With a database structure like yours, it is possible to build the whole HTML menu with a single query and without recursion.
Yes - I will repeat:
- ONE QUERY
- NO RECURSION
This is the approach I always use myself.
Pasted the code here - fully functional:
http://pastebin.com/GAFvSew4
Jump to line 67 to see the interesting part ("get_menu_html").
The main loop starts at line 85.
There are five "customizable" HTML snippets:
- menu wrapper opening (line 83)
- menu wrapper closing (line 122)
- menu item with childs opening (line 100)
- menu item with childs closing (line 92)
- menu item without childs (line 113)
(The code could be cleaner if I hadn't worried with tabulation.)
SQL to create and populate sample database is available at the end of the script.
You can try and let us know your thoughts.