How to loop an array in array (dynamic navigation) - php

I have an array with some items. Each array could have (or not) an subarray, also with some items.
How can I call the subarray in a loop? It is difficult to describe, here is the code. I know the code/syntax is not correct, but the syntax should clarify my problem:
<?php
$subitemsA = array(
'subA1' => array('num'=>65, 'text'=>'Labor', 'url'=>'#'),
'subA2' => array('num'=>44, 'text'=>'Rare', 'url'=>'#'),
);
$subitemsB = array(
'subB1' => array('num'=>0, 'text'=>'subB1', 'url'=>'#'),
'subB2' => array('num'=>0, 'text'=>'subB2', 'url'=>'#'),
'subB3' => array('num'=>0, 'text'=>'subB3', 'url'=>'#')
);
$navArray = array(
'Home' => array('num'=>0, 'text'=>'Home', 'url'=>'#'),
'Info' => array('num'=>0, 'text'=>'Info', 'url'=>'#', 'subArray'=>$subitemsA),
'Sport' => array('num'=>0, 'text'=>'Sport', 'url'=>'#', 'subArray'=>$subitemsB),
);
$html = '';
foreach($navArray as $item) {
$html .= "<li>";
$html .= "<a href='{$item['url']}'><i class='abc'></i>{$item['text']}</a>\n";
if (count($navArray) > 3) {
foreach($navArray.subArray as $subitem) {
$html .= "<li>";
$html .= "<a href='{$subitem['url']}'>{$subitem['text']}</a>\n";
$html .= "</li>";
}
}
$html .= "</li>";
}
The first foreach loop works. But how can I access the subArray of Info and Sport?

You need a three level foreach for this to work -
foreach($navArray as $key => $item) {
$html .= "<li>";
$html .= "<a href='{$item['url']}'><i class='abc'></i>{$item['text']}</a>\n";
foreach ($item as $itemkey => $value) {
if (is_array($value)) { //Now Check if $value is an array
foreach($value as $valuekey => $subitem) { //Loop through $value
$html .= "<li>";
$html .= "<a href='{$subitem['url']}'>{$subitem['text']}</a>\n";
$html .= "</li>";
}
}
}
$html .= "</li>";
}

This is an answer to your question in more general way: How to deal with multi-level nested array using recursion and template.
function parseArray(array $navArray, &$html, $depth = 0) {
foreach ($navArray as $item) {
$html .= "<li>";
// this function use template to create html
$html .= toHtml($item['url'], $item['text'], $depth);
foreach ($item as $subItem) {
if (is_array($subItem)) {
// use recursion to parse deeper level of subarray
parseArray($item, $html, $depth + 1);
}
}
$html .= "</li>";
}
}
function toHtml($url, $text, $depth)
{
$template = '';
if ($depth == 0) {
$template = '<a href=\'{{url}}\'><i class=\'abc\'></i>{{text}}</a>\n';
} elseif ($depth >= 1) {
$template = '<a href=\'{{url}}\'>{{text}}</a>\n';
}
// define more template for deeper level here if you want
$template = str_replace('{{url}}', $url, $template);
$template = str_replace('{{text}}', $text, $template);
return $template;
}
$html = '';
parseArray($navArray, $html);
Just hurrily forge this code out of mind, haven't test it yet. Hope it help.
Regards,

Related

How to refactor similar foreach loops to follow DRY approach

If you have foreach loops of multiple arrays, and you need to run one of them inside another - Is it possible to refactor the following code to only have one loop to stop duplicating code:
<?php
$apples = array(
'red',
);
$bananas = array(
'yellow',
);
foreach ( $apples => $apple ) {
echo '<div class="apple">' . $apple . '</div>';
}
foreach ( $bananas => $banana ) {
echo '<div class="banana">';
echo $banana;
echo '<div class="child">';
foreach ( $apples => $apple ) {
echo '<div class="apple">' . $apple . '</div>';
}
echo '</div>';
echo '</div>';
}
/*
output:
<div class="apple">red</div>
<div class="banana">
yellow
<div class="child">
<div class="apple">red</div>
</div>
</div>
*/
This code logic that is duplicate is the one that outputs the <div class="apple">red</div>:
foreach ( $apples => $apple ) {
echo '<div class="apple">' . $apple . '</div>';
}
I'm not sure if it's much improvement without being given better context, but using a function for generating the list should help reduce duplicate code.
<?php
$apples = array(
'red',
);
$bananas = array(
'yellow',
);
function getFruitListHTML(array $fruitList, string $class, string $child = ''): string {
$out = '';
foreach ( $fruitList as $fruitItem ) {
$out .= "<div class=\"$class\">";
$out .= $fruitItem;
$out .= $child;
$out .= '</div>';
}
return $out;
}
$applesHTML = getFruitListHTML($apples, 'apple');
echo $applesHTML;
echo getFruitListHTML($bananas, 'banana', "<div class=\"child\">$applesHTML</div>");

