Cooking Advanced CGridView

This post will be interesting to those who begin to get acquainted with the Yii framework, that is, users from the initial to the average level of knowledge of this framework, as well as those who like to hollivat on which of the frameworks is cooler.

And so, here I want to show you how flexibly and functionally you can customize CGridView, how you can add various filters according to relational data, how to add widgets such as CJuiDatePicker.

Necessary tools
When writing a post, I used Yii Framework 1.1.12 and used MySQL as a DBMS, so all examples of the above code will be bound to them.

Let `s start?
Consider the situation of a typical blog, we will have the most common plate of posts, users, categories of these posts, tags and comments on posts.

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 ;


We will consider setting up CgridView on the post label, since blog posts are the main entity and all other entities are attached to them.

Customizing Relations for the Post Model
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),
	);
}


We will leave the controller almost by default
        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,
		));
	}


In the Tag model, we will add a new method that will return to us an array suitable for format for inserting a plate into the filter as a drop-down list
	public function getForFilter(){
		return CHtml::listData(
			self::model()->findAll(array(
				'select' => array('id', 'name')
			)), 'id', 'name'
		);
	}


Add a method that returns the full name, in the User model
	public function getFullFio(){
		return $this->last_name.' '.$this->first_name.' '.$this->father_name;
	}


We need to add a similar method to the Post model to display a list of all tags that relate to the article.
	public function getTagsToString(){
		$t = CHtml::listData( $this->tags, 'id', 'name' ); 
		return implode(',', $t);
	}


Further we will work only with the Post model and with the display in which our CGridView is displayed.
To search for tags with the MANY_MANY relational, I use an additional field in the post model
	public $id_tag = '';


To filter, we must extend the Post :: search () method.
It looks like this to me -
	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->addSearchCondition( 
                       new CDbExpression( 'CONCAT(author.last_name, " ", author.first_name, " ", author.father_name)' ),  
                       $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);
		return new CActiveDataProvider('Post', array(
			'criteria'=>$criteria,
			'sort'=>array(
				'defaultOrder'=>'t.status, t.update_time DESC',
			),
		));
	}


To display the list of tags in the label, in a single line, we will use the previously created magic method Post :: getTagsToString ()
    ...
		array(
			'name'=>'id_tag',
			'value'=>'$data->tagsToString',
			'filter'=>Tag::model()->forFilter,
		),
    ...


Similarly, we will do with the mapping of the full name post author
    ...
		array(
			'name'=>'author_id',
			'value'=>'isset($data->author) ? $data->author->fullFio : ""'
		),
    ...


More interesting, we fasten the standard CJuiDatePicker widget to the plate filter.
Add a new column for the plate with such parameters.
    ...
		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)
		),
    ...


But that's not all, if your plate uses ajaxUpdate then the date filter will break after updating the plate with ajax. To fix this, we must re-initialize the calendar by the afterAjaxUpdate event, this is done
    ...
	'afterAjaxUpdate' => 'function(){
    	    jQuery("#create_date").datepicker({
        	 dateFormat: "yy-mm-dd",
                 changeYear:true
    	    });
        }',
    ...



Customization of the plate is also quite easy, for example, if we need to replace the image of one of the buttons or hide them, for standard buttons we can use the properties -
{button_name} ButtonImageUrl
{button_name} ButtonLabel
{button_name} ButtonOptions
{button_name} ButtonUrl

example we replace the button picture with the standard update buttons
    ...
		array(
			'class'=>'CButtonColumn',
			'updateButtonImageUrl' => Yii::app()->baseUrl.'/images/configure.gif' 
		),
    ...


or for example we want to create our own button
...
      	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")',
                 ),
             ),
        )
...


In the end,
I would like to leave here some useful links to extensions that will help you with the upgrade of your CGridView.
Allows you to hide the standard view, update and delete buttons according to the specified criteria.
Alphabetical pagination. We
display the table in the tree format

Source , database dump inside.

Also popular now: