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.
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.
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 :)
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"><< First</a></li> <li class="previous hidden"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned">< 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&OrderItems_page=2">2</a></li> <li class="next"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned&OrderItems_page=2">Next ></a></li> <li class="last"><a href="/eh/index.php/customer/wallettab/deposit?OrderItems_sort=discount_earned&OrderItems_page=2">Last >></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?
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...
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!
Not so good
Still refreshing filters row.
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.
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.
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...
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
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
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
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
@xrx
Thanks for this article.
It helps me much to avoid rendering whole page on pagination call in List View.
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 ?
@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'), ) ) ); }
@Wiseon3
That was a great comment... Thank you
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.