Single Query Dynamic Multi-level Menu

Since writing Dynamic Multi-level CSS Menu with PHP and MySQL. SEO Ready I have learned quite a lot and in doing so found a much more efficient way of building this menu. This method varies in that it only makes one query to the menu table and compiles the results into a multidimensional array. The basic recurring function was just about the same, just taking into account the changes in data structure. Lets start with the query and array.

// Select all entries from the menu table
$result=mysql_query("SELECT id, label, link, parent FROM menu ORDER BY parent, sort, label");
// Create a multidimensional array to conatin a list of items and parents
$menu = array(
    'items' => array(),
    'parents' => array()
);
// Builds the array lists with data from the menu table
while ($items = mysql_fetch_assoc($result))
{
    // Creates entry into items array with current menu item id ie. $menu['items'][1]
    $menu['items'][$items['id']] = $items;
    // Creates entry into parents array. Parents array contains a list of all items with children
    $menu['parents'][$items['parent']][] = $items['id'];
}

The $menu contains 2 other arrays, items holds every result from the menu table query, the parents array holds a list of all item ids that have children. Next we use a while statement to run through the sql results and assign items to the arrays. If the items parent id already exists in the parents array it will be overwritten so there will only be 1 of each parent id listed.

// Menu builder function, parentId 0 is the root
function buildMenu($parent, $menu)
{
   $html = "";
   if (isset($menu['parents'][$parent]))
   {
      $html .= "
      <ul>\n";
       foreach ($menu['parents'][$parent] as $itemId)
       {
          if(!isset($menu['parents'][$itemId]))
          {
             $html .= "<li>\n  <a href='".$menu['items'][$itemId]['link']."'>".$menu['items'][$itemId]['label']."</a>\n</li> \n";
          }
          if(isset($menu['parents'][$itemId]))
          {
             $html .= "
             <li>\n  <a href='".$menu['items'][$itemId]['link']."'>".$menu['items'][$itemId]['label']."</a> \n";
             $html .= buildMenu($itemId, $menu);
             $html .= "</li> \n";
          }
       }
       $html .= "</ul> \n";
   }
   return $html;
}
echo buildMenu(0, $menu);

This version signifigantly reduces the strain on your server if you have hundreds or thousands of pages and still allows you to keep a completely dynamic menu.

Incoming search terms:

  • select a name as parent b name as child from menus as a menus as b where a id = b parent_id
  • php mysql multi level menu
  • multi level accordion menu
  • php multi level categories
  • multi level menu in php
  • create multi level category php
  • php multi level menu database
  • multi level menu
  • 56 inurl:/engine/rss php 0
  • multilevel accordion menu
  • vertical menu with mysql
  • dynamic multi level menu
This entry was posted in Web & Database Design and tagged , , , , , , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

