0

OK. I'll try to be clear as mud ...
I am learning php, css and javascript and I am having some problems...
I wrote the code below to generate a directory structure with the values ​​returned by another php function.
For this example I forced some values ​​in the roots array.
As it is, the code works very well, however, only with the three levels of depth. When adding a new level (child 2.2.2) we already have a problem (as expected the code shows "array" and does not create the new depth level, because there is no support implemented for more than three levels). My intention is to replace the if loop $counter_n != $last_n with something more "elegant" and allow the creation of new levels "dynamically", otherwise for each new level I need to include a new if loop.
In a second moment, I want to use javascript to be able to collapse the tree (changing the parentOpen class by parentClosed).

Note: I know of jstree's existence and it was from him that I "stole" the image sprite, but I'm reinventing the wheel just so I can learn ...
Note2: the 32px.png (edited to my needs) can be found at https://i.stack.imgur.com/ttnQ9.jpg

Eager for answers.

Grateful,

css:

ul
{
    list-style-type: none;
    text-decoration: none;
}

.flex-container
{
    padding: 0;
    margin: 0;
    list-style: none;
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-flex-flow: row wrap;
    align-items: center;
    justify-content: flex-start;
}

.flex-item
{
    width:32px;
    height:32px;
    position: absolute;
    color: white;
    font-weight: bold;
    font-size: 3em;
    text-align: center;
    content:"";
    display:block;
    position:relative;
    background-image: url(32px.png);
    background-repeat:no-repeat;
    overflow:hidden;
    background-position: -0px -32px;
}

.line
{
    background-position: -0px -0px;
}

.child
{
    background-position: -32px -0px;
}
.lastChild
{
    background-position: -32px -32px;
}

.parentClosed
{
    background-position: -64px -0px;
}
.parentOpen
{
    background-position: -96px -0px;
}
.lastParentClosed
{
    background-position: -64px -32px;
}
.lastParentOpen
{
    background-position: -96px -32px;
}

.checkBoxClear
{
    background-position: -128px -0px;
}
.checkBoxPartial
{
    background-position: -160px -0px;
}
.checkBoxChecked
{
    background-position: -192px -0px;
}


.icon1
{
    background-position: -128px -32px;
    margin-left: -12px;
}

.icon2
{
    background-position: -160px -32px;
    margin-left: -12px;
}

.icon3
{
    background-position: -192px -32px;
    margin-left: -12px;
}

php

$parents1 = array("parent1.1"=>array("1.1.1", "1.1.2"), "parent1.2"=>array("1.2.1"));
$parents2 = array("parent2.1"=>array("2.1.1", "2.1.2"), "parent2.2"=>array("2.2.1", "2.2.2"=>array("2.2.2.1", "2.2.2.2"), "2.2.3"), "parent2.3"=>array("2.3.1", "2.3.2"));
$parents3 = array("parent3.1"=>array("3.1.1"));
$roots = array("root1"=>$parents1, "root2"=>$parents2, "root3"=>$parents3);

echo '<div><ul>';   
    $last_a = count($roots);
    $counter_a = 1;
    foreach ($roots as $root => $parent)
    {
        echo '<li class="flex-container">';
        if ($counter_a != $last_a)
            echo '<div class="flex-item parentOpen"></div>';
        else
            echo '<div class="flex-item lastParentOpen"></div>';

        echo '<div class="flex-item checkBoxClear"></div><div class="flex-item icon3"></div><div class="flex-text">'
            .$root
        .'</div></li>';

        $last_b = count($parent);
        $counter_b = 1;
        foreach ($parent as $name => $children)
        {
            echo '<li class="flex-container">';
            if ($counter_a != $last_a)
                echo '<div class="flex-item line"></div>';
            else
                echo '<div class="flex-item"></div>';

            if ($counter_b != $last_b)
                echo '<div class="flex-item parentOpen"></div>';
            else
                echo '<div class="flex-item lastParentOpen"></div>';

            echo '<div class="flex-item checkBoxClear"></div><div class="flex-text">'.$name.'</div></li>';

            $last_c = count($children);
            $counter_c = 1; 
            foreach ($children as $key => $value)
            {
                echo '<li class="flex-container">';
                if ($counter_a != $last_a)
                    echo '<div class="flex-item line"></div>';
                else
                    echo '<div class="flex-item"></div>';

                if ($counter_b != $last_b)
                    echo '<div class="flex-item line"></div>';
                else
                    echo '<div class="flex-item"></div>';

                if ($counter_c != $last_c)
                    echo '<div class="flex-item child"></div>';
                else
                    echo '<div class="flex-item lastChild"></div>';

                echo '<div class="flex-item checkBoxClear"></div><div class="flex-item icon2"></div><div class="flex-text">'.$value.'</div></li>';

                $counter_c++;
            }

            $counter_b++;
        }

        $counter_a++;
    }
