DToggleColumn: Колонка-переключатель для CGridView
При администрировании многих разделов сайта возникла необходимость работы с логическими переключаемыми атрибутами модели (опубликована новость или нет, прочитано сообщение или нет и т.п.). В сети нашлось решение использовать колонку из чекбоксов с автосохранением данных Ajax запросом по событию onChange()
. Эстетически выводить несколько колонок чекбоксов было не очень красиво. Было решено для наглядности сделать столбик из иконок-ссылок.
Сначала код вывода колонки «Опубликовано» был таким:
<?php $this->widget('zii.widgets.grid.CGridView', array( 'dataProvider'=>$model->search(), 'filter'=>$model, 'columns'=>array( 'date', 'title', array( 'name'=>'public', 'header'=>'О', 'filter'=>array(1=>'Опубликовано', 0=>'Не опубликовано'), 'htmlOptions'=>array('style'=>'width:30px;text-align:center'), 'value' => function($data) { $url = Yii::app()->controller->createUrl('toggle', array('id'=>$data->id, 'param'=>'public', 'token'=>Yii::app()->request->csrfToken)); $src = Yii::app()->request->baseUrl . ($data->public ? '/images/yes.png' : '/images/no.png'); $title = $data->public ? 'Опубликован' : 'Не опубликован' return CHtml::link(CHtml::image($src), $url, array('title'=>$title)); }, 'type' => 'raw', ), ) ));
Здесь в зависимости от текущего значения атрибута подставлялась определённая иконка со своей подсказкой. Переключение осуществлялось простым переходом по ссылке (GET-запросом) и редиректом обратно, поэтому для безопасности в обработчик actionToggle()
приходилось передавать токен и вручную сравнивать его значение с текущим.
Этот код выглядел громоздко. При выполнении очередного проекта обнаружилось, что на хостинге стоит PHP 5.2 и от анонимных функций пришлось отказаться. Преобразовать эту анонимную функцию в строку, пригодную для обработки функцией eval()
было затруднительно, поэтому весь код перекочевал в метод отдельного класса-хэлпера.
Это действовало, но было неким костылём, так как в отличие от стандартной кнопки «Удалить» работало по GET-запросу, а также не было обновления грида по Ajax. Для устранения этих недостатков на основе стандартной колонки CButtonColumn
был реализован класс колонки-триггера DToggleColumn.
В простейшем случае для отображения текущего статуса в колонке необходимо указать атрибут модели и, при необходимости, адреса иконок. В такой ячейке будет выведена нужная иконка в зависимости от значения указанного атрибута модели. Этот пример можно использовать для пометки важных сообщений в списке обратной связи или срочных заказов в гриде интернет-магазина:
<?php $this->widget('zii.widgets.grid.CGridView', array( 'dataProvider'=>$model->search(), 'filter'=>$model, 'columns'=>array( 'date', 'title', array( 'class'=>'DToggleColumn', // поле модели 'name'=>'important', // иконка для значения 1 или true 'onImageUrl' => Yii::app()->request->baseUrl . '/images/important.png', // иконка для значения 0 или false 'offImageUrl' => Yii::app()->request->baseUrl . '/images/spacer.gif', // убираем генерацию ссылки по умолчанию 'linkUrl'=>false, ), array( 'class'=>'CButtonColumn', ), ), ));
Здесь для несрочных заказов мы установили пустое изображение. Для скрытия ячейки лучше использовать параметр visible
:
<?php $this->widget('zii.widgets.grid.CGridView', array( 'dataProvider'=>$model->search(), 'filter'=>$model, 'columns'=>array( 'date', 'title', array( 'class'=>'DToggleColumn', // поле модели 'name'=>'important', // иконка для значения 1 или true 'onImageUrl' => Yii::app()->request->baseUrl . '/images/important.png', // убираем генерацию ссылки по умолчанию 'linkUrl'=>false; // отображать ли ячейку 'visible'=>'$data->important', ), array( 'class'=>'CButtonColumn', ), ), ));
Для колонок «Опубликовано» «На главной странице» и похожих нужно задать ссылку на обработчик linkUrl
<!-- list.php --> <h1>Записи блога</h1> $this->renderPartial('_grid',array('model'=>$model));
<!-- _grid.php --> <?php $this->widget('zii.widgets.grid.CGridView', array( 'dataProvider'=>$model->search(), 'filter'=>$model, 'columns'=>array( 'date', 'title', // иконка со ссылкой по умолчанию на экшен toggleAction($id, $attribute) текущего контроллера array( 'class'=>'DToggleColumn', // атрибут модели 'name'=>'public', // заголовок столбца 'header'=>'О', // запрос подтвердждения (если нужен) 'confirmation'=>'Изменить статус публикации?', // фильтр 'filter'=>array(1=>'Опубликованные', 0=>'Не опубликованные'), // alt для иконок (так как отличается от стандартного) 'titles'=>array(1=>'Опубликовано', 0=>'Не опубликовано'), 'htmlOptions'=>array('style'=>'width:30px'), ), // иконка с другой ссылкой array( 'class'=>'DToggleColumn', 'name'=>'inhome', 'header'=>'Г', 'filter'=>array(1=>'На главной', 0=>'Не на главной'), // своя ссылка для переключения состояния 'linkUrl'=>'Yii::app()->controller->createUrl("customToggle", array("id"=>$data->id, "param"=>"inhome"))', 'htmlOptions'=>array('style'=>'width:30px'), ), array( 'class'=>'CButtonColumn', ), ), ));
Действие-обработчик actionToggle()
весьма стандартное и похоже на действие для actionDelete()
. Оно содержит проверку имени атрибута по списку разрешённых и, соответственно, само переключение значения:
class PostController extends Controller { // ... public function filters() { return array( 'postOnly + delete, toggle', ); } public function actionAdmin() { $model = new Post('search'); $model->unsetAttributes(); if(isset($_GET['Post'])) $model->attributes=$_GET['Post']; if (Yii::app()->request->isAjaxRequest) { $this->renderPartial('_grid', array( 'model'=>$model, )); } else { $this->render('list', array( 'model'=>$model, )); } } public function actionToggle($id, $attribute) { if (!in_array($attribute, array('public', 'inhome'))) throw new CHttpException(400, 'Некорректный запрос'); $model = $this->loadModel($id); $model->$attribute = $model->$attribute ? 0 : 1; $model->save(); if (!Yii::app()->request->isAjaxRequest) $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin')); } // ... public function loadModel($id) { // ... } }
Теперь ячейки нашей колонки будут отображать статус и полноценно переключать значения полей по щелчку.
Здравствуйте !
Не могли бы Вы дать ссылку на упомянутое в самом начале решение с чекбоксами ?
Хотя бы для сравнения с Вашим, которое мне очень нравится, но все-таки хотелось бы иметь возможность групповых toggle/untoggle.
Можно взять обычную ячейку, вписать в неё код чекбокса и указать тип 'row':
Здравствуйте !
Пытался воспользоваться вашим компонентом.
Работает до тех пор, пока мне не нужно задавать параметр linkUrl.
При этом возникает ошибка
Что это может значить ?
Здравствуйте. В вашем фреймворке не нашёлся класс CJavaScriptExpression. Он доступен с версии 1.1.11.
Спасибо.
Обновил до текущей версии.
Работает
Скажите пожалуйста, можно ли использовать DToggleColumn, если поле, с которым она связана, может принимать значения не только 0 и 1, но 2,3,4 и т.д. ?
0 трактуется как false, все остальные значения - true.
Можно, так как там простое условие с такой же трактовкой
А как в этом случае быть со свойствами filter и titles ?
Будут нормально работать. Только значки и titles будут также отображать всего два состояния (Активно/Нет), а filter нормально работает сам по себе напрямую с провайдером данных.
Увы, увы ...
Во время исполнения в строке 178 DToggleColumn.php возникает ошибка Undefined offset: 9
Ладно, с этим я справился, заменив $title = $this->titles[(int)$value]; на $title = $this->titles[($value)?1:0];
Но с фильтром проблема серьезнее
Это, конечно, сугубо моя проблема, ибо поле num есть вычисляемое и действительно отсутствует в запросе, сформированном автоматически при выборе значения из списка 'filter'=>array(1=>'full', 0=>'empty').
Тем не менее, насколько я понимаю, невозможно прописать все возможные значения данного поля при задании 'filter', так что от его использования придется отказаться.
В свежей версии многое переделано и Undefined offset не выскочит. А с фильтром без поля действительно не получится с любой колонкой (не только с моей). Да и DToggleColumn не для этого изначально сделан.
нужно использовать все возможности Yii, поэтому вместо:
После генерации CRUD контроллера я бы дописал так:
Да-да. Я тоже использую фильтр postOnly. А это просто пример.
ну как вариант, конечно использовать проверку в контроллере, но и про postOnly нужно упомянуть, а то мало ли кто прочитает статью)
Понятно. Поменял.
Доброго времени суток, Дмитрий. Подскажите пожалуйста как правильно сделать две колонки с переключателями.
Пока не получилось. Добавил вторую колонку по примеру вашему. Но при клике на ссылке(иконке) уходит два пост запроса. Если вторую колонку убираю, то первая со статусом работает нормально.
У меня нормально работает:
В текущей версии 1.1 классы уже генерировались через rand. В версии 1.2 они генерируются через автоинкремент, чтобы не терялась привязка скриптов при включенном ajaxUpdate.
Отличное решение! Все работает!
Но есть один вопрос. Как можно по запросу организовать полное ajax обновление грида? Естественно без перезагрузки всей страницы.
Не хочется лепить фрэймы и так же не хочется это делать в попапе.
Если дадите хотя бы направление в какую сторону копать - буду признателен.
В старой версии было полное обновление посредством вызова
Можете взять фрагмент оттуда.
Огромное спасибо!
Получается для каждой такой колонки надо писать свой экшн?
Можно сделать один ToggleAction (или взять из комплекта действий) и подключать к каждому контроллеру.
Добрый день, Дмитрий. Спасибо за ответ. То есть в ToggleAction надо анализировать какой параметр был изменен, я правильно понимаю?
И еще вопрос. А как можно сделать то же самое, но без иконок, а с обычными чекбоксами?
Да, в ToggleAction передаётся имя параметра.
Вместо иконки CHtml::image нужно вывести чекбокс CHtml::checkBox и заменить JavaScript обработчик с onClick на onChange.
Здравствуйте! Хочу сделать lazy load для грида. В самом виджете стоит
$model->search() задается в модели в одноименной функции search. Там как раз идет выборка данных, но если я допишу limit для lazy load там - get запрос не сработает. А если пишу в actionAdmin() в контроллере или отдельном экшене тоже почему-то не работает. Подскажите, пожалуйста, как это правильно сделать.
А как именно вы там limit вписываете?
Сейчас limit удалось передать параметром в search:
И указав его в DataProvider в виджете самого грида:
Получить результат из GET хотела в actionAdmin (у меня в админе формируется грид), но вместе с ним идет html код страницы, даже, если делаю проверку на аякс. Поэтому пока ловлю действие в отдельном экшене.
Передаю вот так:
Подскажите, как получить результат в actionAdmin?
Дозагрузку я реализовывал у себя так.
Здравствуйте,
подскажите где найти _grid.php?
как через ajax можно обновить рисунок? Например, после того как я нажал published чтобы рисунок автоматический показал unpublished?
Спасибо
В листинге есть _grid.php.
Рисунок сам обновляется.
спасибо, получилось
Не получилось вывести записи у которых «Опубликовано» «На главной странице» = true.
Что сделал:
1) В PostController.php->actionIndex() записал код:
2) В index.php вместо ...CListView... записал
3) В _grid.php - код, согласно приведенного выше в статье.
В итоге получил обратную картину: вижу записи у которых «Опубликовано»=false AND «На главной странице»=false.
Что сделать, что бы результат был правильным (показаны записи у которых «Опубликовано» «На главной странице» = true) ?
Спасибо.
Спасибо. Все получилось.
Добрый день,
Не подскажите, как изменить ссылку в этом переключателе с текущего контроллре на сторонний.
Необходимо для переключеня состояния в гриде связанных данных
Пробовал так:
'linkUrl'=>'/sadmin/food/toggle?id='.$model->id.'&attribute=exist',
Но в гриде выходят ошибки:
Parse error: syntax error, unexpected '/' in C:\OpenServer\domains\hotel\framework\base\CComponent.php(612) : eval()'d code on line 1
Решено
Не подскажите, почему при фильтре по показывать/не показывать опубликованные может вываливаться ошибка: cgridview ReferenceError: afterAjaxUpdate is not defined
Здравствуйте, Дмитрий!
Мучает меня вопрос по фильтрации GridView. По умолчанию фильтрация происходить по событию "change". А как сделать так чтобы фильтрация производилась по событию "input"?
Нашел в скрипте yii.gridView.js строчку:
Меняю значение change.yiiGridView на input.yiiGridView - результат становится похож на требуемый но с поля в процессе ввода постоянно пропадает фокус.
Может в Yii2 есть какая-то стандартная возможность заставить фильтрацию работать по событию input?
Вроде есть только это.