Refresh CGridView after CButtonColumn Ajax request

This seems like it should be obvious, but I haven’t been able to figure it out or find anything in the forum or cookbook about it.

I have a CGridView listing items retrieved that have a specific state (in the database); that part is working fine. I also have a button in CButtonColumn making an ajax request that changes that buttons state. The end result is that the row’s state is changed, and it should not appear in that list anymore (much like the CButtonColumn delete action).

How do I get the CGridView to refresh after making this request? I don’t want to reload the page each time, but haven’t been able to figure out how to use a renderPartial() in the controller to refresh the grid; I also looked over the CButtonColumn’s delete action, but couldn’t figure out how to implement that.

I don’t even think my CButtonColumn link is ajaxified right now, and I haven’t been able to figure out how to implement that in CButtonColumn - I’d assume it has something to do with the ‘click’ function, but don’t know how to do that with the newer versions of Yii; would anybody be able to give me a snippet illustrating how to implement that?

Here’s the CGridView column’s code:




$this->widget('zii.widgets.grid.CGridView', array(

                'id'=>'needApproval-grid',

                'dataProvider'=>$dataProvider,

                'ajaxUpdate'=>true,

                'columns'=>array(

                        'project_name',

                        array(

                                'class'=>'CButtonColumn',

                                'template'=>'{view} {approve}',

                                'buttons'=>array(

                                        'approve'=>array(

                                                'label'=>'Approve for Review >>',

                                             'url'=>'Yii::app()->controller->createUrl("project/makeReviewable",array("id"=>$data->project_oid))',

                                                'options'=>array('class'=>'cssGridButton'),

                                        ),

                                ),

                        ),

                ),

        ));



… and here’s my controller action:




public function actionMakeReviewable() {

        if(Yii::app()->request->isAjaxRequest) {

            $model = $this->loadModel();

            $model->status='2';

            if($model->save() && Generic::newState($model->project_oid,$model->status)) {

                $this->redirect(array('/workflow/needApproval'));

            }

        }

    }



I’m not sure if this does exactly what you need, but this is how you would refresh CGridView. You pass this into the ajaxOptions argument of any ajaxLink or ajaxSubmitButton




array(

	'success' => "function(data, textStatus, XMLHttpRequest) { $('#html-id-of-your-grid').yiiGridView.update('html-id-of-your-grid'); }"

)



I am assuming that you are not seeing anything happen when you click your buttons, or they are bringing you to a new page. Have you tried something like




$this->widget('zii.widgets.grid.CGridView', array(

                'id'=>'needApproval-grid',

                'dataProvider'=>$dataProvider,

                'ajaxUpdate'=>true,

                'columns'=>array(

                        'project_name',

                        array(

                                'class'=>'CButtonColumn',

                                'template'=>'{view} {approve}',

                                'buttons'=>array(

                                        'approve'=>array(

                                                'label'=>'Approve for Review >>',

                                                'url'=>'echo "#"',

                                                'options'=>array('class'=>'cssGridButton'),

                                                'click' => "$.ajax({

								url: '" . Yii::app()->controller->createUrl("project/makeReviewable",array("id"=>$data->project_oid))  . "',

								success: function() { $('#needApproval-grid').yiiGridView.update('needApproval-grid'); }

								error: function() { alert('Ajax button failed'); }

					     });"

                                        ),

                                ),

                        ),

                ),

        ));



This should attempt to attach an ajax call to the onlcick event of your button, and if it is succsesful, update the grid. If it is not successful, it will alert an error message. Please keep in mind I typed this by hand into the form, and I don’t have your project to test it on, but I think this should work. Let me konw!

I suppose it won’t work. ‘click’ can’t contain $data or $row references, only url. Check out help:

[i]In the PHP expression for the ‘url’ option and/or ‘visible’ option, the variable $row refers to the current row number (zero-based), and $data refers to the data model for the row.

[/i]

Try this,

http://www.yiiframework.com/forum/index.php?/topic/9558-ajax-action-in-cgridview/page__view__findpost__p__48071

Hmm, I gave it a try, but I really don’t have any idea how to interpret the CButtonColumn code and the jquery.yiigridview.js files, because I’m not sure how they end up being put together (there’s a lot of pieces), and I can’t find the generated code anywhere to examine for examples.

So something simple - I tried this in my CButtonColumn button definition (that part is all working fine):




...

'url'=>'Yii::app()->controller->createUrl("#",array("id"=>$data->project_oid))',

'click'=>'js: function(){

     Approve_clicked( $(this).attr("href") );

     return false;

     function Approve_clicked() {

          alert("Oi oi oi there lad.");

     }

}',

