Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CodeIgniter create n-level deep navigation

I'd like some help please. I have created dynamic a menu navbar that displays the menu items accoridning to the order that I've set them. I'm using this nestedsortable plugin, to order my menu items, but currently my menu has only 2 levels, so basicly it goes like this:

Item1
Item2
 > Subitem2.1
 > Subitem2.2
Item3
etc etc.

What I'd like to do is make it with n-levels, so basicly something like this:

Item1
Item2
  > Subitem2.1
    >> Subitem2.1.1
  > Subitem2.2
Item3
etc etc.

and each item can go n-level deep. The problem is that if I set a new order to my menu items that is more than 2 levels deep I get an error and the order is not stored in the database. How can I fix this please ???

The database structure is this:

table: Menu
id (pk)
menu_item
parent_id // it is the id of the parent menu item
order

Here are my main (model) functions:

// save the order of the menu items
public function save_order($items){
    if (count($items)>0) {
        foreach ($items as $order => $item) {
            if ($item['item_id'] != '') {

                $data = array(
                    'parent_id' => (int)$item['parent_id'], 
                    'order'     => $order
                );

                $this->db->set($data)
                ->where($this->_primary_key, $item['item_id'])
                ->update($this->_table_name);
            }
        }
    }
}

// fetch the menu items (parents & children) from the last order set
public function get_menu(){

    $this->db->select('id, menu_item, parent_id');
    $this->db->order_by('parent_id, order');
    $menu_items = $this->db->get('menu')->result_array();

    $arr = array();
    foreach ($menu_items as $item) {

        // the item has no parent
        if (!$item['parent_id']) {
            $arr[$item['id']] = $item; // e.g. $arr(4 => array())
        } // the item is a child
        else {
            // e.g. $arr(4 => array('children' => array()))
            $arr[$item['parent_id']]['children'][] = $item;
        }
    }
    return $arr;
 } 

Update

For additional help: I did a test and dumped the array of the items on the screen in both cases:

1st case: with 2 levels (as it is currently): I set the items with this order

  • Item1
  • Item2
    • Item4
  • Item3
  • Item5

and the result looks like this, as expected:

Array
(
    [1] => Array
        (
            [id] => 1
            [menu_item] => Item1
            [parent_id] => 0
        )

    [2] => Array
        (
            [id] => 2
            [menu_item] => Item2
            [parent_id] => 0
            [children] => Array
                (
                    [0] => Array
                        (
                            [id] => 4
                            [menu_item] => Item4
                            [parent_id] => 2
                        )

                )

        )

    [3] => Array
        (
            [id] => 3
            [menu_item] => Item3
            [parent_id] => 0
        )

    [5] => Array
        (
            [id] => 5
            [menu_item] => Item5
            [parent_id] => 0
        )

)

2nd case: with n-levels: I tried to set the menu items with this order:

  • Item1
  • Item2
    • Item5
      • Item4
  • Item3

and the result looks like this:

Array
(
    [1] => Array
        (
            [id] => 1
            [menu_item] => Item1 
            [parent_id] => 0
        )

    [2] => Array
        (
            [id] => 2
            [menu_item] => Item2
            [parent_id] => 0
            [children] => Array
                (
                    [0] => Array
                        (
                            [id] => 5
                            [menu_item] => Item5
                            [parent_id] => 2
                        )

                )

        )

    [3] => Array
        (
            [id] => 3
            [menu_item] => Item3
            [parent_id] => 0
        )

    [4] => Array
        (
            [children] => Array
                (
                    [0] => Array
                        (
                            [id] => 4
                            [menu_item] => Item4
                            [parent_id] => 4
                        )

                )

        )

)

This is the case where I get the error and not working. The errors I get are:

Message: Undefined index: page_id Message: Undefined index: menu_item

in my view file:

function nav($menu_items, $child = false){
    $output = '';

    if (count($array)) {
        $output .= ($child === false) ? '<ol class="sortable">' : '<ol>' ;

        foreach ($menu_items as $item) {
            $output .= '<li id="list_' . $item['id'] . '">'; // here is the line of the 1st error
            $output .= '<div>' . $item['menu_item'] . '</div>'; // 2nd error

            //check if there are any children
            if (isset($item['children']) && count($item['children'])) {
                $output .= nav($item['children'], true);
            }
            $output .= '</li>';
        }
        $output .= '</ol>';
    }
    return $output;
}


echo nav($menu_items); 
like image 982
ltdev Avatar asked Nov 02 '22 05:11

ltdev


1 Answers

Considering the database output, it seems the items are stored in the database correctly. And the problem belongs to the get_menu() method and its algorithm to create the output.

In order to create a n-level deep menu, you should iterate through the items recursively.

Here we go:

function prepareList(array $items, $pid = 0)
{
    $output = array();

    # loop through the items
    foreach ($items as $item) {

        # Whether the parent_id of the item matches the current $pid
        if ((int) $item['parent_id'] == $pid) {

            # Call the function recursively, use the item's id as the parent's id
            # The function returns the list of children or an empty array()
            if ($children = prepareList($items, $item['id'])) {

                # Store all children of the current item
                $item['children'] = $children;
            }

            # Fill the output
            $output[] = $item;
        }
    }

    return $output;
}

You could use the above logic as a helper function (in CodeIgniter) or a private method within your Controller class.

Then call that function/method inside get_menu() method as follows:

public function get_menu()
{
    $this->db->select('id, menu_item, parent_id');
    $this->db->order_by('parent_id, order');
    $menu_items = $this->db->get('menu')->result_array();

    return prepareList($menu_items);
}

Note: I used the prepareList() as a helper (global) function. If you decide to use that as a private method, you should replace the function name by $this->prepareList() everywhere (even inside the function itself).

Here is the Online Demo.

like image 99
Hashem Qolami Avatar answered Nov 09 '22 06:11

Hashem Qolami