Получение измененной модели обхода дерева предзаказов (вложенный набор) в <ul>
Я пытаюсь получить свои данные, которые иерархически настроены с помощью модели обхода дерева в <ul> , чтобы показать на моем сайте.
Вот мой код:
function getCats($) {
// retrieve all children of $parent
$query = "SELECT max(rght) as max from t_categories";
$row = C_DB::fetchSingleRow($query);
$max = $row["max"];
$result ="<ul>";
$query = "SELECT * from t_categories where lft >=0 and rght <= $max";
if($rs = C_DB::fetchRecordset($query)){
$p_right ="";
$p_left ="";
$p_diff="";
while($row = C_DB::fetchRow($rs)){
$diff = $row["rght"] -$row["lft"];
if($diff == $p_diff){
$result.= "<li>".$row['title']."</li>";
}elseif (($row["rght"] - $row["lft"] > 1) && ($row["rght"] > $p_right)){
$result. "<ul>";
$result.= "<li>".$row['title']."</li>";
}else{
$result.= "<li>".$row['title']."</li>";
}
$p_right = $row["rght"];
$p_left = $row["lft"];
$p_diff = $diff;
}
}
$result.= "</ul>";
return $result;
}
Вот моя примерная таблица:
|ID | TITLE | lft| rght |
|1 | Cat 1 | 1 | 16 |
|18 | Cat 2 | 3 | 4 |
|22 | Cat 3 | 5 | 6 |
|28 | Cat 4 | 7 | 8 |
|34 | Cat 5 | 9 | 9 |
|46 | Cat 6 | 11 | 10 |
|47 | Cat 7 | 13 | 12 |
|49 | Cat 8 | 15 | 14 |
Теперь он выводит что-то вроде:
<ul>
<li>Cat 1</li>
<li>Cat 2</li>
<li>Cat 3</li>
<li>Cat 4</li>
<li>Cat 5</li>
<li>Cat 6</li>
<li>Cat 7</li>
<li>Cat 8</li>
</ul>
Может ли кто-нибудь сказать мне, почему или как он выводит список в иерархической структуре?
Связанный раздел
Ответы
Ответ 1
Хорошо, давайте немного охоты за головами;)
Шаг 0 - пример Sanitize:
Как уже упоминалось, данные вашего примера нарушены, так как он не определяет допустимый вложенный набор. Если вы взяли эти данные из приложения, вы должны проверить логику вставки/удаления.
Итак, для тестирования я использовал санированную версию, например:
(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
Это вернет ваш набор, упорядоченный по порядку, начиная с корня node и заканчивая в preorder. Самое главное, это добавит глубину каждого node как положительное целое число, указывая, сколько уровней node ниже корня (уровень 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 corner: Questioner явно запросил <ul>
, но упорядочил неупорядоченные списки!? Давай...
; -)
Ответ 2
Лучшая функция дерева рендеринга, которая работала для меня (php-функция для подготовки источника HTML для использования в jsTree jQuery-плагине) вместо Henrik Opel one:
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> </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> </ins>ELECTRONICS</a>
<ul>
<li id="c2" class="open"><a href="#"><ins> </ins>TELEVISIONS</a>
<ul>
<li id="c3"><a href="#"><ins> </ins>TUBE</a></li>
<li id="c4"><a href="#"><ins> </ins>LCD</a></li>
<li id="c5"><a href="#"><ins> </ins>PLASMA</a>
<ul>
<li id="c14"><a href="#"><ins> </ins>PLASMA1</a></li>
<li id="c15"><a href="#"><ins> </ins>PLASMA2</a></li>
</ul>
</li>
</ul>
</li>
<li id="c6" class="open"><a href="#"><ins> </ins>PORTABLE ELECTRONICS</a>
<ul>
<li id="c7"><a href="#"><ins> </ins>MP3 PLAYERS</a>
<ul>
<li id="c8"><a href="#"><ins> </ins>FLASH</a></li>
</ul>
</li>
<li id="c9"><a href="#"><ins> </ins>CD PLAYERS</a></li>
<li id="c10"><a href="#"><ins> </ins>2 WAY RADIOS</a></li>
</ul>
</li>
</ul>
</li>
</ul>
Ответ 3
Там PEAR-пакет для работы с вложенными наборами: DB_NestedSet.
Вы также можете быть заинтересованы в статье Управление иерархическими данными в MySQL.
Ответ 4
Это должно быть то, что вы ищете:
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, я не знаю, так как я не знаком с ним.
Ответ 5
Простой цикл через результат будет:
$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;
Ответ 6
$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 чисел деревьев, это относится к одному из моих проектов. и я использую char как ссылку на глубину, когда я извлекаю строки из DB
Ответ 7
i`m используя запрос CROSS JOIN, отображающий jQuery меню jsTree; Все работает просто отлично!
В существующей таблице я добавил столбец для позиции. Однако, когда я определяю позицию и упорядочиваю все по положению, соответствующие элементы не группируются должным образом. Я предполагаю, что это проблема запроса, попробовал много комбинаций, но не имел успеха.