...



And - nothing; so apparently I’m not understanding the concepts at play here. Would anybody happen to have an example of how this works - if I saw one example, I think the “Aha!” moment would happen, and it seems most people have figured out how this works on their own - not sure what I’m missing.

Ok, basically your button code works. So we want to do someting with it. Let we say we want to modify somhow the current db record of the table row.

So first let’s implement the controller action method (in your controller class). Foodata is the db table (or a model) where we want the changes:

 public function actionBar() {


    // Client posts the new values in Foodata post array. Id entry 


    // holds the specific id belongs to the 'clicked' row


    if(isset($_POST['Foodata'])) {


      $model = $this->loadModel($_POST['Foodata']['Id']); // loads the egisting record 


      $model->attributes = $_POST['Foodata']; // set the new values...


      //  other data manipulation can go here ....


      if(false === $model->validate()) { // lets validate 


         $this->sendAjaxError($model);   // if something goes wrong send back error messages


      } else {


     $model->save(false);  // false because: we've allready validated


      }


 }





 // Get validation errors and send back to client JSON enc...


 private function sendAjaxError($model) {


if($model->hasErrors()) {


      $data['error'] = $model->getErrors();


  echo CJSON::encode($data);


      exit();


}

Ok we’ve done the serverside…

Let we see the client. In our view file we must implement Approve_clicked javascript func.

function Approve_clicked(url) {

alert(url); 


// url is href so we can 'parse' id from url


var id = url.substring(url.indexOf(.... blah blah however U feel


// then we can set other values


var field1 = ....


var field2 = .... blah blah...


// etc...





// let we use GridView update method post our new datas, then call again to


// refresh the grid. Internally a simple jquery ajax call goes...


$.fn.yiiGridView.update('id_of_my_grid_view_dontforgettosetone', {





    type:'POST',





    // this is the url of my controller I usually set this into href but we can give it here


    // for instance: index.php?r=mycontroller/bar 


    url: 'url of our controller action',  





    // lets pass the datas. This will appear in $_POST['Foodata'] array


    data: {'Foodata':{'Id':id, 'field1':field1,  'field2':field2}},





    // if success (and our custom errors will be success because it's the jquery's world 


    // error callback only called when server error code is different from OK (200)       


    // (in case of error yiidatagrid will pops up an alert box, check out the js source)


    success:function(data, status) {





         // here we've done succesfully our ajay code so refresh our datagrid


         // calling againg update without parameters simple read 


         // dataProvider and fills the visible portion of our grid with new datas 


         // so we can see the changes...





         $.fn.yiiGridView.update('id_of_my_grid_view_dontforgettosetone');





         // If validationd failed we have json serialised error messages 


         // in data from sendAjaxError ...


     //alert(data);


     if(data != "") {


	// error handling eval it or so...


         }


    },


});

}

Ok that’s all. The key stuff is yiiDataGrid js (client part) update() method. That makes ott painful thinks for us. Hopefully tis helps you.

(Sorry I know my js and php syntaxes sometimes strange I’m coming from C# dotnet world, but I’d tried to use yii and jquery conventions).

regards:

Balázs

Ah, thanks for the reply GBA - that all was pretty helpful.

Unfortunately, I discovered a problem before I can test any of that - the function in the click item doesn’t seem to be activating, at all. I tried this:




...

     'label'=>'Approve',


     /*'url'=>'Yii::app()->controller->createUrl("/communityPartner/approvePartner",array("id"=>$data->community_partner_oid))',*/


     'imageUrl'=>Yii::app()->request->baseUrl.'/images/i_checkmark.png',

     'click'=>'js: function(){

          Approve_clicked();

          return false;

      }',

...



With Approve_click() being something simple for testing:




<script type="text/javascript">

    alert('OI OI!');

    function Approve_clicked() {

         alert('OI!');        

    }

    

</script>



… The first 'OI OI! alerts when the page loads, as expected, and if I call the function from inside that <script> block, it works; so it’s not a problem with that; but for whatever reason the grid can’t seem to call it. The only problems I can think of are either this not being the proper way to call the function from the ‘click’ event, which makes little sense; or the CGridView click function not being able to call <script> functions on the page (that <script> block is in the same view file as the CGridView).

Is this a bug? Oh - one other thing; the CGridView is inside a CJuiTabs - I wonder if that’s causing something odd with the javascript… I don’t have any idea why though, as the CGridView has a unique ID.

AS I see there is no bug, in your code so it must work. There will be a bug in other place in your code. I’m using the yii-1.1.2.r2086p.zip framework version.

I made a very simple test about your description:

Controller file: protected\controllers\TestController.php




<?php

class TestController extends Controller {

	public function actionIndex() {

		$dp = new CActiveDataProvider( 

			'Test',  

			array(

				'pagination' => array( 

					'pageSize' => 5, 

				),

			)

		);

		$this->render('index', array('dp' => $dp));

	}


	public function actionAdd() {

		if(isset($_POST['Test'])) {

			$model = Test::model()->findbyPk($_GET['id']); // or $_POST['id'] ...

			$model->attributes = $_POST['Test'];

			$model->Result     = intval($model->A) + intval($model-><img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='B)' /> ;

			$model->save(true);

		}

	}

}



The model file with CJuiTabs: protected\views\test\index.php




<?php		

$this->widget('zii.widgets.jui.CJuiTabs', array(

    'tabs'=>array(

        'StaticTab 1'=>'Content for tab 1',

        'StaticTab 2'=>array('content'=>'Content for tab 2', 'id'=>'tab2'),

        // panel 3 contains the content rendered by a partial view

        'Grid'     =>  $this->renderPartial('grid',array('dp' => $dp),true),

    ),

    // additional javascript options for the tabs plugin

    'options'=>array(

        'collapsible'=>true,

    ),

));

?>



And finally the well known grid view file: protected\views\test\grid.php




<h1>Test A + B = ?</h1>


<?php 

$this->widget('zii.widgets.grid.CGridView', array(

	'dataProvider' => $dp,

	'id' => 'test_grid',

	'selectableRows' => 0,

	'columns'=>array(

		array(

		  'name'=>'ID',

		  'value' => '$data->id',

		),

		array(

		  'name'=>'A number',

		  'type'=>'raw',

		  'value' => 'CHtml::textField("A_".$data->id, $data->A)',

		),

		array(

		  'name'=>'B number',

		  'type'=>'raw',

		  'value' => 'CHtml::textField("B_".$data->id, $data-><img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='B)' />',

		),

		array(

			'name'  => 'A + B',

			'value' => '$data->Result',

		),

		array(

			'class'		=> 'CButtonColumn',

			'template' => '{calc}',

			'buttons' => array(

				'calc' => array(

					'label' => 'A + B',

					'url'	=> 'Yii::app()->controller->createUrl("add",array("id"=>$data->id))', 

					'click' => 'js:function() {calc_onclick( $(this).attr("href") ); return false;}',

				),

			),

		),


	),

));

?>


<script type="text/javascript">

/*<![CDATA[*/

// This stand alone script block can't be the problem, is OK. I rather 

// use this separated js block format against the inline js jungle...


//alert('page loaded');

function calc_onclick(url) {


	var id = parseInt(url.substring(url.indexOf('id=') + 3), 10);

	//alert('here we go - url:' + url + ' - id:' + id); 


	var data2post = {

		'Test' : {

			'id' : id, 

			'A'  : $('#A_' + id).attr('value'), 

			'B'  : $('#B_' + id).attr('value')

		}

	};


	$.fn.yiiGridView.update('test_grid', {

		type :'POST',

		url  : url,

		data : data2post,

		success:function(data,status) {

			$.fn.yiiGridView.update('test_grid');

			//alert(data);

		}

	});

}


/*]]>*/

</script>



One more think, the db table definition (mysql) I know it’s really stupid just for testing. The model I used was generated from this table by the yii code generator.




CREATE TABLE `t_test` (

	`id` INT(10) NOT NULL AUTO_INCREMENT,

	`A` INT(10) NULL DEFAULT '0',

	`B` INT(10) NULL DEFAULT '0',

	`Result` INT(10) NULL DEFAULT '0',

	PRIMARY KEY (`id`)

) ENGINE=InnoDB



As I understood yo want somtehing similar and this works like a charm. Try to use logging or other debug thechnics to discover the evil piece of code or split your program smaller pices to test out.

good luck,

regarsd:

Balázs

EDIT: I changed


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

to


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

… which didn’t work before, but now it does. It’s only nested under one tab though (parts of the page have CGridViews nested under two tabs, which hasn’t been working at all).


Hmm, so I tried moving the CGridView and the <script> blocks (same as my example) outside the CJuiTabs, and - it worked. Apparently it’s more of this nested-widget-AJAX-bug that’s been plaguing my life lately; I haven’t quite figured out what’s causing it or found a consistent workaround for it yet on the forums. Thanks for your help though, I really appreciate it :)

This should actually be fairly simple - just imitate the Delete button jQuery.

Please see http://www.yiiframework.com/forum/index.php?/topic/20512-how-to-return-to-current-page-in-gridview/ for more details.