php create navigation menu from multidimensional array dynamically - php

I did research on this, and wasn't able to find an exact answer. Most of the questions/answers on here pertaining to this seem to be unfinished. If anyone knows of a finished solution similar to my question, please point me in that direction!
Here is my array:
Array
(
['home'] => Array
(
[0] => sub-home1
[1] => sub-home2
)
['about'] => Array
(
[0] => sub-about
['about2'] => Array
(
[0] => sub-sub-about
)
)
['staff'] => Array
(
[0] => sub-staff1
[1] => sub-staff2
)
['contact'] => contact
)
And here is what I would like to turn it into:
<ul>
<li><a href="">home<a/>
<ul>
<li>sub-home1</li>
<li>sub-home2</li>
</ul>
</li>
<li><a href="">about<a/>
<ul>
<li>sub-about</li>
<li>about2
<ul>
<li><a href="">sub-sub-about<a/></li>
</ul>
</li>
</ul>
</li>
<li><a href="">staff<a/>
<ul>
<li>sub-staff1</li>
<li>sub-staff2</li>
</ul>
</li>
<li><a href="">contact<a/></li>
</ul>
The array will be dynamically generated, but will have a limit of 3 levels ex: about->about2->sub-sub-about. I tried going off of this question: PHP/MySQL Navigation Menu but they didn't really seem to come to a conclusion? I am familiar with foreach's whiles and for loops but I just can't seem to wrap my head around this one.
EDIT: Enzino, your code works!

Here is my solution:
<?php
function MakeMenu($items, $level = 0) {
$ret = "";
$indent = str_repeat(" ", $level * 2);
$ret .= sprintf("%s<ul>\n", $indent);
$indent = str_repeat(" ", ++$level * 2);
foreach ($items as $item => $subitems) {
if (!is_numeric($item)) {
$ret .= sprintf("%s<li><a href=''>%s</a>", $indent, $item);
}
if (is_array($subitems)) {
$ret .= "\n";
$ret .= MakeMenu($subitems, $level + 1);
$ret .= $indent;
} else if (strcmp($item, $subitems)){
$ret .= sprintf("%s<li><a href=''>%s</a>", $indent, $subitems);
}
$ret .= sprintf("</li>\n", $indent);
}
$indent = str_repeat(" ", --$level * 2);
$ret .= sprintf("%s</ul>\n", $indent);
return($ret);
}
$menu = Array(
'home' => Array("sub-home1", "sub-home2"),
'about' => Array("sub-about", "about2" => Array("sub-sub-about")),
'staff' => Array("sub-staff1", "sub-staff2"),
'contact' => "contact"
);
print_r($menu);
echo MakeMenu($menu);
?>

Calvin's solution worked for me. Here's the edited version. We can use more nested loops to get sub - sub menu items.
echo '<ul>';
foreach ($menu as $parent) {
echo '<li>' . $parent . '';
if (is_array($parent)) {
echo '<ul>';
foreach ($parent as $children) {
echo '<li>' . $children . '';
}
echo '</ul>';
}
echo '</li&gt';
}
echo '</ul>';

I think you can use recursion? Here is some pseudocode, not very familiar with php.
function toNavMenu(array A){
for each element in A{
echo "<li>" + element.name + ""
if (element is an array){
echo "<ul>"
toNavMenu(element)
echo "</ul>"
}
echo "</li>"
}
}

I would probably slightly adapt the array to be something like the following:
Array(
0 => Array(
'title' => 'Home',
'children' => Array()
),
1 => Array(
'title' => 'Parent',
'children' => Array(
0 => Array(
'title' => 'Sub 1',
'children' => Array(),
),
1 => Array(
'title' => 'Sub 2',
'children' => Array(
0 => Array(
'title' => 'Sub sub 2-1',
'children' => Array(),
),
),
),
)
)
)
With a structure like this you could use recursion to build your menu HTML:
function buildMenu($menuArray)
{
foreach ($menuArray as $node)
{
echo "<li><a href='#'/>" . $node['title'] . "</a>";
if ( ! empty($node['children'])) {
echo "<ul>";
buildMenu($node['children']);
echo "</ul>";
}
echo "</li>";
}
}

