Готовим продвинутые CGridView

в 9:48, , рубрики: cgridview, yii, yii framework, Веб-разработка, Песочница, метки: ,

Данный пост будет интересен тем кто начинает знакомится с Yii framework, то есть пользователям от начального до среднего уровня знаний данного фреймворка, а так же тем кто любит похоливарить на тему какой из фреймворков круче.

И так, я здесь хочу вам показать насколько гибко и функционально можно настроить CGridView, как можно добавлять в нее различные фильтры по реляционным данным, как добавлять виджеты такие как CJuiDatePicker.

Необходимые инструменты
При написание поста, я использовал Yii Framework 1.1.12 и в качестве СУБД использовал MySQL и по этому все примеры приведенного кода будут привязаны к ним.

Ну что начнем?
Рассмотрим ситуацию типичного блога, у нас будет самая обычная табличка постов, пользователей, категорий данных постов, тегов и комментариев к постам.

CREATE TABLE IF NOT EXISTS `tbl_comment` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` text COLLATE utf8_unicode_ci NOT NULL,
  `status` int(11) NOT NULL,
  `create_time` int(11) DEFAULT NULL,
  `author` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `url` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `post_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_comment_post` (`post_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;


CREATE TABLE IF NOT EXISTS `tbl_post` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `content` text COLLATE utf8_unicode_ci NOT NULL,
  `status` int(11) NOT NULL,
  `create_time` datetime NOT NULL,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `author_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_post_author` (`author_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;


CREATE TABLE IF NOT EXISTS `tbl_posts_tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `id_post` int(11) NOT NULL,
  `id_tag` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


CREATE TABLE IF NOT EXISTS `tbl_tag` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `frequency` int(11) DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;


CREATE TABLE IF NOT EXISTS `tbl_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `first_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `last_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `father_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `password` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `salt` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `profile` text COLLATE utf8_unicode_ci,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=2 ;

Мы будим рассматривать настройку CgridView, на табличке постов, поскольку посты в блоге являются основной сущностью и все остальные сущности привязаны к ним.

Настраиваем реляции для модели Post

public function relations(){
	return array(
                //Теги
		'tags' => array(self::MANY_MANY, 'Tag', 'tbl_posts_tags(id_post, id_tag)'),
                //Автор поста
		'author' => array(self::BELONGS_TO, 'User', 'author_id'),
                //Комментарии
		'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'condition'=>'comments.status='.Comment::STATUS_APPROVED, 'order'=>'comments.create_time DESC'),
                //Подсчет количества комментариев
		'commentCount' => array(self::STAT, 'Comment', 'post_id', 'condition'=>'status='.Comment::STATUS_APPROVED),
	);
}

Контроллер мы оставим практически по дефолту

        public function actionAdmin(){
		$model=new Post('search');
		if(isset($_GET['Post'])){
			$model->setAttributes($_GET['Post'], false);
			$model->id_tag = isset($_GET['Post']['id_tag']) ? $_GET['Post']['id_tag'] : '';
		}
		$this->render('admin',array(
			'model'=>$model,
		));
	}

В модель Tag мы добавим новый метод который будет возвращать нам массив подходящий по формату для вставки в фильтр таблички в качестве выпадающего списка

	public function getForFilter(){
		return CHtml::listData(
			self::model()->findAll(array(
				'select' => array('id', 'name')
			)), 'id', 'name'
		);
	}

Добавим метод возвращающий полное Ф.И.О., в модель User

	public function getFullFio(){
		return $this->last_name.' '.$this->first_name.' '.$this->father_name;
	}
<source>


Подобный метод нам нужно добавить в модель Post для отображения списка всех тегов которые относятся к статье
<source lang="PHP">
	public function getTagsToString(){
		$str = '';
		$i = 0;
		foreach($this->tags as $tag){
			$str .= ($i ? ', ' : '') . $tag->name;
			$i++;
		}
		return $str;
	}
<source>


Дальше мы будим работать только с моделью Post и с отображением в котором у нас выводится наша CGridView.
Для поиска по тегам с реляцией MANY_MANY я использую дополнительное поле в модели постов
<source lang="PHP">
	public $id_tag = '';

Для фильтрации мы должны расширить метод Post::search().
У меня он выглядит так —

	public function search($defaultCriteria = null){
		$criteria = $defaultCriteria != null ? $defaultCriteria : new CDbCriteria;

		//Указываем какие реляции нам нужно доставать при создании таблички
		//для облегчения запроса мы не будим тащить все поля, мы укажем какие поля нам нужны для поиска и отображения
		$criteria->with = array(
			'tags' => array(
				'select' => array('id', 'name')
			), 
			'author' => array(
				'select' => array('last_name', 'first_name', 'father_name')
			),
			'commentCount'
		);
		$criteria->together = true;

		if(!empty($this->author_id)){
			//В фильтр id_author у нас есть возможность писать любой критерий поиска по имени, фамилии или отчеству
			$criteria->condition = (!empty($criteria->condition) ? $criteria->condition : '').
									'author.last_name LIKE :aid 
									OR author.first_name LIKE :aid 
									OR author.father_name LIKE :aid 
									OR CONCAT(author.last_name, " ", author.first_name, " ", author.father_name) LIKE :aid';

			if(!count($criteria->params)){
				$criteria->params = array();
			}

			$criteria->params[':aid'] = '%'.$this->author_id.'%';
		}


		//составляем критерий запроса
		if(isset($this->id_tag) && !empty($this->id_tag)){
			$criteria->compare('tags.id', '='.$this->id_tag, true);
		}

		$criteria->compare('t.id', '='.$this->id, true);
		$criteria->compare('t.create_time', '='.$this->create_time, true);
		$criteria->compare('t.update_time', '='.$this->update_time, true);
		$criteria->compare('t.title', $this->title, true);
		$criteria->compare('t.status', '='.$this->status);

		print_r($criteria);
		return new CActiveDataProvider('Post', array(
			'criteria'=>$criteria,
			'sort'=>array(
				'defaultOrder'=>'t.status, t.update_time DESC',
			),
		));
	}

Для вывода списка тегов в табличке, в виде одной строки мы воспользуемся созданным нами ранее магическим методом Post::getTagsToString()

    ...
		array(
			'name'=>'id_tag',
			'value'=>'$data->tagsToString',
			'filter'=>Tag::model()->forFilter,
		),
    ...

Аналогично мы сделаем с отображением полного Ф.И.О. автора поста

    ...
		array(
			'name'=>'author_id',
			'value'=>'isset($data->author) ? $data->author->fullFio : ""'
		),
    ...

Далее интереснее, прикручиваем стандартный виджет CJuiDatePicker к фильтру таблички
Добавляем новую колонку таблички с такими параметрами

    ...
		array(
			'name' => 'create_time',
		        'type' => 'raw',
	    	        'htmlOptions' => array('align' => 'center', 'style' => 'width: 123px;'),		      	
	    	       'filter' => $this->widget('zii.widgets.jui.CJuiDatePicker', array(
				'model' => $model,
		      		'id' => 'create_time',
				'attribute' => 'create_time',
                                'htmlOptions' => array('style' => 'width: 80px;'),
				'options' => array(
					'dateFormat' => 'yy-mm-dd',
                                        'changeYear' => true
				),
			), true)
		),
    ...

Но это еще не все, если ваша табличка использует ajaxUpdate то фильтр с датами будет ломаться после обновления таблички с помощью ajax. Что бы исправить это мы должны по событию afterAjaxUpdate пере инициализировать календарик, делается это так

    ...
	'afterAjaxUpdate' => 'function(){
    	    jQuery("#create_date").datepicker({
        	 dateFormat: "yy-mm-dd",
                 changeYear:true
    	    });
        }',
    ...

