Получение вложенной модели набора в <ul>, но скрытие «закрытых» поддеревьев - PullRequest
9 голосов
/ 11 октября 2011

На основе Получение модифицированной модели обхода дерева предзаказа (вложенный набор) в

    Один из ответов дал правильный код для отображения полного дерева. Что мне нужно, это всегда показывать первый уровень (глубина = 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);
    ?>
    

    Ответы [ 8 ]

    8 голосов
    / 16 октября 2011

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

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

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

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

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

    Редактировать: Проблемы с демо-кодовым блоком, вот исходный код: Суть
    Получение модели вложенного множества вскрытие «закрытых» поддеревьев

    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 Iterators лучше.

    2 голосов
    / 19 октября 2011

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

    См. Образцы 2 и 3

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

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

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

    1 голос
    / 21 января 2012

    Основано на ответе satrun77.Я создал помощника для + + (http://www.doctrine -project.org / projects / orm / 1.2 / docs / manual / иерархические данные/ ru):

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

    Дополнительные теги: ,

    1 голос
    / 19 октября 2011

    http://www.jstree.com/ - это плагин jQuery, который сделает это для вас гораздо более элегантно и быстро, чем попытка создать решение на основе PHP.

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

    1 голос
    / 14 октября 2011

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

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

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

    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);
    
    0 голосов
    / 20 октября 2011

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

    Он работает правильно со структурой массива, опубликованной @ 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();
    }
    
    0 голосов
    / 20 октября 2011

    не лучшее ли это решение. почему есть так много классов, объекты бла-бла ..? эта простая функция идеальна и гибка во всех отношениях. 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>';
    }
    
    0 голосов
    / 19 октября 2011

    Этот метод проверяет, является ли узел родителем выбранного узла, выбранного узла или глубины = 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 для активного элемента списка распечатать MyRenderTree ($ category, $ current); ?>

    ...