Phil Jackson
Phil Jackson

Reputation: 10288

php - nav/subnav structure from array

I'm trying to create a "plugin" like script for adding to a "menu array".... I'll go straight in..

Lets say I initiate a nav item like so:

$sections->add_section('dashboard');
$DashBoardSection = $sections->load_section('dashboard');
$DashBoardSection->set_role(NULL);
$DashBoardSection->set_nav_item(array(
    'identifier' => 'dashboard',
    'text' => 'Dashboard',
    'url' => NULL,
    'position' => NULL
));

starts off by creating a new section and the getting the instance of.

We are then setting a "role" in which we will test against to see if the user is authenticated as being verified to view.

The set nav item simply stores the array. identifier is a reference to the item ( its for when we want to add sub items), all standard apart from "position" which states where it is to sit in the nav i.e. NULL is top level, array('topnav','subnav') will be topnav->subnav->dashboard.

as a test, this could be stored as follows:

Array
(
    [0] => Array
        (
            [identifier] => dashboard
            [text] => Dashboard
            [url] => 
            [position] => 
        )
[1] => Array
    (
        [identifier] => dashboard2
        [text] => Dashboard2
        [url] => 
        [position] => Array
            (
                [0] => dashboard
            )

    )

)

my question being how I turn that into the following structure:

Array
(
    [0] => Array
    (
        [identifier] => dashboard
        [text] => Dashboard
        [url] => 
        [position] => 
        [children] => Array
            (
                [0] => Array
                    (
                        [identifier] => dashboard2
                        [text] => Dashboard2
                        [url] => 
                    )
            )
    )
)

pulling my hair out over this one, any help would be very much appreciated.

regards

I currently have

public function build_navigation( $role ){
    $role = (int)$role;
    $nav  = array();
    foreach( $this->sections as $section ) {
        if( $section->get_role() === NULL || $section->get_role() === $role ) {
            $nav_array = $section->get_nav();
            foreach( $nav_array as $key => $nav_item ) {
                if( $nav_item['position'] === NULL ) {
                    $nav[$nav_item['identifier']] = $nav_item;
                }elseif( is_array( $nav_item['position'] ) ){
                    #...#
                }
            }
        }
    }
    return $nav;
}

EDIT

Imagine this is the array given (it can be in any order)

Array
(
    [0] => Array
        (
            [identifier] => dashboard_child2
            [text] => Dashboard Child 2
            [url] => 
            [position] => Array
                (
                    [0] => dashboard
                )

        )

    [1] => Array
        (
            [identifier] => dashboard_child_child_1
            [text] => Dashboard Child Child 1
            [url] => 
            [position] => Array
                (
                    [0] => dashboard
                    [1] => dashboard_child1
                )

        )



[2] => Array
    (
        [identifier] => dashboard_child1
        [text] => Dashboard Child 1
        [url] => 
        [position] => Array
            (
                [0] => dashboard
            )

    )

[3] => Array
    (
        [identifier] => dashboard
        [text] => Dashboard
        [url] => 
        [position] => 
    )

[4] => Array
    (
        [identifier] => dashboard2
        [text] => Dashboard2
        [url] => 
        [position] => Array
            (
                [0] => dashboard
            )

    )

)

Which needs to be formatted as:

Array
(
    [dashboard] => Array
        (
            [text] => Dashboard
            [url] => 
            [children] => Array
                (
                    [dashboard_child2] => Array
                        (
                            [text] => Dashboard Child 2
                            [url] => 
                        )
                    [dashboard_child1] => Array
                        (
                            [text] => Dashboard Child 1
                            [url] => 
                            [children] => Array
                                (
                                    [dashboard_child_child_1] => Array
                                        (
                                            [text] => Dashboard Child Child 1
                                            [url] => 
                                        )
                                )
                        )
                    [dashboard2] => Array
                        (
                            [text] => Dashboard2
                            [url] =>
                        )
                )
        )
) 

Upvotes: 2

Views: 392

Answers (2)

complex857
complex857

Reputation: 20753

Here's my take on the problem, solved with recursion.

You can use multiple positions (i guess this is why it's an array), it will ignore missing positions if at least on of the positions is found, but will complain if every position is missing.

function translate($in) {

    $out = array();

    // first pass, move root nodes to output
    foreach ($in as $k => $row) {
        if (!$row['position']) {
            $out[$row['identifier']] = $row;
            unset($in[$k]);
        }
    }

    // while we have input
    do {
        $elements_placed = 0;
        // step trough input
        foreach ($in as $row_index => $row) {
            foreach ($row['position'] as $pos) {
                // build context for the node placing
                $data = array(
                    'row' => $row,
                    'in'  => &$in,
                    'row_index' => $row_index,
                    'elements_placed' => &$elements_placed,
                    'pos' => $pos,
                );
                // kick of recursion
                walker($out, $data);
            }
        }
    } while ($elements_placed != 0);
    if (count($in)) {
        trigger_error("Error in user data, can't place every item");
    }
    return $out;
}

function walker(&$out, $data) {
    foreach ($out as &$row) {
        // it looks like a node array
        if (is_array($row) && isset($row['identifier'])) {
            // if it has children recurse in there too
            if (isset($row['children'])) {
                walker($row['children'], $data);
            }

            // it looks like a node array that we are looking for place the row
            if ($row['identifier'] == $data['pos']) {
                if (!isset($row['children'])) {
                    $row['children'] = array($data['row']['identifier'] => $data['row']);
                } else {
                    $row['children'][$data['row']['identifier']] = $data['row'];
                }
                // report back to the solver that we found a place
                ++$data['elements_placed'];
                // remove the row from the $in array
                unset($data['in'][$data['row_index']]);
            }
        }
    }
}

$in = array (
    array (
        'identifier' => 'secondlevelchild2',
        'text' => 'secondlevelchild2',
        'url' => '',
        'position' => array (
            'dashboard2',
        ),
    ),
    array (
        'identifier' => 'secondlevelchild',
        'text' => 'secondlevelchild',
        'url' => '',
        'position' => array (
            'dashboard2',
        ),
    ),
    array (
        'identifier' => 'dashboard',
        'text' => 'Dashboard',
        'url' => '',
        'position' => '',
    ),
    array (
        'identifier' => 'dashboard2',
        'text' => 'Dashboard2',
        'url' => '',
        'position' => array (
            'dashboard', 'home',
        ),
    ),
    array (
        'identifier' => 'thirdlevelchild',
        'text' => 'thirdlevelchild',
        'url' => '',
        'position' => array (
            'secondlevelchild2',
        ),
    ),
);

$out = translate($in);
var_export($out);

In it's current form it doesn't remove the identifier or position keys from the node arrays once they are placed.

Upvotes: 2

hakre
hakre

Reputation: 197767

You need a temporary array that maps the identifier to the children array of it so that you can add it there.

If I see that right, you have something like that already here when you add the parent:

$nav[$nav_item['identifier']] = $nav_item;

Edit: Adding the parent needs some caution as pointed out in the comment:

$node = &$nav[$nav_item['identifier']];
$children = isset($node['children']) ? $node['children'] : array();
$node = $nav_item;
$node['children'] = $children;
unset($node);

Just add to the children then:

foreach($nav_item['position'] as $identifier)
{
    $nav[$identifier]['children'][] = $nav_item;
}

Also you could use objects, not arrays, so that you do not duplicate data that much (or you can change it later), however, just thinking, this must not be necessary to solve your problem so probably something for later.

Upvotes: 2

Related Questions