Получение модифицированной модели обхода дерева предзаказа (вложенный набор) в <ul> - PullRequest
21 голосов
/ 21 августа 2009

Я пытаюсь перевести мои данные, которые иерархически настроены с помощью модели обхода дерева, в

Ответы [ 7 ]

51 голосов
/ 27 августа 2009

Хорошо, давайте поохотимся;)

Шаг 0 - Пример дезинфекции:
Как уже упоминалось, ваш пример данных не работает, так как он не определяет допустимый вложенный набор. Если вы взяли эти данные из приложения, вы должны проверить логику вставки / удаления.

Итак, для тестирования я использовал продезинфицированную версию примерно так:
(MySQL здесь, так как он был первым под рукой)

CREATE TABLE t_categories`(
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(45) NOT NULL,
  `lft` INTEGER UNSIGNED NOT NULL,
  `rght` INTEGER UNSIGNED NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 1',1,16);
INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 2',2,3);
INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 3',4,7);
INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 4',5,6);
INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 5',8,13);
INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 6',9,12);
INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 7',10,11);
INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 8',14,15);

Шаг 1 - Пусть база данных выполняет порядок
Вложенные наборы, в первую очередь, изобретены как удобный способ хранения деревьев в базах данных, поскольку они позволяют довольно легко запрашивать поддеревья, родительские отношения и, что особенно интересно в этом случае, для порядка и глубины:

SELECT node.title, (COUNT(parent.title) - 1) AS depth
 FROM t_categories AS node
 CROSS JOIN t_categories AS parent
 WHERE node.lft BETWEEN parent.lft AND parent.rght
 GROUP BY node.title
 ORDER BY node.lft

Это вернет ваш набор аккуратно упорядоченным, начиная с корневого узла и продолжая до конца в предзаказе. Что наиболее важно, он добавит глубину каждого узла в виде положительного целого числа, указывающего, сколько уровней узел находится ниже корня (уровень 0). Для приведенных выше примеров данных результат будет:

title, depth
'Cat 1', 0
'Cat 2', 1
'Cat 3', 1
'Cat 4', 2
'Cat 5', 1
'Cat 6', 2
'Cat 7', 3
'Cat 8', 1

В коде:

// Grab ordered data
$query = '';
$query .= 'SELECT node.title, (COUNT(parent.title) - 1) AS depth';
$query .= ' FROM t_categories AS node';
$query .= ' CROSS JOIN t_categories AS parent';
$query .= ' WHERE node.lft BETWEEN parent.lft AND parent.rght';
$query .= ' GROUP BY node.title';
$query .= ' ORDER BY node.lft';

$result = mysql_query($query);

// Build array
$tree = array();
while ($row = mysql_fetch_assoc($result)) {
  $tree[] = $row;
}

Полученный массив будет выглядеть так:

Array
(
    [0] => Array
        (
            [title] => Cat 1
            [depth] => 0
        )

    [1] => Array
        (
            [title] => Cat 2
            [depth] => 1
        )
    ...
)

Шаг 2 - Вывод в виде фрагмента списка HTML:

Использование цикла while:

// bootstrap loop
$result = '';
$currDepth = -1;  // -1 to get the outer <ul>
while (!empty($tree)) {
  $currNode = array_shift($tree);
  // Level down?
  if ($currNode['depth'] > $currDepth) {
    // Yes, open <ul>
    $result .= '<ul>';
  }
  // Level up?
  if ($currNode['depth'] < $currDepth) {
    // Yes, close n open <ul>
    $result .= str_repeat('</ul>', $currDepth - $currNode['depth']);
  }
  // Always add node
  $result .= '<li>' . $currNode['title'] . '</li>';
  // Adjust current depth
  $currDepth = $currNode['depth'];
  // Are we finished?
  if (empty($tree)) {
    // Yes, close n open <ul>
    $result .= str_repeat('</ul>', $currDepth + 1);
  }
}

print $result;

Та же логика, что и у рекурсивной функции:

function renderTree($tree, $currDepth = -1) {
  $currNode = array_shift($tree);
  $result = '';
  // Going down?
  if ($currNode['depth'] > $currDepth) {
    // Yes, prepend <ul>
    $result .= '<ul>';
  }
  // Going up?
  if ($currNode['depth'] < $currDepth) {
    // Yes, close n open <ul>
    $result .= str_repeat('</ul>', $currDepth - $currNode['depth']);
  }
  // Always add the node
  $result .= '<li>' . $currNode['title'] . '</li>';
  // Anything left?
  if (!empty($tree)) {
    // Yes, recurse
    $result .=  renderTree($tree, $currNode['depth']);
  }
  else {
    // No, close remaining <ul>
    $result .= str_repeat('</ul>', $currNode['depth'] + 1);
  }
  return $result;
}

print renderTree($tree);

Оба выведут следующую структуру:

<ul>
    <li>Cat 1</li>
    <li>
        <ul>
            <li>Cat 2</li>
            <li>Cat 3</li>
            <li>
                <ul>
                    <li>Cat 4</li>
                </ul>
            </li>
            <li>Cat 5</li>
            <li>
                <ul>
                    <li>Cat 6</li>
                    <li>
                        <ul>
                            <li>Cat 7</li>
                        </ul>
                    </li>
                </ul>
            </li>
            <li>Cat 8</li>
        </ul>
    </li>
</ul>

Уголок Nitpickers: спрашивающий явно попросил <ul>, но упорядочил неупорядоченные списки !? Давай ...
; -)

16 голосов
/ 24 ноября 2009

Лучшая функция дерева рендеринга , которая работала для меня ( функция php для подготовки исходного кода html для использования в плагине jSuree jQuery ) вместо Генриха Опеля:

function MyRenderTree ( $tree = array(array('name'=>'','depth'=>'')) ){

$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="#"><ins>&nbsp;</ins>'.$node_name.'</a>';
    ++$counter;
}
 $result .= str_repeat('</li></ul>',$node_depth).'</li>';

$result .= '</ul>';

return $result;}

Результат HTML:

<ul>
    <li id="c1" class="open"><a href="#"><ins>&nbsp;</ins>ELECTRONICS</a>
        <ul>
            <li id="c2" class="open"><a href="#"><ins>&nbsp;</ins>TELEVISIONS</a>
                <ul>
                    <li id="c3"><a href="#"><ins>&nbsp;</ins>TUBE</a></li>
                    <li id="c4"><a href="#"><ins>&nbsp;</ins>LCD</a></li>
                    <li id="c5"><a href="#"><ins>&nbsp;</ins>PLASMA</a>
                        <ul>
                            <li id="c14"><a href="#"><ins>&nbsp;</ins>PLASMA1</a></li>
                            <li id="c15"><a href="#"><ins>&nbsp;</ins>PLASMA2</a></li>
                        </ul>
                    </li>
                </ul>
            </li>
            <li id="c6" class="open"><a href="#"><ins>&nbsp;</ins>PORTABLE ELECTRONICS</a>
                <ul>
                    <li id="c7"><a href="#"><ins>&nbsp;</ins>MP3 PLAYERS</a>
                        <ul>
                            <li id="c8"><a href="#"><ins>&nbsp;</ins>FLASH</a></li>
                        </ul>
                    </li>
                    <li id="c9"><a href="#"><ins>&nbsp;</ins>CD PLAYERS</a></li>
                    <li id="c10"><a href="#"><ins>&nbsp;</ins>2 WAY RADIOS</a></li>
                </ul>
            </li>
        </ul>
    </li>
</ul>
4 голосов
/ 21 августа 2009

Существует пакет PEAR для работы с вложенными наборами: DB_NestedSet .
Вас также может заинтересовать статья Управление иерархическими данными в MySQL .

1 голос
/ 16 января 2011

Просто зациклите результат:

$sql = "SELECT node.name, (COUNT(parent.name) - 1) AS depth
        FROM nested_category AS node,
        nested_category AS parent
        WHERE node.lft BETWEEN parent.lft AND parent.rgt
        GROUP BY node.name
        ORDER BY node.lft";

$query_result = mysql_query($sql)

$result = "<ul>";
$currDepth = 0;

while($row = mysql_fetch_assoc($query_result))
{
  if($row['depth'] > $currDepth)
  {
    $result .= "<li><ul>"; // open sub tree if level up
  }

  if($row['depth'] < $currDepth)
  {
    $result .= str_repeat("</ul></li>", $currDepth - $row['depth']); // close sub tree if level down
  }

  $result .= "<li>$row['name']</li>"; // Always add node
  $currDepth = $row['depth'];
}
$result .= "</ul>";

echo $result;
1 голос
/ 26 августа 2009

Это должно быть то, что вы ищете:

function getCats($left = null, $right = null)
{
    $sql = array();
    $result = null;

    if (isset($left) === true)
    {
        $sql[] = 'lft >= ' . intval($left);
    }

    if (isset($right) === true)
    {
        $sql[] = 'rght <= ' . intval($right);
    }

    if (empty($sql) === true)
    {
        $sql[] = 'lft = 1';
    }

    $sql = 'SELECT * FROM t_categories WHERE ' . implode(' AND ', $sql) . ';';

    if ($rs = C_DB::fetchRecordset($sql))
    {
        // you need to make sure that the query returns
        // something to correctly display the ULs
        if (empty($rs) === false)
        {
            $result .= '<ul>' . "\n";

            while ($row = C_DB::fetchRow($rs))
            {
                $result .= '<li>' . $row['title'] . '</li>' . "\n";
                $result .= getCats($row['lft'], $row['rght']);
            }

            $result .= '</ul>' . "\n";
        }
    }

    return $result;
}

Чтобы получить HTML для вашего вложенного дерева, вы должны сделать:

echo getCats();

Обратите внимание, что пример с вложенным множеством выглядит неправильно, также вы должны убедиться, что я не допустил ошибок при вызове вашего класса C_DB, я не знаю, так как я не знаком с ним. *

0 голосов
/ 03 ноября 2011

я использую запрос CROSS JOIN, отображающий меню jsTree jQuery; Все работает просто отлично! К существующей таблице я добавил столбец для позиции. Однако, когда я определяю позицию и упорядочиваю все по позиции, соответствующие элементы не группируются должным образом. Я предполагаю, что это вопрос запроса, перепробовал много комбинаций, но безуспешно.

0 голосов
/ 02 августа 2010
$linaje='';
    $lastnode='';
    $sides['izq']=array();
    $sides['der']=array();
    $print = '<ul>'; 
    foreach ($array as $key1 => $value1){ //Proyectos

        if(strpos($info[$key1]['linaje'],'-') !== false)
            $compare = strstr($info[$key1]['linaje'],'-',true);
        else
            $compare  = $info[$key1]['linaje'];

        if($linaje != ''){
            if ($linaje !=   $compare){
                $linaje= $compare;
                $sides['izq']=array();
                $sides['der']=array();
                //for($i=1;$i <= substr_count($lastnode,'`')-substr_count($value1,'`');$i++)
                    //$print .= '</ul></li>';
            }
        }


        if ($lastnode != '')
            for($i=1;$i<= substr_count($lastnode,'`')-substr_count($value1,'`');$i++)
                $print .= '</ul></li>'; 

        if (count($sides['der'])>0)
            if  ($sides['der'][count($sides['der'])-1] > $info[$key1]['der'])
                $print .= '<ul>';

        $print .= '<li><a href="#'.$info[$key1]['id'].'#'.$info[$key1]['linaje'].'">'.substr($value1,substr_count($value1,'`')).'</a>';

        if  ($info[$key1]['der'] - $info[$key1]['izq'] == 1)
                $print .= '</li>';

        if ($key1 == count($info)-1)
            for($i=1;$i <= substr_count($lastnode,'`')-1;$i++)
                $print .= '</ul></li>';

        $sides['der'][] = $info[$key1]['der'];
        $sides['izq'][] = $info[$key1]['izq'];

        if ($linaje =='')
                $linaje = $info[$key1]['linaje'];

        $lastnode = $value1;
    }
    $print .= '</ul>';
    echo $print;

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

...