Scott Bowers
Scott Bowers

Reputation: 175

Build recursive nested menu from array

I'm trying to build a recursive menu which would look something like this when finished, with an infinite amount of nested items:

Homepage
Services
  Members
  Brokers
  Insurers
Products

My data is being linked together by a simple parent_id column in my table.

I have currently managed to grab all the pages in the table and add them into an array based on their parent_id (see below).

Array
(
    [0] => 0-0-1 0-1-2 0-2-12 0-3-27 0-4-28 0-5-29 0-6-30 0-7-31 0-8-33 
    [2] => 2-0-3 2-0-6 2-0-7 2-0-8 
    [3] => 3-0-4 3-0-5 
    [8] => 8-0-9 8-0-10 
    [12] => 12-0-13 12-0-20 
    [13] => 13-0-14 13-1-15 13-2-17 13-3-16 13-4-19 13-5-18 13-9-34 
    [20] => 20-0-21 20-1-22 20-2-24 20-3-23 20-4-26 20-5-25 20-6-11 
)

This is formatted as [parent_id] => parent_id-sort_order-id parent_id-sort_order-id and so on.

My code so far is as follows:

$sqlGetDropdownPages = "SELECT * FROM `cms_staticPages` ORDER BY `sortOrder` ASC";
$qryGetDropdownPages = mysql_query($sqlGetDropdownPages) or die(mysql_error());
$resGetDropdownPages = mysql_fetch_assoc($qryGetDropdownPages);
$totGetDropdownPages = mysql_num_rows($qryGetDropdownPages);

do {    

    $pageArray[$resGetDropdownPages['parent_id']] .= $resGetDropdownPages['parent_id'].'-'.$resGetDropdownPages['sortOrder'].'-'.$resGetDropdownPages['id'].' ';

} while($resGetDropdownPages = mysql_fetch_assoc($qryGetDropdownPages));

ksort($pageArray);

foreach($pageArray as $i => $value) {

    $explodePages = explode(' ',$value);

    foreach(array_filter($explodePages) as $i2 => $page) {

        $pageInfoExplode = explode('-',$page);

        $getPageInfo = mysql_fetch_assoc(mysql_query("SELECT * FROM `cms_staticPages` WHERE `id` = '".$pageInfoExplode[2]."'"));

        echo $getPageInfo['identifier'].'<br />';

    }

}

Ran into a brick wall at this point and can't figure out what to do next.

I tried to explain this as well as I possibly can so if any clarification is needed just ask.

How to achieve this?

Upvotes: 2

Views: 3431

Answers (3)

Yasin UYSAL
Yasin UYSAL

Reputation: 619

Suppose that after extracting all menu items from the database, it outputs an output as follows.

$items = [
[
    'id' => '1',
    'parent_id' => '0',
    'title' => 'Menu 1',
],
[
    'id' => '2',
    'parent_id' => '0',
    'title' => 'Menu 2',
],
[
    'id' => '3',
    'parent_id' => '2',
    'title' => 'Menu 2 1',
],
[
    'id' => '4',
    'parent_id' => '0',
    'title' => 'Menu 3',
],
[
    'id' => '5',
    'parent_id' => '4',
    'title' => 'Menu 3 1',
],
[
    'id' => '6',
    'parent_id' => '5',
    'title' => 'Menu 3 1 1',
],
[
    'id' => '7',
    'parent_id' => '5',
    'title' => 'Menu 3 1 2',
],
[
    'id' => '8',
    'parent_id' => '7',
    'title' => 'Menu 3 1 2 1',
],
[
    'id' => '9',
    'parent_id' => '4',
    'title' => 'Menu 3 2',
],
[
    'id' => '10',
    'parent_id' => '0',
    'title' => 'Menu 4',
]];

Let's write a function to process the data in Array format and return html

/**
 * Recursive and prints the elements of the given parent id.
 * @param $items
 * @param string $parentId
 */
