Nested Sets + CTreeView, CDropdownList, CListBox

Выкладываем свои наработки
Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.04.08, 22:19

Итак, как и обещал, выкладываю рабочую (подправленную мной) версию расширения NestedSet и еще одного класса, который из модели, которую генерирует расширение NestedSet, генерирует массив отдаваемый на съедение CTreeView.

Подключаем само расширение. Для этого копируем в папку /protected/extensions/ содержимое архива (архив смотим ниже в прикрепленных файлах).

Должна получиться такая структура:

Код: Выделить всё

/protected
    /extensions
        /nestedset
            CNestedSetBehavior.php
            NestedSetException.php
            TreeBehavior.php
            TreeException.php
            TreeViewTreebehavior.php
Далее подключаем расширение. Открываем файл нужной модели и там пишем:

Код: Выделить всё

public function behaviors(){
    return array(
        'TreeBehavior' => array(
            'class' => 'application.extensions.nestedset.TreeBehavior',
            '_idCol' => 'id',
            '_lftCol' => 'lft',
            '_rgtCol' => 'rgt',
            '_lvlCol' => 'level',
        ),
        'TreeViewTreebehavior' => array(
            'class' => 'application.extensions.nestedset.TreeViewTreebehavior',
        )
    );
}
 
Теперь сделаем виджет, который будет выводить наше дерево. В директории /protected/components/ создаем файл Tree.php. Открываем его и пишем:

Код: Выделить всё

<?php

class Tree extends CWidget {
    public function run() {
        $model = Categories::model()->findByPK(1); // Здесь вместо Categories меняем на свою модель
        $tree = $model->getTreeViewData(false);
        $this->render('tree',array('tree'=>$tree,));
    }
}
 
Далее в этой же директории создаем (если не существует) папку /views в которой создаем файл tree.php. Открываем его и пишем:

Код: Выделить всё

<?php
$this->widget('CTreeView', array(
        'data'=>$tree, // передаем массив
        'animated'=>'medium', // скорость анимации свертывания/развертывания
        'collapsed'=>true, // если тру, то при генерации дерева, все его узлы будут свернуты
        'persist'=>'location', // метод запоминания открытого узла
        'unique'=>true, // если тру, то при открытии одного узла, будут закрываться остальные
        'cssFile'=>'/css/treeview/jquery.treeview.css', // меняем расположение css файла (он немного подправлен мной)
    ));
?>
Далее подключаем сам виджет, например в боковой панеле в /protected/views/layouts/main.php

Код: Выделить всё

<?php $this->widget('application.components.Tree'); ?>
Копируем стили в директорию /css. В архиве подправленные стили и мой вариант иконок. Должно получиться так:

Код: Выделить всё

/css
    /treeview
           /img
               treeview-modern.gif
        jquery.treeview.css
Теперь создаем таблицу в базе данных. Первая запись в базе обязательна! Отображаться в дереве она не будет.

