PHP array with levels to HTML tree - php

I have an array in php that looks kind of like this:
array[x][0]=item name
array[x][1]=level
so, for example,
array[0][0]="Node 1"
array[0][1]=0
array[1][0]="Node 2"
array[1][1]=0
array[2][0]="Node 2.1"
array[2][1]=1
array[3][0]="Node 2.1.1"
array[3][1]=2
array[4][0]="Node 2.2"
array[4][1]=1
I need to turn it into an html ul list, but one that looks like this:
<ul>
<li>
<input type="checkbox" /><span>Node 1</span>
</li>
<li>
<input type="checkbox" /><span>Node 2</span>
<ul>
<li>
<input type="checkbox" /><span>Node 2.1</span>
<ul>
<li><input type="checkbox"><span>Node 2.1.1</span></li>
</ul>
</li>
<li>
<input type="checkbox" /><span>Node 2.2</span>
</li>
</ul>
</li>
</ul>
The biggest catch is the position of the </li> tag - it needs to be after the next <ul> tag. So it's <li> current node <ul><li> child node </li></ul></li>. It's that last </li> that gets me.
This format is necessary to work with the jquery plugin I'm using (https://github.com/daredevel/jquery-tree).
I've seen the suggestions here: create html list from array with levels but that doesn't work due to the extra .
I think there's a solution to this using recursion and array slicing, but that seems inefficient. So far, my attempts at an iterative solution have failed...

You can use this function to convert your array to a nested HTML list (echo nestedHtmlList($array);)
function nestedHtmlList($array) {
$depth = 0;
$result = "";
foreach ($array as $node) {
$newDepth = $node[1];
if ($newDepth > $depth) {
$result .= "<ul><li>";
} else if ($newDepth < $depth) {
for ($d = $newDepth; $d < $depth; $d++) {
$result .= "</li></ul>";
}
$result .= "</li><li>";
} else if ($result == "") {
$result .= "<ul><li>";
} else {
$result .= "</li><li>";
}
$result .= "<input type='checkbox' /><span>";
$result .= $node[0];
$result .= "</span>";
$depth = $newDepth;
}
while ($depth > 0) {
$result .= "</li></ul>";
$depth -= 1;
}
return $result;
}

Tell me what you think:
Code
$array = [];
$array[0][0]="Node 1";
$array[0][1]=0;
$array[1][0]="Node 2";
$array[1][1]=0;
$array[2][0]="Node 2.1";
$array[2][1]=1;
$array[3][0]="Node 2.1.1";
$array[3][1]=2;
$array[4][0]="Node 2.2";
$array[4][1]=1;
function makeList( &$array, $level = null ) {
$html = '<ul>';
$html .= '<li>Level: ' . $level;
while( $element = array_shift( $array ) ) {
if( $level == $element[1] ) {
$html .= '</li><li><label><input type="checkbox"/> ' . $element[0] . '</label>';
} else if( $level < $element[1] ) {
array_unshift( $array, $element );
$html .= makeList( $array, $element[1] ) . '</li>';
} else {
array_unshift( $array, $element );
break;
}
}
$html .= '</ul>';
return $html;
}
echo '<pre>';
echo makeList($array);
Output (human rendered html :D)
Level:
[] Node 1
[] Node 2
Level: 1
[] Node 2.1
Level: 2
[] Node 2.1.1
[] Node 2.2

Related

Recursive foreach() producing duplicate results when looping