function buildNestedItems($items, $parentId = "0", $wrapperTag = 'ul', $itemTag = 'li')
{
    // Parent items control
    $isParentItem = false;
    foreach ($items as $item) {
        if ($item['parent_id'] === $parentId) {
            $isParentItem = true;
            break;
        }
    }

    // Prepare items
    $html = "";
    if ($isParentItem) {
        $html .= "<$wrapperTag>";
        foreach ($items as $item) {
            if ($item['parent_id'] === $parentId) {
                $html .= "<$itemTag>" . $item['title'] . "</$itemTag>";
                $html .= buildNestedItems($items, $item['id']);
            }
        }
        $html .= "</$wrapperTag>";
    }
    return $html;
}

Let's print the prepared html i page

<div class="menu">
    <?php echo buildNestedItems($items); ?>
</div>

Upvotes: 2

Jopie
Jopie

Reputation: 332

This is my example code to build nested menu for Zurb Foundation, using recursive function in PHP. Very simple and clean.

// call the function
build_menu();

//----------------------------

// recursive function
function build_menu($parent = "", $tab=""){
    $sql = "SELECT * FROM cat WHERE parent='$parent' ORDER BY category ASC";
    $q = mysql_query($sql);
    if (mysql_num_rows($q)) {
        if (empty($parent)):
            print $tab."<ul>\n";
        else:
            print $tab."<ul class='dropdown'>\n";
        endif;
        while($d = mysql_fetch_object($q)) {
            $has_child = mysql_num_rows(mysql_query("select * from cat where parent='$d->code'") );
            $tag = ($has_child ? " class='has-dropdown'" : "");
            $nl  = ($has_child ? "\n" : "");

            print $tab."\t"."<li".$tag."><a href='cat.php?cat=$d->code'>$d->category</a>$nl";

            if ($has_child) build_menu($d->code, $tab."\t");

            print $tab."</li>\n";
        }
        print $tab."</ul>\n";
    }
}

Upvotes: 1

Weltschmerz
Weltschmerz

Reputation: 2186

I would index all the menu items (pages) by id first. In the second loop associate menu items with their parents and keep track of top level items.

One of the ways I would do it (Render method is just an example):

$sqlGetDropdownPages = "SELECT * FROM `cms_staticPages` ORDER BY `sortOrder` ASC";
$qryGetDropdownPages = mysql_query($sqlGetDropdownPages) or die(mysql_error());

//indexing pages by id
while ($pageRow = mysql_fetch_assoc($qryGetDropdownPages)) {
    $menuItems[$pageRow['id']] = new MenuItem($pageRow);
}

//associating pages with parents
foreach ($menuItems as $menuItem) {
    if ($menuItem->hasParent()) {
        $menuItem->addToParent($menuItems);
    } else {
        $topLevel[] = $menuItem;
    }
}

//$topLevel is an array of menuItems without parents
$render = '<ul>';
foreach ($topLevel as $menuItem) {
    $render .= $menuItem->render(true);
}
$render .= '</ul>';

MenuItem class would look something like this

class MenuItem {

    /**
     * @var array
     */
    private $data;

    /**
     * @var MenuItem[]
     */
    private $children = array();

    /**
     * @var MenuItem
     */
    private $parent;

    /**
     * @param array $data
     */
    public function __construct($data) {
        $this->data = $data;
    }

    /**
     * Finds the parent in the collection and adds itself to it
     * @param MenuItem[] $menuItems
     */
    public function addToParent($menuItems) {
        if (isset($menuItems[$this->data['parent_id']])) {
            $this->parent = $menuItems[$this->data['parent_id']];
            $this->parent->addChild($this);
        }
    }

    /**
     * @return bool
     */
    public function hasParent() {
        return empty($this->data['parent_id']);
    }

    /**
     * @param MenuItem $child
     */
    public function addChild(MenuItem $child) {
        $this->children[] = $child;
    }

    /**
     * Renders the menu with ul li
     * @param bool $withChildren
     * @return string
     */
    public function render($withChildren = false) {
        $render = '<li>' . $this->data['identifier'] . '</li>';

        if ($withChildren) {
            $render .= '<ul>';
            foreach ($this->children as $child) {
                $render .= $child->render(true);
            }
            $render .= '</ul>';
        }

        return $render;
    }

}

Upvotes: 1

Related Questions