Avoiding rendering entire page when using CGridView via AJAX

You are viewing revision #4 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version.

next (#5) »

Since I used CGridView for a first time, I didn't like how it handled operations like sorting, filtering, changing page and etc using AJAX.

Little background for those who are not aware of problem. When you, for example, change page on CGridView, you send AJAX request to current action. Entire page is rendered again just with different parameters to that particular CGridView. After that, widget gets only CGridView part of HTML code and replaces it with current CGridView HTML. Bad thing with this is: when entire page is rendered, it executes all other things on page including other widgets, queries, models and etc making it unusable for any complicated page you might have.

You can avoid this with filters, which will be executed before controller's action. The idea is that you extract CGridView code in separate method, so you can invoke it from view and in case of AJAX request. When you call AJAX request for specific CGridView, filter will intercept it and call method where we have CGridView code. Clear as mud, right?

Let's start with code, first let's create filter itself: Create folder filters and file in it called GridViewHandler.php with following code:

<?php
class GridViewHandler extends CFilter {

	protected function preFilter($filterChain){
		if (Yii::app()->request->getIsAjaxRequest() && isset($_GET["ajax"])) {
			$selectedTable = $_GET["ajax"];
			$method='_getGridView'.$selectedTable;
			if(method_exists($filterChain->controller,$method)){
				$filterChain->controller->$method();
				Yii::app()->end();
			}else{
				throw new CHttpException(400,"CGridView handler function {$method} not defined in controller ".get_class($filterChain->controller));
			}
        }
        return true;
    }
}
?>

In controller where you have CGridView (famous Post controller), add filter (after accessControl if you have it), and extract CGridView code in separate method like this:

<?php
class PostController extends Controller{

	public function filters(){
		return array(
			array(
				'application.filters.GridViewHandler' //path to GridViewHandler.php class
			)
		);
	}

	public function actionIndex(){
		//you call _getGridViewPost in index view to display CGridView
		$this->render('index');	
	}

	//This is called by filter when CGridView (with id="Post") is invoked with AJAX request
	public function _getGridViewPost(){ 
		//create data provider and renderPartial CGridView widget
	}
}

So in index view, put _$this->getGridViewPost() in place where you want your CGridView. In _ getGridViewPost method, create your dataProvider and call $this->renderPartial() of view where you have your CGridView widget.

That's it! On first request, everything works as normal but when you interact with CGridView, filter intercepts request and call only method that is related to CGridView. This also satisfies Convention Over Confituration thing. Method with CGridView must have name _getGridView{$ajaxVar} where {$ajaxVar} is widget ID (which is passed by $_GET['ajax']).

Also, in theory, this should work in CListView too but I didn't try it to be honest :)