Создаём свои типы ячеек для CGridView в Yii
В предыдущем рецепте по стилизации виджетов мы рассмотрели, как можно кастомизировать стандартные и персональные виджеты Yii, упростив при этом код представлений до минимума. В этом уроке мы на примере грида записей своего мини-блога попробуем улучшить вид и минимизировать код ячеек виджета CGridView
.
Представим, что записи блога на Yii содержат дату, изображение, заголовок и текст. Тогда стандартный грид в панели управления можно вывести так:
<?php $this->widget('zii.widgets.grid.СGridView', array( 'dataProvider' => $model->search(), 'filter' => $model, 'columns' => array( 'date', 'title', array( 'class' => 'СButtonColumn', ), ), ));
Но это будет смотреться не очень стильно. Для большего удобства было бы лучше добавить в грид превью изображений и сделать даты и заголовки постов ссылкам на страницу редактирования записи.
Мы можем это сделать так:
<?php $this->widget('zii.widgets.grid.CGridView', array( 'dataProvider' => $model->search(), 'filter' => $model, 'columns' => array( array( 'name' => 'image', 'value' => '$data->image ? CHtml::link(CHtml::image("/upload/images/" . $data->image), Yii::app()->controller->createUrl("update", array("id" => $data->id))) : ""', 'type' => 'html', ), array( 'name' => 'date', 'value' => 'CHtml::link(CHtml::encode($data->date), Yii::app()->controller->createUrl("update", array("id" => $data->id)))', 'type' => 'html', ), array( 'name' => 'title', 'value' => 'CHtml::link(CHtml::encode($data->title), Yii::app()->controller->createUrl("update", array("id" => $data->id)))', 'type' => 'html', ), array( 'class' => 'СButtonColumn', 'viewButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/view.png'; 'updateButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/edit.png'; 'deleteButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/del.png'; ), ), ));
Здесь мы, вывели изображения постов, сделали текст ячеек ссылками на редактирование записи и поменяли иконки кнопок управления.
Но полёт фантазии этим иногда не ограничивается. Порой в сети можно встретить и такое:
'value'=> 'file_exists($_SERVER[DOCUMENT_ROOT] . Yii::app()->urlManager->baseUrl . "/images/assortiment_img/thumb/" . $data->id . "_assortiment.jpg") ? Yii::app()->urlManager->baseUrl . "/images/assortiment_img/thumb_small/" . $data->id . "_assortiment.jpg" : Yii::app()->urlManager->baseUrl . "/images/assortiment_img/thumb_small/no_photo.gif"',
Мы собрали все ссылки в каждой ячейке явно. Копировать такие длинные и сложные строки из файла в файл неудобно. Попробуем это автоматизировать и упростить.
Стилизация колонок CGridView
Итак, у нас изображение, дата и заголовок статьи должны быть ссылками на действие редактирования. Чтобы не собирать каждый раз HTML-код со ссылкой нам необходимо сделать ячейки, которые бы делали это сами.
В нашем примере строка
'class' => 'СButtonColumn',
указывает, какой класс будет использоваться для генерации заголовка и ячеек столбца. Если класс не указан, то по умолчанию используется CDataColumn
. Содержимое ячейки генерируется в его методе CDataColumn::renderDataCellContent
. Следовательно, чтобы создать любой новый тип ячейки, можно переопределить данный метод в классе-наследнике и указывать этот класс в списке колонок.
Нам нужны пока два типа ячеек: с простой ссылкой на редактирование записи и с изображением-ссылкой.
Напишем первый класс на основе CDataColumn
:
/** * @author ElisDN <mail@elisdn.ru> * @link https://elisdn.ru */ Yii::import('zii.widgets.grid.CDataColumn'); class DLinkColumn extends CDataColumn { public $link; protected function renderDataCellContent($row, $data) { $url = $this->getItemUrl($row, $data); $value = $this->getItemValue($row, $data); $text = $this->grid->getFormatter()->format($value, $this->type); echo $value === null ? $this->grid->nullDisplay : CHtml::link($text, $url); } protected function getItemValue($row, $data) { if (!empty($this->value)) return $this->evaluateExpression($this->value, array('data' => $data, 'row' => $row)); elseif (!empty($this->name)) return CHtml::value($data, $this->name); return null; } protected function getItemUrl($row, $data) { if (!empty($this->link)) return $this->evaluateExpression($this->link, array('data' => $data, 'row' => $row)); elseif ($this->link !== false) return Yii::app()->controller->createUrl('update', array('id' => $data->getPrimaryKey())); return ''; } }
Он автоматически оборачивает текст ячейки в ссылку.
Теперь вместо записи
array( 'name' => 'title', 'value' => 'CHtml::link(CHtml::encode($data->title), Yii::app()->controller->createUrl("update", array("id" => $data->id)))', 'type' => 'html', ),
можно просто указать столбцу нужный класс:
array( 'name' => 'title', 'class' => 'DLinkColumn', ),
Этот столбец ввиду наследования поддерживает все атрибуты стандартного столбца CDataColumn
, то есть можно также указывать value
, header
, type
, htmlOptions
и т.д.; использовать сортировку и фильтрацию.
Но мы намеренно добавили новый параметр link
, используя который можно изменить ссылку на любую другую. Если его не указывать, то будет автоматически подставляться адрес на действие update
текущего контроллера.
Вот, например, подстановка ссылки на просмотр записи:
array( 'name' => 'title', 'class' => 'DLinkColumn', 'link' => '$data->getUrl()', ),
Для этого в модели должен быть метод getUrl
. И вообще, удобно добавлять метод getUrl
ко всем моделям, у которых есть страница просмотра, и обращаться к ним по $model->url
:
array( 'name' => 'title', 'class' => 'DLinkColumn', 'link' => '$data->url', ), array( 'name' => 'author_id', 'class' => 'DLinkColumn', 'value'=> '$data->author->username', 'link' => '$data->author->url', ), array( 'name' => 'category_id', 'class' => 'DLinkColumn', 'value'=> '$data->category->title', 'link' => '$data->category->url', ),
Второй тип столбца, который нам нужен, аналогичен первому, но должен выводить изображение со ссылкой. Чтобы не повторять в нём метод getItemUrl
возьмём его прямо из DLinkColumn
:
/** * @author ElisDN <mail@elisdn.ru> * @link https://elisdn.ru */ class DImageLinkColumn extends DLinkColumn { public $width = 0; public $height = 0; protected function renderDataCellContent($row,$data) { $url = $this->getItemUrl($row, $data); $value = $this->getItemValue($row, $data); $options = $this->getImageOptions(); $image = CHtml::image($value, '', $options); echo $value===null ? $this->grid->nullDisplay : CHtml::link($image, $url); } protected function getImageOptions() { $options = array(); if ($this->width) $options['width'] = $this->width; if ($this->height) $options['height'] = $this->height; return $options; } }
Эта колонка теперь содержит дополнительные поля link
, width
и height
.
Теперь вместо ручного генерирования ссылки
array( 'name' => 'image', 'value' => '$data->image ? CHtml::link(CHtml::image("/upload/images/" . $data->image), Yii::app()->controller->createUrl("update", array("id" => $data->id))) : ""', 'type' => 'html', ),
мы аналогично указываем класс столбца и адрес изображения
array( 'class' => 'DImageLinkColumn', 'value' => '"/upload/images/blog/" . $data->image', 'width' => 150, ),
Ещё приятнее создать метод getThumbUrl
в каждой модели
class Post extends CActiveRecord { const IMAGE_PATH = 'upload/images/blog'; // ... private $_url; public function getUrl() { if ($this->_url === null) $this->_url = Yii::app()->createUrl('post/view', array('id'=>$this->id)); return $this->_url; } public function getImageUrl() { return $this->image ? $this->getImagePath() . '/' . $this->image : ''; } public function getThumbUrl() { return $this->image ? $this->getImagePath() . '/prev_' . $this->image : ''; } protected funtion getImagePath() { return Yii::app()->request->baseUrl . '/' . self::IMAGE_PATH; } }
и во всех гридах использовать его:
array( 'class' => 'DImageLinkColumn', 'value' => '$data->getThumbUrl()', 'width' => 150, ),
Замена иконок кнопок в CButtonColumn
Следующий шаг – замена сложной записи по изменению изображений кнопок просмотра, редактирования и удаления
array( 'class' => 'СButtonColumn', 'viewButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/view.png'; 'updateButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/edit.png'; 'deleteButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/del.png'; ),
на более простую и настраиваемую независимо форму.
Фактически, здесь мы видим использование класса СButtonColumn
с указанием значений трёх его публичным полей, как и при задании опций виджетам. Но колонка грида – это не виджет, генерируемый с помощью фабрики widgetFacory
, поэтому использовать скины для классов колонок мы не можем. Поэтому стоит воспользоваться наследованием с переопределением параметров исходного класса CButtonColumn
:
Yii::import('zii.widgets.grid.CButtonColumn'); class DButtonColumn extends CButtonColumn { public function init() { if ($this->viewButtonImageUrl === null) $this->viewButtonImageUrl = Yii::app()->request->baseUrl . '/images/admin/view.png'; if ($this->updateButtonImageUrl === null) $this->updateButtonImageUrl = Yii::app()->request->baseUrl . '/images/admin/edit.png'; if ($this->deleteButtonImageUrl === null) $this->deleteButtonImageUrl = Yii::app()->request->baseUrl . '/images/admin/del.png'; parent::init(); } }
Перед инициализацией колонки мы проверяем эти параметры на заполнение, и если изображения не указаны пользователем, то подставляем свои.
С этим классом теперь вместо записи
array( 'class' => 'СButtonColumn', 'viewButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/view.png'; 'updateButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/edit.png'; 'deleteButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/del.png'; ),
можно обойтись всего одной строкой
array( 'class' => 'DButtonColumn', ),
В совокупности это упростит наш исходный фрагмент кода
<?php $this->widget('zii.widgets.grid.CGridView', array( 'dataProvider' => $model->search(), 'filter' => $model, 'columns' => array( array( 'name' => 'image', 'value' => '$data->image ? CHtml::link(CHtml::image("/upload/images/" . $data->image), Yii::app()->controller->createUrl("update", array("id" => $data->id))) : ""', 'type' => 'html', ), array( 'name' => 'date', 'value' => 'CHtml::link(CHtml::encode($data->date), Yii::app()->controller->createUrl("update", array("id" => $data->id)))', 'type' => 'html', ), array( 'name' => 'title', 'value' => 'CHtml::link(CHtml::encode($data->title), Yii::app()->controller->createUrl("update", array("id" => $data->id)))', 'type' => 'html', ), array( 'class' => 'СButtonColumn', 'viewButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/view.png'; 'updateButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/edit.png'; 'deleteButtonImageUrl' => Yii::app()->request->baseUrl . '/images/admin/del.png'; ), ), ));
до состояния
<?php $this->widget('zii.widgets.grid.СGridView', array( 'dataProvider' => $model->search(), 'filter' => $model, 'columns' => array( array( 'class' => 'DImageLinkColumn', 'value' => '$data->getThumbUrl()', ), array( 'name'=>'date', 'class' => 'DLinkColumn', ), array( 'name' => 'title', 'class' => 'DLinkColumn', ), array( 'class' => 'DButtonColumn', ), ), ));
Аналогичный подход с наследованием позволяет нам сделать любой персональный тип колонок, например DToggleColumn или image-column.
В этом моменте случайно нет опечатки?
ну и ниже
"до состояния"...
Спасибо. Исправил.
У вас ошибка в
Спасибо, исправил. Мимо клавиши промахнулся.
Прошу прощение за тупой вопрос, но:
"Напишем первый класс на основе CDataColumn:"
а где мы этот свой класс пишем?
Одноимённым файлом в protected/components, например.
Не могли бы Вы подсказать?
На главной странице через action admin выводится grid и поиск по нему. Хочу дать возможность пользователю дать выбрать доп.поля. Для этого создаю action ajax. Когда ввожу данные, находясь по урл article/ajax, пост приходит нормальным, а когда по article/admin в посте приходит еще html код страницы. Что я делаю не так?
Вот код:
Во view admin.php вывожу форму
Форма article_form.php
Модель:
Добавьте условие:
Спасибо, попробую так сделать
Здравствуйте,
такая проблема.
Если я изменяю класс столбца, то в хедере с фильтром инпут не помещается в div. И из-за этого инпут залезает на соседний столбец
пока решил вот так:
А вы не пробовали просто поменять type с 'html' на 'image' или 'url' ;) Глядишь и кода меньше писать бы пришлось.
Пробовал, но приходилось URL самому каждый раз сочинять и width картинкам костылями через стили подцеплять. И одновременно image+url вывести не получалось.
Это же стандартный геттер, зачем писать так сложно, если достаточно $AR->{propertyName} ?
А где Вы это нашли?
yii\base\CComponent.php:107
Имелось ввиду, что вместо $data->getThumbUrl(), например, можно писать $data->thumbUrl
Да, можно.