This class is designed to iterate over a WordPress menu structure (nested arrays/objects) to produce a finished menu. While the source of my data is WordPress, I feel this question belongs on SO instead of WP.SO because the issue is rooted more in PHP (applies to anyone attempting a recursion).
For some reason, I'm seeing duplicate results in the hierarchy. Also, I'm noticing that certain HTML elements are not closing properly. It seems like I've nested everything properly, but the result is what you see here.
To assist with debugging, I've added some * to impact the markup visually. Maybe you guys know something I don't. Fingers crossed and thanks in advance for your input!
My class
class Nav_Menu
{
public $wp_nav;
public $nested_nav;
public $recursion_depth = 0;
function __construct( $menu, $args = array() )
{
$format = new Format;
if( $menu )
{
$this->wp_nav = wp_get_nav_menu_items($menu, $args);
$this->nested_nav = $this->build_tree($this->wp_nav);
$output = $this->build_output($this->nested_nav);
$output_formatted = $format->HTML($output);
// echo $output;
echo $output_formatted;
}
}
private function build_output( $menu = array() )
{
$output = '**';
$output.= $this->recurse_menu($menu, $output);
return $output;
}
private function recurse_menu( $menu = array(), $output )
{
global $post;
if( !empty($menu) && !empty($output) )
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// process list wrappers
$output.= '<ul class="' . $this->process_classes($classes_ul) . '">';
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// process list items
$output.= '<li>' . $menu_val->title;
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$output.= $this->recurse_menu($menu_val->children, $output);
}
// process list items
$output.= '</li>';
}
// process list wrappers
$output.= '</ul>';
}
return $output;
}
private function process_classes($classes = array())
{
if( !$classes )
{
return;
}
return trim(implode(' ', $classes));
}
private function build_tree( $elements = array(), $parent_id = 0 )
{
$branch = array();
foreach($elements as $element)
{
if ($element->menu_item_parent == $parent_id)
{
$children = $this->build_tree($elements, $element->ID);
if ($children)
{
$element->children = $children;
}
$branch[] = $element;
}
}
return $branch;
}
}
$mynav = new Nav_Menu('Test Menu');
The resulting output
****
<ul class="depth-1">
<li>
One**
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three**
<ul class="depth-1">
<li>
One**
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three
<ul class="sub-menu depth-3">
<li>
Sub One
</li>
<li>
Sub Two
</li>
</ul>
</li>
<li>
Four
</li>
</ul>
WordPress menu in the backend
If someone knows the reason why, I'd be willing to know, so I'll reserve choosing an answer for now. My guess is some kind of odd namespacing/scope issue for the variable $output. Who knows, I'm kinda tired right now.
The fix to get a legit structure was this...
Class
class Nav_Menu
{
public $wp_nav;
public $nested_nav;
public $recursion_depth = 0;
public $output = '';
function __construct( $menu, $args = array() )
{
$format = new Format;
if( $menu )
{
$this->wp_nav = wp_get_nav_menu_items($menu, $args);
$this->nested_nav = $this->build_tree($this->wp_nav);
$this->build_output($this->nested_nav);
$output_formatted = $format->HTML($this->output);
// echo $this->output;
echo $output_formatted;
}
}
private function build_output( $menu = array() )
{
$this->recurse_menu($menu);
}
private function recurse_menu( $menu = array() )
{
global $post;
if( !empty($menu) )
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// process list wrappers
$this->output.= '<ul class="' . $this->process_classes($classes_ul) . '">';
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// process list items
$this->output.= '<li>';
$this->output.= $menu_val->title;
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$this->recurse_menu($menu_val->children);
}
// process list items
$this->output.= '</li>';
}
// process list wrappers
$this->output.= '</ul>';
}
}
private function process_classes($classes = array())
{
if( !$classes )
{
return;
}
return trim(implode(' ', $classes));
}
private function build_tree( $elements = array(), $parent_id = 0 )
{
$branch = array();
foreach($elements as $element)
{
if ($element->menu_item_parent == $parent_id)
{
$children = $this->build_tree($elements, $element->ID);
if ($children)
{
$element->children = $children;
}
$branch[] = $element;
}
}
return $branch;
}
}
$mynav = new Nav_Menu('Test Menu'); exit;
Resulting output
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three
<ul class="sub-menu depth-3">
<li>
Sub One
</li>
<li>
Sub Two
</li>
</ul>
</li>
<li>
Four
</li>
</ul>
I just created a private variable for the class, and each time I need to reference it as a storage location, I just append to it. Same as before, but no more having to pass $output down some crazy method chain.
If anyone has any other ideas that could help the community, please share!
Update your build_output method as below:
private function build_output( $menu = array() )
{
$output = '<ul>';
$output = $this->recurse_menu($menu, $output);
$output.= '</ul>';
return $output;
}
Update your recurse_menu method as below:
private function recurse_menu( $menu = array(), $output = '')
{
global $post;
if( !empty($menu))
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$output.= '<li>'.$menu_val->title.'<ul class="' . $this->process_classes($classes_ul) . '">'.$this->recurse_menu($menu_val->children).'</ul></li>';
}
else {
$output.= '<li>'.$menu_val->title.'</li>';
}
}
}
return $output;
}
NOTE: I have tested it with more further sub-level and it is working fine.

