Recursive Nightmare unordered list with a twist

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
afrogontop
Forum Newbie
Posts: 2
Joined: Mon Jun 08, 2009 7:00 am

Recursive Nightmare unordered list with a twist

Post by afrogontop »

Hi all,

I've had some luck producing recursive lists, but i seem to have pinned myself into a recursive corner I am having trouble altering elements/adding based on db fields. If anybody can point me in the right direction, i would ever so grateful.

I have a table called 'fms_parent' with the fields 'id, name, parent, header'

id is the id of the item- 0 being the root and they can stack, name is the name of the item, parent is the name of the id that the item associates with, and header is a boolean attribute that either displays the item as a LI item or as an <h3> item see below.

Sample Data
id name parent header
________________________________________________
1 Menu Item 1 0 1 (1 displays it as a header but with rules!)
2 Menu Item 2 0 1
3 Menu Item 3 0 1
4 Sub Item 1 2 1
5 Sub Item 2 2 0
6 Sub Item 3 2 0
7 Sub Sub Item 1 4 0
8 Sub Sub Item 2 4 0

The above should produce output like this:

Code: Select all

 
<h3>Menu Item 1</h3>
<h3>Menu Item 2</h3>
   <ul>
             <li>Sub Item 1
                    <ul>
                          <li>Sub Sub Item 1</li
                          <li>Sub Sub Item 2</li
                     </ul>
              </li>
              <li>Sub Item 2</li>
              <li>Sub Item 3</li>
    </ul>
   
<h3>Menu Item 3</h3>
 
So essentitally it is a recursive list with the following rules:

Items on the base level '0' flagged as headers show up as headers
Items flagged as headers not on 0 display as <li> elements but will create another <ul> item if they have children.

Anbody have any suggestions?

The below code is as close as i've come to, but still very far away.

Code: Select all

$parentid = 0; // assuming that 0 is the main category. 
 
get_sub_cats($parentid); 
 
