6

I'm trying to create a Mega Menu using PHP and I'm having a problem getting the structure to output correctly. I've hard-coded the Mega Menu to test everything and it works fine, but obviously I need PHP to create it for me.

I have an example with the Mega Menu hard-coded so everyone can see what I'm trying to create:

http://www.libertyeaglearms.com/dev

Or here's the code:

DESIRED OUTPUT:

    <div id="wrapper">
        <ul class="mega-menu">
            <li class="mega-menu-drop">
                <a href="#">Firearms</a>
                 <div class="mega-menu-content">
                     <div class="column">
                         <h4>Rifles</h4>
                          <ul>
                              <li><a href="#">One</a></li>
                              <li><a href="#">Two</a></li>
                              <li><a href="#">Three</a></li>
                              <li><a href="#">Four</a></li>
                              <li><a href="#">Five</a></li>
                          </ul>
                      </div>
                      <div class="column">
                          <h4>Handguns</h4>
                          <ul>
                              <li><a href="#">One</a></li>
                              <li><a href="#">Two</a></li>
                              <li><a href="#">Three</a></li>
                              <li><a href="#">Four</a></li>
                              <li><a href="#">Five</a></li>
                          </ul>
                      </div>
                      <div class="column">
                          <h4>Shotguns</h4>
                          <ul>
                              <li><a href="#">One</a></li>
                              <li><a href="#">Two</a></li>
                              <li><a href="#">Three</a></li>
                              <li><a href="#">Four</a></li>
                              <li><a href="#">Five</a></li>
                          </ul>
                      </div>
                  </div>
              </li>
              <li class="mega-menu-drop">
                  <a href="#">Archery</a>
                  <div class="mega-menu-content">
                      <div class="column">
                          <h4>Bows</h4>
                          <ul>
                              <li><a href="#">One</a></li>
                              <li><a href="#">Two</a></li>
                              <li><a href="#">Three</a></li>
                              <li><a href="#">Four</a></li>
                              <li><a href="#">Five</a></li>
                          </ul>
                      </div> 
                      <div class="column">
                          <h4>Arrows</h4>
                          <ul>
                              <li><a href="#">One</a></li>
                              <li><a href="#">Two</a></li>
                              <li><a href="#">Three</a></li>
                              <li><a href="#">Four</a></li>
                              <li><a href="#">Five</a></li>
                          </ul>
                      </div>
                  </div>
              </li> 
          </ul>
      </div>

CURRENT OUTPUT: (very messed up. be warned, lol)

<div id="wrapper">
    <ul class="mega-menu">
    <li class="mega-menu-drop">
            <a href="#">archery</a>
            <div class="mega-menu-content">
                <div class="column">
                    <h4>compound</h4>
            <ul>
            <h4>bows</h4>
            <ul>
            </div>
        </li>
        <li class="mega-menu-drop">
            <a href="#">firearms</a>
            <div class="mega-menu-content">
                <div class="column">
                    <h4>rifle ammunition</h4>
                    <ul>
            <h4>ammunition</h4>
                    <ul>
            </div>
        </li>
            </ul>  
        </div>

HERE MY PHP:

$json = json_decode($category->buildDepartments(NULL),true);
function buildDepartments($array,$parent)
{
    $html = '';                 
    foreach($array as $category)
    {
        if($category['parent'] == $parent)
        {
             if($category['parent'] == NULL)
             {
                 $html .= '<li class="mega-menu-drop">' . "\n\t\t\t\t";
                 $html .= '<a href="#">'.$category['category_name'].'</a>' . "\n\t\t\t\t";
                 $html .= '<div class="mega-menu-content">' . "\n\t\t\t\t\t";                            
                 $html .= '<div class="column">' . "\n\t\t\t\t\t\t";
                 $html .= buildDepartments($array,$category['category_id']);                            
                 $html .= '</div>' . "\n\t\t\t";
                 $html .= '</li>' . "\n\t\t\t";
              }
              else
              {
                  $html .= buildDepartments($array,$category['category_id']);
                  $html .= '<h4>'.$category['category_name'].'</h4>' . "\n\t\t\t\t\t\t";
                  $html .= '<ul>' . "\n\t\t\t\t";

               }                        
          }
    }
    return $html;
}
print(buildDepartments($json,NULL));

