В БД имеется структура данных Nested sets и проект с использованием yii2/
Нужно отобразить эту структуру, в виде вложенных друг в друга Collapse
.
Вот так выглядит структура отсортированная по RGT:
В итоге должно получится следующее:
-*spoiler*
--*spoiler*
--*spoiler*
----*spoiler*
-*spoiler*
--*spoiler*
----*spoiler*
-*spoiler*
-*spoiler*
-*spoiler*
-*spoiler*
-*spoiler*
-*spoiler*
-*spoiler*
--*spoiler*
-*spoiler*
Добавление спойлера происходит следующим образом. Это yii2 виджет, который возвращает верстку спойлеров, с уже наложенными стилями и проставленными id
по которым будет осуществлятся js-сворачивание-разворачивание :
private static function createCollapse($label, $content)
{
return Collapse::widget(
[
'items' => [
[
//В таблице поле LABEL
'label' => $label,
//В качестве контента ID записи
'content' => $content
]
]
]
);
}
Верстка выглядит примерно так:
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><a class="collapse-toggle collapsed" href="#w7-collapse7" data-toggle="collapse" data-parent="#w7" aria-expanded="false">Main Spoiler</a></h4>
</div>
<div id="w7-collapse7" class="panel-collapse collapse" aria-expanded="false" style="height: 0px;">
<div class="panel-body">
<div id="w6" class="panel-group collapse in" aria-expanded="true" style="">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><a class="collapse-toggle collapsed" href="#w6-collapse1" data-toggle="collapse" data-parent="#w6" aria-expanded="false">Sub spoiler</a></h4>
</div>
<div id="w6-collapse1" class="panel-collapse collapse" aria-expanded="false" style="height: 0px;">
<div class="panel-body">
content
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><a class="collapse-toggle collapsed" href="#w6-collapse2" data-toggle="collapse" data-parent="#w6" aria-expanded="false">Sub Spoiler</a></h4>
</div>
<div id="w6-collapse2" class="panel-collapse collapse" aria-expanded="false" style="height: 0px;">
<div class="panel-body">
content
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Как я понимаю, просто пройти из корня по всем детям не получится, потому что каждый верхний виджет-спойлер уже должен знать о своем содержании, которое может в свою очередь состоять из неограниченного количества таких же виджетов-спойлеров, и так до бесконечности.
Один из вариантов решения - одним запросом получить таблицу отсортированную по RGT, и проверять текущий LVL на 3 случая - больше\меньше\равно - пример реализации с <ul><li>
. Но я никак не могу адаптировать этот пример под использование виджета :(
Второй вариант, как предложил @fedornabilkin, но nested sets
не хранят в себе parent id
, и опять же, возникает трудность с оборачиванием в Collapse::widget
Буду рад любой помощи!
Полагаю, что надо одним запросом получить все данные. Затем подготовить многомерный массив, в который разложить потомков для каждого родителя.
$cats = [];
foreach($rows as $model){
$cats[$model->parent][] = $model;
}
А затем этот массив скинуть в рекурсивный метод. Что-то типа такого для построения списков в виде дерева.
public static function createTree($cats, $parent)
{
if(isset($cats[$parent]) && is_array($cats[$parent])) {
$tree = '<ul>';
foreach ($cats[$parent] as $model) {
$tree .= '<li>' . $model->title;
$tree .= self::createTree($cats, $model->id);
$tree .= '</li>';
}
$tree .= '</ul>';
}
else{
return null;
}
return $tree;
}
Во вьюшке вызываем метод echo className::createTree($cats, 1);
UPD
Для наглядности можно выполнить пример кода:
$rows = [];
$rows[] = ['id' => 1, 'title' => 'title 1', 'parent' => 0];
$rows[] = ['id' => 2, 'title' => 'title 2', 'parent' => 0];
$rows[] = ['id' => 3, 'title' => 'title 1 1', 'parent' => 1];
$rows[] = ['id' => 4, 'title' => 'title 1 2', 'parent' => 1];
$rows[] = ['id' => 5, 'title' => 'title 1 2 1', 'parent' => 4];
$rows[] = ['id' => 6, 'title' => 'title 1 2 2', 'parent' => 4];
$rows[] = ['id' => 7, 'title' => 'title 3', 'parent' => 0];
$rows[] = ['id' => 8, 'title' => 'title 3 1', 'parent' => 7];
$rows[] = ['id' => 9, 'title' => 'title 3 2', 'parent' => 7];
foreach($rows as $model){
$cats[$model['parent']][] = $model;
}
function createTree($cats, $parent)
{
if(isset($cats[$parent]) && is_array($cats[$parent])) {
$tree = '<ul>';
foreach ($cats[$parent] as $model) {
$tree .= '<li>' . $model['title'];
$tree .= createTree($cats, $model['id']);
$tree .= '</li>';
}
$tree .= '</ul>';
}
else{
return null;
}
return $tree;
}
echo createTree($cats, 0);
Итак, немного разобравшись с рекурсией и nested sets
было написано вот такое решение:
Получаем дерево, которое нужно отрисовать, одним запросом из бд:
SELECT * FROM table WHERE ROOT = $root ORDER BY lft
Далее, создадим вспомогательные функции для поиска потомков и фильтрации дерева по LVL
:
/**
* Выделяет из дерева $tree потомков узла $node
*
* @param Tree[] $tree Массив узлов дерева для фильтрации
* @param Tree $node Узел дерева потомки которого будут возвращены
* @return Tree[]|[]
*/
public static function filterChildren(array $tree, Tree $node)
{
return array_filter(
$tree,
function ($element) use ($node) {
return $element->LFT > $node->LFT
&& $element->RGT < $node->RGT
&& $element->ROOT === $node->ROOT;
}
);
}
/**
* Фильтрация дерева по параметру LVL;
* Результирующий массив будет содержать только узлы с уровнем `$lvl`
*
* @param Tree[] $tree Массив узлов дерева для фильтрации
* @param int $lvl Уровень по которому осущесвляется фильтрация
* @return Tree[]
*/
public static function filterByLvl(array $tree, $lvl)
{
return array_filter(
$tree,
function ($element) use ($lvl) {
return $element->LVL === $lvl;
}
);
}
Метод для рендеринга коллапсов, по сути, для сокращения кода:
private static function createCollapse(Tree $node, $content)
{
return Collapse::widget(
[
'items' => [
[
'label' => $node->NAME,
'content' => $content
]
]
]
);
}
Сама рекурсивная функция будет выглядеть вот так:
/**
* Рендеринг дерева в виде спойлеров.
*
* Метод возвращает HTML верстку дерева выполненную в виде вложенных друг в друга спойлеров.
*
* @param array $roots Массив узлов дерева для которых необходимо построить спойлеры
* @param array $fullTree Полное дерево, включая детей и предков всех элементов
*
* @return string
*/
public static function renderAsCollapses(array $roots, array $fullTree)
{
$collapses = '';
foreach ($roots as $key => $node) {
/** @var Tree $node */
//Проверка на наличие потомков
if ($node->RGT - $node->LFT > 1) {
$collapses .= self::createCollapse(
$node,
self::renderAsCollapses(
ClassifierTree::filterChildren($fullTree, $node),
$fullTree,
)
);
} else {
$collapses .= self::createCollapse(
$node,
'content'
);
}
}
return $collapses;
}
Использование:
//$tree содержит массив элементов дерева, полученный из БД с помощью запроса
//SELECT * FROM table WHERE ROOT = $root ORDER BY lft
$collapses = TreeViewHelper::renderAsCollapses(
//Для корректной работы функции, первым аргументом необходимо передать
//массив корневых элементов, для которого будет строится дерево.
//Начинать построение можно от любого уровня вложенности.
Tree::filterByLvl($tree, 1),
$tree,
);
Немного странным выглядит то, что в функцию нужно передавать два массива. Это сделано для того, что бы при первом вызове функции спойлеры отрисовывались только для корневых элементов. Если же в функцию передать всё дерево целиком, то функция рендеринга выполнится вообще для каждого узла дерева. В таком случае на выходе мы получим столько спойлеров, сколько элементов в нашем дереве, плюс каждый элемент имеющий потомков так же будет содержать в себе вложенные спойлеры. Возможно этой проблемы можно избежать более лаконичным способом, но я, к сожалению, не смог его найти. Буду рад если кто то подскажет.
Единственное, что меня смущает в этом решении, это вопрос о переполнении стэка. На сколько большой должна быть вложенность, что бы переполнился стэк?
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Всем привет, подскажите пожалуйста хочу создать pdf файл, есть html таблица со стилямиКак мне вставить мой html код?
Работаю с фреймворком Yii2Установил widget, https://github