Page 1 of 1

Recursive Nightmare unordered list with a twist

Posted: Mon Jun 08, 2009 7:35 am
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>'; 
 
} 
 

Re: Recursive Nightmare unordered list with a twist

Posted: Mon Jun 08, 2009 11:03 am
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

Re: Recursive Nightmare unordered list with a twist

Posted: Mon Jun 08, 2009 12:13 pm
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'?

Re: Recursive Nightmare unordered list with a twist

Posted: Mon Jun 08, 2009 12:41 pm
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

Re: Recursive Nightmare unordered list with a twist

Posted: Wed Jun 10, 2009 10:54 am
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.

Re: Recursive Nightmare unordered list with a twist

Posted: Wed Jun 10, 2009 12:22 pm
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;
}