2
votes

I am developing a content management system and I have run into an issue with child-parent relationships of menus in the CMS.

Basically I have a system that can create menus and sub menus. And here is the arrays in the database look like.

 array:6 [
       0 => array:4 [
        "id" => 4
        "name" => "Contacts"
        "order" => 0
        "parent_menu_id" => null
      ]
      1 => array:4 [
        "id" => 1
        "name" => "Leads"
        "order" => 1
        "parent_menu_id" => null
      ]
      2 => array:4 [
        "id" => 2
        "name" => "List Leads"
        "slug" => "list-leads"
        "order" => 1
        "parent_menu_id" => 1
      ]
      3 => array:4 [
        "id" => 5
        "name" => "Edit Leads"
        "slug" => "edit-leads"
        "order" => 1
        "parent_menu_id" => 1
      ]
      4 => array:4 [
        "id" => 3
        "name" => "Create New"
        "slug" => "new"
        "order" => 2
        "parent_menu_id" => 2
      ]
      5 => array:4 [
        "id" => 14
        "name" => "Tasks"
        "slug" => "tasks"
        "order" => 3
        "parent_menu_id" => null
      ]
    ]

I am inserting the menu and sub-menus using parent id in menus table. I am fetching the menus from db as above. So, how I will generate/render menus in hierarchical form like tree structure?

I need to loop through an array that can have any number of navigation and intelligently sort it into its parent child relationships. I was able to do it but only one level deep. It needs to manage children with children with children etc. with an infinite number of layers and output it to HTML dropdown-menu and dropdown-item nested lists.

I am creating a function and it just shows parents and it's child but not showing the children's for any child. How to do I fix it?

    public static function render_module_menu() {
          $menus = MenusModel::leftJoin('modules', 'menus.module_id', '=', 'modules.id')->where('modules.enable', 1)->select('menus.*')->orderBy('order')->get()->toArray();

          foreach ($menus as $menu) {
            $childMenus = MenusModel::where('parent_menu_id', $menu['id'])->orderBy('order')->get()->toArray();

            if ($childMenus) {
              echo '<li class="nav-item dropdown">
                      <a class="nav-link dropdown-toggle" href="#" id="pagesDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                        <i class="'.$menu['icon'].'"></i>
                        <span>'.$menu['name'].'</span>
                      </a>
                      <div class="dropdown-menu" aria-labelledby="pagesDropdown">';
              foreach ($childMenus as $child) {
                 echo '<a class="dropdown-item" href="'.url($menu['slug'].'/'.$child['slug']).'">
                          <i class="'.$child['icon'].'" style="margin-right: 10px;"></i>
                       '.$child['name'].'</a>';

              }
              echo '</div>
                    </li>';
            } else {
                echo '<li class="nav-item">
                        <a class="nav-link" href="'.url($menu['slug']).'">
                          <i class="'.$menu['icon'].'"></i>
                          <span>'.$menu['name'].'</span></a>
                      </li>';
            }

          }
        }
2

2 Answers

2
votes

1st function:

public static function GenerateMenuArray($arr, $parent = 0)
{
$pages = Array();
foreach($arr as $page)
{
if($page['parent_menu_id'] == $parent)
{
$page['sub'] = isset($page['sub']) ? $page['sub'] : self::GenerateMenuArray($arr, $page['id']);
$pages[] = $page;
}
}
return $pages;
}

2nd function:

// loop the multidimensional array recursively to generate the HTML
public static function GenerateMenuHTML($nav, $level=0)
{
$menus = MenusModel::leftJoin('modules', 'menus.module_id', '=', 'modules.id')->select('menus.*')->where('modules.enable', 1)->where('parent_menu_id', null)->orderBy('order')->get()->toArray();

$html = '';
foreach($nav as $page)
{
// dd($page);
if (empty($page['sub'])) {
if ($page['parent_menu_id'] == null) {
$html .= '<li class="nav-item">
<a class="nav-link" href="'.url($page['slug']).'">
<i class="'.$page['icon'].'"></i>
<span>'.$page['name'].'</span></a>
</li>';
} else {
foreach ($menus as $parent) {
$parent_slug = $parent['slug'];
if($parent['id'] == $page['parent_menu_id'] AND $page['external'] == 0) {
$html .= '<a class="dropdown-item" href="'.url($parent_slug.'/'.$page['slug']).'">
<i class="'.$page['icon'].'" style="margin-right: 10px;"></i>
'.$page['name'].'</a>';
}
}

if ($page['external'] == 1) {
$html .= '<a class="dropdown-item" href="'.url($page['slug']).'">
<i class="'.$page['icon'].'" style="margin-right: 10px;"></i>
'.$page['name'].'</a>';
}

}

} else {
if ($level > 0) {
$html .= '</div></li>';
}
$html .= '<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="pagesDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="'.$page['icon'].'"></i>
<span>'.$page['name'].'</span></a>
<div class="dropdown-menu" aria-labelledby="pagesDropdown">';


$level = ++$level;
$html .= self::GenerateMenuHTML($page['sub'], $level);
$level = --$level;

if ($level > 0) {
$html .= '</div></li>';
}
}
}
return $html;
}

// loop the multidimensional array recursively to generate the HTML
public static function GenerateNavHTML($nav)
{
$html = '';
foreach($nav as $page)
{
$html .= '<ul><li>';
$html .= '<a href="' . $page['slug'] . '">' . $page['name'] . '</a>';
$html .= self::GenerateNavHTML($page['sub']);
$html .= '</li></ul>';
}
return $html;
}

Please try this.

0
votes

You are only looping through your menu and its direct children.
Best solution here is to create a function that prints the children and then call the function inside itself, so that it will keep getting called as long as the current menu item has children.

function printChildren($children){
    foreach($children as $child){
        //(add code to print menu item)
        $moreChildren = //(add code to find children of this menu item)
        printChildren($moreChildren);
    }
}