How to add ajaxButton in the CGridView

Let’s imagine I have a field name is_published now I want to add an ajax “Publish toggle” button in the CGridView which is able to set true/false flag.

I’ve tried to extend CButtonColumn but I failed. Please help.

Any help would be much appreciated.

You are probably best off making a custom column with an ajaxLink defined. I am currently doing this for one of my own sites. I’ll post up the code once I get it sorted in a couple of hours.

Thank you Wotton! Cannot wait for your code :)

OK, this is going to have to wait, sorry. This is slowing me down:

CGridView filters and urlFormat = path

Did anybody figure this out yet? I’d be very thankful if someone could explain how to do this!

I think you can do something like this




array(

  'class'=>'CButtonColumn',

  'template'=>'{togglebutton}',

  'buttons'=>array(

    'togglebutton'=>array(

      'options'=>array('name'=>'mytogglebutton'),

      'click'=>'my javascript',

    ),

  ),

),



Either use "my javascript" for whatever you want to do, or add some jQuery code like $("#mytogglebutton").click(function(){$("#someothercontrol").toggle()})

Edit: I was too fast here, please disregard. I could at least have tested the name option first. Of course a row id will have to be appended. I think I will need this functionality in the near future, but I have to complete some requirement work before diving deeper into this.

(not tested)

/Tommy

Putting this in the CGridView definition as a seperate column works:


        array(

            'type'=>'raw',

            'value'=>'CHtml::ajaxLink(CHtml::image("/themes/default/images/up_16.png"),"/profile/reposition",array(

                            "data"=>array(

                                    "id"=>$data->id,

                                    "direction"=>"up"

                            ),

                            "update"=>"#profileGrid",

                        ))." ".

                    CHtml::ajaxLink(CHtml::image("/themes/default/images/down_16.png"),"/profile/reposition",array(

                            "data"=>array(

                                    "id"=>$data->id,

                                    "direction"=>"down"

                            ),

                            "update"=>"#profileGrid",

                        ));',

        ),

But how to put this in the buttons definition of a CButtonColumn…

very interesting… especially if combine with cjuidialog.

this is exactly, definetly what we want.

I don’t completely understand what you intend to do with the ajax return value. I propose something like this for the ajax call:

Extend CButtonColumn




<?php

Yii::import('zii.widgets.grid.CButtonColumn');


class ButtonColumnEx extends CButtonColumn

{

	protected function renderButton($id,$button,$row,$data)

	{

		if (isset($button['visible']) && !$this->evaluateExpression($button['visible'],array('row'=>$row,'data'=>$data)))

  			return;

		$label=isset($button['label']) ? $button['label'] : $id;

		$url=isset($button['url']) ? $this->evaluateExpression($button['url'],array('data'=>$data,'row'=>$row)) : '#';

		$options=isset($button['options']) ? $button['options'] : array();

		if(!isset($options['title']))

			$options['title']=$label;

//*** start of new code

    $ajax=isset($button['ajax']) ? $this->evaluateExpression($button['ajax'],array('data'=>$data,'row'=>$row)) : null;

		if(isset($ajax))

		{

		  if(isset($button['imageUrl']) && is_string($button['imageUrl']))

			echo CHtml::ajaxLink(CHtml::image($button['imageUrl'],$label),$url,$ajax,$options);

		  else

		    echo CHtml::ajaxLink($label,$url,$ajax,$options);

		}

		else

		{

//*** end of new code

		  if(isset($button['imageUrl']) && is_string($button['imageUrl']))

			echo CHtml::link(CHtml::image($button['imageUrl'],$label),$url,$options);

		  else

			echo CHtml::link($label,$url,$options);

		}

	}

}



‘buttons’ definition




'buttons'=>array(

  'up'=>array(

      'label'=>'up',

      'imageUrl'=>'/themes/default/images/up_16.png',

      'url'=>'Yii::app()->createUrl("/item/reposition")',

      'ajax'=>'

        array(

          "url"=>Yii::app()->createUrl("/item/reposition"),

          "data"=>array("id"=>$data->id,"direction"=>"up",),

          "update"=>"#profileGrid"

        )

      ',

    ),

  'down'=>array(

      'label'=>'down',

      'imageUrl'=>'/themes/default/images/down_16.png',

      'url'=>'Yii::app()->createUrl("/item/reposition")',

      'ajax'=>'

        array(

          "url"=>Yii::app()->createUrl("/item/reposition"),

          "data"=>array("id"=>$data->id,"direction"=>"down",),

          "update"=>"#profileGrid"

        )

      ',

    ),

  ),



Notes:

(1) Save extended class e.g. as ButtonColumnEx.php in protected/components.