How to convert <br>'s to (un)ordered list in PHP?

I have an array which is coming from db. Array has strings which is delimited by <br>. So I want to explode them and convert it to a
<ul>
<li>...</li>
</ul>
structure using a function (listIt($list)):
function listIt($list)
{
$list = mb_convert_case($list, MB_CASE_TITLE, 'UTF-8');
$text = explode('<br>', $list);
$menu = '<ul>';
foreach ($text as $li)
{
$menu .= '<li>' . $li . '</li>';
}
return $menu . '</ul>';
}
My Array (rawMenu) is the following:
array (size=5)
0 => string 'Banana<br>Cheese<br>Egg<br>Salad<br>Water<br>Juice<br>Coffee' (length=62)
1 => string 'Soup<br>Potato<br>Chicken<br>Fish<br>Juice<br>Wine<br>Salad' (length=61)
2 => string 'Banana<br>Cheese<br>Egg<br>Salad<br>Water<br>Juice<br>Coffee' (length=62)
3 => string 'Soup<br>Potato<br>Chicken<br>Fish<br>Juice<br>Wine<br>Salad' (length=61)
4 => string 'Banana<br>Cheese<br>Egg<br>Salad<br>Water<br>Juice<br>Coffee' (length=62)
The problem is string which I pass to listIt($rawMenu[4]) function returns:
<ul>
<li>Banana<br>Cheese<br>Egg<br>Salad<br>Water<br>Juice<br>Coffee</li>
</ul>
This happens because MB_CASE_TITLE converts the first letter to upper case (<Br>), then you have to split on Br:
$st = 'Banana<br>Cheese<br>Egg<br>Salad<br>Water<br>Juice<br>Coffee';
$list = mb_convert_case($st, MB_CASE_TITLE, 'UTF-8');
$text = explode('<Br>', $list);
$menu = '<ul>';
foreach ($text as $li){
$menu .= '<li>' . $li . '</li>';
}
$menu .= '</ul>';
print_r($menu);
Outputs:
<ul><li>Banana</li><li>Cheese</li><li>Egg</li><li>Salad</li><li>Water</li><li>Juice</li><li>Coffee</li></ul>
Working example in sandbox here.
As noted in the comments by #LightnessRacesinOrbit good practice would be to swap explode and conversion:
$st = 'Banana<br>Cheese<br>Egg<br>Salad<br>Water<br>Juice<br>Coffee';
$text = explode('<br>', $st);
$menu = '<ul>';
foreach ($text as $li){
$menu .= '<li>' . $li . '</li>';
}
$menu .= '</ul>';
$menu = mb_convert_case($menu, MB_CASE_TITLE, 'UTF-8');
print_r($menu);

Php Multidimensional array for navigation