HERE'S MY DATABSE:

enter image description here

EDIT AFTER BOUNTY

Building off what icktoofay suggested, I cannot seem to figure out the foreach() loops. The problem I'm getting is I get the department name inside the mega menu when I should see category and sub categories. I think the problem is I need to perform a loop with a specific parent id in order to get all the children, but I'm not really sure if that's the problem. Here's the code:

<div id="wrapper">
<ul class="mega-menu">         
    <?php $json = json_decode($category->buildDepartments(NULL)); ?>
    <?php foreach($json as $category): ?>
        <li class="mega-menu-drop">
            <a href="#"><?php if($category->parent === NULL){echo $category->category_name;} ?></a>
            <div class="mega-menu-content">                 
                <?php foreach($category as $subcategory): ?>
                <div class="column">
                    <?php if($category->category_id === $category->parent){echo '<h4>' . $category->category_name . '</h4>';}?>
                    <ul>
                        <?php foreach($category as $subcategory)
                        {
                            echo '<li>' . $category->category_name . '</li>';
                        }
                        ?>
                    </ul>
                </div>
                <?php endforeach; ?>                   
            </div>
        </li>
     <?php endforeach; ?>  
</ul>  
</div>
Mike
  • 1,760
  • 5
  • 21
  • 40
  • I have such a script somewhere but it's all with Albanian language comments IIRC. – hakre Apr 07 '13 at 22:33
  • I don't mind the Albanian comments if you wouldn't mind sharing. – Mike Apr 07 '13 at 22:36
  • Those were actually joking about the militaria analogy your question has, however, we have exisitng Q&A material about the parent child relation-ship you have here for your categories. As an entry question I would suggest the following as the reference question for the topic: [How can I convert a series of parent-child relationships into a hierarchical tree?](http://stackoverflow.com/q/2915748/367456). Watch for related links in that Q&A, they cover querying form Mysql as well as turning in HTML in great detail. And all in plain English ;) – hakre Apr 07 '13 at 22:54
  • Awesome link! Thank you for the information, very helpful. It took me a while, but I got the Albanian reference ;-) Great! Now I'm gonna be on some "big brother" watch list, lol. – Mike Apr 07 '13 at 23:12

3 Answers3

8

Since it's a fixed depth and the format for each level is different, recursion may not be appropriate. Additionally, putting everything in strings is inelegant. Instead, you may want to try weaving the HTML and loops together like this:

<div id="wrapper">
    <ul class="mega-menu">
        <?php foreach($categories as $category): ?>
            <li class="mega-menu-drop">
                <a href="#"><?php echo $category->name; ?></a>
                <div class="mega-menu-content">
                    <?php foreach($category->subcategories as $subcategory): ?>
                        <!-- and so on -->
                    <?php endforeach; ?>
                </div>
            </li>
        <?php endforeach; ?>
    </ul>
</div>

The code I've used here assumes you've already got it from a sort-of-flat-array to a tree-like structure. For example, if we've got a Category class:

class Category {
    public $id;
    public $parentID;
    public $name;
    public $parent;
    public $subcategories;

    public function __construct($id, $parentID, $name) {
        $this->id = $id;
        $this->parentID = $parentID;
        $this->name = $name;
        $this->parent = null;  // will be filled in later
        $this->subcategories = array();  // will be filled in later
    }
}