/Tommy

Updated my previous post. Code seems to work now. Unfortunately, both of the url declarations are needed.

/Tommy

Sort of a hack, but I managed to implement a working Publish button (with alternating labels). Feel free to improve from this state. I’m not going to use this in a while.

The name ‘ajax’ might not be a good choice, it was added (in my previous example) to distinguish from ‘options’, which wasn’t addressed by deferred server side evaluation (while rendering buttons). Neverthless I ended up with an evaluated ‘option’ (see below).

I use the attribute ‘Active’ to initalize the button label, this attribute is updated by the ajax call. The result code controls the choice of updated label.




'buttons'=>array(

  'pub'=>array(

    'options'=>'array("title"=>"{$data->Active}"?"Unpublish":"Publish", "id"=>"pub{$row}")',

    'url'=>'Yii::app()->createUrl("/item/publish")',

    'ajax'=>'

      array(

        "url"=>Yii::app()->createUrl("/item/publish"),

        "data"=>array("id"=>$data->ItemId),

        "success"=>\'js:function(html){

          jQuery("#pub\'.$row.\'")

          .html((html>0)?"Unpublish":"Publish");

        }\',

      )

    ',

  ),

),



Extended CButtonColumn




class ButtonColumnEx extends CButtonColumn

{

  protected function renderButton($id,$button,$row,$data)

  {

    if (isset($button['visible']) && !$this->evaluateExpression($button['visible'],array('row'=>$row,'data'=>$data)))

      return;

    $label=isset($button['label']) ? $button['label'] : $id;

    $url=isset($button['url']) ? $this->evaluateExpression($button['url'],array('data'=>$data,'row'=>$row)) : '#';


//*** start of new code

    //$options=isset($button['options']) ? $button['options'] : array();

    $options=isset($button['options']) ? $this->evaluateExpression($button['options'],array('data'=>$data,'row'=>$row)) : null;


    if(!isset($options['title']))  //*** was here before

      $options['title']=$label;    //*** 

    elseif (!isset($button['label']))

      $label = $options['title'];


    $value=isset($button['value']) ? $this->evaluateExpression($button['value'],array('data'=>$data,'row'=>$row)) : null;

    if(isset($value))

    {

      echo $this->grid->getFormatter()->format($value,"raw");

    }

    $ajax=isset($button['ajax']) ? $this->evaluateExpression($button['ajax'],array('data'=>$data,'row'=>$row)) : null;

    if(isset($ajax))

    {

      if(isset($button['imageUrl']) && is_string($button['imageUrl']))

        echo CHtml::ajaxLink(CHtml::image($button['imageUrl'],$label),$url,$ajax,$options);

      else

        echo CHtml::ajaxLink($label,$url,$ajax,$options);

    }

    else

    {

//*** end of new code

  

      if(isset($button['imageUrl']) && is_string($button['imageUrl']))

        echo CHtml::link(CHtml::image($button['imageUrl'],$label),$url,$options);

      else

        echo CHtml::link($label,$url,$options);

    } //*** new

  }

}



/Tommy

‘buttons’ definition




'buttons'=>array(

  'up'=>array(

      'label'=>'up',

      'imageUrl'=>'/themes/default/images/up_16.png',

      'url'=>'Yii::app()->createUrl("/item/reposition")',

      'ajax'=>'

        array(

          "url"=>Yii::app()->createUrl("/item/reposition"),

          "data"=>array("id"=>$data->id,"direction"=>"up",),

          "update"=>"#profileGrid"

        )

      ',

    ),

  'down'=>array(

      'label'=>'down',

      'imageUrl'=>'/themes/default/images/down_16.png',

      'url'=>'Yii::app()->createUrl("/item/reposition")',

      'ajax'=>'

        array(

          "url"=>Yii::app()->createUrl("/item/reposition"),

          "data"=>array("id"=>$data->id,"direction"=>"down",),

          "update"=>"#profileGrid"

        )

      ',

    ),

  ),



[/quote]

Which line of the Cgridview so I place this ‘buttons’ definition codes?