Related

Loop through json array and group by key values

After json_decode I have the following Array :::
$json = Array ( [name] => Array ( [0] => Peter [1] => David ) [dep] => Array ( [0] => accounts [1] => sales ) [date] => Array ( [0] => 10/27/2015 [1] => 09/25/2015 ) );
How can I group the values by key so I get the following in a PHP foreach loop ? :::
<ul>
<li>Peter, accounts, 10/27/2015</li>
<li>David, sales, 09/25/2015</li>
</ul>
I've tried a similar foreach loop ( see below ) without the desired results, I'm able to print all the key & value's but I'm not able to group values by key eg. [1] :::
foreach($json as $row){
foreach($row as $key=>$val){
echo $key . ': ' . $val . '<br>';
}
}
Any assistance would be appreciated :::
You could use the following code to sort the list:
<?php
$json = [
"name" => ["Peter","David"],
"dep" => ["accounts", "sales"],
"date" => ["10/27/2015","09/25/2015"]
];
$final = [];
foreach($json as $section)
{
foreach($section as $key=>$info)
{
if(!isset($final[$key])) $final[$key] = "";
$final[$key] .= $info . ", ";
}
}
echo "<ul>";
foreach($final as $row)
{
echo "<li>" . substr($row, 0, strlen($row) - 2) . "</li>"; // -2 to remove the extra ", "
}
echo "</ul>";
?>
Result:
<ul>
<li>Peter, accounts, 10/27/2015</li>
<li>David, sales, 09/25/2015</li>
</ul>
Perhaps try something like this:
$arrayCount = count($json['name']);
$output = '<ul>';
for($i = 0; $i < $arrayCount; $i++) {
$output .= '<li>';
$output .= $json['name'][$i] . ', ';
$output .= $json['dep'][$i] . ', ';
$output .= $json['date'][$i];
$output .= '</li>';
}
$output .= '</ul>';
echo $output;
You would also be better off using an array format like this:
$json =
Array(
Array(
'name' => 'Peter',
'dep' => 'accounts',
'date' => '10/27/2015'
),
Array(
'name' => 'David',
'dep' => 'accounts',
'date' => '09/25/2015'
)
);
This is because you can create a easier to understand foreach loop like this:
$output = '<ul>';
foreach($json as $row) {
$output .= '<li>';
$output .= $row['name'] . ', ';
$output .= $row['dep'] . ', ';
$output .= $row['date'];
$output .= '</li>';
}
$output .= '</ul>';
echo $output;

Dynamic menu from PHP array

