Based on Getting a modified preorder tree traversal model (nested set) into a
One of answers gave right code to display full tree. What i need is to alway
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 .= '><a href="#">' . $node_name .'(' . $node['lft'] . '-' . $node['rgt'] . ')' . '</a>';
++$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.
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>';
}
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"><a href="">%s</a>%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();
}
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 .= '><a href="#">'.$node_name.'</a>';
++$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); ?>
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.