Кастомизация таблички происходит тоже достаточно легко, например если нам нужно заменить изображение какой то из кнопочек или скрыть их, для стандартных кнопочек мы можем использовать свойства —
{button_name}ButtonImageUrl
{button_name}ButtonLabel
{button_name}ButtonOptions
{button_name}ButtonUrl

пример заменим картинку кнопки стандартной кнопки update

    ...
		array(
			'class'=>'CButtonColumn',
			'updateButtonImageUrl' => Yii::app()->baseUrl.'/images/configure.gif' 
		),
    ...

или например мы хотим создать свою кнопочку

...
      	array(
		'class' => 'ext.myButtonColumn',
                'template'=> '{on} {off} ',
            'buttons' => array(
            	'off' => array(
                    'label' => 'Активировать',
                    'imageUrl' => Yii::app()->baseUrl.'/images/cancel.gif',
                    'visible' => '$data->active == 0',
                    'url'   => 'Yii::app()->createAbsoluteUrl("post/on")',
                 ),
                 'on' => array(
                     'label' => 'Деактивировать',
                     'imageUrl' => Yii::app()->baseUrl.'/images/flag.gif',
                     'visible' => '$data->active == 1',
                     'url'   => 'Yii::app()->createAbsoluteUrl("post/off")',
                 ),
             ),
        )
...

Напоследок
Я бы хотел оставить здесь несколько полезных ссылок на экстеншены которые помогут вам с апгдейдом ваших CGridView
Позволяет скрывать стандартную кнопку delete по заданному критерию
Алфавитная пагинация
Отображаем табличку в древовидном формате

Автор: Igogo2012

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js