bootstrap nested navbar with php recursive iteration - php

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;
}

Related

Echo multidimensional array in a html table

I am having a multidimensional array with same values like this,
[documents] => Array
(
[0] => Array
(
[doc_id] => 3
[doc_type] => driving_licence
[expiry_date] => 2015-11-26
[added_date] => 2015-11-16
)
[1] => Array
(
[doc_id] => 3
[doc_type] => driving_licence
[expiry_date] => 2015-11-26
[added_date] => 2015-11-16
)
)
So now I need to echo this array in a html table with single <tr>.
This is how I tried it :
foreach ($userData as $key => $value) {
$i=0;
foreach ($value['documents'] as $k => $v) {
$i++;
$html = "<tr>\n";
$html .= " <td class='center'>{$i}</td>";
$html .= " <td>{$v['doc_type']}</td>";
$html .= " <td>{$v['expiry_date']}</td>";
$html .= " <td>{$v['added_date']}</td>";
$html .= "</tr>\n";
echo $html;
}
}
But its repeating table <tr> with same values.
Can anybody tell me how to avoid this?
Thank you.
As requested, here is an example for you.
$userData = $input = array_map("unserialize", array_unique(array_map("serialize", $userData)));
foreach ($userData as $key => $value) {
$i=0;
foreach ($value['documents'] as $k => $v) {
$i++;
$html = "<tr>\n";
$html .= " <td class='center'>{$i}</td>";
$html .= " <td>{$v['doc_type']}</td>";
$html .= " <td>{$v['expiry_date']}</td>";
$html .= " <td>{$v['added_date']}</td>";
$html .= "</tr>\n";
echo $html;
}
}
That will give you a table with 1 <tr> row.
this is the way i would do what you are trying to do:
$movies = array
(
array('Office Space' , 'Comedy' , 'Mike Judge' ),
array('Matrix' , 'Action' , 'Andy / Larry Wachowski' ),
array('Lost In Translation' , 'Comedy / Drama' , 'Sofia Coppola' ),
array('A Beautiful Mind' , 'Drama' , 'Ron Howard' ),
array('Napoleon Dynamite' , 'Comedy' , 'Jared Hess' )
);
echo "<table border=\\"1\\">";
echo "<tr><th>Movies</th><th>Genre</th><th>Director</th></tr>";
for ( $row = 0; $row < count($movies); $row++ )
{
echo "<tr><td>";
for ( $column = 0; $column < count($movies); $column++ )
{
echo $movies[$row][$column] ;
}
echo "<td></tr>";
}

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;

php create navigation menu from multidimensional array dynamically

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>";
}
}

Getting nested set model into a <ul> but hiding "closed" subtrees