echo '</ul></div>';

Using the answer of @martindilling I was able to improve my code. Follow new php code: /* * An item in the tree. Have a name and can have a parent and children. */ class Item { public $name = ''; public $parent = null; public $children = [];

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function addChild(Item $child)
    {
        $this->parent = $this;
        $this->children[] = $child;
    }
}

/*
 * Build our tree.
 * Should be possible to make a function that takes a simple array
 * and builds this from that.
 */
$roots = array("root1"=>array("parent1.1"=>array("1.1.1", "1.1.2"), "parent1.2"=>array("1.2.1")), "root2"=>array("parent2.1"=>array("2.1.1", "2.1.2"), "parent2.2"=>array("2.2.1", "2.2.2"=>array("2.2.2.1"), "2.2.3"), "parent2.3"=>array("2.3.1", "2.3.2")), "root3"=>array("parent3.1"=>array("3.1.1"=>array("3.1.1.1", "3.1.1.2"=>array("3.1.1.2.1", "3.1.1.2.2", "3.1.1.2.3", "3.1.1.2.4")))));

/*
 * Our recursive function to create the html for our tree.
 */
function buildTreeHtml(Item $item, $level) {
if ($level!=0)
{
    if ($level==1)
        $out = '<div class="flex-item checkBoxClear"></div><div class="flex-item icon3"></div>';
    else
        $out = '<div class="flex-item checkBoxClear"></div><div class="flex-item icon2"></div>';
    $out .= '<div class="flex-text">'.$item->name.'</div></li>';
}
    if (!empty($item->children))
    {

        $last_a1 = count($item->children);
        $counter_a1 = 1;
        foreach ($item->children as $child)
        {
            $out .= '<li class="flex-container">';
            for ($cnt = 0; $cnt < $level; $cnt++)
            {
                $out .='<div class="flex-item line"></div>';
            }

            if ($counter_a1 != $last_a1)
                $out .= '<div class="flex-item child"></div>';
            else
                $out .= '<div class="flex-item lastChild"></div>';

            $out .= buildTreeHtml($child, $level+1);
            $counter_a1++;
        }
    }
    return $out;
}


/*
 * Build the Item object hierarchy from an array.
 */
function buildTree2($root, array $array) {
    $root = new Item($root);
    foreach ($array as $key => $value) {
        if (!is_array($value)) {
            $key = $value;
            $value = [];
        }
        $root->addChild(buildTree2($key, $value));
    }
    return $root;
}


/*
 * Do the building and printing the html.
 */
$root2 = buildTree2(null, $roots);
echo buildTreeHtml($root2);

Here is the image of the final result, the new problem (the highlighted lines should not exist) and how it should be... result
If anyone still has an interest in helping, I'll appreciate it.

Grateful,

EvilCoop
  • 1
  • 3
  • 1
    I think you should look into recursion for this :) http://stackoverflow.com/questions/2648968/what-is-a-recursive-function-in-php – martindilling Dec 23 '16 at 12:19
  • `@martindilling's_comment()` – moopet Dec 23 '16 at 12:21
  • In recursion method we need a general case and a base case. But because of nesting in this situation we will not get the base case so we cannot use recursion. – aavrug Dec 23 '16 at 12:23

1 Answers1

0

This is not the same as your setup, but a 5 minute example of a way you could handle structuring the tree and building the html using recursion :)

/*
 * An item in the tree. Have a name and can have a parent and children.
 */
class Item {
    public $name = '';
    public $parent = null;
    public $children = [];

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function addChild(Item $child)
    {
        // Was `$this->parent = $this;` before, that was a mistake :)
        $child->parent = $this; 
        $this->children[] = $child;
    }

    public function isRoot() : bool
    {
        return (bool) is_null($this->parent);
    }

    public function isLeaf() : bool
    {
        return (bool) empty($this->children);
    }

    public function hasChildren() : bool
    {
        return (bool) !$this->isLeaf();
    }
}

/*
 * Build our tree.
 * Should be possible to make a function that takes a simple array
 * and builds this from that.
 */
