Yii 1.1: Avoiding rendering entire page when using CGridView and CListView via AJAX

30 followers

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']).

This thing also works for CListView in very similar way.

Total 16 comments

#17110 report it
Wiseon3 at 2014/05/05 01:55am
@mecano

I personally restricted the filter to apply only to specific actions, by adding:

/**
     * The action names for which this filter should be applied.
     * @var array 
     */
    public $applyToActions = array('admin');

and replacing the condition with

if (Yii::app()->request->getIsAjaxRequest() && isset($_GET["ajax"]) && in_array(Yii::app()->getController()->getAction()->getId(), $this->applyToActions)) {

Whenever I have grids in a different action then actionAdmin, I put this in the respective controller:

/**
    * @return array action filters
    */
    public function filters(){
        return array_merge(
            parent::filters(),
            array(
                array(
                    'application.filters.GridViewHandler',
                    'applyToActions' => array('admin', 'otheraction1', 'otheraction2'),
                )
            )
        );
    }
#17078 report it
mecano at 2014/04/30 12:53pm
delete in Cgridview

this work very well...

but when i try to delete one record... the code...

if (Yii::app()->request->getIsAjaxRequest() && isset($_GET["ajax"])) {
            $selectedTable = $_GET["ajax"];

redirect to the render partial nod to the delete code.... what i can do ?

#14149 report it
kiran sharma at 2013/07/23 03:03am
@xrx

Thanks for this article.

It helps me much to avoid rendering whole page on pagination call in List View.

#11701 report it
Liam Carter at 2013/01/28 07:52am
Finally Fixed

Ok i found i had another issue.

As my grids are rendered within CJuiTabs i had to retrun the renderPartial instead of just echoing it.

The problem then arose when i then changed the page as this required the view to be rendered not returned.

So i fixed with the following _getGridView function

public function _getGridViewinvoicesGrid($first = false)
    { 
        Custom::addLog(print_r(get_defined_vars(), 1), 'filter');
        $id = Yii::app()->user->username;
        $dataProvider=new CActiveDataProvider('Invoice', array(
                'criteria'=>array(
                'condition'=>'acc_id='.$id.'',
                'order'=>'id DESC',
            ),  
            'pagination'=>array(
                'pageSize'=>8,
            ),
        ));
        if ($first) return $this->renderPartial('_view_bottom_invoices', array('dataProvider'=>$dataProvider), true);
        else $this->renderPartial('_view_bottom_invoices', array('dataProvider'=>$dataProvider));
    }

Then on the first call within the tabs call it like this:

<?php $this->widget('zii.widgets.jui.CJuiTabs', array( 
                                                                                'tabs'=>array(
                                                                                                        ...
                                                                                                        'Invoices' =>$this->_getGridViewinvoicesGrid(true),
                                                                                                        ... 
                                                                                                    ),
                                                                ));
    ?>

I hope this helps

Liam

#11699 report it
Liam Carter at 2013/01/28 05:37am
Ok Ignore me

Hiya.

Sorry please ignore me.

The system i am using is a bit specific and i use the username as the id for the customer,

I would normally just reference to $_GET['id'] i assume.

Once again thanks and it is nearly working great now.

Just one last issue.

I use this

$this->renderPartial('_view_bottom_invoices', array('dataProvider'=>$dataProvider), true);

I have to as the grid is within some tabs see here

<?php $this->widget('zii.widgets.jui.CJuiTabs', array( 
                                                                                'tabs'=>array(
...
                                                                                                        'Invoices' =>$this->_getGridViewinvoicesGrid(),
 
...                                                                                                 ),
                                                                ));
    ?>

I have tried with 2 x true and if i dont use any of the last true parameters the grid gets rendered but outside the tabs. any ideas

Regards

Liam

#11698 report it
Liam Carter at 2013/01/28 05:23am
How to pass model or model id within this

This seems a great solution as i have multiple grids on my accounts view page.

The issue i have is within _getGridViewinvoicesGrid()

i call a dataprovider that requires the model id for me to load the records just for that customer.

I cannot see any defined variables that can be passed.

I am sorry if this makes no sense.

For example i run this CActiveDatProvider

$dataProvider=new CActiveDataProvider('Invoice', array(
                'criteria'=>array(
                'condition'=>'acc_id='.$model->id.'',
                'order'=>'id DESC',
            ),  
            'pagination'=>array(
                'pageSize'=>8,
            ),
        ));

How can i get that model id again?

I know i can pass the varibale the first time it is generated, but when the filter runs i dont have this variable/object to work with

Regards

Liam

#8689 report it
xrx at 2012/06/19 05:12pm
Re: Non-filters controller only solution

@dRock In case you have several grids in this action, you would have to do the same thing for each grid and that is already a bad thing. Also, you can see that both parts of the "if" statement are similar (actually calling DataSource is same). And using echo in actions is considered as a bad practice in MVC frameworks.

So, if you follow those things I just said, you would get similar solution as I did :)

Thanks for comment tho

#8688 report it
dRock at 2012/06/19 03:52pm
Non-filters controller only solution

We can detect if the request is ajax, and use renderParial instead of render to just return the grid's/list's content.

Assuming you have an out of the box style index action, replace your render section with something like this:

if ( Yii::app()->request->getIsAjaxRequest() && isset($_GET["ajax"])) {
    $this->renderPartial('index',array(
                    'dataProvider'=>$model->search(),
            ),false,false); //Don't process this output
    Yii::app()->end();
} else {
    $this->render('index',array(
                    'dataProvider'=>$model->search(),               
    ));
}

Edit: ditched an echo...

#8640 report it
xrx at 2012/06/15 03:35am
Re: Passing params to getGridview function

@nightmove

I don't know what your code looks like, but you will probably have to set those parameters in javascript and call CGridView's jQuery plugin to refresh content. On PHP just implement that parameter in DataSource to filter out what you want. It's not particularly related to my wiki but I'm sure you can find plenty related posts on this wiki and forums.

#8617 report it
nightmove at 2012/06/14 10:16am
Passing params to getGridview function

First, this works great! My grid view works as expected with ajax update.

My problem is, that I have checkboxes with an onclick. I want to pass an id from my checkbox and do a filter with this id. I don not know how to pass this param into my getGridView function?

Can you help?

Thanks in advance.

#8450 report it
andrew.b at 2012/06/05 03:46am
Not so good

Still refreshing filters row.

#8409 report it
nightmove at 2012/05/31 07:57pm
Really, really good

Thanks for this demo. Works like a charm. Two hours ago I still had the problem that filtering in grid views reloads the complete page. Now, only the grid view is reloaded with filtered data.

Thanks! Great work!

#6527 report it
xrx at 2012/01/16 11:07am
Re: with tab view ajax grid is not working

@bhaumik25

Anything I explained above shouldn't change the way that JS is called, so my guess is that you have other problem here. Are you using ajax request to load tab with grid?

If you do, then you have to initialize JS for grid in view that renders tab view or find other way to call grid's JS. If you load it with ajax, only HTML will be displayed and JS will not be executed, and your grid will behave like ajax is disabled (and will refresh entire page). You can find similar posts in forums...

#6517 report it
Bhaumik at 2012/01/16 09:03am
with tab view ajax grid is not working

i've used the tab control, i've also use this code, but page is still refresh.

my controller code is as follows.

public function actionDeposit()
{
  $this->render('/wallet/_deposit');
}
 
public function _getGridViewHistoryGrid()
{
    $this->renderPartial('/wallet/_history', array('historyData'=> OrderItems::model()->getCustomerWalletHistory()));
}

/////////////////////////////////////////

_history.php

$this->widget('zii.widgets.grid.CGridView', array(
        'id'=>'historyGrid',
 
        'ajaxUrl'=> CHtml::normalizeUrl('history'),
        'ajaxUpdate'=>true,
        'dataProvider'=>$historyData,
        'columns' => array(
        'deals_purchased', 
        'transfer_code', 
        'order_date', //->getRelated(\'order\')->order_date',
        'discount_earned',
        'order_amount',
         )
     ));

/////////////////////////////////////////

/////////////////////////////////////////

deposit.php

<?php $this->_getGridViewHistoryGrid();?>

/////////////////////////////////////////

/////////////////////////////////////////

view Source HTML:

<div id="historyGrid" class="grid-view">
<div class="summary">Displaying 1-2 of 3 result(s).</div>
<table class="items">
<thead>
<tr>
<th id="historyGrid_c0">Deals Purchased</th><th id="historyGrid_c1"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=transfer_code">Transfer Code</a></th><th id="historyGrid_c2">Order Date</th><th id="historyGrid_c3"><a class="asc" href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned.desc">Discount Earned</a></th><th id="historyGrid_c4"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=order_amount">Order Amount</a></th></tr>
 
</thead>
<tbody>
<tr class="odd"><td>11/01/2012 Between 08:07 PM TO 12:00 AM</td><td>XCAFDFF</td><td>11/01/2012</td><td>50%</td><td>20.00</td></tr>
<tr class="even"><td>11/01/2012 Between 08:07 PM TO 12:00 AM</td><td>XCAFDFG</td><td>11/01/2012</td><td>29%</td><td>50.00</td></tr>
</tbody>
</table>
<div class="pager">Go to page: <ul id="yw0" class="yiiPager"><li class="first hidden"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned">&lt;&lt; First</a></li>
 
<li class="previous hidden"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned">&lt; Previous</a></li>
<li class="page selected"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned">1</a></li>
<li class="page"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned&amp;OrderItems_page=2">2</a></li>
<li class="next"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned&amp;OrderItems_page=2">Next &gt;</a></li>
<li class="last"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned&amp;OrderItems_page=2">Last &gt;&gt;</a></li></ul></div><div class="keys" style="display:none" title="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned"><span></span><span></span></div>
</div>  </tbody>
</table>            </div>

/////////////////////////////////////////

view Source script :

<script type="text/javascript" src="/eh/assets/e2c39eda/gridview/jquery.yiigridview.js"></script>
<script type="text/javascript">
/*<![CDATA[*/
jQuery(function($) {
jQuery('#historyGrid').yiiGridView({'ajaxUpdate':['1','historyGrid'],'ajaxVar':'ajax','pagerClass':'pager','loadingClass':'grid-view-loading','filterClass':'filters','tableClass':'items','selectableRows':1,'url':'history','pageVar':'OrderItems_page'});
});
/*]]>*/

Page is still refreshing, please let me know where is the problem?

#3178 report it
xrx at 2011/03/24 08:27am
Re: another method

You're right and that's what I do. My point was to make elegant and universal solution without radical changes on controller/view except extracting existing widget code to separate method. Filter is used just to avoid massive switches in actions that would handle those renderPartial calls (I'm talking about complicated pages with several grids in same page). And with filter you avoid creating those switches in every action that use grid. So, I would say that this is just nice DRY approach :)

#3177 report it
Maurizio Domba Cerin at 2011/03/24 08:05am
another method

The code generated by gii is just an example, simple enough that a new user can understand it... and usable in most of the situations...

If you don't want the complete page... you can just renderPartial()... this way you would return just the grid.

Leave a comment

Please to leave your comment.

Write new article