Открытие узлов дерева через AJAX
Рубрика: Перевод Cookbook
11 мая 2010Сегодня разговор снова пойдет про деревья :) Наткнулась мне заметка «Display an AJAX tree from your DB using CtreeView» которую я решил рассказать у себя на DbHelp...
---В этой статье мы научимся создавать дерево на yii (через CtreeView) заполняя его данными из базы. Но самое интересное во всем этом — мы будем использовать AJAX при открытии узлов, тем самым уменьшив нагрузку на базу.
НАЧНЕМ
Для начала нам нужно создать таблицу в базе где и будут узлы дерева. Вариант дерева id-id_parent я очень часто использую в своих статьях, поэтому вы уже должны быть немного «в теме» :)
Заходим в базу и создаем новую таблицу:
CREATE TABLE `tree` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`name` VARCHAR( 50 ) NOT NULL ,
`parent_id` INT UNSIGNED NOT NULL
);
id – номер узла в дереве
name – название узла
parent_id – id узла родителя, которому подчиняется наш узел
КОНТРОЛЛЕР
В вашем контроллере должно быть два метода, первый (actionTree) — который будет рендерить виджет на экран и второй (actionAjaxFillTree) — который будет заниматся «открытием» дерева.
public function actionTree()
{
// рендерим файлик отображения tree.php
$this->render('tree');
}
public function actionAjaxFillTree()
{
// если пробуют получить прямой доступ к экшину (не через ajax)
// тогда обрубаем "крылья")) т.е. возвращаем белую страницу
if (!Yii::app()->request->isAjaxRequest) {
exit();
}
// с какого узла начинаем вывод дерева? 0 - с первого
$parentId = 0;
if (isset($_GET['root'])) {
// переменная $_GET['root'] говорит нам о том, по какому узлу в дереве
// кликнули. Исходя из этого получаем данные детей (подкатегорий)
$parentId = (int) $_GET['root'];
}
// сам запрос на получение данных детей (через обычный LEFT JOIN)
$req = Yii::app()->db->createCommand(
"SELECT m1.id, m1.name AS text, m2.id IS NOT NULL AS hasChildren "
. "FROM tree AS m1 LEFT JOIN tree AS m2 ON m1.id=m2.parent_id "
. "WHERE m1.parent_id <=> $parentId "
. "GROUP BY m1.id ORDER BY m1.name ASC"
);
$children = $req->queryAll();
// возвращаем данные
echo str_replace(
'"hasChildren":"0"',
'"hasChildren":false',
CTreeView::saveDataAsJson($children)
);
exit();
}
Я написал комментарии к коду что б вам было легче понять к чему там дело. На самом деле все достаточно просто:
- При первом входе на localhost/controller/tree/ происходит простой рендер дерева на экран и заполняется узлами первого уровня
- При клике на узел дерева — происходит обращение к методу actionAjaxFillTree() для получения детей следующего узла
VIEW
В файле view/controller/tree.php просто инициализируем виджет:
<?php
$this->widget(
'CTreeView',
array('url' => array('ajaxFillTree'))
);
И ЧТО ТЕПЕРЬ?
Добавтие в таблицу `tree` пару узлов с нулевым parent_id (т.е. первый уровень). Затем создайте несколько узлов со значениями parent_id = id узлов первого уровня. Например как у меня:
INSERT INTO `tree` (`id`, `name`, `parent_id`) VALUES
(10, 'молоко', 8),
(9, 'мед', 8),
(8, 'я НЕ люблю', 0),
(7, 'я люблю', 0),
(11, 'солнце', 8),
(12, 'море', 8),
(13, 'людей', 7),
(14, 'природу', 7),
(15, 'воздух', 7),
(16, 'спорт', 7),
(17, 'Футбол', 16),
(18, 'Биатлон', 16),
(19, 'Прыжки на лыжах с трамплина', 16),
(20, 'Сноубординг', 16);
После чего открывайте localhost/controller/tree и пробуйте как все красиво работает :)
Ссылки:
Если хотите опубликовать этот материал у себя - пожалуйста, разместите ссылку на страницу откуда вы его взяли.
- Так получилось что xampp на компе умер. Куча файлов удалилась и только благодаря рестору файлов получилось получить папку "data" ... "Чем открыть файлы frm/MYD/MYI"
- Надо было мне как то выводить сообщения об ошибках на экран. Использовать исключения - плохой подход. Поэтому для того чтобы ... "Messager + jQuery"
- Мы очень рады обьявить о выпуске стабильной версии Yii Framework 1.1.0!
next
Этот релиз играет важную роль в истории разв ... "Стабильная версия Yii Framework 1.1"

[adm] zolter
Было сказано: Среда, 12 Май 2010
У мну блог просто на 1.0.х написан))
Постараюсь поддомен сделать для 1.1 и туда кидать примеры

[guest] Гость
Было сказано: Четверг, 13 Май 2010
спасибо, ещё бы и в правду демо, посмотреть в действии.
Так бы ещё без применения виджетов, было бы круто.
Спасибо за пост.

[adm] zolter
Было сказано: Четверг, 13 Май 2010
Готово :) Пример SQL-а тоже кинул для быстрого старта