Hey I have question about Dynamic menu which is created in php.
Code is from stackoverflow, what I want is to get my parent styled with red color if children of those parent is selected, here is code:
$menu = Array(
Array(
'title' => 'Home',
'link' => 'a'
),
Array(
'title' => 'Parent',
'link' => 'b',
'children' => Array(
Array(
'title' => 'Sub 1',
'link' => 'c'
),
Array(
'title' => 'Sub 2',
'link' => 'd'
),
)
)
);
function buildMenu($menuArray)
{
foreach ($menuArray as $node)
{
$selected = ($node['link']== $_GET['menu']) ? $selected = 'style="color: red;"' : null;
echo "<li ".$selected."><a href='?menu=".$node['link']."'/>" . $node['title'] . "</a>";
if ( ! empty($node['children'])) {
echo "<ul>";
buildMenu($node['children']);
echo "</ul>";
}
echo "</li>";
}
}
buildMenu($menu);
So how it needs to go:
Home
Parent - selected
Sub 1 - selected
Sub 2
or
Home
Parent - selected
Sub 1
Sub 2 - selected
Hope someone understand what i want? If my children under parent is selected also parent needs to be selected.
I have added one function to check element in children array. May be something better solution are there. But at this its quick solution for you :)
$menu = array(
array(
'title' => 'Home',
'link' => 'a'
),
array(
'title' => 'Parent',
'link' => 'b',
'children' => array(
array(
'title' => 'Sub 1',
'link' => 'c'
),
array(
'title' => 'Sub 2',
'link' => 'd'
),
)
)
);
function buildMenu($menuArray) {
foreach ($menuArray as $node) {
$getMenu = isset($_GET['menu']) ? $_GET['menu'] : '';
$checkParent = (isset($node['children']) && !empty($node['children'])) ? checkInChildArray($getMenu, $node['children']) : '';
$parentSelected = ($checkParent) ? $selected = 'style="color: red;"' : null;
echo "<li " . $parentSelected . "><a href='?menu=" . $node['link'] . "'>" . $node['title'] . "</a></li>";
if (isset($node['children']) && !empty($node['children'])) {
echo "<ul>";
foreach ($node['children'] as $subMenu) {
$childSelected = ($subMenu['link'] == $getMenu) ? $selected = 'style="color: red;"' : null;
echo "<li " . $childSelected . "><a href='?menu=" . $subMenu['link'] . "'>" . $subMenu['title'] . "</a></li>";
}
echo "</ul>";
}
echo "</li>";
}
}
// Checking if selected menu inside children array.
function checkInChildArray($needle, $haystack, $strict = false) {
foreach ($haystack as $item) {
if (($strict ? $item['link'] === $needle : $item == $needle) || (is_array($item) && checkInChildArray($needle, $item, $strict))) {
return true;
}
}
return false;
}
echo buildMenu($menu);
Working Demo
Use jQuery to add parent li a background color
$('li.selected').parent().closest('li').css("color","red");
Pass every menu level to the url. You can add the "selected" class for every menu level. So:
$current_menu_level_1 = (isset($_GET['menu_level_1'])) ? $_GET['menu_level_1'] : false;
$current_menu_level_2 = (isset($_GET['menu_level_2'])) ? $_GET['menu_level_2'] : false;
When building your menu, compare the 'to be build item' to the $current_menu_level1/2 variable and add echo the class when they are the same.
please, consider create a css class selected for <li> elements and <ul> elements.
With PHP insert this styles when needed, Like this:
$selected = ($node['link']== $_GET['menu']) ? $selected = 'selected' : '';
echo "<li class='".$selected."'>";
echo "<ul class='".$selected."'>";
buildMenu($node['children']);
echo "</ul>";

bootstrap nested navbar with php recursive iteration

