
Что я добавил/изменил – в основном адаптировал его для использования со стандартным виджетом Yii2 GridView. Теперь вместо такой страницы:

получаем:

Элементы (в данном примере пункты меню) можно перемещать перетаскиванием мышью (Drag-and-drop), что сразу (AJAX) сохраняет их новое положение в базе данных. Без визуального отображения структуры и перемещения путем перетаскивания было бы сложно, т.к. в базе данных хранится числовое обозначение текущего местоположения элемента, которое вычисляется специальным методом.
Расширение позволяет изменять структуру и вложенность элементов, что можно использовать в древовидных структурах, например в рубриках, сложном меню для сайта и тд. Такие структуры, со множеством вложенных элементов, создаются по методу Nested Sets, подробнее про который можно почитать, например, тут.
Для использования метода Nested Sets при работе с древовидными структурами, а именно (выборке, вставке и тд.) в фреймворке yii2 популярностью пользуется расширение yii2-nested-sets, ссылка на GitHub. Оно используется и здесь так же – прописано в качестве зависимостей в файле composer.json и будет установлено автоматически.
Установка и настройка.
composer require klisl/yii2-nested-sets-drag-and-drop
Далее создаем таблицу в базе данных с древовидной структурой и подключаем расширение для работы с ним согласно описания:
https://github.com/creocoder/yii2-nested-sets
Там в качестве примера приведена миграция для создания меню сайта. Вы можете добавлять любые свои поля в таблицу, главное сохранить основу:
- id
- lft
- rgt
- depth
- name
В своем примере я тоже буду создавать меню сайта.
После этого необходимо создать модель, для этого можно воспользоваться gii генератором.
Далее, как следует из описания установки расширения yii2-nested-sets, подключаем поведение и два небольших метода в созданную модель и потом создаем отдельный файл, который будет добавлять свое поведение в класс ActiveQuery. Например создадим его в common\models, с указанием пространства имен:
namespace common\models;Кроме этого в модели нужно убрать проверку на заполнение полей 'lft', 'rgt', 'depth' из метода rules. Т.е. не применять к ним правило «required».
После этого воспользуемся gii, а именно CRUD Generator. Таким образом автоматически у нас создадутся все нужные действия контроллера, такие как просмотр/создание/редактирование/удаление элементов меню и соответствующие файлы представления. Всем этим действиям будут соответствовать стандартные, сгенерированные автоматически виды, кроме основного – index. Например это будет административная часть (backend) для работы с меню сайта.
Изменяем действие index у контроллера (у меня это MenuController), меняем
public function actionIndex() { $searchModel = new MenuSearch(); $dataProvider = $searchModel->search(Yii::$app->request->queryParams); return $this->render('index', [ 'searchModel' => $searchModel, 'dataProvider' => $dataProvider, ]); }на
public function actionIndex() { //объект ActiveQuery содержащий данные для дерева. depth = 0 - корень. $query = Menu::find()->where(['depth' => '0']); return $this->render('index', [ 'query' => $query, ]); }
Т.к. searchModel не используется, удаляем данный файл, который был сгенерирован (у меня это MenuSearch).
Что еще нужно поменять в контроллере – метод actionCreate, т.к. при использовании Nested Sets и создании нового элемента, значения полей lft, rgt, depth должны вычисляться расширением. А вставка новых элементов будет происходить в конец корневого, с дальнейшей корректировкой путем перетаскивания.
public function actionCreate() { /** @var $model Menu|NestedSetsBehavior */ $model = new Menu (); //Поиск корневого элемента $root = $model->find()->where(['depth' => '0'])->one(); if ($model->load(Yii::$app->request->post())) { //Если нет корневого элемента (пустая таблица) if (!$root) { /** @var $rootModel Menu|NestedSetsBehavior */ $rootModel = new Menu(['name' => 'root', 'url' => '/']); $rootModel->makeRoot(); //делаем корневой $model->appendTo($rootModel); } else { $model->appendTo($root); //вставляем в конец корневого элемента } if ($model->save()){ return $this->redirect('index'); } } return $this->render('create', [ 'model' => $model, 'root' => $root ]); }
Тут сначала проверяется наличие корневого элемента (у которого 'depth' равно 0) и если его нет – сначала создает корневой элемент с названием 'root', а далее привязывает все к нему. Это требование метода Nested Sets – всегда должен быть корневой элемент.
И еще в контроллере добавляем отдельное действие nodeMove из расширения путем добавления метода actions:
public function actions() { return [ 'nodeMove' => [ 'class' => 'klisl\nestable\NodeMoveAction', 'modelName' => Menu::className(), ], ]; }
Про отдельные действия я писал тут: http://klisl.com/individual_actions.html
Теперь при перемещении элементов меню будет вызываться метод nodeMove расширения для сохранения новой позиции в базе данных используя AJAX.
Теперь займемся файлами представлений. Прежде всего в backend/views/menu/_form.php удаляем строки
<?= $form->field($model, 'lft')->textInput() ?>
<?= $form->field($model, 'rgt')->textInput() ?>
<?= $form->field($model, 'depth')->textInput() ?>
т.к. значения данных полей не нужно корректировать вручную, это будет делать расширение.
И меняем полностью содержимое файла backend/views/menu/index.php на
<?php use yii\helpers\Html; use klisl\nestable\Nestable; use yii\helpers\Url; /* @var $query \yii\db\ActiveQuery */ $this->title = 'Menus'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="menu-index"> <h1><?= Html::encode($this->title) ?></h1> <p><?= Html::a('Create new item', ['create'], ['class' => 'btn btn-default']) ?></p> <?= Nestable::widget([ 'type' => Nestable::TYPE_WITH_HANDLE, 'query' => $query, 'modelOptions' => [ 'name' => 'name', //поле из БД с названием элемента (отображается в дереве) ], 'pluginEvents' => [ 'change' => 'function(e) {}', //js событие при выборе элемента ], 'pluginOptions' => [ 'maxDepth' => 10, //максимальное кол-во уровней вложенности ], 'update' => Url::to(['menu/update']), //действие по обновлению 'delete' => Url::to(['menu/delete']), //действие по удалению 'viewItem' => Url::to(['menu/view']), //действие по удалению ]); ?> <div id="nestable-menu"> <button class="btn btn-default" type="button" data-action="expand-all">Expand All</button> <button class="btn btn-default" type="button" data-action="collapse-all">Collapse All</button> </div> </div>Все готово!
Error. Can't be moved!
Может быть это связано с тем что стоит модуль мультиязычности. В браузере бьет 404 ошибку.
http://yii.loc/ru/admin/pages/nodeMove?id=31&par=3...
ответ на комментарий Влада от 21.06.2018
ответ на комментарий Сергей от 21.06.2018
ответ на комментарий Влада от 22.06.2018
ответ на комментарий Сергей от 22.06.2018
ответ на комментарий Влада от 22.06.2018
Наверное вы behavior в модели неправильно подключили.
https://github.com/creocoder/yii2-nested-sets#conf...