views:

393

answers:

5

Hello:

I am stuck with a bunch of foreach loops and would like to know if there is a way to hone them down to a simple 'for' loop or a recursive function? I'm trying to generate HTML with the elements nested inside each other. I guess what I'm trying to get at is an arrays of arrays. But I don't know how to move forward with what I've created so far. Can someone please help me to make this monster into something more tamable? Thank you!

Here's my code:

$containers     = DISPLAY::displayParentElements($data);
$subcontainers  = DISPLAY::displayChildElements($data2);


foreach($containers as $parent) {
    $parentDiv = $parent['parentDiv'];
    echo '<div id="'.$parentDiv.'">';

    foreach($subcontainers as $child) {
        echo '<div id="'.$child['childDiv'].'">';

        foreach($subcontainers as $grandChild) {
            echo '<div id="'.$grandChild['grandChildDiv'].'">';

            foreach($subcontainers as $greatGrandChild) {
                echo '<div id="'.$greatGrandChild['greatGrandChildDiv'].'">';
                echo '</div>';
            }
            echo '</div>';
        }
        echo '</div>';
    }
echo '</div>';
}


The results will then look like this:

<div id="siteContainer">
  <div id="header">
        <div id="logoContainer">/div>
        <div id="logo"></div>
        <div id="links"></div>
        <div id="contactInfo">
              <div id="logoText">
                    <div id="shortDiv">
                          <div class="headerText"></div>
                    </div>
              </div>
         </div>
  </div>
  <div id="body">
        <div id="longDiv"></div>
        <div id="greetings"></div>
  </div>
<div>


The $containers array has the following info:

Array
(
    [attribute_value] => siteContainer
)

Array
(
    [attribute_value] => header
)

Array
(
    [attribute_value] => logoContainer
)

Array
(
    [attribute_value] => logo
)

Array
(
    [attribute_value] => logoText
)

Array
(
    [attribute_value] => links
)

Array
(
    [attribute_value] => contactInfo
)

Array
(
    [attribute_value] => body
)

Array
(
    [attribute_value] => longDiv
)

Array
(
    [attribute_value] => shortDiv
)

Array
(
    [attribute_value] => headerText
)

Array
(
    [attribute_value] => greetings
)


The $subcontainers array has pretty much the same info but with an extra key:

Array
(
    [parent_container_name] => siteContainer
    [attribute_value] => header
)

Array
(
    [parent_container_name] => header
    [attribute_value] => logoContainer
)

Array
(
    [parent_container_name] => header
    [attribute_value] => logo
)

Array
(
    [parent_container_name] => contactInfo
    [attribute_value] => logoText
)

Array
(
    [parent_container_name] => header
    [attribute_value] => links
)

Array
(
    [parent_container_name] => header
    [attribute_value] => contactInfo
)

Array
(
    [parent_container_name] => siteContainer
    [attribute_value] => body
)

Array
(
    [parent_container_name] => body
    [attribute_value] => longDiv
)

Array
(
    [parent_container_name] => logoText
    [attribute_value] => shortDiv
)

Array
(
    [parent_container_name] => shortDiv
    [attribute_value] => headerText
)

Array
(
    [parent_container_name] => body
    [attribute_value] => greetings
)

I'm pretty sure the two arrays could be narrowed down into one or by only using the $containers array.

+1  A: 

I can't quite tell from your post, but is the code you posted is the code you have, and the output you posted is the output you want? The code you posted... I want to say won't, but in truth I don't know php well enough to be sure... shouldn't produce the output you posted. In fact, I'd be shocked if it produced any output at all. The array's you posted have key-value pairs. None of the keys are anything like 'parentDiv', 'childDiv', 'grandChildDiv' or 'greatGrandChildDiv'. Instead they are just 'attribute_value' for the first one and for the second one 'attribute_value' and 'parent_container_name' for the second one.

In order to generate the output you want, try something along these lines:

$containerWasGenerated = new array();