Код: Выделить всё

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `lft` int(11) NOT NULL,
  `rgt` int(11) NOT NULL,
  `level` int(11) NOT NULL,
  `name` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `lft` (`lft`),
  KEY `rgt` (`rgt`),
  KEY `level` (`level`),
  KEY `name` (`name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=104 ;

INSERT INTO `categories` (`id`, `lft`, `rgt`, `level`, `name`) VALUES
(1, 0, 1, 0, 'root');
 
Все! Наше дерево готово... Следующий пост о том, как и где менять пути в ссылках узлов дерева, как добавлять и управлять узлами.

Вот, что должно получиться в итоге:
Изображение
Вложения
treeview.tar.gz
Стили
(2.62 КБ) 1122 скачивания
nestedset.tar.gz
Расширение NestedSet
(8.75 КБ) 1157 скачиваний
Последний раз редактировалось timlar 2010.04.09, 12:17, всего редактировалось 3 раза.
Twitter: @timlar_ua

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.04.08, 22:19

Чтобы изменить путь в ссылках (в моем примере это /categories/id) открываем файл /protected/extensions/nestedset/TreeViewTreebehavior.php. Ищем строку номер 42.

Код: Выделить всё

$rawitem->owner->name = '<a href="/category/'.$rawitem->owner->id.'">'.$rawitem->owner->name.'</a>';
 
Теперь пару слов об управлении деревом. Примеры банальны для наглядности. В экшене контроллера пишем следующее:

Код: Выделить всё

        $model = Categories::model()->findByPK(1);
        
        if($_POST['tree'] == 'manage') {
            
            $node = Categories::model()->findByPK($_POST['node']);
            $nodeTo = Categories::model()->findByPK($_POST['nodeto']);
            
            // Добавление узла
            if($_POST['add']) {
                $newNode = new Categories();
                $newNode->name = $_POST['name'];
                $node->appendChild($newNode);
            }
            
            // Удаление узла
            if($_POST['delete']) {
                $node->deleteNode(true);
            }
            
            // Перемещение узла на уровень выше
            if($_POST['up']) {
                $node->moveLeft();
            }
            
            // Перемещение узла на уровень ниже
            if($_POST['down']) {
                $node->moveRight();
            }
            
            // Перемещение узла А перед узлом Б
            if($_POST['before']) {
                $node->moveBefore($nodeTo);
            }
            
            // Переместить узел А внутрь узла Б (в подкатегорию)
            if($_POST['below']) {
                $node->moveBelow($nodeTo);
            }
            
            $this->refresh();
        }
        
        // создаем массив, который будем кормить CDropdownList и CListBox
        $data = CHtml::listData($model->findAll(array('order'=>'lft')), 'id', 'nameExt'); // Здесь переопределяем поле name на nameExt. Ниже описано зачем.
        
        $this->render('index',array(
            'data'=>$data,
        ));
 
Заходим в нашу модель, которая работает с таблицей дерева и пишем:

Код: Выделить всё

public function getNameExt()
{
    // это нужно для наглядности дерева в контролах CDropdownList и CListBox
    return str_repeat('----',$this->level).' '.$this->name;
}
 
И теперь довавляем в отображение /protected/views/tree/index.php следующую форму и контролы:

Код: Выделить всё

<h1>Управление категориями товаров</h1>

<?php echo CHtml::beginForm(); ?>
<?php echo CHtml::hiddenField('tree','manage'); ?>
<?php echo CHtml::listBox('node','1',$data,array('size'=>'10')); ?>
<br /><br />
<?php echo CHtml::textField('name'); ?>
<?php echo CHtml::submitButton('Добавить категорию',array('name'=>'add')); ?><br /><br />
<?php echo CHtml::submitButton('Удалить выбранную категорию',array('name'=>'delete')); ?>
<br /><br />
<?php echo CHtml::submitButton('Переместить категорию выше',array('name'=>'up')); ?>
<?php echo CHtml::submitButton('Переместить категорию ниже',array('name'=>'down')); ?>
<br /><br />
<?php echo CHtml::dropDownList('nodeto','1',$data); ?>
<br />
<?php echo CHtml::submitButton('Переместить перед выбранной категорией',array('name'=>'before')); ?>
<?php echo CHtml::submitButton('Переместить в выбранную категорию',array('name'=>'below')); ?>
<?php echo CHtml::endForm(); ?>
Запускаем и тренируемся :)

Если найдутся какие-то неточности в моем мануале, пишите в этой теме, исправлю.

Если есть какие-то предложения по улучшению и оптимизации всего этого - тоже пишите, не стесняйтесь. Здесь все свои. :)
Последний раз редактировалось timlar 2010.04.09, 12:06, всего редактировалось 3 раза.
Twitter: @timlar_ua

Аватара пользователя
alaevka
Сообщения: 77
Зарегистрирован: 2010.03.12, 11:34
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение alaevka » 2010.04.09, 07:25

Спасибо! очень полезная вещь! В описании есть небольшая ошибочка:
В файле /protected/components/Tree.php

Код: Выделить всё

class CategoriesTree extends CWidget
заменить на

Код: Выделить всё

class Tree extends CWidget
И еще. Чтобы у людей не было путаницы, в контроллере, исходя из дампа Вашей базы:

Код: Выделить всё

$data = CHtml::listData($model->findAll(array('order'=>'lft')), 'id', 'nameExt');
 
на

Код: Выделить всё

$data = CHtml::listData($model->findAll(array('order'=>'lft')), 'id', 'name');
 

Так же заметил небольшой баг: если катерия, имеющая вложенность стоит последней в дереве, например:
О компании
Контакты
+Каталог
Продукция 1
Продукция 2

В данном случае при свернутой группе каталог, ее иконка будет как "не имеющая вложенность", однако при разворачивании, иконка отображается как надо.
1.jpg
1.jpg (5.01 КБ) 16612 просмотров
2.jpg
2.jpg (12.65 КБ) 16612 просмотров


Отличное расширение!

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.04.09, 11:48

Спасибо, исправлю :) Просто вырезАл код из своего проекта, потому был уверен, что не везде все поисправлял.
Последний раз редактировалось timlar 2010.04.09, 12:28, всего редактировалось 1 раз.
Twitter: @timlar_ua

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.04.09, 12:05

Я дописал еще на счет name и nameExt ;)
Twitter: @timlar_ua

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.04.09, 12:17

Исправил баг в стилях, с отображением последнего раскрываемого узла. Залил в первом посте новый архив со стилями.

Изображение

Изображение
Twitter: @timlar_ua

Archmage
Сообщения: 78
Зарегистрирован: 2010.03.03, 03:07
Откуда: Иркутск

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение Archmage » 2010.04.14, 16:50

Начну по порядку.

1. Почему то не обрабатывается css. Файл данный вами подключается, но картинки у дерева не отображаются.

2. Если в дереве нету узлов, кроме главного вываливалась ошибка в TreeViewTreebehavior на 57 строке о том, что нету в массиве ключа 'children'. Заменил:

Код: Выделить всё

$tree = $tree[key($tree)]['children']; 
на

Код: Выделить всё

$tree = $tree[key($tree)];
$tree = isset($tree['children']) ? $tree['children'] : array();
 
Ошибка перестала появляться.

3. В данном вами примере постоянно вываливаются ошибки на коде обработки пост запроса. Например если написать как у вас, и если пользователь не нажимал на кнопку удалить, то вылетит ошибка. Если же заменить:

Код: Выделить всё

if($_POST['delete']) {
                $node->deleteNode(true);
} 
на

Код: Выделить всё

if(isset($_POST['delete'])) {
                $node->deleteNode(true);
}
 
И это справедливо для всех ваших ифов.

Ошибка перестает появляться. Я так подозреваю дело тут в настройке в php обработки нотисов. Но опыта в этом у меня нету, так что я просто всегда перестраховываюсь и проверяю переменную на существование.

4. Что это за ссылка:

Код: Выделить всё

$rawitem->owner->name = '<a href="/category/'.$rawitem->owner->id.'">'.$rawitem->owner->name.'</a>'; 
Она у меня в верхнем правом углу появилась на каждой странице, и как ее отключить.
Animum rege, qui nisi paret, imperat. (Управляй своим настроением, ибо оно, если не повинуется, то повеливает)

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.04.14, 17:17

Хм...

1. Возможно не правильно подключили. Проверьте FireBug'ом, правильный ли путь к css файлу и проверьте путь к изображению в файле css.

2. Если вы очистили таблицу, то у первой записи должно быть 1 0 1 0 root, тогда никаких ошибок возникать не должно. Собственно, у меня не возникало.

3. Все, что я писал, исключительно наглядные примеры. :) Нотисы выскакивают скорее всего из-за настроек в php.ini

4. Не знаю, почему она у Вас в верхнем углу... Это ссылка конечных узлов. Допустим, есть узел "Тест", у него есть вложенный узел "Под тест". При нажатии на "Тест" узел раскрывается, а вот эта ссылка и есть тем самым "Под тест". Возможно Вы что-то в коде поменяли, потому и появляется вверху. У меня все отлично работает. Проверьте код еще раз. ;)
Twitter: @timlar_ua

Archmage
Сообщения: 78
Зарегистрирован: 2010.03.03, 03:07
Откуда: Иркутск

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение Archmage » 2010.04.19, 14:01

А что нужно поменять что бы дерево всегда было открыто и ключевые узлы тоже стали ссылками?
Animum rege, qui nisi paret, imperat. (Управляй своим настроением, ибо оно, если не повинуется, то повеливает)

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.04.19, 15:02

В файле /protected/extensions/nestedset/TreeViewTreebehavior.php заменить строки:

Код: Выделить всё

            if(!$rawitem->hasChildNodes()) {
                    $rawitem->owner->name = '<a href="/category/'.$rawitem->owner->id.'">'.$rawitem->owner->name.'</a>';
            } else
                $rawitem->owner->name = '<span>'.$rawitem->owner->name.'</span>'; 
На:

Код: Выделить всё

$rawitem->owner->name = '<a href="/category/'.$rawitem->owner->id.'">'.$rawitem->owner->name.'</a>'; 
В отображении у виджета CTreeView убрать 'collapsed'=>true и 'unique'=>true
Twitter: @timlar_ua

Archmage
Сообщения: 78
Зарегистрирован: 2010.03.03, 03:07
Откуда: Иркутск

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение Archmage » 2010.04.20, 17:05

Спасибо большое, работает!
Animum rege, qui nisi paret, imperat. (Управляй своим настроением, ибо оно, если не повинуется, то повеливает)

Аватара пользователя
yuran80
Сообщения: 114
Зарегистрирован: 2010.03.21, 10:39
Откуда: Украина, Луцк
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение yuran80 » 2010.04.22, 00:18

Проблема с добавлением элемента.
$node = Comment::model()->findByPK(1);
$newNode = new Comment();
$newNode->user_id = '1';
$newNode->target = 'news';
$newNode->target_id = '34';
$newNode->content = 'Тестовый комментарий';
$newNode->status = '1';
$newNode->date = '';
$node->appendChild($newNode)
Меняет лишь в базе данных поле rgt рут элемента (+2) а сам child не добавляет. В чем может быть причина?

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.04.22, 00:37

В том, что код абсолютно не логичен. Ты создаешь модель комментария и пытаешься засунуть ее в таблицу категорий :) Это то же самое, что пытаться засунуть голову в 3-х литровую банку... Какая-то часть залезет (напр. ухо или нос), но все равно голова не пролезет. :)

Я же примеры там привел, как добавлять узлы. Делай так, как в примере.
Twitter: @timlar_ua

AbS_
Сообщения: 207
Зарегистрирован: 2010.03.27, 14:02

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение AbS_ » 2010.05.04, 16:01

Странно, у меня почему то дерево получается не сворачивается и не разворачивается.
В таблице есть:
_id _lft _rgt _level _name
1 0 7 0 root
126 1 4 1 Second Node 2
129 2 3 2 Second Node 3
130 5 6 1 Second Node 4

А на деле просто список :(

UX.InfoGate
Сообщения: 5
Зарегистрирован: 2010.05.06, 22:19
Откуда: Lutsk, Ukraine
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение UX.InfoGate » 2010.05.07, 00:36

Здравствуйте. Извиняюсь за тупой вопрос, но вы писали
выкладываю рабочую (подправленную мной) версию расширения NestedSet
так вот - если вам не сложно можете вкратце описать изменения (привести список изменений) внесенные в код расширения или скинуть ссылку где это написано? Или возможно будет лучше сделать какой-то патч или что-то типа того... Спасибо!

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.05.07, 00:46

UX.InfoGate писал(а):Здравствуйте. Извиняюсь за тупой вопрос, но вы писали
выкладываю рабочую (подправленную мной) версию расширения NestedSet
так вот - если вам не сложно можете вкратце описать изменения (привести список изменений) внесенные в код расширения или скинуть ссылку где это написано? Или возможно будет лучше сделать какой-то патч или что-то типа того... Спасибо!
К сожалению, я не наделен феноменальной памятью, чтобы запоминать такие мелочи. ;) А что мешает скачать исправленную версию?
Twitter: @timlar_ua

UX.InfoGate
Сообщения: 5
Зарегистрирован: 2010.05.06, 22:19
Откуда: Lutsk, Ukraine
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение UX.InfoGate » 2010.05.07, 01:08

:) . Спасибо уже нашол - < в расширении писалось (много раз писалось) $this->getIsNewRecord() (непойму как оно так работало)) ) а вы заменили на $this->getOwner()->getIsNewRecord() + форматирование кода + TreeViewTreebehavior.php.
А что мешает скачать исправленную версию?
это внутреннее :) - с оф. источника качать както проще))

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.05.07, 02:58

UX.InfoGate писал(а):это внутреннее :) - с оф. источника качать както проще))
Хм... А что понимается под "оф. источником"? :) Или если код лежит на официальном сайте, но не рабочий, то он наделен какими-то магическими способностями? :lol: Проще - это не тогда, когда качаешь с "оф. источника", а когда идешь по пути наименьшего сопротивления. А с таким подходом как у тебя, проще написать свой фреймворк, чем пользоваться уже написанным :)
Twitter: @timlar_ua

UX.InfoGate
Сообщения: 5
Зарегистрирован: 2010.05.06, 22:19
Откуда: Lutsk, Ukraine
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение UX.InfoGate » 2010.05.07, 13:05

А что понимается под "оф. источником"?
Ну я подразумевал источник от создателя расширения. Я просто не особо любитель форков, так как бывают моменты, когда их приходится объединять...
проще написать свой фреймворк, чем пользоваться уже написанным
тоже вариант =)

Аватара пользователя
timlar
Сообщения: 1382
Зарегистрирован: 2009.09.19, 17:49
Откуда: Украина, Днепропетровск
Контактная информация:

Re: Nested Sets + CTreeView, CDropdownList, CListBox

Сообщение timlar » 2010.05.07, 14:36

Это не форк :) Там всего лишь исправлены ошибки для работы с новыми версиями фреймворка. :)
Twitter: @timlar_ua

Ответить