And an array of associative arrays like you might get from a database call (which we'll call $flatCategories), we can build a bunch of not-yet-connected Category instances like this:

$categories = array();
foreach($flatCategories as $flatCategory) {
    $categories[$flatCategory['id']] =
        new Category($flatCategory['category_id'],
                     $flatCategory['parent'],
                     $flatCategory['category_name']);
}

Then we can connect them all up into a hierarchal structure:

foreach($categories as $category) {
    if($category->parentID !== null) {
        $category->parent = $categories[$category->parentID];
        $category->parent->subcategories[] = $category;
    }
}

Now they're all linked up and we only care about keeping references to the roots:

$roots = array();
// This could, of course, be merged into the last loop,
// but I didn't for clarity.
foreach($categories as $category) {
    if($category->parentID === null) {
        $roots[] = $category;
    }
}

Now $roots contains all the root categories, and it's fairly simple to traverse. In my example at the top, I was assuming $categories had something similar to $roots in it.

icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • Initially I had two functions, but I couldn't get it working either. I thought recursion would be my best bet, but I will mess with what you got and see what I can put together. Thank you :) – Mike Mar 31 '13 at 05:52
  • I tried what you suggested and I'm getting caught up on the for loops. I think I need to structure it in a way that I can pass a long the `parent` id. Could you take a look and let me know what you think? I added my code in my description. – Mike Apr 06 '13 at 18:31
  • @Mike: When I first wrote the code, I was assuming you could figure out that I was requiring it to be in a hierarchal structure rather than a flat array straight out of the database. In hindsight, it wasn't so obvious. I added some code showing how to convert the database output into the more workable structure that I was using in the code. – icktoofay Apr 06 '13 at 22:01
  • I really appreciate your patience and time. Sometimes certain things, even basic tasks, takes me a while to digest. Once again,people like you make this site an invaluable educational tool. I have it worked out in my head on what I need to do, so I'll implenent this and get back :-) – Mike Apr 07 '13 at 00:06
4

you need to generate something called a super tree and use a recursive function to echo out each branch, this will allow for an unlimited depth tree, but, it also allows for an unknown depth tree.

First, SQL:

SELECT category_id, parent, category_name

Second, use SQL to create tree:

$super_tree = array();
while(($row = mysql_fetch_assoc($res)) != NULL) {
  $parent = $row['parent'] == NULL ? 0 : $row['parent']; //clean up the parent

  $super_tree[$parent][$row['category_id']] = $row['category_name'];
}

Third, echo out the tree

function echo_branch($tree_branch, $tree_root) {
  echo("<ul>\n"); //open up our list for this branch

  foreach($tree_branch as $id => $name) { //foreach leaf on the branch
    echo("<li>"); //create a list item

    echo("<a href=\"#{$id}\">{$name}</a>"); //echo out our link

    if(!empty($tree_root[$id])) { //if our branch has any sub branches, echo those now
      echo_branch($tree_root[$id], $tree_root); //pass the new branch, plus the root
    }

    echo("</li>\n"); //close off this item
  }
  echo("</ul>\n"); //close off this list
}

echo_branch($super_tree[0], $super_tree); //echo out unlimited depth tree structure

this allows for unlimited depth in your tree structure and allows for simple code to be able to create the structure within the code base.

All that you would need to do now is add in your extra's such as classes and your extra html elements in the correct places.

if you are looking to track the current depth of the tree to be able to echo different things depending on the depth, you can make the following alterations

In the function definition

function echo_branch($tree_branch, $tree_root, $depth) {

In the recursive call within the if

echo_branch($tree_root[$id], $tree_root, $depth++);

In the initial call

echo_branch($super_tree[0], $super_tree, 0);
bizzehdee
  • 20,289
  • 11
  • 46
  • 76
0

Look like you are using the variable

$category 

when you should use

$subcategory

Try this:

<div id="wrapper">
<ul class="mega-menu">         
    <?php $json = json_decode($category->buildDepartments(NULL)); ?>
    <?php foreach($json as $category): ?>
        <li class="mega-menu-drop">
            <a href="#"><?php if($category->parent === NULL){echo $category->category_name;} ?></a>
            <div class="mega-menu-content">                 
                <?php foreach($category as $subcategory): ?>
                <div class="column">
                    <?php if($subcategory->category_id === $category->parent){echo '<h4>' . $category->category_name . '</h4>';}?>
                    <ul>
                        <?php foreach($subcategory as $subcategory2)
                        {
                            echo '<li>' . $subcategory2->category_name . '</li>';
                        }
                        ?>
                    </ul>
                </div>
                <?php endforeach; ?>                   
            </div>
        </li>
     <?php endforeach; ?>  
</ul>  
</div>
Marcos
  • 1,240
  • 10
  • 20