BobbyP
BobbyP

Reputation: 2245

How to generate nested navigation menu from database

I have the pages of my website stored in a database. Using Laravel (although that isn't really important for my question), i can output the hierarchy of the site and see the following. (I have the flexibility to change the database structure and add more columns if required)

[
    {
        "ref_id": "1",
        "parent_id": "0",
        "name": "Item 1",
        "child_count": 0
    },
    {
        "ref_id": "2",
        "parent_id": "0",
        "name": "Item 2",
        "child_count": 2
    },
    {
        "ref_id": "3",
        "parent_id": "2",
        "name": "Item 2 Sub 1",
        "child_count": 0
    },
    {
        "ref_id": "4",
        "parent_id": "2",
        "name": "Item 2 Sub 2",
        "child_count": 0
    },
    {
        "ref_id": "5",
        "parent_id": "0",
        "name": "Item 3",
        "child_count": 2
    },
    {
        "ref_id": "6",
        "parent_id": "5",
        "name": "Item 3 Sub 1",
        "child_count": 0
    },
    {
        "ref_id": "7",
        "parent_id": "5",
        "name": "Item 3 Sub 2",
        "child_count": 0
    },
    {
        "ref_id": "12",
        "parent_id": "0",
        "name": "Item 4",
        "child_count": 0
    },
    {
        "ref_id": "13",
        "parent_id": "0",
        "name": "Item 5",
        "child_count": 3
    },
    {
        "ref_id": "14",
        "parent_id": "13",
        "name": "Item 5 Sub 1",
        "child_count": 0
    },
    {
        "ref_id": "15",
        "parent_id": "13",
        "name": "Item 5 Sub 2",
        "child_count": 2
    },
    {
        "ref_id": "16",
        "parent_id": "15",
        "name": "Item 5 Sub 2 SubSub 1",
        "child_count": 0
    },
    {
        "ref_id": "17",
        "parent_id": "15",
        "name": "Item 5 Sub 2 SubSub 2",
        "child_count": 0
    },
    {
        "ref_id": "18",
        "parent_id": "13",
        "name": "Item 5 Sub 3",
        "child_count": 0
    },
    {
        "ref_id": "19",
        "parent_id": "0",
        "name": "Item 6",
        "child_count": 0
    }
]

I need to iterate through these nodes and output a nested UL navigation that looks like the following. The UL's could be nested several layers deep so the code must be dynamic.

<ul>
    <li>Item 1</li>
    <li>Item 2
        <ul>
            <li>Item 2 Sub 1</li>
            <li>Item 2 Sub 2</li>
        </ul>
    </li> 
    <li>Item 3
        <ul>
            <li>Item 3 Sub 1</li>
            <li>Item 3 Sub 2</li>
        </ul>
    </li> 
    <li>Item 4</li>  
    <li>Item 5
        <ul>
            <li>Item 5 Sub 1</li>
            <li>Item 5 Sub 2
                <ul>
                    <li>Item 5 Sub 2 SubSub 1</li>
                    <li>Item 5 Sub 2 SubSub 2</li>
                </ul>
            </li>
            <li>Item 5 Sub 3</li>
        </ul>
    </li>   
    <li>Item 6</li>
</ul>  

What I have done so far is pretty close. This is as good as it gets at the moment. This is Laravel's blade syntax but it is the logic I am after so this applies to vanilla PHP as well.

<ul>
    @php
        $child_count = 0;
        $total_children = 0;
    @endphp
    @foreach($q_list as $row)
        @if($total_children)
            @php $child_count++; @endphp
        @endif
        <li>{{ $row->name }}
            @if($row->child_count)
                @php
                    $total_children = $row->child_count;
                    $child_count = 0;
                @endphp
                <ul>
            @elseif($total_children == $child_count && $total_children != 0)
                @php
                    $total_children = 0;
                    $child_count = 0;
                @endphp
                </ul>
            @endif
        @if($total_children == $child_count || $row->child_count == 0)
            </li>
        @endif
    @endforeach
</ul>

This results in:

<ul>
    <li>Item 1</li>
    <li>Item 2
        <ul>
            <li>Item 2 Sub 1</li>
            <li>Item 2 Sub 2
        </ul>
    </li>
    <li>Item 3
        <ul>
            <li>Item 3 Sub 1</li>
            <li>Item 3 Sub 2
        </ul>
    </li>
    <li>Item 4</li>
    <li>Item 5
        <ul>
            <li>Item 5 Sub 1</li>
            <li>Item 5 Sub 2
                <ul>
                    <li>Item 5 Sub 2 SubSub 1</li>
                    <li>Item 5 Sub 2 SubSub 2
                </ul>
            </li>
            <li>Item 5 Sub 3</li>
            <li>Item 6</li>
</ol>

I think the script is very messy, but more importantly, there are two problems here. Firstly, the last item of each nested UL has the closing </li> omitted. Secondly, each nested <ul> is only closed once. That is fine if the nested <ul> is only at level two, but any greater (like Item 5 Sub 2 SubSub *) doesn't have enough closing <li></ul> tags resulting in Item 6 being at the wrong level. (i.e. is still a child of Item 5)

Can anyone help fill in the blanks or let me know of a better way of achieving this. Thanks

Upvotes: 0

Views: 1172

Answers (1)

Bishnu Bhusal
Bishnu Bhusal

Reputation: 1098

You can implement recursion Logic.

Create a helper

function generate_tree($categories)
{
    foreach ($categories as $category) {
        echo '<li id="categoryId_' . $category->id . '">';
        echo $category->name;
        if ($category->children) {
            echo '<ul>';
            generate_tree($category->children);
            echo '</ul>';
        }
        echo '</li>';
    }
}

On view

<ul>
   {!! generate_tree($categories) !!}
</ul>

Upvotes: 1

Related Questions