dynamic custom menu php

trying to get some thing like that dynamically
<ul>
<li>Home</li>
<li>About Us</li>
<li>Academics
<ul style="overflow: hidden; display: block; height: 0px; z-index: 51; opacity: 0.00980392;">
<li>Bs Computer Science</li>
<li>Diplomas (DIT & DCHE)</li>
<li>MBAIT</li>
</ul>
</li>
<li><a class=" " href="#">College</a>
</ul>
the code is
<?php
//========================================================
$result = mysql_query(" SELECT id, parentId, name
FROM
menu
ORDER BY
parentId, name");
$menuData = array(
'items' => array(),
'parents' => array()
);
while ($menuItem = mysql_fetch_assoc($result))
{
$menuData['items'][$menuItem['id']] = $menuItem;
$menuData['parents'][$menuItem['parentId']][] = $menuItem['id'];
}
function buildMenu($parentId, $menuData)
{
$html = '';
if (isset($menuData['parents'][$parentId]))
{
$html = '<ul id="main_menu">';
foreach ($menuData['parents'][$parentId] as $itemId)
{
$html .= '<li>' . $menuData['items'][$itemId]['name'];
// find childitems recursively
$html .= buildMenu($itemId, $menuData);
$html .= '</li>';
}
$html .= '</ul>';
}
return $html;
}
// output the menu
echo buildMenu(0, $menuData);
//=======================================================
?>
above code is showing only first parents elements in the menu and the remaing elements are not showing.. the menu is not working correctly becoz of the class id is not given in the ul tag.. and by writing this
echo '<ul id="main_menu">';
// output the menu
echo buildMenu(0, $menuData);
echo "</ul>";
it shows nothing in the menu
Ok, try this:
<?php
//========================================================
$result = mysql_query(" SELECT id, parentId, name, link
FROM
menu
ORDER BY
parentId, name");
$menuData = array(
'items' => array(),
'parents' => array()
);
while ($menuItem = mysql_fetch_assoc($result)) {
$menuData['items'][$menuItem['id']] = $menuItem;
$menuData['parents'][$menuItem['parentId']][] = $menuItem['id'];
}
function buildMenu($parentId, $menuData)
{
$html = '';
if (isset($menuData['parents'][$parentId]) && count( $menuData['parents'][$parentId] ) > 0 ) {
if( $parentId == "0" ){
$html = '<ul id="main_menu">';
}else{
$html = '<ul id="sub_menu">';
}
foreach ($menuData['parents'][$parentId] as $itemId) {
$html .= '<li>';
$html .= strlen($menuData['items'][$itemId]['link']) > 2?
''.$menuData['items'][$itemId]['name'].'':
$menuData['items'][$itemId]['name'];
$html .= buildMenu($itemId, $menuData);
$html .= "</li>";
}
$html .= '</ul>';
} else {
$html .= '<li>' . $menuData['items'][$parentId]['name'].'</li>';
}
return $html;
}
// output the menu
echo buildMenu(0, $menuData);
//=======================================================
?>

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;

How to increment $var value without a loop in PHP?

I have this issue where i want to increase a value with 1 and apply this to my HTML, but i can't use a for() or while() loop (at least i think i can't). I'm customizing an e-merchandise program (opencart) and my php knowledge isn't enough to tackle the problem.
There is this function which displays the categories from the store. It uses a variable that is constantly updated via $var .= "value".
I'm so far that i know how many sub-categories there are, but i don't know how to apply this range to my HTML.
I'm working towards a situation like below
<ul id="cats">
<li id="cat1">Cat
<ul id="sub1">
<li>item</li>
<li>item</li>
</ul>
</li>
<li id="cat2">Cat
<ul id="sub2">
<li>item</li>
<li>item</li>
</ul>
</li>
</ul>
I don't have a clue how to increase the count of the second unordered lists. Below the code where the second unordered lists are generated.
[..]
$cPiD = strlen($parent_id);
if ($results) {
if ($parent_id == 0) {
$output .= '<ul id="cats">';
} else {
$output .= '<ul id="sub'.$cPiD.'">';
}
}
[..]
The variable $cPiD holds the total amount of sub categories (in this case 2). I want this variable to automatically apply the correct number to the unordered list (so apply id="sub1" to the first unordered list and id="sub2" tot he second one (as in my example above)).
The problem is that i can't use a for() loop after the else part, because in my HTML i wil get two <ul> tags instead of one.
Below the PHP code where it all happens
$category_id = array_shift($this->path);
$output = '';
$results = $this->model_catalog_category->getCategories($parent_id);
$count = 0;
$cPiD = strlen($parent_id);
if ($results) {
if ($parent_id == 0) {
$output .= '<ul id="cats">';
} else {
$output .= '<ul id="sub'.$cPiD.'">';
}
}
foreach ($results as $result) {
$count++;
if (!$current_path) {
$new_path = $result['category_id'];
$output .= '<li id="cat'.$count.'">';
} else {
$new_path = $current_path . '_' . $result['category_id'];
$output .= '<li>';
}
$children = '';
$children = $this->getCategories($result['category_id'], $new_path);
$output .= $result['name'];
$output .= $children;
if (!$current_path) {
$output .= '</li>';
} else {
$output .= '</li>';
}
}
if ($results) {
if ($parent_id == 0) {
$output .= '</ul>';
} else {
$output .= '</ul>';
}
}
Does anybody maybe have an idea how to solve this?
EDIT:
Oh, i tries adding the following construction in the foreach() loop, but that gave problems when a certain categories don't have any sub categories.
if (!$current_path) {
$output .= '$result['name'] . ' <ul id="sub'.$count.'">';
}else{
$output .= $result['name'];
}
You can use this:
// at the top of your code (ouside of the loop)
$cPiD = 1;
// inside the loop you need to increment the parameter
$output .= '<ul id="sub'.$cPiD++.'">';
After each time the item is used, its value will be incremented by 1. (after already using it)

Php: Generate ul-li tree from active directory results

I've got a result set from adLDAP of the form
OU=LEAF1,OU=PARENT1,OU=ROOT,DC=datacenter,DC=local
OU=PARENT1,OU=ROOT,DC=datacenter,DC=local
OU=ROOT,DC=datacenter,DC=local
OU=LEAF2,OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
Where each line is a string element in an array.
The tree structure it represents is :
Root
|--Parent1
|--Leaf1
|--Parent2
|--Child
|--Leaf2
and I want to generate this
<ul>
<li>root
<ul>
<li>Parent1
<ul>
<li>leaf1</li>
</ul>
</li>
<li>Parent2
<ul>
<li>Child
<ul>
<li>Leaf2</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
</ul>
I know I need to process the strings backwards, and I know the solution is recursive, but it's friday afternoon, it's a long time since I've done it and my brain is stuck.
Here is my attempt:
<?php
$lines = array(
'OU=LEAF1,OU=PARENT1,OU=ROOT,DC=datacenter,DC=local',
'OU=PARENT1,OU=ROOT,DC=datacenter,DC=local',
'OU=ROOT,DC=datacenter,DC=local',
'OU=LEAF2,OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
'OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
'OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
);
//build tree structure
$tree = array();
foreach ($lines as $line) {
$ancestry = getLineAncestry($line);
$node = & $tree;
foreach ($ancestry as $nodeName) {
if (! isset($node[$nodeName])) {
$node[$nodeName] = array();
}
$node = & $node[$nodeName];
}
}
print makeUl($tree);
//recurse through tree to build unordered-list
function makeUl($array) {
$result = '<ul>';
foreach ($array as $nodeName => $children) {
$result .= '<li>' . ucfirst($nodeName);
if (count($children)) {
$result .= makeUl($children);
}
$result .= '</li>';
}
$result .= '</ul>';
return $result;
}
function getLineAncestry($line) {
$result = array();
$params = explode(',', $line);
foreach ($params as $param) {
$tmp = explode('=', $param);
if ($tmp[0] == 'OU') {
$result[] = $tmp[1];
}
}
$result = array_reverse($result);
return $result;
}

Categories