Получение вложенной модели набора в <ul>, но скрытие «закрытых» поддеревьев

На основе получения измененной модели обхода дерева предзаказов (вложенного набора) в <ul>

Один из ответов дал правильный код для отображения полного дерева. Мне нужно всегда показывать первый уровень (глубина = 0) и братьев и сестер + детей для активного элемента списка. Цель состоит в том, чтобы развернуть видимую часть дерева, когда пользователь выбирает элемент списка, который является родительским для большего количества элементов списка.

Итак, если я получил этот список:

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 

и если текущий элемент списка «2.», список должен выглядеть так:

 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 

и если текущий элемент списка «2.2.», список должен выглядеть так:

 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 

Ниже приведен пример кода, который хорошо работает для отображения полного дерева. Я также добавил lft / rgt / current, который понадобится для решения моей проблемы.

 <?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 .= '><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); ?> 

Как вам уже удалось отсортировать последовательность, почему бы просто не выводить по мере необходимости?

Поскольку некоторые листы должны появляться закрытыми, поэтому итератор должен иметь возможность пропускать дочерние объекты из невыбранных узлов.

Это приводит меня к идее решить проблему завершения выходного дерева (output = parsing). Что делать, если последний действительный узел в последовательности находится на более высокой глубине, чем 0? Я добавил для этого терминатор NULL. Таким образом, все еще открытые уровни могут быть закрыты до завершения цикла.

Кроме того, итератор перегружает узлы, чтобы предлагать им общие методы, например, сравнение с выбранным в данный момент элементом.

Функция MyRenderTree ( демо / полный код )

Изменить: у Demo Codepad есть проблемы, вот исходный код: Gist
Вставка вложенной модели набора в скрытые «закрытые» поддеревья

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

Это можно решить и в одном foreach и некоторых переменных, но я думаю, что для повторного использования небезопасно лучше реализовать реализацию на основе Итераторов SPL .

Вместо использования скрипта PHP для обработки навигации по дереву можно использовать JQuery. После генерации дерева остальная часть вещей будет обрабатываться на самом клиенте, она также сохранит запросы сервера.

См. Примеры 2 и 3

http://jquery.bassistance.de/treeview/demo/

http://docs.jquery.com/Plugins/Treeview

Это может помочь в соответствии с вашим требованием.

Функция ожидает, что $ tree будет порядком по левому.

Я изменил вашу функцию на выбранные элементы на основе значения «left» и «right». Надеюсь, это то, что вам нужно.

Измененная функция:

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

Применение:

 $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/ – это плагин jQuery, который будет обрабатывать это для вас гораздо более элегантно и быстро, чем попытка сделать решение на базе PHP.

Ознакомьтесь с http://www.jstree.com/demo для демонстрации в реальном времени и инструкции о том, как реализовать tom.

На основании ответа сатрун77. Я создал помощник для 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; } 

Дополнительные теги: дерево , узел

Этот метод проверяет, является ли узел родителем выбранного узла, выбранного узла или глубины = 0. Только итерации для узлов, которые соответствуют одному из этих условий, добавляют элементы списка в строку результатов. Все узлы получают либо выбранный класс, либо открытый класс, либо оба. В противном случае это ваш код.

 $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» может содержать category_id, lft, rgt для активного элемента списка print MyRenderTree ($ categories, $ current); ?>

Просто хотел предоставить OOP, более чистую версию, которая должна облегчить добавление какой-либо логики, кроме выбранной.

Он корректно работает с структурой массива, размещенной @ 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(); } 

это не лучшее решение. почему так много классов, объектов bla bla ..? эта простая функция является совершенной и гибкой во всех отношениях. 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>'; } эта $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>'; } не $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>'; }