[guest] Roman
Было сказано: Пятница, 14 Май 2010
Про индексы писать нужно, а то кто нить прочтет, скопипастит, а потом будет удивляться чего у него база подтормаживает. (Видел таких)
Вот видно что происходило до добавления индекса и после. http://pastebin.com/wRe40WWv
Все еще напрягает temporary (group? )filesort (order? )
Вот тут с подзапросам без временной таблицы http://pastebin.com/asLFJDQm , как вариант я сортировку бы вообще на javascript перенес :)

[guest] zolter
Было сказано: Пятница, 14 Май 2010
Согласен, индексы всегда намного ускоряют выборки и работу с данными.
Я думаю что если читатель будет создавать таблицу tree у себя - то через мой SQL пример, а там id у меня как PRIMARY KEY.

[guest] Гость
Было сказано: Пятница, 14 Май 2010
Но parent_id не индекс. А именно он сократит количство рассматриваемых строк при джойне.

[guest] Alex
Было сказано: Вторник, 25 Май 2010
Подскажите, как сделать так чтобы каждый элемент поддерева был ссылкой?

[guest] Maxx
Было сказано: Вторник, 25 Май 2010
Я бы попробовал вместо имени типа "Дерево" делать "<a href='...'>Дерево</a>". Хотя может Золт что лучше скажет

[guest] Alex
Было сказано: Среда, 26 Май 2010
Попробывал ни чего не получилось, дерево вообще перестало отображаться.

[guest] zolter
Было сказано: Среда, 26 Май 2010
Вот так заработает:
INSERT INTO `zolter_blog`.`tree` (`id`, `name`, `parent_id`) VALUES (NULL, '<a href=''http://www.dbhelp.ru''>DbHelp.ru</a>', '7');т.е. кавычки одинарные нужны или экранирование.

[adm] zolter
Было сказано: Среда, 26 Май 2010
Там слилось. Короче вместо:
<a href="http://www.dbhelp.ru">DbHelp.ru</a>делай:
<a href='http://www.dbhelp.ru'>DbHelp.ru</a>и все будет ок!

[guest] Alex
Было сказано: Среда, 26 Май 2010
я пытаюсь сделать древовидный каталог, ссылка будет примерно такой localhost/books/list/theme_id=3, где themes_id - это id элемента дерева, взятого из базы.
Нужно чтобы ссылки формировались вместе с деревом.

[guest] Гость
Было сказано: Пятница, 28 Май 2010
Еще, как вариант, можно ссылку организовать через CHtml::link();

[guest] Alex
Было сказано: Воскресенье, 30 Май 2010
Как я понял будет, вот так:
$link = str_replace(
'"hasChildren":"0"',
'"hasChildren":false',
CTreeView::saveDataAsJson($children)
);
echo CHtml::link($link, array('books/view',
'themes_id'=>$children->id)
);
Поправьте, если не верно, а то страшно.

[guest] Sova
Было сказано: Воскресенье, 13 Июнь 2010
Здравствуйте, автор, сделал все как у вас, но дерево не отображается в чем может быть проблема?
class TreesController extends Controller
{
public $layout='column2';
public function actionIndex()
{
// рендерим файлик отображения tree.php
$this->render('tree');
}
public function actionAjaxFillTree()
{
//ваш текст действия
}
}

[guest] Sova
Было сказано: Вторник, 15 Июнь 2010
В базе все есть. А конфликта версий нет, делал на 1.1.2 ?

Виталий
Было сказано: Среда, 16 Июнь 2010
Смотрю исходный код демо страницы в файрфоксе
...
<body>
<div class="container">
<div id="content">
<ul id="yw0">
</ul> </div><!-- content -->
</div>
<script type="text/javascript">
/*<![CDATA[*/
jQuery("#yw0").treeview({'url':'/yii-1.1.1.r1907/demos/blog/index.php/post/ajaxFillTree'});
/*]]>*/
</script>
Правильно ли я понимаю, что с точки зрения СЕО такие деревья не есть хорошо?

[guest] zolter
Было сказано: Среда, 16 Июнь 2010
Да, как и все другие ajax данные на странице - они не индексируются поисковиками.

[guest] net
Было сказано: Среда, 23 Июнь 2010
а есть ли возможность к данному виджету прикрутить запоминание состояния дерева?

[guest] zolter
Было сказано: Среда, 23 Июнь 2010
Вот этого не знаю.. Прикрутить то можна, а есть ли что то для этого в штатных средствах - подсказать немогу

[guest] Гость
Было сказано: Суббота, 12 Март 2011
там кто-то сверху писал, что не работает, хотя было все сделано как здесь.
тоже не работало.
потом глянул в файрбаге.
оказалось что просто в accessRules забыл добавить разрешение для действия actionAjaxFillTree.
файрбаг решает, при работе со скриптами

[guest] sape
Было сказано: Вторник, 12 Апрель 2011
>>>Да, как и все другие ajax данные на странице - они не индексируются поисковиками.
Если про яндекс, то да, но про гугель такого сказать нельзя. Недавно в логах обнаружил как гугель по ссылкам в яваскриптах лазил тобишь индексировал контент, что AJAXом грузится


