How to recursively build a <select> with unk

2019-01-23 00:07发布

问题:

I have a MySQL table with a tree data structure. The fields are _id, name and parentId. When the record hasn't a parent, parentId defaults as 0. This way I can build an array and then recursively print each record.

The builded array looks like this:

Array
(
    [1] => Array
        (
            [parentId] => 0
            [name] => Countries
            [_id] => 1
            [children] => Array
                (
                    [2] => Array
                        (
                            [parentId] => 1
                            [name] => America
                            [_id] => 2
                            [children] => Array
                                (
                                    [3] => Array
                                        (
                                            [parentId] => 2
                                            [name] => Canada
                                            [_id] => 3
                                            [children] => Array
                                                (
                                                    [4] => Array
                                                        (
                                                            [parentId] => 3
                                                            [name] => Ottawa
                                                            [_id] => 4
                                                        )

                                                )

                                        )

                                )

                        )

                    [5] => Array
                        (
                            [parentId] => 1
                            [name] => Asia
                            [_id] => 5
                        )

                    [6] => Array
                        (
                            [parentId] => 1
                            [name] => Europe
                            [_id] => 6
                            [children] => Array
                                (
                                    [7] => Array
                                        (
                                            [parentId] => 6
                                            [name] => Italy
                                            [_id] => 7
                                        )

                                    [11] => Array
                                        (
                                            [parentId] => 6
                                            [name] => Germany
                                            [_id] => 11
                                        )

                                    [12] => Array
                                        (
                                            [parentId] => 6
                                            [name] => France
                                            [_id] => 12
                                        )

                                )

                        )

                    [8] => Array
                        (
                            [parentId] => 1
                            [name] => Oceania
                            [_id] => 8
                        )

                )

         )

 )

Printing an unordered list <ul> is very simple with recursion. Here's the function I use:

function toUL ($arr) {

    $html = '<ul>' . PHP_EOL;

    foreach ( $arr as $v ) {

        $html.= '<li>' . $v['name'] . '</li>' . PHP_EOL;

        if ( array_key_exists('children', $v) ) {
            $html.= toUL($v['children']);
        }

    }

    $html.= '</ul>' . PHP_EOL;

    return $html;
}

But I'm stuck at printing a <select> in a tree-structured way:

Countries
-- America
---- Canada
------ Ottawa
-- Asia
-- Europe
---- Italy
---- Germany
---- France
-- Oceania

I thought to print -- as many times as the element's depth, but I don't know how to calculate the depth.

My question is: is it possible to build a <select> without knowing the depth?

Thank you in advance.

回答1:

Pass a parameter to count the iteration like $pass

function toUL ($arr, $pass = 0) {

    $html = '<ul>' . PHP_EOL;

    foreach ( $arr as $v ) {           

        $html.= '<li>';
        $html .= str_repeat("--", $pass); // use the $pass value to create the --
        $html .= $v['name'] . '</li>' . PHP_EOL;

        if ( array_key_exists('children', $v) ) {
            $html.= toUL($v['children'], $pass+1);
        }

    }

    $html.= '</ul>' . PHP_EOL;

    return $html;
}


回答2:

Your problem is already solved within the SPL. The RecursiveIteratorIteratorDocs has the information about one's item's depth:

$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), SELF_FIRST);
foreach ($it as $key => $element)
{
    if ($key !== 'name') continue;
    $inset = str_repeat('--', $it->getDepth());
    printf('<option>%s %s</option>', $inset, $element);
}


回答3:

function toSelect($arr, $depth = 0) {

    $html = '';

    foreach ( $arr as $v ) {           

        $html.= '<option>' . str_repeat("--", $depth) . $v['name'] . '</option>' . PHP_EOL;

        if ( array_key_exists('children', $v) ) {
            $html.= toSelect($v['children'], $depth++);
        }

    }


    return $html;
}


回答4:

My final solution (thanks to Starx and varan):

