I'm trying to create a multidimensional array which stores a website navigation. Currently my navigation consists of Main -> Level 1 -> Level 2. I am having issues accessing level 2 correctly.
My Array example array is...
$pages = array
(
array('page_name' => 'Home'),
array('page_name' => 'About us', 'level_one' => array('first1','first2', 'level_two' => array('second'), 'first3')),
array('page_name' => 'Gallery', 'level_one' => array('first1','first2','first3','first4')),
array('page_name' => 'Contact us')
);
My code to retrieve the navigation so far is...
$count = count($pages);
for ($x=0; $x<$count; $x++) {
# echo yes for active page
if ($filename == $pages[$x][1]) { echo 'yes'; }
# echo main navigation
echo $pages[$x]['page_name'];
# check if the item has a sub page
$firstcount = count($pages[$x]['level_one']);
# if the item has a sub page echo
if ($firstcount > 0) { for ($y=0; $y<$firstcount; $y++) {
echo "\r\n" . '.' . $pages[$x]['level_one'][$y];
# check if the page has a sub page
$secondcount = count($pages[$x]['level_one']['level_two']);
if ($secondcount > 0) { for ($z=0; $z<$secondcount; $z++) {
if($pages[$x]['level_one']['level_two'][$z] != '') { echo "\r\n" . '..' . $pages[$x]['level_one']['level_two'][$z]; }
}}
} }
echo "\r\n";
}
And the output I'm currently getting is...
Home
About us
.first1
..second
.first2
..second
.first3
..second
.
..second
Gallery
.first1
.first2
.first3
.first4
Contact us
I am trying to create a multi level navigation. My expected output would be...
<!-- Expected output
Home
About us
.first1
.first2
..second
.first3
Gallery
.first1
.first2
.first3
.first4
Contact us
-->
I think you should check out some php recursion examples:
function returnArrayAsString($ar, $level = 0){
$str = '<ul class="level' . $level . '">';
foreach($ar as $k => $v){
if(is_array($v)){
$str .= '<li>' . $k . returnArrayAsString($v, $level + 1) . '</li>';
} else {
$str .= '<li>' . $v . '</li>';
}
}
$str .= '</ul>';
return $str;
}
if you then have a simple array:
$ar = array(
'Home',
'About us' => array(
'sub1a',
'sub1b',
'sub1c' => array(
'sub2a',
'sub2b'
)
)
);
Put array in function:
echo returnArrayAsString($ar);
This would give you a nice ordered list no matter the depth of sub items in the array.
As you can see, the function calls itself if it finds an array value. the $level variable can be used as a indicator, you could also use it for breadcrumbs ( About us > sub1c > sub2a).
Hope it helps....
The problem is in your loop code. Using foreach to make things more clear, results in the following, which outputs your result as you want it.
foreach($pages as $page)
{
echo "{$page['page_name']}\n";
foreach($page['level_one'] as $key => $level_one)
{
if($key === 'level_two')
{
foreach($level_one as $level_two)
{
echo "..$level_two\n";
}
}
else
{
echo ".$level_one\n";
}
}
}
You can use a lot of functions/ways to do this:
1.By foreach(Reference)
2.use var_dump to view any arrayvar_dump()
But remember to use <pre></pre> for prettifying.
Related
I've got this array:
Array
(
[0] => Array
(
[name] => System
[order] => 1
[icon] => stats.svg
[0] => Array
(
[title] => Multilingual
)
[1] => Array
(
[title] => Coloring
)
[2] => Array
(
[title] => Team work
)
[3] => Array
(
[title] => Tutorials
)
)
)
I want to loop into this to show the section name and after all the features containing in the following array.
So, this is what I made:
foreach ($features as $feature => $info) {
echo '
'.$info['name'].'
<ul class="menu-vertical bullets">
';
foreach (array_values($info) as $i => $key) {
echo '
<li>'.$key['title'].'</li>
';
}
echo '
</ul>
';
}
It works except for the first third <li> where I have the first char of name, order and icon value.
Do you know why ?
Thanks.
array_values return value of array so for info values is name, order, icon, 0, 1, ...
Your values foreach is wrong if you just want print title you can use:
foreach ($features as $feature => $info) {
echo '
'.$info['name'].'
<ul class="menu-vertical bullets">
';
//Remove some keys from info array
$removeKeys = array('name', 'order', 'icon');
$arr = $info;
foreach($removeKeys as $key) {
unset($arr[$key]);
}
foreach (array_values($arr) as $i => $key) {
echo '
<li>'.$key['title'].'</li>
';
}
echo '
</ul>
';
}
In php, array_values means all the values of the array. So array_values($info) is array($info['name'], $info['order'], $info['icon'], $info[0], $info[1], $info[2], $info[3])
in your example, you can skip the non-integer keys of the $info to get your titles:
<?php
$features = array();
$info = array();
$info['name'] = 'System';
$info['order'] = 1;
$info['icon'] = 'stats.svg';
$info[] = array('title'=>'Multilingual');
$info[] = array('title'=>'Coloring');
$features[] = $info;
foreach ($features as $feature => $info) {
echo $info['name'] . PHP_EOL;
echo '<ul class="menu-vertical bullets">' . PHP_EOL;
foreach ($info as $k => $item) {
if(!is_int($k)) continue;
echo '<li>' . $item['title'] . '</li>' . PHP_EOL;
}
echo '</ul>' . PHP_EOL;
}
BUT, your original data structure is not well designed and hard to use. For a better design, you can consider the following code, move your items to a sub array of $info:
<?php
$features = array();
$info = array();
$info['name'] = 'System';
$info['order'] = 1;
$info['icon'] = 'stats.svg';
$info['items'] = array();
$info['items'][] = array('title'=>'Multilingual');
$info['items'][] = array('title'=>'Coloring');
$features[] = $info;
foreach ($features as $feature => $info) {
echo $info['name'] . PHP_EOL;
echo '<ul class="menu-vertical bullets">' . PHP_EOL;
foreach ($info['items'] as $item) {
echo '<li>' . $item['title'] . '</li>' . PHP_EOL;
}
echo '</ul>' . PHP_EOL;
}
Sample output of the two demos:
System
<ul class="menu-vertical bullets">
<li>Multilingual</li>
<li>Coloring</li>
</ul>
It works except for the first third li where I have the first char of name, order and icon value. Do you know why ?
Why you see first chars of the values of 'name', 'order', 'icon'? Let see how PHP works.
Take the first loop as an example: foreach (array_values($info) as $i => $key)
Then $i == 0, $key == 'System'
We know that $key[0] == 'S', $key[1] == 'y', $key[2] == 's', etc.
Then you try to access $key['title'], but the string 'title' is not valid as a string offset, so it is converted to an integer by PHP: intval('title') == 0.
Then $key['title'] == $key[intval('title')] == 'S'
That's what you see.
array_value() returns the values of the array, here you will get the value of the array $info and what I understand is that is not what you need. See details for array_value().
You can check if the key for the $info is an integer. if yes, echo the title. Give this a try.
foreach ($features as $feature => $info) {
echo $info['name'].'<ul class="menu-vertical bullets">';
foreach ($info as $key => $value) {
if (is_int($key)) {
echo '<li>'.$key['title'].'</li>';
}
}
echo '</ul>';
}
First assumption: Assume, we defined a variable (its name is $tmp) in a function(functioin name is 'ExpMenu') for temporary calculating and in end of function we return this variable.
Second assumption: Assume, we call that function recursively for create a navigation menu base on a multidimensional array.
My question is about scope of that variable ($tmp). In every call funtion, will its value overwritten? In other words, by every function call we lose previous value?
For more detail, please review below code:
/// --- { Declaration Block
$content = array(
array(
'level'=>'1',
'order'=>'1',
'text'=>'New Solution WorkFlow',
'is_parent'=>'yes',
'child'=> array(
array(
'level'=>'2',
'order'=>'1',
'text'=>'Define New Solution',
'is_parent'=>'no',
'url'=>'#'
),
array(
'level'=>'2',
'order'=>'2',
'text'=>'View Solutions',
'is_parent'=>'no',
'url'=>'#'
),
array(
'level'=>'2',
'order'=>'3',
'text'=>'View Solutions',
'is_parent'=>'no',
'url'=>'#'
)
)
),
array(
'level'=>'1',
'order'=>'2',
'text'=>'Solution Modify WorkFlow',
'is_parent'=>'yes',
'child'=> array(
array(
'level'=>'2',
'order'=>'1',
'text'=>'Request For Solution Modify',
'is_parent'=>'no',
'url'=>'#'
)
)
),
array(
'level'=>'1',
'order'=>'3',
'text'=>'Solution Close WorkFlow',
'is_parent'=>'yes',
'child'=> array(
array(
'level'=>'2',
'order'=>'1',
'text'=>'Declare For Solution Close',
'is_parent'=>'no',
'url'=>'#'
)
)
)
);
function ExpMenu($item_array ) {
$tmp='';
foreach ($item_array as $item) {
if ($item['is_parent']=='yes') {
$tmp = '<li class="hasChild">' . $item["text"] . '<ul>';
$tmp .= ExpMenu($item['child']);
$tmp .= '</ul></li>';
} else {
$tmp = '<li>';
$tmp .= ''. $item['text'] . '' ;
$tmp .= '</li>';
}
}
return $tmp;
}
/// --- }
$menu='<div><ul>';
$menu .= ExpMenu($content);
$menu.='</ul></div>';
echo $m . '<br />';
It seams by every call function we lose pervious value.
I thank #l0rkaY for her/him solution, But I found another solution that doesn't need add new parameter in my function.
Because $tmp scope is in 'ExpMenu' function and we call recursively function, therefore variable still alive and wasn't terminated.
So, I modify my function a bit:
function ExpMenu($item_array ) {
$tmp='';
foreach ($item_array as $item) {
if ($item['is_parent']=='yes') {
$tmp .= '<li class="hasChild">' . $item["text"] . '<ul>';
$tmp .= ExpMenu($item['child']);
$tmp .= '</ul></li>';
} else {
$tmp .= '<li>';
$tmp .= ''. $item['text'] . '' ;
$tmp .= '</li>';
}
}
return $tmp;
}
I assume your actual problem is, that your function generates only single item with a single child item.
It's because you are overwriting your previous item in your if/else blocks. That's why you only get the last item.
You just need to concatenate them to the existing items.
function ExpMenu($item_array ) {
$tmp='';
foreach ($item_array as $item) {
if ($item['is_parent']=='yes') {
$tmp .= '<li class="hasChild">' . $item["text"] . '<ul>';
$tmp .= ExpMenu($item['child']);
$tmp .= '</ul></li>';
} else {
$tmp .= '<li>';
$tmp .= ''. $item['text'] . '' ;
$tmp .= '</li>';
}
}
return $tmp;
}
I want to get my navigation from an array and show an active class if the page is active. Everything is working but i don't get how i can make the page active with the class. Here is a little bit of my code:
foreach($navlinks as $key => $value){
if ($value === $key){
echo '<li class="active">'.$value.'';
}else {
echo '<li>' . $value . '';
}
}
Can anybody help me with this?
Thank you very much
EDIT:
My Array:
$navlinks = array ('index' => 'Home', 'over-ons' => 'Over ons', 'contact' => 'Contact');
EXAMPLE:
<li class="active">Contact</li>
Your code is almost there. You just need to fix the comparison to determine the current page. You may need to do something like
$current_page = substr($_SERVER['REQUEST_URI'], 1); // trim off the leading slash
$current_page = str_replace('.php', '', $current_page); // trim off the extension
foreach ($navlinks as $key => $value) {
if ( $current_page == $key ) {
...
}
else {
...
}
}
I'm not so strong with arrays but I need to determine how to count the number of parents a child array has in order to determine the indenting to display it as an option in a SELECT.
So, if I have this array:
array(
'World'=>array(
'North America'=>array(
'Canada'=>array(
'City'=>'Toronto'
)
)
)
);
How would I go about determining how many parents 'City' has in order to translate that into the number of spaces I want to use as an indent?
Thanks for any help.
EDIT: Let's see if I can explain myself better:
I have this code I'm using to build the OPTIONS list for a SELECT:
function toOptions($array) {
foreach ($array as $key=>$value) {
$html .= "<option value=\"" . $key . "\" >";
$html .= $value['title'];
$html .= "</option>";
if (array_key_exists('children', $value)) {
$html .= toOptions($value['children']);
}
}
return $html;
}
print toOptions($list);
So, I'm trying to determine how to get the number of parents in order to add spaces before the title in this line:
$html .= $value['title'];
Like:
$html .= " " . $value['title'];
But, I'm not sure how to figure out how many spaces to add.
Hopefully this is more clear.
Thanks for any help so far.
$x = array(
'World'=>array(
'North America'=>array(
'Canada'=>array(
'City'=>'Toronto'
)
)
)
);
// This function do something with the key you've found in the array
function visit($name, $depth)
{
echo $name . ' has ' . $depth . ' parents.';
}
// This function visits all the contents aff $array
function find_recursive($array, $depth = 0)
{
if (is_array($array)) {
foreach ($array as $k => $value) {
visit($k, $depth + 1);
find_recursive($array, $depth + 1);
}
}
}
For visiting:
find_recursive($x);
Well. Off the top what you are dealing with is a multi dimensional array.
You could run a count w/ foreach on each level of the array, and use the count number returned +1 for each level the foreach loops through.
I'm not sure if this answers your question, but I am trying to see exactly what it is you are trying to achieve.
As you are already using a recursive function to display that data, you can just extend your function. There is no need to traverse the array more often than one time:
function getWhitespaces($count) {
$result = '';
while($count--) {
$result .= '$nbsp;';
}
return $result;
}
function toOptions($array, $level=0) {
foreach ($array as $key=>$value) {
$html .= "<option value=\"" . $key . "\" >";
$html .= getWhitespaces($level) + $value['title'];
$html .= "</option>";
if (array_key_exists('children', $value)) {
$html .= toOptions($value['children'], $level + 1);
}
}
return $html;
}
print toOptions($list);
Try the following.. Your solution screams for recursion in my mind. Its a bit ugly but it seems to work
$totraverse = array(
'Moon' => array(
'Dark Side' => "Death Valley"
),
'Halley Commet' => "Solar System",
'World' => array(
'North America' => array(
'Canada' => array(
'City' => 'Toronto'
)
), 'South America' => array(
'Argentina' => array(
'City' => 'Toronto'
)
)
)
);
function traverse($totraverse_, $path="", $count=0) {
global $array;
// echo count($totraverse_) . " count\n";
if (!is_array($totraverse_)) {
echo "returning $path and $key\n";
return array($path, $count);
} else {
foreach ($totraverse_ as $key => $val) {
echo "assting $path and $key\n";
$result = traverse($val, $path . "/" . $key, $count + 1);
if($result){
$array[]=$result;
}
}
}
echo false;
}
$array = array();
traverse($totraverse);
foreach($array as $item){
echo "{$item[0]}--->{$item[1]}\n";
}
The question: How do I generate navigation, allowing for applying different classes to different sub-items, from a multi-dimensional array?
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\">$name</li>\n";
} else {
$thelink = "<li>$name</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\">$name\n";
// recursively show subitems
// NOTE: path starts now with the selected subitem
buildNav($sublinks, $path, $classes);
echo "</li>\n";
}
else
{
echo "<li>$name</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}>{$name}\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