开发者

Generate navigation from a multi-dimensional array

The question: How do I generate navigation, allowing for applying different classes to different sub-items, from a multi-dimensional ar开发者_开发知识库ray?

Here is how I was doing it before I had any need for multi-level navigation:

Home 
Pics 
About

and was generated by calling nav():

function nav(){       
    $links = array(
        "Home" => "home.php",
        "Pics" => "pics.php",
        "About" => "about.php"
    );

    $base = basename($_SERVER['PHP_SELF']);

    foreach($nav as $k => $v){
        echo buildLinks($k, $v, $base);
    }
}

Here is buildLinks():

function buildLinks($name, $page, $selected){
    if($selected == $page){
       $theLink = "<li class=\"selected\"><a href=\"$page\">$name</a></li>\n";
    } else {
       $thelink = "<li><a href=\"$page\">$name</a></li>\n";
    }

    return $thelink;
}

My question, again:

how would I achieve the following nav (and notice that the visible sub navigation elements are only present when on that specific page):

Home
    something1
    something2 
Pics 
About

and...

Home
Pics
    people
    places 
About

What I've tried

From looking at it it would seem that some iterator in the SPL would be a good fit for this but I'm not sure how to approach this. I have played around with RecursiveIteratorIterator but I'm not sure how to apply a different style to only the sub menu items and also how to only show these items if you are on the correct page.

I built this array to test with but don't know how to work with the submenu1 items individually:

$nav = array(
array(
"Home" => "home.php",
"submenu1" => array(
    "something1"=>"something1.php",
    "something2" => "something2.php")
),
array("Pics" => "pics.php"),
array("About" => "about.php")
);

The following will print out the lot in order but how do I apply, say a class name to the submenu1 items or only show them when the person is on, say, the "Home" page?

$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($nav));

foreach($iterator as $key=>$value) {
    echo $key.' -- '.$value.'<br />';
}

And this gets me:

Home
something1
something2
Pics
About

But I have no way to apply classes to those sub items and no way to only display them conditionally because I don't see how to target just these elements.


Don't reinvent the wheel, use Zend_Navigation and you will be happy.


You were on the right track with RecursiveIteratorIterator. It essentially flattens a recursive iterator. Here is the correct way:

$nav = array(
    array(
    "Home" => "home.php",
    "submenu1" => array(
        "something1"=>"something1.php",
        "something2" => "something2.php")
    ),
    array("Pics" => "pics.php"),
    array("About" => "about.php"),
);

$it = new RecursiveIteratorIterator(
    new RecursiveArrayIterator($nav),
    RecursiveIteratorIterator::SELF_FIRST
);

foreach ($it as $k => $v) {
    if ($it->getDepth() == 0)
        continue;
    echo str_repeat("    ", $it->getDepth() - 1) .
        "$k => $v\n";
}

gives

Home => home.php
submenu1 => Array
    something1 => something1.php
    something2 => something2.php
Pics => pics.php
About => about.php


It seems like you might want to do this in a more object oriented way. If not, it seems like you should at least define an algorithm that makes sense, right now you are just blindly guessing. Instead, DEFINE.

For example:

I am defining my navigation to be a php hash based tree. A navigation item will have the following:

A) if there is a top level link, the array hash will contain an item(sub array) labeled "navigation leaf"

b) A navigation Leaf will contain elements labeled "Display value", "link value", and "alt value". These items will be used to generate an anchor tag.

c) if an element has a submenu, in addition to containing a "Navigation Leaf", a "subnavigation" element will be present. A subnavigation element will have a "Navigation Leaf" if it has a displayable navigation item.

You can then write functions/methods that will display your navigation based on the definition you choose.


What I would do, is something along these lines:

class MenuItem {
    protected $active = false;
    protected $children = array();
    protected $name = '';
    protected $link = '';

    public function __construct($name, $link, $active) {}

    public function __toString() {
        //render this item
        $out = ''; #render here
        if (!$this->isActive()) { 
            return $out;
        }
        $out .= '<ul>';
        foreach ($this->children as $child) {
            $out .= (string) $child;
        }
        $out .= '</ul>';
        return $out;
    }

    public function isActive() {
        if ($this->active) {
            return true;
        }
        foreach ($this->children as $child) {
            if ($child->isActive()) {
                return true;
            }
        }
        return false;
    }
 }

Then, all you have is a collection of root menu items in an array... To build your menu, you just do:

$rootItems = array($item1, $item2);
$out = '<ul>';
foreach ($rootItems as $item) {
    $out .= (string) $item;
}
$out .= '</ul>';

I'll leave the semantics of constructing the object, adding children, etc to the user...


What about rewrite nav function in the next way:

function nav($links, $level){       
    foreach($links as $k => $v) {
        if (is_array($v)) {
            nav($v, $level + 1)
        } else {
            echo buildLinks($k, $v, $base);
        }
    }
}

And than call it:

$links = array(
array(
"Home" => "home.php",
"submenu1" => array(
    "something1"=>"something1.php",
    "something2" => "something2.php")
),
array("Pics" => "pics.php"),
array("About" => "about.php")
);
nav($links, 0);


Simplest way, IMHO, is to just make a recursive call, and use a tree structured description of your navigation (that is, nested arrays). Untested example code:

<?php
$links = array(
    "Home" => array("home.php", array(
        "something1"=> array("something1.php", array()),
        "hello"=> array("hello.php", array(
            "world" => array("world.php", array()),
            "bar" => array("bar.php", array()),
        )),
    )),
    "Pics" => array("pics.php", array(
        "people"=>"people.php",
        "places" => "places.php",
    )),
    "About" => array("about.php", array()), // example no subitems
);

// use the following $path variable to indicate the current navigational position
$path = array(); // expand nothing
$path = array('Home'); // expand Home
$path = array('Home', 'hello'); // also expand hello in Home

// map indent levels to classes
$classes = array(
    'item',
    'subitem',
    'subsubitem',
);


// recursive function to build navigation list
function buildNav($links, $path, $classes)
{
    // selected page at current level
    // NOTE: array_shift returns NULL if $path is empty.
    // it also alters the array itself
    $selected = array_shift($path);
    $class = array_shift($classes);

    echo "<ul>\n";

    foreach($links as $name => $link)
    {
        list($href, $sublinks) = $link;
        if ($name == $selected)
        {
            echo "<li class=\"selected $class\"><a href=\"$href\">$name</a>\n";
            // recursively show subitems
            // NOTE: path starts now with the selected subitem
            buildNav($sublinks, $path, $classes);
            echo "</li>\n";
        }
        else
        {
            echo "<li><a href=\"$href\" class=\"$class\">$name</a></li>\n";
        }
    }

    echo "<ul>\n";
}

// actually build the navigation
buildNav($links, $path, $classes);
?>


@catchmeifyoutry

Thank you, you saved my life LoL.

I changed your function a little to adapt it to my use and this came out:

$html['navi'] = array(
    "Home"          => "/home/",
    "DJs & Shows"   => "/djs-shows/",
    "Playlists"     => "/playlists/",
    "Newsbeat"      => "/newsbeat/",
    "Reviews"       => "/reviews/",
    "TV"            => "/tv/",
    "Contact"       => "/contact/",
    "Test"          => array("/test/",
        array("Submenu 1" => "/test/link1",
            "Submenu 2" => "/test/link2",
            "Submenu 3" => "/test/link3",
            "Submenu 4" => "/test/link4",
            "Submenu 5" => "/test/link5",
            "Submenu 6" => "/test/link6"
        )
    )
);

$classes = array(
    'first-level',
    'second-level',
    'third-level',
);

function siteNavi($links, $classes) {
    // The best way for MultiArray navigation (LOVE IT!)
    // Array Shift selects first element and removes it from array
    $class = array_shift($classes);

    echo "<ul class=\"$class\">\n";

    foreach($links as $name => $link) {

        if (is_array($link) AND $class != "") {

            list($link, $sublinks) = $link;
            if ($_GET['site'] == basename($link)) { $selected = ' class="current"'; } else { $selected = ""; }

            echo "<li{$selected}><a href=\"{$link}\">{$name}</a>\n";
            // recursively show subitems
            // NOTE: path starts now with the selected subitem
            siteNavi($sublinks, $classes);
            echo "</li>\n";

        } else {

            if ($_GET['site'] == basename($link)) { $selected = ' class="current"'; } else { $selected = ""; }

            echo "<li{$selected}><a href=\"{$link}\" >{$name}</a></li>\n";
        }
    }

    echo "</ul>\n";
}

Thank you very much !

I wonder how much impact does have this kind of code on the page speed tho. Few microseconds of milliseconds :D

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