Obtener el modelo de conjunto nested en un pero ocultar subárboles “cerrados”

Basado en Obtener un modelo de cruce de árbol de preordenamiento modificado (conjunto nested) en un

    Una de las respuestas dio el código correcto para mostrar el árbol completo. Lo que necesito es mostrar siempre el primer nivel (profundidad = 0) y los hermanos + niños para el elemento de la lista activa. El objective es expandir la parte visible del árbol cuando el usuario selecciona el elemento de la lista que es principal para más elementos de la lista.

    Entonces, si tengo esta lista:

    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 

    y si el elemento de la lista actual es “2”, la lista debería ser así:

     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 

    y si el elemento de la lista actual es “2.2.”, la lista debería ser así:

     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 

    A continuación hay un código de ejemplo que funciona bien para mostrar el árbol completo. También agregué lft / rgt / current que serán necesarios para resolver mi problema.

     '','depth'=>'', 'lft'=>'','rgt'=>'')) , $current=false){ $current_depth = 0; $counter = 0; $result = '
      '; 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 .= ''; } elseif($node_depth > $current_depth){ $result .= '
        '; $current_depth = $current_depth + ($node_depth - $current_depth); } elseif($node_depth < $current_depth){ $result .= str_repeat('
      ',$current_depth - $node_depth).''; $current_depth = $current_depth - ($current_depth - $node_depth); } $result .= '<li id="c'.$node_id.'"'; $result .= $node_depth '.$node_name.''; ++$counter; } $result .= str_repeat('
    ',$node_depth).''; $result .= '
'; return $result; } // "$current" may contain category_id, lft, rgt for active list item print MyRenderTree($categories,$current); ?>

Como ya se las arregló para ordenar la secuencia, ¿por qué no solo producir según sea necesario?

Como algunas hojas deben aparecer cerradas, el iterador debería poder omitir los elementos secundarios de nodos no seleccionados.

Hacerlo me lleva a una idea para resolver el problema de terminar el árbol de salida (salida = análisis). ¿Qué hacer si el último nodo válido en la secuencia tiene una profundidad mayor que 0? Agregué un terminador NULL para eso. Por lo tanto, los niveles abiertos se pueden cerrar antes de que termine el ciclo.

Además, el iterador sobrecarga los nodos para ofrecer métodos comunes sobre ellos, como la comparación con el elemento seleccionado actualmente.

La función MyRenderTree ( Demo / Código completo )

Editar: El Codepad Demo tiene problemas, aquí está el código fuente: Gist
Obteniendo el modelo de conjunto nested en un subárboles “cerrado” pero oculto

 function MyRenderTree($tree = array(array('name'=>'','depth'=>'', 'lft'=>'','rgt'=>'')) , $current=false) { $sequence = new SequenceTreeIterator($tree); echo '
    '; $hasChildren = FALSE; foreach($sequence as $node) { if ($close = $sequence->getCloseLevels()) { echo str_repeat('
', $close); $hasChildren = FALSE; } if (!$node && $hasChildren) { echo '', "\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('
  • %s', implode(' ', $classes), $node['name']); if ($hasChildren) if ($isSelected) echo '
      '; else $sequence->skipChildren() ; else echo '
  • ' ; } echo ''; }

    Esto también se puede resolver en un foreach único y en algunas variables, sin embargo, creo que para la reutilización, la implementación basada en los iteradores de SPL es mejor.

    En lugar de usar script PHP para manejar la navegación de árbol, se puede usar Jquery. Una vez que se genera el árbol, el rest de las cosas se gestionarán en el propio cliente, y también se guardarán las solicitudes del servidor.

    Ver muestra 2 y 3

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

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

    Puede ayudar según su requisito.

    La función espera que $ tree se ordene por ‘left’.

    Modifiqué tu función a los elementos seleccionados en función del valor ‘izquierda’ y ‘derecha’. Espero que sea lo que buscas.

    La función modificada:

     function MyRenderTree($tree = array(array('name' => '', 'depth' => '', 'lft' => '', 'rgt' => '')), $current=false) { $current_depth = 0; $counter = 0; $found = false; $nextSibling = false; $result = '
      '; 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 .= ''; } elseif ($node_depth > $current_depth) { $result .= '
      '; $current_depth = $current_depth + ($node_depth - $current_depth); } elseif ($node_depth < $current_depth) { $result .= str_repeat('
    ', $current_depth - $node_depth) . ''; $current_depth = $current_depth - ($current_depth - $node_depth); } $result .= '
  • '; ++$counter; } unset($found); unset($nextSibling); $result .= str_repeat('
  • ', $node_depth) . ''; $result .= ''; return $result; }

    Uso:

     $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/ es un plugin de jQuery que manejará esto para usted mucho más elegante y rápidamente que tratar de hacer una solución basada en PHP.

    Visite http://www.jstree.com/demo para obtener una demostración en vivo e instrucciones sobre cómo implementar el tom.

    Basado en la respuesta de satrun77. Creé un ayudante para 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 .= ''; } elseif ($node_level > $current_node_level) { $html .= '
      '; $current_node_level = $current_node_level + ($node_level - $current_node_level); } elseif ($node_level < $current_node_level) { $html .= str_repeat('
    ', $current_node_level - $node_level) . ''; $current_node_level = $current_node_level - ($current_node_level - $node_level); } $html .= sprintf('
  • %s
    ', $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('
  • ', $node_level) . ''; $html = '
      '. $html .'
    '; return $render ? print($html) : $html; }

    Etiquetas adicionales: árbol , nodo

    Este método comprueba si el nodo es padre del nodo seleccionado, el nodo seleccionado o profundidad = 0. Solo las iteraciones para nodos que cumplen con una de estas condiciones agregan elementos de lista a la cadena de resultados. Todos los nodos obtienen la clase seleccionada, la clase abierta o ambas. De lo contrario, es tu código.

     $current_depth = 0; $counter = 0; $result = '
      '; 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 .= ''; } elseif($node_depth > $current_depth) { $result .= '
      '; $current_depth = $current_depth + ($node_depth - $current_depth); } elseif($node_depth < $current_depth) { $result .= str_repeat('
    ',$current_depth - $node_depth).''; $current_depth = $current_depth - ($current_depth - $node_depth); } $result .= '
  • '; ++$counter; } } $result .= str_repeat('
  • ',$node_depth).''; $result .= ''; return $result; }

    // “$ current” puede contener category_id, lft, rgt para el elemento activo de la lista print MyRenderTree ($ categories, $ current); ?>

    Solo quería proporcionar una versión más limpia de OOP, que debería facilitar agregar cualquier tipo de lógica aparte de la seleccionada.

    Funciona correctamente con la estructura de matriz publicada por @ 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( '
  • %s%s
  • ', $this->category, $this->name, $renderedNodes ); } protected function renderNodes() { $renderedNodes = ''; foreach ( $this->nodes as $node ) { $renderedNodes .= $node->render(); } return sprintf( '
      %s
    ', $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(); }

    ¿No es la mejor solución? ¿Por qué hay tantas clases, objetos bla bla …? esta simple función es perfecta y flexible en todos los sentidos. MANIFESTACIÓN

     $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 .= '
  • '.$category['name'].'
  • '; continue; } $haveSelected |= $isVisible | $isSelected; $html .= '
  • '.$category['name'].$childHtml.'
  • '; } return $parent ? array('
      '.$html.'
    ',$haveSelected) : '
      '.$html.'
    '; }