Yii 1.1: Cgridview: Use Special Variable $Data In The Options Of A Button (I.e. Evaluate Options Attribute)

Presentation of the Problem

For each column of the CGridView, we can specify customized buttons : Customize Buttons in CGridWiew

Let's take the example of the above article to display an email button next to a delete button:

array
(
    'class'=>'CButtonColumn',
    'template'=>'{email}{delete}',
    'buttons'=>array
    (
        'email' => array
        (
            'label'=>'<i class="icon-enveloppe"></i>',
            'url'=>'Yii::app()->createUrl("users/email", array("id"=>$data->id))',
            'options' => array(
                                'rel' => 'tooltip',
                                'data-toggle' => 'tooltip', 
                                'title'       => '"Send an email', ),
            ),
    ),
),

For each one of those customized buttons, we can specify options. Those options will be the attribute of the button's link. In the above example, we use the options to use font awesome envelope icon instead of an picture (as realtebo suggested it the comments). So the button html code will look like this :

<a rel="tooltip" data-toggle="tooltip" title="Send an email" href="index.php?r=users/email&id=1">
  <i class="icon-envelope"></i>
</a>

Now we might want to use $data in the declaration of the options attribute, which is normally not possible, to generate for example the title "Send an email to foo@bar.com", where foo@bar.com is the email of the data model for the current row, i.e. $data->email.

That is, we want to be able to use:

'options' => array(
                                'rel' => 'tooltip',
                                'data-toggle' => 'tooltip', 
                                'title'       => '"Send an email to $data->email"', ),
            ),

Here's a way to accomplish this :

Extend the class CButtonColumn

Under protected/components/ create the file ButtonColumn.php with the following content:

<?php
/**
 * ButtonColumn class file.
 * Extends {@link CButtonColumn}
 */

class ButtonColumn extends CButtonColumn
{


        /**
         * Renders a link button.
         * @param string $id the ID of the button
         * @param array $button the button configuration which may contain 'label', 'url', 'imageUrl' and 'options' elements.
         * See {@link buttons} for more details.
         * @param integer $row the row number (zero-based)
         * @param mixed $data the data object associated with the row
         */
        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 modification
                if( isset ( $options['evaluateOptions'] ) ) 
                {
                        foreach( $options['evaluateOptions'] as $key=>$value) 
                        {
                            $options[$value] = $this->evaluateExpression($options[$value],array('data'=>$data,'row'=>$row));
                        }
        
                        unset($options['evaluateOptions']);
                }
                // END of modifications

                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);
        }
}

Originally the options attribute was used without any evalution, like this:

$options=isset($button['options']) ? $button['options'] : array();
...
echo CHtml::link($label,$url,$options);

What we do differently from the original is:

  1. We introduce the new array variable $options['evaluateOptions']. This array contains a list of options, to control whether the evaluation of an option attribute should be done or not.

  2. If an option is not set in this array, nothing changes. If one or many option is set in this array, so those options and only those ones will be evaluated.

Use the new class ButtonColumn

We can use this new class like this:

$this->widget(
    'bootstrap.widgets.CGridView',
    array(
        'dataProvider' => $emails,
        'columns' => array(
            array('name'=>'destination_email', 'header'=>'Destination Email'),             
         ),

         array
         (
             // Here, we use our new class ButtonColumn instead of CButtonColumn
             'class'=>'ButtonColumn',
             'template'=>'{email}{delete}',
             'buttons'=>array
             (
                 'email' => array
                 (
                     'label'=>'Send an e-mail to this user',
                     'url'=>'Yii::app()->createUrl("users/email", array("id"=>$data->id))',
                     'options' => array(
                                         'rel' => 'tooltip',
                                         'data-toggle' => 'tooltip', 

                                         // Here, we use the $data
                                         'title'       => '"Send an email to $data->email',
                                         // Here, we specify that we want title to be evaluated
                                         'evaluateOptions' => array('title'),

                                       ),
                 ),
             ),
        ),
    )
);

Example of button with evaluated option attribute

This tutorials have been inspired by Update "CGridView: Use special variable $data in the htmlOptions of a column (i.e. evaluate htmlOptions attribute)"