$root = new Item('Root');
$child_1 = new Item('1');
$child_1_1 = new Item('1.1');
$child_1->addChild($child_1_1);
$child_1_2 = new Item('1.2');
$child_1->addChild($child_1_2);
$root->addChild($child_1);

$child_2 = new Item('2');
$root->addChild($child_2);

$child_3 = new Item('3');
$child_3_1 = new Item('3.1');
$child_3->addChild($child_3_1);
$child_3_2 = new Item('3.2');
$child_3_2_1 = new Item('3.2.1');
$child_3_2->addChild($child_3_2_1);
$child_3_2_2 = new Item('3.2.2');
$child_3_2->addChild($child_3_2_2);
$child_3->addChild($child_3_2);
$child_3_3 = new Item('3.3');
$child_3->addChild($child_3_3);
$root->addChild($child_3);

/*
 * Our recursive function to create the html for our tree.
 */
function buildTreeHtml(Item $item, $indent = 2) {
    // Manually indent the tree with non-breaking space (&nbsp;).
    $out = '<li>' . str_repeat('&nbsp;', $indent);

    // Decide what arrow to use depending on if the item has any children.
    if ($item->hasChildren()) {
        $out .= '▼ ';
    } else {
        $out .= '▶ ';
    }

    // Write out an indicator for which kind of item have.
    if ($item->isRoot()) {
        $out .= '[R]: ';
    } else if ($item->isLeaf()) {
        $out .= '[L]: ';
    } else {
        $out .= '[ ]: ';
    }

    $out .= $item->name;

    if (!empty($item->children)) {
        foreach ($item->children as $child) {
            // Here the function calls itself with a child as parameter, this is the recursion.
            $out .= '<ul>' . buildTreeHtml($child, $indent + 2) . '</ul>';
        }
    }

    $out .= '</li>';
    return $out;
}

/*
 * Very simple styling for the page to make example easier to read
 */
echo '<style>
        body {font-family:monospace;}
        ul {padding: 0; list-style-type: none;}
      </style>';

/*
 * Print the html tree to the screen
 */
echo '<ul>' . buildTreeHtml($root) . '</ul>';

You should get this output printed:

▼ [R]: Root
  ▼ [ ]: 1
    ▶ [L]: 1.1
    ▶ [L]: 1.2
  ▶ [L]: 2
  ▼ [ ]: 3
    ▶ [L]: 3.1
    ▼ [ ]: 3.2
      ▶ [L]: 3.2.1
      ▶ [L]: 3.2.2
    ▶ [L]: 3.3

And here's an example of building the tree from an array instead :)

/*
 * Build the Item object hierarchy from an array.
 */
function buildTree(string $root, array $array) {
    $root = new Item($root);
    foreach ($array as $key => $value) {
        if (!is_array($value)) {
            $key = $value;
            $value = [];
        }
        $root->addChild(buildTree($key, $value));
    }
    return $root;
}

/*
 * The array we build from.
 */
$array = [
    '1' => [
        '1.1',
        '2.2',
    ],
    '2',
    '3' => [
        '3.1',
        '3.2' => [
            '3.2.1',
            '3.2.2',
        ],
        '3.3',
    ],
];

/*
 * Do the building and printing the html.
 */
$root2 = buildTree('Root', $array);
echo buildTreeHtml($root2);
martindilling
  • 2,839
  • 3
  • 16
  • 13
  • martindilling youre the man! This solution works perfectly to solve the "structuring the tree" problem, but ... (there is always one but) it broke my original solution for css "art". I wanted to link the items of the same hierarchy together and put a gape in it when I reached the last item of that level (so the $counter_n != $last_n test). check the attached image. [link](http://imgur.com/VFCvPcL). Any ideas? – EvilCoop Dec 23 '16 at 14:25
  • For the example in the picture, I would probably create each of the root as their own root Items. So you have for example $root1, $root2, $root3, and then you can just print and style them whereever and however you want :) If that's not what you meant, I need a bit more details to understand your question :) – martindilling Dec 23 '16 at 16:03
  • In short, I would like all the sibling elements to be linked by an image, and the last one on the stack would have a different image. When one of the sibling had a child, the blank space between him and the next sibling would be filled by another graphic ... [link](http://imgur.com/0Vn9m47) The problem is that I do not know how to make it compatible with your suggestion... – EvilCoop Dec 23 '16 at 16:36
  • I updated my answer, to show a bit more how you can use the Item objects :) If you need more information for a specific Item (for example a link, or if all items could have different icons etc), you can add those as fields on the Item class the same way as the `name` is a field :) Does that help? – martindilling Dec 28 '16 at 12:57