Needed Navigation Html
Home
Pages
About
Services
Products
Contact
FAQs
Sitemap
Privacy Policy
Column Layouts
1 Column
2 Column (Left Sidebar)
2 Column (Right Sidebar)
3 Column
4 Column
I want to use php arrays and foreach loops to output the needed html.
The php code I have thus far is:
<?php
$data = array("navigation");
$data['navigation']['Home'] = base_url();
$data['navigation']['Pages'] = base_url('pages');
$data['navigation']['Pages']['About'] = base_url('pages/about');
echo '<ul>';
foreach($data as $nav) {
foreach($nav as $subNavKey => $subNavHref) {
echo "<li><a href='$subNavHref'>$subNavKey</a>";
}
}
echo '</ul>';
?>
I was thinking I would need three foreach loops nested but php warnings/errors are generated when the third loop is reached on lines such as:
$data['navigation']['Home'] = base_url();
$data['navigation']['Pages'] = base_url('pages');
I'm not quite sure how to test for 3rd level depths such as:
$data['navigation']['Pages']['About'] = base_url('pages/about');
Also, outputting the needed li and ul tags in the proper positions has given me trouble aswell.
Use recursion
$data['navigation']['Home'] = base_url();
$data['navigation']['Pages'] = base_url('pages');
$data['navigation']['Pages']['About'] = base_url('pages/about');
$data['navigation']['Pages']['About']['Team'] = base_url('pages/team');
$data['navigation']['Pages']['About']['Team']['Nate'] = base_url('pages/nate');
echo "<ul>"
print_list($data);
echo "</ul>"
function print_list($menu) {
foreach($menu as $key=>$item) {
echo "<li>";
if(is_array($item)) {
echo "<ul>";
print_list($item);
echo "</ul>";
} else {
echo "<a href='{$val}'>$key</a>";
}
echo "</li>";
}
}
<?php
function nav($data) {
$html = '<ul>';
foreach ($data as $k => $v) {
if (is_array($v)) {
$html .= "<li>$k" . nav($v) . "</li>";
}
else {
$html .= "<li><a href='$k'>$v</a>";
}
}
$html .= '</ul>';
return $html;
}
echo nav($data);
A recursive function can get the job done:
$items = array(
"Home",
"Pages" => array(
"About",
"Services",
"Products",
"Contact",
"FAQs",
"Sitemap",
"Privacy Policy",
"Column Layouts" => array(
"1 Column",
"2 Column (Left Sidebar)",
"2 Column (Right Sidebar)",
"3 Column",
"4 Column"
)
)
);
function getMenu($array) {
foreach($array as $key => $value) {
if(is_array($value)) {
echo "<li>" . $key . "</li>";
echo "<ul>";
getMenu($value);
echo "</ul>";
} else {
echo "<li>" . $value . "</li>";
}
}
}
echo "<ul>";
getMenu($items);
echo "</ul>";
Output:
You should use a recursive function, for example (Working Demo):
function makeMenu($array)
{
$menu = '';
foreach($array as $key => $value) {
if(is_array($value)) {
$menu .= '<li>' . $key . '<ul>' . makeMenu($value) . '</ul></li>';
}
else {
$menu .= "<li><a href='". $value ."'>" . $value ."</a></li>";
}
}
return $menu;
}
Then call it like:
$data = array(
"Home",
"Pages" => array("About", "Services"),
"Column Layouts" => array("1 Column", "2 Column (Left Sidebar)")
);
echo '<ul>' . makeMenu($data) . '</ul>';

Iterate through folder array ( file system like ) and return HTML nested list

