views:

868

answers:

6

Well, to build my menu my menu I use a db similar structure like this Check this 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>
+4  A: 

You need to use recursive functions for this. Technically, there's a few ways to do it, but recursion is really the best option here.

Here's the basic gist of how it would work:

function drawMenu ($listOfItems) {
    echo "<ul>";
    foreach ($listOfItems as $item) {
        echo "<li>" . $item->name;
        if ($item->hasChildren()) {
            drawMenu($item->getChildren()); // here is the recursion
        }
        echo "</li>";
    }
    echo "</ul>";
}

The properties and methods of $item are just examples, and I'll leave it up to you to implement these however you need to, but I think it gets the message across.

nickf
While your approach is valid, this amount of recursion may result in a very large number of database hits (perhaps one for each call to hasChildren, and another for getChildren). If these were items stored in memory, no problem, but to hit the database each time is going to slow things down.
Kazar
@Kazar - well that depends entirely on the implementation. You could do it all in one query and then parse the results into a tree, or look at using Nested Sets http://dev.mysql.com/tech-resources/articles/hierarchical-data.html - though, since this is a menu, it's unlikely to have that many levels of recursion that it's worth worrying about.
nickf
where's the meat ?
f00
+1  A: 

I would suggest that you look into pre-ordered tree traversal. There is an article on the issue at:

Managing Hierarchical Data in MySQL

Effectively, you take each page as a 'node'. Each node has a reference to it's parent. When you change the layout of the nodes (add a child, move nodes, etc), you recalculate a 'left' and 'right' value for each node (the article above explains this in great detail, with links to source code in php). What you end up with is the ability to very quickly determine if a given node is a direct or indirect child of any other node, as well as get all the child nodes of a given node.

Kazar
A: 

i would use a recursive function.

i know this isn't exactly like your code, but I think you can get the general concept if you understand recursion. if you don't understand recursion check out http://en.wikipedia.org/wiki/Recursion_(computer_science)

$list = new List();

function print_menu($list) {

    echo '<ul>';
    foreach($list as $item) {
        echo '<li><a href="#">' . $item->name . '</a>';
        if($item->has_child) {
            print_menu($item);
        }
        echo '</li>';
    }
    echo '</ul>';
}
jordanstephens
where's the meat ?
f00
A: 

alt text http://pastie.org/969286

f00
+1  A: 

Hi, Starx,

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:

  1. menu wrapper opening (line 83)
  2. menu wrapper closing (line 122)
  3. menu item with childs opening (line 100)
  4. menu item with childs closing (line 92)
  5. 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.

Thanks.

J. Bruni
nice one J. Bruni, real sleek. however I do believe that's a lot of coding than just using the plain recursion
Starx
@Starx: Thanks. But you got the wrong impression, because the code I linked is not merely a snippet, but a "fully featured", working solution (OOP, connects database, etc). I will add another answer, containing a "user-friendly" snippet. (A simplified version, easier to understand, where I keep the important "core" and remove the "extras".)
J. Bruni
+1  A: 

Here is a "developer-friendly" version of the "one query, no recursion" solution.

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.

J. Bruni