A quick example of using buttons including translation of default fields using yii::t:




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

    'id'=> 'profileGrid',

    'dataProvider'=>$model->search(),

    'filter'=>$model,

    'enableSorting'=>'true',

    'emptyText'=>yii::t('coree','No rules yet.'),

    'summaryText'=>yii::t('core','Displaying {start}-{end} of {count} result(s).'),

    'pager'=>array(

        'class'=>'CLinkPager',

        'nextPageLabel'=>yii::t('core','Next'),

        'prevPageLabel'=>yii::t('core','Previous'),

        'firstPageLabel'=>yii::t('core','First'),

        'lastPageLabel'=>yii::t('core','Last'),

        'header'=>yii::t('core','Go to page').': ',

        ),

    'columns'=>array(

        'name',

        'description',

        array(

            'class'=>'CButtonColumn',

            'buttons'=>array(

              'up'=>array(

                  'label'=>'up',

                  'imageUrl'=>'/themes/default/images/up_16.png',

                  'url'=>'Yii::app()->createUrl("/item/reposition")',

                  'ajax'=>'

                    array(

                      "url"=>Yii::app()->createUrl("/item/reposition"),

                      "data"=>array("id"=>$data->id,"direction"=>"up",),

                      "update"=>"#profileGrid"

                    )',

                ),

              'down'=>array(

                  'label'=>'down',

                  'imageUrl'=>'/themes/default/images/down_16.png',

                  'url'=>'Yii::app()->createUrl("/item/reposition")',

                  'ajax'=>'

                    array(

                      "url"=>Yii::app()->createUrl("/item/reposition"),

                      "data"=>array("id"=>$data->id,"direction"=>"down",),

                      "update"=>"#profileGrid"

                    )',

                ),

              ),

            'header'=>yii::t('core','Actions'),

            'viewButtonImageUrl'=>'/themes/default/images/search_16.png',

            'updateButtonImageUrl'=>'/themes/default/images/pencil_16.png',

            'deleteButtonImageUrl'=>'/themes/default/images/delete_16.png',

            //'viewButtonUrl'=> 'Yii::app()->createUrl("/item/view", array("name" => $data->id))',

            //'updateButtonUrl'=> 'Yii::app()->createUrl("/item/update", array("name" => $data->id))',

            //'deleteButtonUrl'=>'Yii::app()->createUrl("/item/delete", array("id" => $data->id))',

            'viewButtonLabel'=>yii::t('core','View'),

            'updateButtonLabel'=>yii::t('core','Update'),

            'deleteButtonLabel'=>yii::t('core','Delete'),

            'template'=> '{up} {down} {view} {update} {delete}',

        ),

    ),

)); ?>



Hi toMeloos,

Can you also show a quick example of how the corresponding reposition action look like in the controller?

I cannot get the ajax button to work. Nothing happens when click the button although I have some process in the action.

Thanks

In my example I reload the entire grid. You can see that the button has the ajax attribute ‘update’=>’#profileGrid’, which is the same as ‘id’=> of the main grid definition.

This means if I want to do an ajax action, the output of that action should be an entire grid. I solved this by placing the grid code in a seperate partial file called _admin.php. This allows me to use the same renderPartial() command in the admin.php view and in the ajax controller action. Also means that the $dataProvider needs to be the same. Perhaps I should move that to the Model to increase code re-use, thus improving consistency and reducing the risk of mistakes.




<?php


class RepositionAction extends CAction

{

    // Note: if you change this PAGE_SIZE, also change the PAGE_SIZE of AdminAction.php

    const PAGE_SIZE=10;


    public function run()

    {

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

        {

            $model=$this->controller->loadModel();

            $model->setScenario('reposition');


            /**

             * essence of the code here. I removed it to keep it simple for you. In my case the code changes some numbers in a column named 'position'.

             **/


            $dataProvider=new CActiveDataProvider('ProfileField', array(

            'criteria'=>array(

                'with'=>'category',

                'order'=>'category.position, t.position',

            ),

            'pagination'=>array(

                'pageSize'=>self::PAGE_SIZE,

            ),

        ));

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

        }

        else

            throw new CHttpException(400,yii::t('core','Invalid request. Please do not repeat this request again.'));

    }

}



p.s. as you can see I’ve moved every action to a seperate action file, as explained here (in the paragraph ‘Action’)

I am following your structure as closely as possible except for the separate Action class.

Got this error, after clicking on the "up/down" buttons on the grid

Error 400

Invalid request. Please do not repeat this request again.

admin.php




<?php

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

?>



_admin.php




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

	'id'=>'profileGrid',

	'dataProvider'=>$dataProvider,

	'columns'=>array(

		'Id',

		'Title',

    array(

      'class'=>'CButtonColumn',

      'buttons'=>array(

        'up'=>array(

            'label'=>'up',

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

            'url'=>'Yii::app()->createUrl("/item/reposition")',

            'ajax'=>'

              array(

                "url"=>Yii::app()->createUrl("/item/reposition"),

                "data"=>array("id"=>$data->id,"direction"=>"up",),

                "update"=>"#profileGrid"

              )',

          ),

        'down'=>array(

            'label'=>'down',

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

            'url'=>'Yii::app()->createUrl("/item/reposition")',

            'ajax'=>'

              array(

                "url"=>Yii::app()->createUrl("/item/reposition"),

                "data"=>array("id"=>$data->id,"direction"=>"down",),

                "update"=>"#profileGrid"

              )',

          ),

        ),

      'template'=> '{up} {down}',

    ),

),