Based on Getting a modified preorder tree traversal model (nested set) into a <ul>
One of answers gave right code to display full tree. What i need is to always show first level (depth=0) and siblings+childrens for active list item. Goal is to expand visible part of tree when user selects list item which is parent for more list items.
So, if i got this list:
1. item
2. item
2.1. item
2.2. item
2.2.1. item
2.2.2. item
2.2.3. item
2.3. item
2.4. item
2.4.1. item
2.4.2. item
3. item
4. item
4.1. item
4.2. item
4.2.1. item
4.2.2. item
5. item
and if current list item is "2.", list should look like that:
1. item
2. item // this needs class .selected
2.1. item
2.2. item
2.3. item
2.4. item
3. item
4. item
5. item
and if current list item is "2.2.", list should look like that:
1. item
2. item // this needs class .selected
2.1. item
2.2. item // this needs class .selected
2.2.1. item
2.2.2. item
2.2.3. item
2.3. item
2.4. item
3. item
4. item
5. item
Below there is an example code which works well for me to display full tree. I also added lft/rgt/current which will be needed to solve my issue.
<?php
function MyRenderTree ( $tree = array(array('name'=>'','depth'=>'', 'lft'=>'','rgt'=>'')) , $current=false){
$current_depth = 0;
$counter = 0;
$result = '<ul>';
foreach($tree as $node){
$node_depth = $node['depth'];
$node_name = $node['name'];
$node_id = $node['category_id'];
if($node_depth == $current_depth){
if($counter > 0) $result .= '</li>';
}
elseif($node_depth > $current_depth){
$result .= '<ul>';
$current_depth = $current_depth + ($node_depth - $current_depth);
}
elseif($node_depth < $current_depth){
$result .= str_repeat('</li></ul>',$current_depth - $node_depth).'</li>';
$current_depth = $current_depth - ($current_depth - $node_depth);
}
$result .= '<li id="c'.$node_id.'"';
$result .= $node_depth < 2 ?' class="open"':'';
$result .= '>'.$node_name.'';
++$counter;
}
$result .= str_repeat('</li></ul>',$node_depth).'</li>';
$result .= '</ul>';
return $result;
}
// "$current" may contain category_id, lft, rgt for active list item
print MyRenderTree($categories,$current);
?>
As you already managed to sort the sequence, why not just output as needed?
As some leafs need to appear closed, so the iterator should be able to skip children of non-selected nodes.
Doing so lead me to an idea to solve the problem of terminating the output tree (output = parsing). What to do if the last valid node in the sequence is at a higher depth than 0? I appended a NULL terminator for that. So still open levels can be closed before the loop finishes.
Additionally the iterator overloads nodes to offer common methods on them, like comparing against the currently selected element.
The MyRenderTree function (Demo/Full code)
Edit: The Demo Codepad has problems, here is the source-code: Gist
Getting nested set model into a but hiding “closed” subtrees
function MyRenderTree($tree = array(array('name'=>'','depth'=>'', 'lft'=>'','rgt'=>'')) , $current=false)
{
$sequence = new SequenceTreeIterator($tree);
echo '<ul>';
$hasChildren = FALSE;
foreach($sequence as $node)
{
if ($close = $sequence->getCloseLevels())
{
echo str_repeat('</ul></li>', $close);
$hasChildren = FALSE;
}
if (!$node && $hasChildren)
{
echo '</li>', "\n";
}
if (!$node) break; # terminator
$hasChildren = $node->hasChildren();
$isSelected = $node->isSupersetOf($current);
$classes = array();
$isSelected && ($classes[] = 'selected') && $hasChildren && $classes[] = 'open';
$node->isSame($current) && $classes[] = 'current';
printf('<li class="%s">%s', implode(' ', $classes), $node['name']);
if ($hasChildren)
if ($isSelected)
echo '<ul>';
else
$sequence->skipChildren()
;
else
echo '</li>'
;
}
echo '</ul>';
}
This can be solved as well in a single foreach and some variables, however I think for re-useablilty, the implementation based on the SPL Iterators is better.
Instead of using PHP script for handling tree navigation, Jquery can be used.
Once the tree is generated rest of the things will handled on client itself, it will also save server requests.
See Sample 2 and 3
http://jquery.bassistance.de/treeview/demo/
http://docs.jquery.com/Plugins/Treeview
It may help as per your requirement.
The function expect the $tree is order by the 'left'.
I have modified your function to selected items based on the 'left' and 'right' value. Hope it's what you are after.
The modified function:
function MyRenderTree($tree = array(array('name' => '', 'depth' => '', 'lft' => '', 'rgt' => '')), $current=false)
{
$current_depth = 0;
$counter = 0;
$found = false;
$nextSibling = false;
$result = '<ul>';
foreach ($tree as $node) {
$node_depth = $node['depth'];
$node_name = $node['name'];
$node_id = 1;//$node['category_id'];
if ($current !== false) {
if ($node_depth ==0) {
if ($node['lft'] <= $current['lft'] && $node['rgt'] >= $current['rgt']) {
// selected root item
$root = $node;
}
} else if (!isset($root)) {
// skip all items that are not under the selected root
continue;
} else {
// when selected root is found
$isInRange = ($root['lft'] <= $node['lft'] && $root['rgt'] >= $node['rgt']);
if (!$isInRange) {
// skip all of the items that are not in range of the selected root
continue;
} else if (isset($current['lft']) && $node['lft'] == $current['lft']) {
// selected item reached
$found = true;
$current = $node;
} else if ($nextSibling !== false && $nextSibling['depth'] < $node['depth']) {
// if we have siblings after the selected item
// skip any other childerns in the same range or the selected root item
continue;
} else if ($found && $node_depth == $node['depth']) {
// siblings after the selected item
$nextSibling = $node;
}
}
} else if ($node_depth > 0) {
// show root items only if no childern is selected
continue;
}
if ($node_depth == $current_depth) {
if ($counter > 0)
$result .= '</li>';
}
elseif ($node_depth > $current_depth) {
$result .= '<ul>';
$current_depth = $current_depth + ($node_depth - $current_depth);
} elseif ($node_depth < $current_depth) {
$result .= str_repeat('</li></ul>', $current_depth - $node_depth) . '</li>';
$current_depth = $current_depth - ($current_depth - $node_depth);
}
$result .= '<li id="c' . $node_id . '" ';
$result .= $node_depth < 2 ?' class="open"':'';
$result .= '>' . $node_name .'(' . $node['lft'] . '-' . $node['rgt'] . ')' . '';
++$counter;
}
unset($found);
unset($nextSibling);
$result .= str_repeat('</li></ul>', $node_depth) . '</li>';
$result .= '</ul>';
return $result;
}
Usage:
$categories = array(
array('name' => '1. item',
'depth' => '0',
'lft' => '1',
'rgt' => '2'),
array('name' => '2. item',
'depth' => '0',
'lft' => '3',
'rgt' => '22'),
array('name' => '2.1 item',
'depth' => '1',
'lft' => '4',
'rgt' => '5'),
array('name' => '2.2 item',
'depth' => '1',
'lft' => '6',
'rgt' => '13'),
array('name' => '2.2.1 item',
'depth' => '2',
'lft' => '7',
'rgt' => '8'),
array('name' => '2.2.2 item',
'depth' => '2',
'lft' => '9',
'rgt' => '10'),
array('name' => '2.2.3 item',
'depth' => '2',
'lft' => '11',
'rgt' => '12'),
array('name' => '2.3 item',
'depth' => '1',
'lft' => '14',
'rgt' => '15'),
array('name' => '2.4 item',
'depth' => '1',
'lft' => '16',
'rgt' => '21'),
array('name' => '2.4.1 item',
'depth' => '2',
'lft' => '17',
'rgt' => '18'),
array('name' => '2.4.2 item',
'depth' => '2',
'lft' => '19',
'rgt' => '20'),
array('name' => '3. item',
'depth' => '0',
'lft' => '23',
'rgt' => '24'),
array('name' => '4. item',
'depth' => '0',
'lft' => '25',
'rgt' => '34'),
array('name' => '4.1 item',
'depth' => '1',
'lft' => '26',
'rgt' => '27'),
array('name' => '4.2 item',
'depth' => '1',
'lft' => '28',
'rgt' => '33'),
array('name' => '4.2.1 item',
'depth' => '2',
'lft' => '29',
'rgt' => '30'),
array('name' => '4.2.2 item',
'depth' => '2',
'lft' => '31',
'rgt' => '32',
'category_id' => 5),
array('name' => '5. item',
'depth' => '0',
'lft' => '35',
'rgt' => '36'),
);
$current = array('lft' => '9', 'rgt' => '10');
print MyRenderTree($categories, $current);
http://www.jstree.com/ is a jQuery plugin which will handle this for you far more elegantly and quickly than trying to do a PHP based solution.
Check out http://www.jstree.com/demo for a live demo and instruction on how tom implement.
Based on answer by satrun77. I created a helper for symfony + doctrine + nestedset (http://www.doctrine-project.org/projects/orm/1.2/docs/manual/hierarchical-data/en):
function render_tree_html_list($nodes, Doctrine_Record $current_node, $render = true) {
$html = '';
$current_node_level = $current_node->getLevel();
$counter = 0;
$found = false;
$nextSibling = false;
foreach ($nodes as $i => $node):
$node_level = $node->getLevel();
$node_name = $node->getTitulo();
$node_id = $node->getId();
if ($current_node !== false) {
if ($node_level == 0) {
if ($node->getLft() <= $current_node->getLft() && $node->getRgt() >= $current_node->getRgt()) {
// selected root item
$root = $node;
}
} else if (!isset($root)) {
// skip all items that are not under the selected root
continue;
} else {
// when selected root is found
$isInRange = ($root->getLft() <= $node->getLft() && $root->getRgt() >= $node->getRgt());
if (!$isInRange) {
// skip all of the items that are not in range of the selected root
continue;
} else if ($current_node->getLft() && $node->getLft() == $current_node->getLft()) {
// selected item reached
$found = true;
$current_node = $node;
} else if ($nextSibling !== false && $nextSibling->getLevel() < $node->getLevel()) {
// if we have siblings after the selected item
// skip any other childerns in the same range or the selected root item
continue;
} else if ($found && $node_level == $node->getLevel()) {
// siblings after the selected item
$nextSibling = $node;
}
}
} else if ($node_level > 0) {
// show root items only if no childern is selected
continue;
}
if ($node_level == $current_node_level) {
if ($counter > 0)
$html .= '</li>';
}
elseif ($node_level > $current_node_level) {
$html .= '<ol>';
$current_node_level = $current_node_level + ($node_level - $current_node_level);
} elseif ($node_level < $current_node_level) {
$html .= str_repeat('</li></ol>', $current_node_level - $node_level) . '</li>';
$current_node_level = $current_node_level - ($current_node_level - $node_level);
}
$html .= sprintf('<li node="%d" class="%s"><div>%s</div>',
$node_id,
(isset($nodes[$i + 1]) && $nodes[$i + 1]->getLevel() > $node_level) ? "node" : "leaf",
$node->getLevel() > 0 ? link_to($node->getTitulo(), 'cms_categoria_edit', $node) : $node->getTitulo()
);
++$counter;
endforeach;
$html .= str_repeat('</li></ol>', $node_level) . '</li>';
$html = '<ol class="sortable">'. $html .'</ol>';
return $render ? print($html) : $html;
}
Extra tags: tree, node
This method checks to see if the node is a parent of the selected node, the selected node, or depth=0. Only iterations for nodes which meet one of these conditions add list items to the result string. All of the nodes get either the selected class, open class or both. Otherwise, it is your code.
$current_depth = 0;
$counter = 0;
$result = '<ul>';
foreach($tree as $node){
$node_depth = $node['depth'];
$node_name = $node['name'];
$node_id = $node['category_id'];
$selected = false;
if( $node['lft'] <= current['lft'] && $node['rgt'] >= $current['rgt'] ) $selected=true
if ($node_depth == 0 || $selected == true)
{
if($node_depth == $current_depth)
{
if($counter > 0) $result .= '</li>';
}
elseif($node_depth > $current_depth)
{
$result .= '<ul>';
$current_depth = $current_depth + ($node_depth - $current_depth);
}
elseif($node_depth < $current_depth)
{
$result .= str_repeat('</li></ul>',$current_depth - $node_depth).'</li>';
$current_depth = $current_depth - ($current_depth - $node_depth);
}
$result .= '<li id="c'.$node_id.'"';
$result .= ' class="';
$result .= $node_depth < 2 ?' open':' ';
$result .= $select == true ?' selected':' ';
$result .= '"';
$result .= '>'.$node_name.'';
++$counter;
}
}
$result .= str_repeat('</li></ul>',$node_depth).'</li>';
$result .= '</ul>';
return $result;
}
// "$current" may contain category_id, lft, rgt for active list item
print MyRenderTree($categories,$current);
?>
Just wanted to provide a OOP, cleaner version, which should make it easier to add any sort of logic apart from the selected one.
It works properly with the array structure posted by #satrun77.
class Node
{
var $name;
var $category;
var $depth;
var $lft;
var $rgt;
var $selected;
var $nodes = array();
public function __construct( $name, $category, $depth, $lft, $rgt, $selected = false )
{
$this->name = $name;
$this->category = $category;
$this->depth = $depth;
$this->lft = $lft;
$this->rgt = $rgt;
$this->selected = $selected;
}
public function addNode( Node $node )
{
array_push( $this->nodes, $node );
}
public function render()
{
$renderedNodes = '';
if ( $this->isSelected() ) {
$renderedNodes = $this->renderNodes();
}
return sprintf( '<li id="c%s">%s%s</li>', $this->category, $this->name, $renderedNodes );
}
protected function renderNodes()
{
$renderedNodes = '';
foreach ( $this->nodes as $node )
{
$renderedNodes .= $node->render();
}
return sprintf( '<ul>%s</ul>', $renderedNodes );
}
/** Return TRUE if this node or any subnode is selected */
protected function isSelected()
{
return ( $this->selected || $this->hasSelectedNode() );
}
/** Return TRUE if a subnode is selected */
protected function hasSelectedNode()
{
foreach ( $this->nodes as $node )
{
if ( $node->isSelected() )
{
return TRUE;
}
}
return FALSE;
}
}
class RootNode extends Node
{
public function __construct() {}
public function render()
{
return $this->renderNodes();
}
}
function MyRenderTree( $tree, $current )
{
/** Convert the $tree array to a real tree structure based on the Node class */
$nodeStack = array();
$rootNode = new RootNode();
$nodeStack[-1] = $rootNode;
foreach ( $tree as $category => $rawNode )
{
$node = new Node( $rawNode['name'], $category, $rawNode['depth'], $rawNode['lft'], $rawNode['rgt'], $rawNode['lft'] == $current['lft'] );
$nodeStack[($node->depth -1)]->addNode( $node );
$nodeStack[$node->depth] = $node;
end( $nodeStack );
}
/** Render the tree and return the output */
return $rootNode->render();
}
isn't it the best solution. why there are so many classes, objects bla bla.. ?
this simple function is perfect and flexible in everyways.
DEMO
$categories = array(
array('id'=>1,'name'=>'test1','parent'=>0),
array('id'=>2,'name'=>'test2','parent'=>0),
array('id'=>3,'name'=>'test3','parent'=>1),
array('id'=>4,'name'=>'test4','parent'=>2),
array('id'=>5,'name'=>'test5','parent'=>1),
array('id'=>6,'name'=>'test6','parent'=>4),
array('id'=>7,'name'=>'test7','parent'=>6),
array('id'=>8,'name'=>'test7','parent'=>3)
);
$cats = array();
foreach($categories as &$category)
$cats[$category['parent']][] = $category;
unset($categories);
$selected = 6; // selected id;
echo standartCategory($cats,$selected);
function standartCategory(&$categories,$selected = '',$parent = 0 /*MAIN CATEGORY*/)
{
if (!isset($categories[$parent])) return array('',0);
$html = '';
$haveSelected = 0;
foreach($categories[$parent] as $category) {
list($childHtml,$isVisible) = standartCategory($categories,$selected,$category["id"]);
$isSelected = $category['id']===$selected;
if (! ($isVisible | $isSelected)) { // this if to prevent output
$html .= '<li>'.$category['name'].'</li>';
continue;
}
$haveSelected |= $isVisible | $isSelected;
$html .= '<li>'.$category['name'].$childHtml.'</li>';
}
return $parent ? array('<ul>'.$html.'</ul>',$haveSelected) : '<ul>'.$html.'</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