function toSelect ($arr, $depth=0) {    
    $html = '';
    foreach ( $arr as $v ) {

        $html.= '<option value="' . $v['_id'] . '">';
        $html.= str_repeat('--', $depth);
        $html.= $v['name'] . '</option>' . PHP_EOL;

        if ( array_key_exists('children', $v) ) {
            $html.= toSelect($v['children'], $depth+1);
        }
    }

    return $html;
}

echo '<select>';
echo toSelect($array);
echo '</select>';

Even the RecursiveIteratorIterator solution is good (thanks hakre).



回答5:

Hi Guys you could do something like this: I have this object for the $tree:

   Array
(
    [0] => stdClass Object
        (
            [id] => 1
            [nombre] => Category 1
            [subcategorias] => Array
                (
                    [0] => stdClass Object
                        (
                            [id] => 4
                            [nombre] => Category 1.1
                        )

                )

        )

    [1] => stdClass Object
        (
            [id] => 2
            [nombre] => Category 2
        )

    [2] => stdClass Object
        (
            [id] => 3
            [nombre] => Category 3
            [subcategorias] => Array
                (
                    [0] => stdClass Object
                        (
                            [id] => 5
                            [nombre] => Category 3.1
                            [subcategorias] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [id] => 6
                                            [nombre] => Category 3.1.1
                                        )

                                )

                        )

                )

        )

)

Then Here is how to create the array for the HTML select:

    $tree=array();// PUT HERE YOUR TREE ARRAY
    $arrayiter = new RecursiveArrayIterator($tree);
    $iteriter = new RecursiveIteratorIterator($arrayiter);
    $lista=array();
    $i=0;
    foreach ($iteriter as $key => $value) 
    {
        $id=$iteriter->current();
        $iteriter->next();
        $nivel=$iteriter->getDepth();
        $nombre=str_repeat('-',$nivel-1).$iteriter->current();
        $lista[$id]=$nombre;
    }

You will get something like this:

    Array
(
    [1] => Category 1
    [4] => --Category 1.1
    [2] => Category 2
    [3] => Category 3
    [5] => --Category 3.1
    [6] => ----Category 3.1.1
)

Then you just need to create the options for the select with a simple foreach.



回答6:

You could pass the length to the function and increment it when you call recursively and then use the variable $length to determine the depth

function toUL ($arr, $length = 0) {

    $html = '<ul>' . PHP_EOL;

    foreach ( $arr as $v ) {

        $html.= '<li>' . $v['name'] . '</li>' . PHP_EOL;

        if ( array_key_exists('children', $v) ) {
            $html.= toUL($v['children'], $length++);
        }

    }

    $html.= '</ul>' . PHP_EOL;

    return $html;
}

This is what i do usually to keep track of recursion depth and it usually get the job done



回答7:

function toUL ($arr, $depth = 0) {
//    ...
$html .= toUL($v['children'], $depth+1);


回答8:

you can use optgroup as an iterator. For Example:

<select name="list">
  <option value=1>1st option</option>
  <optgroup>
    <option value=10>1st on 1st group</option>
  </optgroup>
</select>

if you want php, you can try this one:

<?PHP
function toSELECT($arr,$depth=0)
{
$html="";
if(is_array($arr))
    {
    $html.=($depth==0)?"<select name='html'>\n":"";
    foreach ($arr as $key=>$value)
        {
        if(is_array($arr[$key]))
            {
            $html.=str_repeat("\t",$depth)."<optgroup>\n";
            $html.=str_repeat("\t",$depth).toHTML($arr[$key],$depth+1);
            $html.=str_repeat("\t",$depth)."</optgroup>\n";
            }
        else
            {
            $html.=str_repeat("\t",$depth)."<option value='".$value."'>".$key."</option>\n";
            }
        }
    $html.=($depth==0)?"</select>\n":"";
    }
return $html;
}

?>