I try to render my nested Categories in a Bootstrap navbar but I have a problem to get the desired output. UL dropwdown is getting wrapped more times as it should because of my recursive iteration. I do not find a good workaround to have the desired result
function getFeedCategories($db) {
$sql = "SELECT * FROM feeds_categories";
$datas = array();
$childrenTree = [];
$categoryNames = [];
//We fill $childrenTree and $categoryNames from database
if ($db->sql($sql)) {
while ($res = $db->getResult('array')) {
$datas[] = $res;
}
}
foreach ($datas as $row) {
extract($row);
$categoryNames[(string) $id] = $name;
$parent = (string) $parent;
if (!array_key_exists($parent, $childrenTree))
$childrenTree[$parent] = array();
$childrenTree[$parent][] = (string) $id;
}
return renderTreeCategories("0" ,$childrenTree,$categoryNames);
}
//Main recursive function. I'll asume '0' id is the root node
function renderTreeCategories($parent, $childrenTree,$categoryNames ) {
$children = $childrenTree[$parent];
if (count($children) > 0) { //If node has children
$html .= '<li class="dropdown open">';
$html .= '<a data-toggle="dropdown" class="dropdown-toggle" href="#">'.$categoryNames[$parent].' <b class="caret"></b></a>';
$html .= '<ul class="dropdown-menu">';
foreach ($children as $child){
$html .= renderTreeCategories($child,$childrenTree,$categoryNames);
}
$html .= "</ul></li>";
} else{
$html .= '<li>'.$categoryNames[$parent].'</li>';
}
if ($parent != "0"){
//$html .= "</li>";
}
return $html;
}
my $childrenTree array looks as it follows
array ( 0 =>array ( 0 => '1', 1 => '2', 2 => '5', 3 => '8', 4 => '9', 5 => '10', 6 => '11', 7 => '12', 8 => '14', ),
1 => array ( 0 => '3', ),
2 => array ( 0 => '4', ),
5 => array ( 0 => '6', 1 => '7', ),
10 => array ( 0 => '13' )
9 => array ( 0 => '15', 1 => '16', )
)
UPDATE
the code what implenented seems to work but not sure if is the best workaround.
//Main recursive function. I'll asume '0' id is the root node
function renderTreeCategories($parent, $childrenTree,$categoryNames ) {
$children = $childrenTree[$parent];
$has_childs = count($children);
if ($parent != "0") {
if ($has_childs > 0) {
$html .= "<li class='dropdown'>";
$html .= ''.$categoryNames[$parent].' <b class="caret"></b>';
$html .= '<ul class="dropdown-menu">';
} else{
$html .= "<li>";
$html .= '' . $categoryNames[$parent] . '';
}
}
if ($has_childs > 0) { //If node has children
foreach ($children as $child)
$html .= renderTreeCategories($child, $childrenTree,$categoryNames);
$html .= "</ul>";
}
if ($parent != "0") {
$html .= "</li>";
}
return $html;
}

iterating over multi dimensional array for a menu