25 Comments

  1. smronju
    Posted February 2, 2012 at 2:01 pm | Permalink

    Thanks I’ve been looking for it. You saved my day cheers!

  2. adcounts86
    Posted December 21, 2011 at 12:42 pm | Permalink

    I was trying to use CSS to style this menu but still cannot figure out how to apply class to the . Can you post the full code with using class?

  3. T.G.D.
    Posted October 5, 2011 at 7:23 am | Permalink

    Oops, the sanitizer removed parts of my code!

  4. T.G.D.
    Posted October 5, 2011 at 7:22 am | Permalink

    I have modified the code to:
    1. Hide content of submenus beyond top level.
    2. If the file is # then the submenu opens without a link.


    $menuiterations = 0; // Check how deep is the function call
    // Menu builder function, parentId 0 is the root
    function buildMenu($parent, $menu) {
    global $menuiterations;
    $html = '';
    if (isset($menu['parents'][$parent])) {
    if ($menuiterations<2) { // Top level menu
    $html .= ''."\n";
    }
    else { // Deeper nested menu does not display submenus
    $html .= ''."\n";
    }
    foreach ($menu['parents'][$parent] as $itemId) {
    if ($menu['parents'][$itemId]==0) {
    $html .= ''.$menu['items'][$itemId]['title_el'].$menuiterations.''."\n";
    }
    else {
    $html .= ''.$menu['items'][$itemId]['title_el'].$menuiterations.'';
    $menuiterations = $menuiterations+1; // Increase function call depth
    $html .= buildMenu($itemId, $menu);
    $html .= '';
    }
    }
    $html .= ''."\n";
    $menuiterations = $menuiterations-1; // Decrease function call depth
    }
    return $html;
    }

    However, I still need that the submenu is open when I load a page that belongs to the submenu. That is the display: none part should be so only when the current file is not in the current menu tree branch. I am afraid I didn’t understand your reply to Neil about the additional array.

    • Posted October 5, 2011 at 12:29 pm | Permalink

      Basically you will need a function that checks if # is a child of this menu item. Use that check before rendering each item.

  5. Ankur Thakur
    Posted September 11, 2011 at 6:19 am | Permalink

    Fantastic work danieliser… :D

    I was making Sub Menus by using two functions.
    1. To get all the Parent elements first.
    2. To get all the childs (called in the above function with parameter as id)

    But the Disadvantage was that it increased number of SQL queries.

    So my Friend Lelebart suggested me this and it works very nice…

    Well done :)

  6. Andre van der Walt
    Posted July 19, 2011 at 11:40 am | Permalink

    Hi danieliser,

    I hope you can still help me with this awesome dynamic menu structure you have build.

    I am trying to build a dynamic accordion menu which is as follows:

    -Home
    -Products
    – Cookware
    – Homeware
    – Decorative
    - Contact Us

    Home

    Products

    Cookware
    Homeware
    Cookware
    Decorative

    Contact Us

    I don’t know if the above makes any sense but the basic function of the menu is that if you click on “Products” it will expand and if you click on “Homeware” under “Products” it will expand as well.
    My main problem is that I each parent to have its own structure so that it can be hidden and displayed when you click on its parent.

    Hope you can assist me

    Thank you,

    Andre

    • Posted July 19, 2011 at 3:05 pm | Permalink

      Ok.. This makes several requests for a similar feature.. I can add functionality like that in the next update. If you know a little jquery and dont want to wait it can be done by setting the menu to show all children and hide them via jquery.. The only thing that is missing to make the menu work like you want is the jquery.

      • Andre van der Walt
        Posted July 20, 2011 at 12:18 am | Permalink

        Hi,

        Sorry I could not get the code that I inserted to show. I surrounded my code with tags but it did not work unless I’m being blond and doing it wrong?

        I am actually using jquery to display and hide the different parents.

        The problem I just found with the jquery example I’m using is that it only allows for a two menu accordion menu!!!!! Which will not work.

        Looking forward to your update on this using jquery or some sort of accordion menu.

        Thank you for replying so quickly,

        Andre

        • Posted July 20, 2011 at 7:40 pm | Permalink

          @Andre, I have to appoligize, i thought your original comment was on my Vertical Menu Widget plugin for wordpress. You should be able to format the menu any way you want based on this script. You can customize this to use divs instead of li if you wanted, try to find a demo of a jquery menu that you like and look at how it needs to be structured, then imitagte that structure in your menu script.

  7. Jelmer Visser
    Posted July 3, 2011 at 11:52 am | Permalink

    Thx!

  8. Alex
    Posted April 22, 2011 at 5:07 am | Permalink

    Hi,

    After reading your tutorial, I realise that my biggest problem is the association of the given script to all the other parts.

    I mean, in this case, if you want to work with a MySQL database you'll need to get the connection with an host.inc.php and than

    include "host.inc.php";

    mysql_connect($host,$user,$pass);

    mysql_select_db($db);

    However, I always wanted to link this to a vertical menu… I already have the vertical menu ), now I just need to

    think a little bit, on how this could be done…

    • Posted May 22, 2011 at 3:09 pm | Permalink

      @Alex, I assumed most people would know to include db connections on their own.

      As for converting it to a vertical menu that is simple.. no changes are necessary to the php script at all..

      You simply need to restyle it via css.. Instead of the li using display:inline-block, you could use block and that would make each on a new line.

      You might have to modify the script for your JS dropdowns depending on how your selecting them.

  9. Neil
    Posted April 20, 2011 at 12:08 am | Permalink

    Hi

    I've been racking my brains to come up with a way to only display and sub menus if they are a child of the level you are in. So similar to what you have shown I am setting up the query string so I know what page I'm on and have that highlighting. I then put in

    if($pageID == $menu['items'][$itemID]['id']){

    $html .= buildLeftMenu($itemID, $menu, 'y');

    }

    However if you are then on a page that is a child then the menu is not drawn.

    Cheers

    Neil

    • Posted May 22, 2011 at 3:33 pm | Permalink

      @Niel, sorry it took so long to reply, been very busy lately.

      What you will need to do is create another function that takes the current page id and returns an array of all parents.

      Then you can use

      if((in_array($menu['items'][$itemID]['id',$parent_id_array)) || ($pageID == $menu['items'][$itemID]['id']))

      Hope this helps.

  10. bruce2046
    Posted January 17, 2011 at 4:50 am | Permalink

    Hi,

    I would like to add the level 0 items with class=”header” and it’s child with class=”nav_body”.

    How can I do it?

    Thanks,

    bruce2046

    • Posted May 22, 2011 at 3:23 pm | Permalink

      @bruce2046, Sorry it took so long to reply. Been extremely busy.

      You can add level functionality by changing

      $html .= buildMenu($itemId, $menu);

      to

      if(!isset($level)) $level = 0;
      $level++;
      $html .= buildMenu($itemId, $menu);
      $level--;

      now you can easily add classes by level by change all <li> lines in the script to

      <li class='level" . $level . "'>

      Now each li will have a level class like ‘level1′

      hope this helps

  11. Dave M
    Posted November 28, 2010 at 11:56 am | Permalink

    OMG mate this little script is so amazing and i have now made a little menu system for phpbb3 with it Thanks

    Dave

    • Posted January 17, 2011 at 10:36 am | Permalink

      Thank you very much. If you’d like to support us link to us or to our content. Again glad to see people getting use of this.

  12. Razzer
    Posted July 27, 2010 at 3:09 am | Permalink

    Hi danieliser,

    This is perfect – you’re the man :)
    Thanks a lot.

    R.

  13. Posted July 26, 2010 at 7:04 pm | Permalink

    Im assuming you are using the :active pseudo class for your clicked link. And also assuming $_GET['p'] is a page id passed with the link to the next page or with the page load itself. so you could do it like this

    <code>function is_active($p_id){
    $active_class = '';
    if (isset($_GET['p']) && $_GET['p'] == $p_id) {
    $active_class = "class='active_class'";
    }
    return $active_class;</code>

    then replace

    <code>$html .= "
    <a href='".$menu['items'][$itemId]['link']."' >".$menu['items'][$itemId]['label']."

    ";</code>

    with

    <code>$html .= "
    <a ".is_active($itemId)." href='".$menu['items'][$itemId]['link']."'>".$menu['items'][$itemId]['label']."

    ";</code>

    that should add the class if the item is = to the current page id. If its not then the $p will be empty

  14. Razzer
    Posted July 26, 2010 at 1:43 pm | Permalink

    Hi there,

    Very nice work :)

    I’m trying to add a css class to the script, but I can’t make it work.
    The idea is that when you click on a specific menu point, it will be highlighted by inserting a class e.g. <a href="?p=1">Home</a>

    I’m using p=[id] instead of link (#home).
    Do you happens to have an idea on how I can build that function into the script?

    if (isset($_GET['p']) && $_GET['p'] != ”) {
    $p = $_GET['p'];
    } else {
    $p = 1;
    }

    function getSelected($v) {
    global $p;
    if ($p == $v) {
    echo ‘ class=”selected”‘;
    }
    }

    Cheers,
    Razzer

    • Starfox
      Posted November 17, 2011 at 8:11 am | Permalink

      Very nice Script,
      I trying to add the active class to the Script, like Danieliser have done it, but it doesn´t work.
      I don´t have an Page ID, i have a Page Variable with description like this: $page = news; and my Links of this Script are named same as the $page variabel.

      How do i make it work ?

      if (isset($_GET["page"])) {
      $page = $_GET["page"];
      }

      if (in_array($page, $menu['items'][$itemId]['link'])) { $activeclass="selected"; }

      $html .= "".$menu['items'][$itemId]['label'].""";

      How I make it work ?

      • Posted November 22, 2011 at 10:03 pm | Permalink

        Best thing you can do in this case is to try printing the variables using print_r(); Try `print_r($menu['items'][$itemId]['link']);` This allows you to debug issues.

One Trackback

  1. By Navigation Mysql Css Active Class Problem - php.de on November 29, 2011 at 9:53 am

    [...] die Runde da ich ja neu hier im Forum bin Ich habe mir ein PHP Navigationsscript von hier geholt: http://wizardinternetsolutions.com/w…ti-level-menu/ Dieses hab ich bereits ein wenig modifiziert damit es meinen Ansprüchen zurecht kommt. Jetzt [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Notify me of followup comments via e-mail. You can also subscribe without commenting.