function get_sub_cats($parentid) { 
 
$sql = "SELECT id, name, parent, header FROM fms_parent WHERE parent_id = ".$parentid.""; 
$run = mysql_query($sql); 
 
echo '<ul>'; 
 
while ($rec = mysql_fetch_assoc($run)) { 
 
echo '<li />'.$rec[name']; 
get_sub_cats($rec['id']); 
 
} 
 
echo '</ul>'; 
 
} 
 
Last edited by Benjamin on Mon Jun 08, 2009 10:29 am, edited 1 time in total.
Reason: Changed code type from text to php.
User avatar
mikemike
Forum Contributor
Posts: 355
Joined: Sun May 24, 2009 5:37 pm
Location: Chester, UK

Re: Recursive Nightmare unordered list with a twist

Post by mikemike »

I have this exact function thast I wrote some weeks ago in work. I'll have a look tomorrow and paste it if I get chance
User avatar
ornerybits
Forum Newbie
Posts: 18
Joined: Thu May 14, 2009 2:08 pm
Location: Kansas

Re: Recursive Nightmare unordered list with a twist

Post by ornerybits »

My suggestion would be to use an iterative (as opposed to recursive) function to build your tree structure. Then use a recursive function to build the output. Putting database queries in a recursive function should probably be avoided when possible, and in your case it is.

Code: Select all

 
// Hard code this to simulate a result set 
$results = array(
    array('id' => 1, 'name'=>'Menu Item 1', 'parent'=>0, 'header'=>1),
    array('id' => 2, 'name'=>'Menu Item 2', 'parent'=>0, 'header'=>1),
    array('id' => 3, 'name'=>'Menu Item 3', 'parent'=> 0, 'header'=> 1), 
    array('id' => 4, 'name'=>'Sub Item 1', 'parent'=> 2, 'header'=> 1), 
    array('id' => 5, 'name'=>'Sub Item 2', 'parent'=> 2, 'header'=> 0), 
    array('id' => 6, 'name'=>'Sub Item 3', 'parent'=> 2, 'header'=> 0), 
    array('id' => 7, 'name'=>'Sub Sub Item 1', 'parent'=> 4, 'header'=> 0), 
    array('id' => 8, 'name'=>'Sub Sub Item 2', 'parent'=> 4, 'header'=> 0)
);
 
/* The hardcoded array above is to simulate something similar to this
$r = mysql_query('SELECT * FROM menu');
foreach(mysql_fetch_assoc($r) as $result){
    $results[] = $result;
}
*/
 
 
/**
* This is an iterative (not recursive) function to build a tree structure from a set of records that have 'parent' ids
*/
function build_tree($records){
    $tree = array();
    foreach($records as &$rec){
        if (empty($rec['parent'])){
            $tree[$rec['id']] =& $rec;
        }   
        else{
            foreach($records as &$parent_rec){
                if ($rec['parent'] == $parent_rec['id']){
                    if (!is_array($parent_rec['children'])) {
                        $parent_rec['children'] = array();
                    }   
                    $parent_rec['children'][$rec['id']] =& $rec;
                }   
            }   
        }   
    }   
    return $tree;
}
 
$menu = build_tree($results);
echo '<pre>'; // formatting for browser
print_r($menu);
echo '</pre>'; // formatting for browser
 
Running the above code will output the following:

Code: Select all

 
Array
(
    [1] => Array
        (
            [id] => 1
            [name] => Menu Item 1
            [parent] => 0
            [header] => 1
        )
 
    [2] => Array
        (
            [id] => 2
            [name] => Menu Item 2
            [parent] => 0
            [header] => 1
            [children] => Array
                (
                    [4] => Array
                        (
                            [id] => 4
                            [name] => Sub Item 1
                            [parent] => 2
                            [header] => 1
                            [children] => Array
                                (
                                    [7] => Array
                                        (
                                            [id] => 7
                                            [name] => Sub Sub Item 1
                                            [parent] => 4
                                            [header] => 0
                                        )
 
                                    [8] => Array
                                        (
                                            [id] => 8
                                            [name] => Sub Sub Item 2
                                            [parent] => 4
                                            [header] => 0
                                        )
 
                                )
 
                        )
 
                    [5] => Array
                        (
                            [id] => 5
                            [name] => Sub Item 2
                            [parent] => 2
                            [header] => 0
                        )
 
                    [6] => Array
                        (
                            [id] => 6
                            [name] => Sub Item 3
                            [parent] => 2
                            [header] => 0
                        )
 
                )
 
        )
 
    [3] => Array
        (
            [id] => 3
            [name] => Menu Item 3
            [parent] => 0
            [header] => 1
        )
 
)
 
Then... you can use recursion to produce the output...

Code: Select all

 
function menu_output($menu_records){
    $output = '<ul>';
    foreach ($menu_records as $menu_rec){
 
        // Wrap menu items flagged as 'header'
        if (!empty($menu_rec['header'])){
            $menu_rec['name'] = '<h3>'.$menu_rec['name'].'</h3>';
        }
 
        // Output the name
        $output .= '<li>'.$menu_rec['name'].'</li>';
 
        // Recurse if we have children
        if (!empty($menu_rec['children'])){
            $output .= menu_output($menu_rec['children']);
        }
    }
    $output .= '</ul>';
    return $output;
}
 
echo menu_ouput($menu);
 
 
... the final echo statement in the code segment above will output:

Code: Select all

 
<ul>
   <li>
     <h3>Menu Item 1</h3>
   </li>
   <li>
      <h3>Menu Item 2</h3>
   </li>
 
    <ul>
         <li>
             <h3>Sub Item 1</h3>
         </li>
         <ul>
             <li>Sub Sub Item 1</li>
             <li>Sub Sub Item 2</li>
         </ul>
 
         <li>Sub Item 2</li>
         <li>Sub Item 3</li>
    </ul>
    <li>
         <h3>Menu Item 3</h3>
    </li>
</ul>
 
This differs from your original requirements a bit in that the whole thing is wrapped in <ul></ul>, but I would suggest putting it in there. It gives you a little more control over the style and is a pretty standard way of doing menus these days. Furthermore, what happens when you have a menu item without children that is not flagged as a 'header'?
Last edited by Benjamin on Mon Jun 08, 2009 1:10 pm, edited 1 time in total.
Reason: Changed code type from text to php.
afrogontop
Forum Newbie
Posts: 2
Joined: Mon Jun 08, 2009 7:00 am

Re: Recursive Nightmare unordered list with a twist

Post by afrogontop »

Thanks for the great start, unfortunately i'll need to format the lists as stated above as they are preformatted for this javascript:

http://www.dynamicdrive.com/dynamicinde ... bullet.htm

I'll tinker with what you posted and see if i can do it that way.

Cheers and thanks very much!

Jeff
User avatar
ornerybits
Forum Newbie
Posts: 18
Joined: Thu May 14, 2009 2:08 pm
Location: Kansas

Re: Recursive Nightmare unordered list with a twist

Post by ornerybits »

After working with it more, the function I gave you gets you close, but not all the way. It will add all of the root level nodes and build the part of the tree that falls under the first root level node, but all children of any remaining root level nodes will be lost.

This one stumps me, and I've posted a new thread here viewtopic.php?f=1&t=101476 for any input.
mischievous
Forum Commoner
Posts: 71
Joined: Sun Apr 19, 2009 8:59 pm

Re: Recursive Nightmare unordered list with a twist

Post by mischievous »

This is the code I run... and I know it can be cleaned up, just haven't had time to do it yet. Comments would be appreciated. This script is tested and working...

The function menu() gets passed a menu name that it searches for in the db and then searches for all menu items within that menu to build any menu given to it...

Code: Select all

function menu($menu)
{
    //uppercases first letter of menu request
    $request = ucfirst(strtolower($menu));
    //sets up initial mysql query to find menu requested.
    $query = "SELECT * FROM menus WHERE name = '$request'";
    $queryresult = $DBC->db->query($query);
    $menuid = 0;
    //finds the menu requested and pulls the id of the menu along with menu id
    foreach ($queryresult->result() as $row)
    {
        $menuid = $row->id;
        $menuHtml = "<ul class='".$row->css_class."'>";
    }
    //sets up second mysql query to find associated menu items
    $query2 = "SELECT * FROM menu_items WHERE menu_id = $menuid ORDER BY 'order'";
    $queryresult2 = $DBC->db->query($query2);
    //finds all menu items and stores in menu html
    foreach($queryresult2->result() as $menu)
        {
            if($menu->has_sub == 1 & $menu->parent < 1){
                //displays category li
                $menuHtml .= "<li class='mw_menu_cat'><a href='".$menu->href."'>".$menu->name."</a>";
                $menuHtml .= "<ul>";
                //sets up query for getting sub menus of category parent
                $query3 = "SELECT * FROM menu_items WHERE parent = ? ORDER BY 'order'";
                $queryresult3 = $DBC->db->query($query3, array($menu->id));
                //Runs through and displays all child li's
                foreach($queryresult3->result() as $submenu){
                    if($submenu->has_sub == 1)
                    {
                        //displays parent sub-menu
                        $menuHtml .= "<li class='mw_menu_subcat'><a href='".$submenu->href."'>".$submenu->name."</a>";
                        $menuHtml .= "<ul>";
                        //sets up mysql query to find all submenu of submenu
                        $query4 = "SELECT * FROM menu_items WHERE parent = ? ORDER BY 'order'";
                        $queryresult4 = $DBC->db->query($query4, array($submenu->id));
                        //runs loops through each result for sub-submenu
                        foreach($queryresult4->result() as $sub_submenu){
                            if($sub_submenu->has_sub == 1)
                            {
                                //displays parent sub_sub-menu
                                $menuHtml .= "<li class='mw_menu_sub_subcat'><a href='".$sub_submenu->href."'>".$sub_submenu->name."</a>";
                                $menuHtml .= "<ul>";
                                //sets up mysql query to find all submenu of submenu
                                $query5 = "SELECT * FROM menu_items WHERE parent = ? ORDER BY 'order'";
                                $queryresult5 = $DBC->db->query($query4, array($sub_submenu->id));
                                foreach($queryresult5->result() as $sub_sub_submenu){
                                    $menuHtml .= "<li><a href='".$sub_sub_submenu->href."'>".$sub_sub_submenu->name."</a></li>";
                                }
                                $menuHtml .= "</ul></li>";
                            } else {
                                $menuHtml .= "<li><a href='".$sub_submenu->href."'>".$sub_submenu->name."</a></li>";
                            }
                        }
                        $menuHtml .= "</ul></li>";
                    } else {
                        //display submenu children 
                        $menuHtml .= "<li><a href='".$submenu->href."'>".$submenu->name."</a></li>";
                    }
                }
                $menuHtml .= "</ul></li>";
            } else if($menu->parent == 0) {
                $menuHtml .= "<li><a href='".$menu->href."'>".$menu->name."</a></li>";
            }
        }
        $menuHtml .= "</ul>";
        return $menuHtml;
}
Post Reply