Hello I have a menu made in codeigniter. but I also want this to have submenu's
Therefore I get an array and go through it with a foreach loop.
<ul>
<?php foreach ($menu_item as $menu =>& $key): ?>
<li><?php echo anchor($menu, $key, $this->uri->slash_segment(1, 'leading') == $menu ? 'class="active"' : '') ?></li>
<?php endforeach ?>
</ul>
Now the problem is that this works great if its just one menu without submenu's but when I get an array like this
$menu_item = array(
'/' => 'Home',
'/about' => 'About',
'/foo' => 'boo',
'/contact' => 'contact',
'test' => array(
'foo' => 'boo'
),
'test2' => 'foo2'
);
Than it doesn't work anymore. How can I loop through everything and output it as a good menu?
You can use recursion to do that job. It takes a bit of getting your head around if you're not familiar with it, but it's very well suited to this kind of problem.
I haven't run this code in PHP, but it will give you an idea.
Basically what happens is that the main menu function checks each item to see if it's an array, and then calls the function again using the sub menu. This will work infinitely deep if required.
<?php
$menu = array(
'/' => 'Home',
'/about' => 'About',
'/foo' => 'boo',
'/contact' => 'contact',
'test' => array(
'foo' => 'boo'
),
'test2' => 'foo2'
);
?>
<ul>
<?php showMenu($menu); ?>
</ul>
<?php
function showMenu($menu)
{
<?php foreach ($menu_item as $menu =>& $key): ?>
<li><?php echo anchor($menu, $key, $this->uri->slash_segment(1, 'leading') == $menu ? 'class="active"' : '') ?></li>
if(is_array($menu_item))
{
echo "<ul>";
showMenu($menu_item);
echo "</ul>";
}
<?php endforeach ?>
}
?>
Hope this helps.
The concept of the other answers is true, but they generate invalid DOM structure, so I decided to fix it.
You can make a helper file and put the drawMenu() function inside. So, you'll be able to call the function as much as you need.
$menu = array(
'/' => 'Home',
'/about' => 'About',
'/foo' => 'boo',
'/contact' => 'contact',
'test' => array(
'foo' => 'bar',
'baz' => 'qux'
),
'test2' => 'foo2'
);
function drawMenu($menu)
{
$CI =& get_instance();
$output = '';
foreach ($menu as $key => $value) {
$output .= "<li>";
if (is_array($value)) {
$output .= anchor('#', $key);
$output .= PHP_EOL."<ul>".PHP_EOL;
$output .= drawMenu($value);
$output .= "</ul>".PHP_EOL."</li>".PHP_EOL;
} else {
$output .= anchor($key, $value, $CI->uri->slash_segment(1, 'leading') == $key ? 'class="active"' : '');
$output .= "</li>".PHP_EOL;
}
}
return $output;
}
$html = drawMenu($menu);
echo '<ul>'. $html .'</ul>';
Side-note: Usage PHP_EOL constant is arbitrary, it just makes generated DOM more readable.
Update:
I improved the drawMenu() functionality, now you can add a URL address for the headers of sub-menus:
$menu = array(
'/' => 'Home',
'/about' => 'About',
'/foo' => 'boo',
'/contact' => 'contact',
'test' => array(
'foo' => 'bar'
),
'This is Test2|/url/to/test2' => array(
'baz' => 'qux'
)
);
You can add the URL after | separator.
function drawMenu($menu)
{
$CI =& get_instance();
$output = '';
foreach ($menu as $key => $value) {
$output .= "<li>";
if (is_array($value)) {
if (strpos($key, '|') !== false) {
$param = explode('|', $key);
$output .= anchor($param[1], $param[0]);
} else {
$output .= anchor('#', $key);
}
$output .= PHP_EOL."<ul>".PHP_EOL;
$output .= drawMenu($value);
$output .= "</ul>".PHP_EOL."</li>".PHP_EOL;
} else {
$output .= anchor($key, $value, $CI->uri->slash_segment(1, 'leading') == $key ? 'class="active"' : '');
$output .= "</li>".PHP_EOL;
}
}
return $output;
}
You can check if the $key is an array: is_array
Then you can use another foreach to loop through the submenus.
try this
<ul>
<?php function buildmenu($menu_item){ ?>
<?php foreach($menu_item as $item){ ?>
<li><?php echo anchor($menu, $key, $this->uri->slash_segment(1, 'leading') == $menu ? 'class="active"' : '') ?></li>
<?php if(is_array($item)){
buildmenu($item);
} ?>
<?php } ?>
<php} ?>
<?php buildmenu($menu_item) ?>
</ul>
$menu = "<ul>\n";
foreach ($menu_item as $key => $value){
if (is_array($value)){
$menu.= "\t<li>".$key."\n\t\t<ul>\n";
foreach ($value as $key2 => $value2){
$menu .= "\t\t\t<li>".$value2."</li>\n";
}
$menu.= "\t\t</u>\n\t</li>\n";
} else {
$menu .= "\t<li>".$value."</li>\n";
}
}
$menu .= "</ul>";
echo $menu;
Output:
<ul>
<li>Home</li>
<li>About</li>
<li>boo</li>
<li>contact</li>
<li>test
<ul>
<li>boo</li>
</u>
</li>
<li>foo2</li>
</ul>

Problem to generate nested ul lists using PHP