I am having the following array
Array
(
[0] => Homework2 (1).java
[test2] => Array
(
)
[1] => space-art-hd-473771.jpg
[2] => Homework2.java
[3] => factura_oct_2013.pdf
[test] => Array
(
[0] => Homework2 (1).java
[1] => space-art-hd-473771.jpg
[2] => Homework2.java
)
[test3] => Array
(
[0] => Homework2 (1).java
[testintest] => Array
(
[0] => Homework2 (1).java
[1] => space-art-hd-473771.jpg
[2] => Homework2.java
)
[1] => space-art-hd-473771.jpg
[2] => Homework2.java
)
)
and I am trying to display an HTML nested list. So each child of the array should be a new UL in the parent UL. I have tried to get this recursive but I only managed to get a lot of values that I already have.
function map2ul($file_map,$html="") {
var_dump($file_map);
print "<br>";
if ($html == "") $html = '<ul>';
else $html .= '<ul>';
foreach ($file_map as $df) :
if (is_array($df)) :
$html .= $this->map2ul($df,$html);
else :
$html .= '<li>';
$html .= $df;
$html .= '</li>';
endif;
endforeach;
$html .= '</ul>';
return $html;
}
This is what I have tried but it seems that the foreach statement is recurred more times than it's needed.
Can someone please point me the error so I can understand better how to recurse this?
The error is in this lines:
if ($html == "") $html = '<ul>';
else $html .= '<ul>';
When you try to call this function recursively, the non-empty $html is passed recursively causing the interior loop to glue sub-folder contents. #AgreeOrNot has already pointed one solution. The other is not to pass $html to the recursively called function:
$html .= $this->map2ul($df);
But I think that your approach introduces some unnecessary logic and processing.
By analyzing the array we can draw one important conclusion: if the array value is an array, the corresponding key is folder name. In other case the value is file name:
function map2ul($file_map) {
$html = '<ul>';
foreach ($file_map as $file_id_or_folder_name => $file_name_or_folder_content) {
// if the element is a file
if ( ! is_array($file_name_or_folder_content)) {
$html .= '<li>' . $file_name_or_folder_content . '</li>';
} else { // if the element is a folder
$html .= '<li>/ ' . $file_id_or_folder_name . '</li>';
}
}
$html .= '</ul>';
return $html;
}
Now it's enough to inject sub-folders contents where it's necessary, i.e. for folders:
function map2ul($file_map) {
$html = '<ul>';
foreach ($file_map as $file_id_or_folder_name => $file_name_or_folder_content) {
// if the element is a file
if ( ! is_array($file_name_or_folder_content)) {
$html .= '<li>' . $file_name_or_folder_content . '</li>';
} else { // if the element is a folder
$html .= '<li>/ ' . $file_id_or_folder_name . '</li>';
// injecting sub-folder's contents
$html .= map2ul($file_name_or_folder_content);
}
}
$html .= '</ul>';
return $html;
}
Edit
if I want for this piece of code $html .= '<li>' . $file_name_or_folder_content . '</li>'; to append the data-filepath attribute, which will be the parent_folder/file_name path, what do I need to do?
You have to pass the full path to the function. And the full path needs to be constructed at the function call:
function map2ul($file_map, $path = '') {
$path = $path . '/';
$html = '<ul>';
foreach ($file_map as $file_id_or_folder_name => $file_name_or_folder_content) {
// if the element is a file
if ( ! is_array($file_name_or_folder_content)) {
$html .= '<li data-filepath="' . $path . $file_name_or_folder_content . '">' . $file_name_or_folder_content . '</li>';
} else { // if the element is a folder
$html .= '<li>/ ' . $file_id_or_folder_name . '</li>';
$html .= map2ul($file_name_or_folder_content, $path . $file_id_or_folder_name);
}
}
$html .= '</ul>';
return $html;
}
Try without second parameter, e.g map2ul($file_map) and with second parameter, e.g map2ul($file_map, 'c:/my_files').
$html .= $this->map2ul($df,$html);
It shouldn't be a concatenation, because the recursive call is based on the parameter $html - that is, the concatenation is already done in the recursive call. Change it to:
$html = $this->map2ul($df,$html);

Better dynamic navigation menu

I have this approach for now:
ctop = $cnew = $cmine = '';
if($actual == 'top') $ctop = 'class="active"';
if($actual == 'last') $new = 'class="active"';
if($actual == 'mine') $cmine = 'class="active"';
$html = '<aside class="panel_derecho">
<div class="tabs"><h4>$donde</h4>
<ul>';
$js = "refrescar_submenu('last_$donde')";
$js_t = "refrescar_submenu('top_$donde')";
$js_r = "refrescar_submenu('mine_$donde')";
$html .= '<li '.$ctop.'>top</li>';
$html .= '<li '.$cnew.'>ultimos</li>';
$html .= '<li '.$mine.'>like</li>';
$html .= ' </ul>
</aside>';
return $html;
wich works as expected:
It generates a list with the desired copy, the desired javascript function parameter, and the active class (for the wanted one)
But i feel it could be less repetitive; and i can already see that it will be expensive to add/edit/remove copys, params, elements, etc.. i just don't know where to beggin..
In case it helps:
$donde represents the type of data (articles, songs, videos, ..)
$actual represents one atribute (new articles, top articles,
articles i like)
// Menu: link => text
$menu = array(
"top" => "top",
"last" => "ultimos",
"mine" => "like"
);
$html = '<aside class="panel_derecho">
<div class="tabs"><h4>$donde</h4>
<ul>';
foreach ($menu as $link => $text)
{
$html .= '<li '.( $link==$actual ? 'class="active"' : '').'>'.$text.'</li>';
}
$html .= ' </ul>
</aside>';
return $html;
$attributes = array("top" => "top", "last" => "ultimos", "mine" => "like");
$html = "<aside class=\"panel_derecho\">
<div class=\"tabs\"><h4>{$donde}</h4>
<ul>";
foreach ($attributes as $key=>$value)
$html .= " <li ". ($actual == $key ? "class=\"active\"" : "") .">{$value}</li>";
$html .= " </ul>
</div>
</aside>";
return $html;

Categories