echo '<div id="siteContainer">';
$containerWasGenerated['siteContainer'] = true;
foreach($containers as $container) {
    if($containerWasGenerated[$container['attribute_value'] != true) {
     generateSubcontainers($container, $containers, $subcontainers, $containerWasGenerated);
     $containerWasGenerated[$container['attribute_value']] = true;
    }
}
echo '</div>';

function generateSubcontainers($parent, $containers, $subcontainers, $containerWasGenerated) {
    echo '<div id="'.$parent['attribute_value'].'">';
    foreach($containers as $subcontainer) {
     if(getParent($subcontainer, $subcontainers) == $parent['attribute_value']) {
      generateContainer($subcontainer, $containers, $subcontainers, $containerWasGenerated);
      $containerWasGenerated[$subcontainer['attribute_value']] = true;
     }
    }
    echo '</div>';
}

function getParent($container, $subcontainers) {
    foreach($subconainters as $subcontainer) {
     if($subcontainer['attribute_value'] == $container['attribute_value']) {
      return $subcontainer['parent_container_name'];
     }
    }
}

Disclaimer: this code is untested and my php is a little rusty, so it may be buggy as all heck. But it should be in the right direction. Also, it's still pretty inefficient. One way to speed it up: keep the subcontainers array ordered by parent_container_name and write a better getParent() function (using a binary search for instance). Hopefully this helps.

Daniel Bingham
A: 

This only uses the subcontainers and the name of the absolute parent container.

function printChildren($parent, $children) {
    echo '<div id="'.$parent.'">'."\n";
        foreach($children as $child) {
            if($child[0] == $parent) {
                printChildren($child[1], $children);
            }
    }
    echo '</div>'."\n";
}

printChildren('siteContainer', $subcontainers);

note: this does not tab the code out nicely...

Samuel
This code was great but didn't work as expected. However, I'm curious as to why I would need to hardcode values (e.g. 'siteContainer') into the following line:printChildren('siteContainer', $subcontainers);
I created a new answer with a lot more information(it includes a way to find the absolute parent(e.g. 'siteContainer'). I also included how i have defined $subcontainers. please make sure i am defining that right and then comment on that answer as to how the code is not working as expected.
Samuel
A: 

You should give the nodes in $container a parent_container_name with a null value. This will represent the top-level menu nodes.

Next, glue your two arrays together using array_merge.

Now that you have one array with common node structure, you can easily recurse downward, rendering child elements that have a parent_container_name matching the current node and repeating recursively.

I think you want something like this:

$menuNodes; // The array of all nodes


function RenderMenu()
{
    // Grab all top-level nodes (nodes with no parent_container_name attribute)
    $topLevelNodes = array();

    foreach ($menuNodes as $node)
     if ($node["parent_container_name"] == null)
      $topLevelNodes[] = $node;


    foreach ($topLevelNodes as $node)
     RenderMenuNode($node);
}

function RenderMenuNode($node)
{
    print "<div id='" . $node["attribute_value"] ."'>";
    $node["isRendered"] = true;

    // This filtering callback will discard elements that are not children or have already been rendered.
    function Filter($element)
    {
     global $node;
     return (!$element["isRendered"] && $element["parent_container_name"] == $node["parent_container_name]"]);
    }

    $children = array_filter($menuNodes, 'Filter');

    foreach ($children as $child)
     RenderMenuNode($child);

    print "</div>";
}

I have not run or tested this code, and it could definitely be more efficient, but hopefully it points you in the right direction. Ideally you will remove rendered nodes from the menuNodes array so that you are not re-iterating over nodes unnecessarily.

By defining your menu structure recursively in this fashion, you can have menus nested as deep as you like without having to change the code. Hope this helps!

jscharf
A: 

The best way to do this would be to make your array more like the data you want to represent. So you would have the key-value pairs be more related. I would create the following array:

$new_array = array(
    [siteContainer] => array(
        [header] => array(
            [0] => "logo",
            [1] => "links",
            [contactInfo"] => array (
                [0] => "logoInfo",

            ....

           )
        )
     )
 );

To do the following in a recursive function you would write:

function makeSubDiv($array)
{
    foreach($array as $key=>$value)
    {
         if(is_array($value))
         {
             echo '<div id="'.$key.'">';
                 makeSubDiv($value);
             echo '</div>';
         }
         else
         {
              echo '<div id="'.$value.'"></div>';
         }
    }
}

Finally to merge the array you currently have I would have something like.

// not sure why you have $containers because the data seems to be in subcontainers
$containers     = DISPLAY::displayParentElements($data);
$subcontainers  = DISPLAY::displayChildElements($data2);

function mergeToNested($subcontainers)
{
    $return = array();
    foreach($containers as $values)
    {
        $key = $values["parent_container_name";
        if(isset($return[$key]))
        {
            if(is_array($return[$key]))
            {
                array_merge($return[$key], mergeToNested($return[$key]));
            }
            else
            {
                $return[$key] = array($values["attribute_value"]);
            }
        }
        else if($index = array_search($key, $return) !== false)
        {
             unset($return[$index]);
             $return[$key] = array();
        }
        else
        {
             $return[] = $key;
        }
     }

     return $return;
}

Argh, Hopefully you can get $containers and $subcontainers nested without having to push them through the mergeToNested() function.

Note:

This code (especially the MergeToNested function) is not tested, it's an idea of how to get the data you want, it may not merge to all nested levels as expected. ... feel free to test and edit any changes.

null
This code became too unwieldy for me manipulate. Any chance of honing it down a bit to a more simpler function?
A: 

I made so many changes to my answer i figured it would be better to just make it a new one instead of editing.

This time i am including the input array i am useing in case i misinterpreted what your starting with. I also created a function to find all the absolute parents, that is parents without any parents.

// list of subcontainers and their parents
$subcontainers = array(
array('siteContainer', 'header'), array('header', 'logoContainer'),
array('header', 'logo'), array('contactInfo', 'logoText'),
array('header', 'links'), array('header', 'contactInfo'),
array('siteContainer', 'body'), array('body', 'longDiv'),
array('logoText', 'shortDiv'), array('shortDiv', 'headerText'),
array('body', 'greetings'));

// recursively prints each container
function printChildren($parent, $children) {
    echo '<div id="'.$parent.'">'."\n";
    foreach($children as $child) {
        if($child[0] == $parent) {
            printChildren($child[1], $children);
        }
    }
    echo '</div>'."\n";
}

// finds all parents that have no parents
function findAbsParent($subcontainers) {
    $absParents = array();
    foreach($subcontainers as $parent) {
        $isAbs = true;
        foreach($subcontainers as $child) {
            if($parent[0] == $child[1]) {
                $isAbs = false;
            }
        }
        if($isAbs) {$absParents[] = $parent[0];}
    }
    return array_unique($absParents);
}

$absparents = findAbsParent($subcontainers);
// this is only needed if it's possible to have more then one absolute parent
if(is_array($absparents)) {
    foreach($absparents as $absparent) {printChildren($absparent, $subcontainers);}
} else {
    printChildren($absparents, $subcontainers);
}
Samuel