This example is for Issue 3084.
The example contains a controller and a view - the sample data is include (this example shoud work ‘as is’.
To demonstrate the bug, select ‘Entity B’ and then ‘Entity A’ again. This reloads the grid.
A click on ‘update’ or ‘delete’ will then open the alert window two times.
The files are also attached - the controller should go in ‘controllers/’ and the view in ‘views/example/’.
ExampleController.php
<?php
class ExampleController extends CController {
public function getSampleData() {
return array(
1 => array(
'displayname'=>'Entity A',
'id' => 1,
'tags'=>array(
array('id'=>1,'tag'=>'tag a'),
array('id'=>2,'tag'=>'tag b'),
array('id'=>3,'tag'=>'tag c'),
array('id'=>4,'tag'=>'tag d'),
array('id'=>5,'tag'=>'tag e'),
),
),
4 => array(
'displayname'=>'Entity B',
'id'=>4,
'tags'=>array(
array('tag'=>'tag f'),
array('tag'=>'tag g'),
array('tag'=>'tag h'),
array('tag'=>'tag i'),
array('tag'=>'tag g'),
),
),
);
}
public function actionGrid() {
$view='grid';
$id = Yii::app()->request->getParam(id,1);
$data = null;
if(is_numeric($id)) {
$alldata = $this->getSampleData();
$data=$alldata[$id];
}
if($data===undefined) {
echo 'Bad id';
} else {
$params = array('data'=>$data);
if(Yii::app()->request->isAjaxRequest) {
$this->renderPartial($view,$params, false, true);
Yii::app()->end();
} else {
$this->render($view,$params);
}
}
}
}
View grid.php
<?php
$id = $data['id'];
$gridid = 'grid-'.$id; // Id of the grid
$gridajaxkey = 'grid'; // Id for updating the grid
$isAjaxRequest = Yii::app()->request->isAjaxRequest;
$ajaxParam = Yii::app()->request->getParam('ajax');
$renderGrid = !$isAjaxRequest||$ajaxParam===$gridajaxkey;
$renderGridContent = !$isAjaxRequest||($ajaxParam===$gridid)||$renderGrid;
if(!$isAjaxRequest) {
echo CHtml::dropDownList('eid',
$id,
array(1=>'Entity A',4=>'Entity B'),
array('encode'=>false,
'onchange'=>CHtml::encode('jQuery("body").trigger("id_change",[this.value]);')
)
);
if(true||Yii::app()->request->enableCsrfValidation)
{
$csrfTokenName = Yii::app()->request->csrfTokenName;
$csrfToken = Yii::app()->request->csrfToken;
$csrf = "\r\ndata:{ '$csrfTokenName':'$csrfToken' },";
} else {
$csrf = '';
}
$href=$this->createUrl('',array('ajax'=>$gridajaxkey));
// Javascript to listen to entity id update event and update the grid view.
$js=<<<EOD
jQuery('body').bind('id_change',
function (event,id) {
jQuery.ajax(
{'type':'POST',
'url':"$href&id="+id,
$csrf
'success':function(data){
jQuery("#entity-detail").replaceWith(data);
},
'error':function(html){alert('Issue fetching entity data from server.');}
});
});
EOD;
Yii::app()->getClientScript()->registerScript('trackinfoeidlistener', $js,CClientScript::POS_READY);
}
if($renderGrid) {
echo '<div id="entity-detail">';
echo '<div id="entity-detail-title" style="font-size:40px">';
echo Yii::t('app','Detail for {displayname}',array('{displayname}'=>$data['displayname']));
echo '</div>';
echo '<div id="entity-detail-content">';
//$dataprovider = new CActiveDataProvider('Tracker');
}
if($renderGridContent) {
$dataprovider = new CArrayDataProvider($data['tags'],
array(
'id'=>'id',
'pagination'=>array(
'pageSize'=>2,
),
));
$grid=$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataprovider,
'id'=>$gridid,
'template'=>"{items}\n{pager}", /* Default: '{summary}\n{items}\n{pager}'*/
'ajaxUrl'=>$this->createUrl('',array('id'=>$id)),
'columns'=>array(
'id',
'tag',
array_replace_recursive(
array('class' =>'CButtonColumn',), //Yii::app()->params['defaultCButtonColumnConfig'],
array(
'template' =>'{update} {delete}',
'buttons'=>array(
'update'=>array(
'click'=>'js:function(){alert("update clicked");return false;}',
),
'delete'=>array(
'url'=>'Yii::app()->controller->createUrl("deletealert",array("id"=>$data->primaryKey))',
'click'=>'js:function(){alert("delete clicked");return false;}',
),
),
)
)
)
)
);
}
if($renderGrid) {
echo '</div>';
}
?>
My fix is the following code in CButtonColumn. The previous handler is removed (if any) before adding the new one. This solution also avoids (‘live’) which is no longer recommended in jQuery [otherwise ‘die’ must be used in stead of ‘off’].
CButtonColumn.php - registerClientScript
protected function registerClientScript()
{
$js=array();
foreach($this->buttons as $id=>$button)
{
if(isset($button['click']))
{
$function=CJavaScript::encode($button['click']);
$class=preg_replace('/\s+/','.',$button['options']['class']);[indent] $js[]="if(typeof(_gridf)==='undefined'){_gridf={};}"
."if(typeof(_gridf['on-{$this->grid->id}-{$class}'])!=='undefined') {jQuery(document).off('click','#{$this->grid->id} a.{$class}',_gridf['on-{$this->grid->id}-{$class}']);}"
."_gridf['on-{$this->grid->id}-{$class}']=$function;"
."jQuery(document).on('click','#{$this->grid->id} a.{$class}',_gridf['on-{$this->grid->id}-{$class}']);";[/indent]
}
}
if($js!==array())
Yii::app()->getClientScript()->registerScript(__CLASS__.'#'.$this->id, implode("\n",$js));
}