itemController.php




public function actionReposition()

{

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

  {

    if(isset($_GET['direction']) && 

    isset($_GET['id']) )

    { 

      $direction=$_GET['direction'];

      $id=$_GET['id'];


      if ($direction=='up') {

        $newSortOrder = $sortOrder-1;

      } else if ($direction=='down') {

        $newSortOrder = $sortOrder+1;

      } 

      //** do my db stuff here **//


      $dataProvider=new CActiveDataProvider('Product', array(

          'criteria'=>array(

              'order'=>'SortOrder ASC',

            ),

         )

      );

    		

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

        

    }

  }

  else

    throw new CHttpException(400,'Invalid request. Please do not repeat this request again.');

}



Its not detecting Yii::app()->request->isAjaxRequest. if remove this line, it does not detect $_GET[‘direction’] or $_GET[‘id’]. Btw, is using GET the way to retrieve params from the ajax?

Appreciate your further help to get ajax working. I am surprise this common need is not in the cookbook and can’t find any resolved post.

Or maybe its me… :(

Check out this.

The framework explicitly sends the value of ajaxVar, but it looks like it’s not sent in this case (you really should use Firebug Console to verify what’s being sent).

The default method for Ajax is GET but you can change it by specifying "type".

Read more here

/Tommy

Ok, will work on isAjaxRequest issue later. Assuming it works now by removing it, why then is my $_GET[‘direction’] and [‘id’] is empty?

Does this mean if isAjaxRequest fail, all the GETS will not be sent too?

@Joe Storm:

The problem with the regular [font=“Courier New”]CButtonColumn[/font] class is that it doesn’t create an ajaxLink if you add the ‘ajax’ variable. You get the 400 error because in the current code, your buttons are regular links in stead of ajaxLinks. You thus correctly get an error because in the controller the [font=“Courier New”]if(Yii::app()->request->isAjaxRequest)[/font] check results in a FALSE.

This is why @tri created a [font=“Courier New”]ButtonColumnEx[/font] class to extend the regular [font=“Courier New”]CButtonColumn[/font] class. Please adopt and adapt his code (don’t forget to edit _admin.php too). This should also solve your [font=“Courier New”]$_GET[‘direction’] [/font]and [font=“Courier New”]$_GET[‘id’][/font] not getting passed through problem.

I’m very interested in your results. Have been working on other parts of my code lately, but if this works I will definitely adopt it.

By the way: I think the ability to create ajax buttons should be a basic capability of the next Yii release so we don’t have to create such an extended class…

But haven’t your codes already working with just CButtonColumn? You did mentioned earlier you reload the grid, so why mine does not when I followed exactly?

Anyway, I have reverted to Tommy’s extended button (ButtonColumnEx)


        array(

            'class'=>'ButtonColumnEx',

            'buttons'=>array(

              'up'=>array(

                  'label'=>'up',

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

                  'url'=>'Yii::app()->createUrl("/item/reposition")',

                  'ajax'=>'

                    array(

                      "url"=>Yii::app()->createUrl("/item/reposition"),

                      "data"=>array("id"=>$data->Id,"direction"=>"up",),

                      "update"=>"#profileGrid"

                    )',

                ),

              'down'=>array(

                  'label'=>'down',

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

                  'url'=>'Yii::app()->createUrl("/item/reposition")',

                  'ajax'=>'

                    array(

                      "url"=>Yii::app()->createUrl("/item/reposition"),

                      "data"=>array("id"=>$data->Id,"direction"=>"down",),

                      "update"=>"#profileGrid"

                    )',

                ),

              ),

            'template'=> '{up} {down}',

        ),

The ajax call is executed but with a problem. It works only the first time. Once the grid is refreshed, the record Ids still refers to the first time the page is called, eventhough the grid now shows the sorted records

First time load


Id | Title |

1 apple (click down button, controller receive Id as 1)

2 banana

3 citrus

Ajax refresh


Id | Title |

2 banana (click down button, controller STILL receive Id as 1, should be 2!!)

1 apple

3 citrus

And another thing, after the Ajax refresh, all the clickable grid title changed from

item/admin?Product_sort=SortOrder (first time the grid is loaded)

TO

item/reposition?_=1278006731330&id=1&direction=down&Product_sort=SortOrder (after I clicked the ajax down button)