I am working on a front-end web app where a nested unordered list would be used for the jQuery plugin mcdropdown.
Here is the data structure from PHP: a nested array of arrays :
Array
(
[0] => Array
(
[fullpath] => ../foil/alphanumeric/
[depth] => 0
)
[1] => Array
(
[fullpath] => ../foil/alphanumeric/letters/
[depth] => 1
)
[2] => Array
(
[fullpath] => ../foil/alphanumeric/numbers/
[depth] => 1
)
[3] => Array
(
[fullpath] => ../foil/alphanumeric/numbers/symbols/
[depth] => 2
)
)
Basically, I took the excellent answer from this question on SO, modified it a bit :
global $fullpaths; // $fullpaths contains the above data structure in print_r
$result = '';
$currentDepth = -1;
while(!empty($fullpaths))
{
$currentNode = array_shift($fullpaths);
if($currentNode['depth'] > $currentDepth)
{
$result .='<ul>';
}
if($currentNode['depth'] < $currentDepth)
{
$result .=str_repeat('</ul>', $currentDepth - $currentNode['depth']);
}
$result .= '<li>'. $currentNode['fullpath'] .'</li>';
$currentDepth = $currentNode['depth'];
if(empty($fullpaths))
{
$result .= str_repeat('</ul>', 1 + $currentDepth);
}
}
print $result;
and got the following output:
<ul>
<li>../foil/alphanumeric/</li>
<ul>
<li>../foil/alphanumeric/letters/</li>
<li>../foil/alphanumeric/numbers/</li>
<ul>
<li>../foil/alphanumeric/numbers/symbols/</li>
</ul>
</ul>
</ul>
Which cannot be accepted by the mcdropdown jQuery plugin, it expects something like this:
<li rel="1">
'Alphanumeric'
<ul>
<li rel="2">'Letters'</li>
<li rel="3">'Numbers'
<ul>
<li rel="4">'Symbols'</li>
</ul>
</li>
</ul>
</li>
To be frank, I don't quite understand how the answer from that question works, I have been trying to modify that solution to cope with my situation, but still failed.
Any help and suggestion is much appropriated in advance.
If you already have the correct depth values, then you don't need recursion. I have a similar function that I use for <ul>-<li>-generation:
function ulli($newlevel, &$level, $UL="ul", $once=1) {
if ($level == $newlevel) {
echo "</li>\n";
}
while ($level<$newlevel) {
$level++;
echo "\n <$UL>\n";
}
while ($level>$newlevel) {
if ($once-->0) { echo "</li>\n"; }
$level--;
echo " </$UL>"
. ($level>0 ? "</li>" : "") . "\n"; // skip for final </ul> (level=0)
}
}
It needs a current $level variable for reference (=$currentDepth). And you pass it your depth as $newlevel. It however needs the first depth to be 1.
Basic usage is like:
$currentDepth=0;
foreach ($array as $_) {
ulli($_["depth"]+1, $currentDepth);
echo "<li>$_[path]";
}
ulli(0, $currentDepth);
Well, quirky. But it worked for me.
Does this code (indentation apart) produces the result you want?
$d = array(
0 => array(
'fullpath' => '../foil/alphanumeric/',
'depth' => 0
),
1 => array(
'fullpath' => '../foil/alphanumeric/letters/',
'depth' => 1
),
2 => array(
'fullpath' => '../foil/alphanumeric/numbers/',
'depth' => 1
),
3 => array(
'fullpath' => '../foil/alphanumeric/numbers/symbols/',
'depth' => 2
)
);
echo "<ul>\n";
$cdepth = 0; $rel = 1; $first = true; $veryfirst = true;
foreach($d as $e)
{
$mpath = "'" . ucfirst(basename($e['fullpath'])) ."'";
if ( $e['depth'] == $cdepth ) {
if ( $first && !$veryfirst) { echo "</li>\n";}
echo "<li rel=\"$rel\">", $mpath;
$rel++; $first = false; $veryfirst = false;
} else {
$depthdiff = $e['depth'] - $cdepth;
if ( $depthdiff < 0 ) {
for($i = 0; $i < -$depthdiff; $i++) {
echo "</ul>\n</li>\n";
}
} else {
for($i = 0; $i < $depthdiff; $i++) {
echo "\n<ul>\n";
$first = true;
// indeed buggy if $depthdiff > 1...
}
}
echo "<li rel=\"$rel\">", $mpath, "\n";
$rel++; $first = true;
}
$cdepth = $e['depth'];
}
for($i = 0; $i < $cdepth; $i++) {
echo "</ul>\n</li>\n";
}
echo "</ul>\n";
EDITED code: Still not perfect but you can work on it... :D

Categories