8

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>
samjudson
  • 56,243
  • 7
  • 59
  • 69
Starx
  • 77,474
  • 47
  • 185
  • 261

8 Answers8

22

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.

ULazdins
  • 1,975
  • 4
  • 25
  • 31
J. Bruni
  • 20,322
  • 12
  • 75
  • 92
  • 1
    @J.Bruni What is `['value']` in `$option['value']['id']`? In sql query does not see `['value']`. Where it is defined? – Andris Jul 03 '15 at 12:19
  • 1
    @user2118559 - the PHP function `each` returns an array containing the `value` key - see http://php.net/manual/en/function.each.php – J. Bruni Jul 06 '15 at 22:12
  • Very good code, thanks! Seems inside `if ( !empty( $children[$option['value']['id']] ) )` must remove ending `li`? Like `$html .= '
  • ' . $option['value']['title'] ;` And at `3) Current parent has no more children:` to insert ending `li`, like `$html .= '
  • ';` – Andris Jul 08 '15 at 03:38
  • @user2118559 - not necessarily; is is fine to close the list-item (`li`), because it will also open a nested unordered list (`ul`) – J. Bruni Jul 22 '15 at 17:54