Can't use CButtonColumn in CGridView fed with CSqlDataProvider

Hi there,

I’m pretty sure, I’ve asked this question about a year ago, but simply can’t find that thread.

After changing datasource of CGridView from CActiveDataProvider to CSqlDataProvider, it becomes unusable and attempt to render view containing it ends with PHP Error saing "Trying to get property of non-object".

Here is a stack trace:


#0 [app-path]\protected\yii\base\CComponent.php(616): eval()

#1 [app-path]\protected\yii\zii\widgets\grid\CButtonColumn.php(280): CButtonColumn->evaluateExpression()

#2 [app-path]\protected\yii\zii\widgets\grid\CButtonColumn.php(259): CButtonColumn->renderButton()

#3 [app-path]\protected\yii\zii\widgets\grid\CGridColumn.php(135): CButtonColumn->renderDataCellContent()

#4 [app-path]\protected\yii\zii\widgets\grid\CGridView.php(457): CButtonColumn->renderDataCell()

#5 [app-path]\protected\yii\zii\widgets\grid\CGridView.php(430): CGridView->renderTableRow()

#6 [app-path]\protected\yii\zii\widgets\grid\CGridView.php(343): CGridView->renderTableBody()

#7 [app-path]\protected\yii\zii\widgets\CBaseListView.php(158): CGridView->renderItems()

#8 unknown(0): CGridView->renderSection()

#9 [app-path]\protected\yii\zii\widgets\CBaseListView.php(141): preg_replace_callback()

#10 [app-path]\protected\yii\zii\widgets\CBaseListView.php(127): CGridView->renderContent()

#11 [app-path]\protected\yii\web\CBaseController.php(166): CGridView->run()

#12 [app-path]\protected\modules\users\views\go\index.php(167): GoController->widget()

#13 [app-path]\protected\yii\web\CBaseController.php(119): require()

#14 [app-path]\protected\yii\web\CBaseController.php(88): GoController->renderInternal()

#15 [app-path]\protected\yii\web\CController.php(798): GoController->renderFile()

#16 [app-path]\protected\yii\web\CController.php(739): GoController->renderPartial()

#17 [app-path]\protected\modules\users\controllers\GoController.php(116): GoController->render()

#18 [app-path]\protected\yii\web\actions\CInlineAction.php(50): GoController->actionIndex()

#19 [app-path]\protected\yii\web\CController.php(300): CInlineAction->run()

#20 [app-path]\protected\yii\web\filters\CFilterChain.php(133): GoController->runAction()

#21 [app-path]\protected\yii\web\filters\CFilter.php(41): CFilterChain->run()

#22 [app-path]\protected\yii\web\CController.php(1049): CAccessControlFilter->filter()

#23 [app-path]\protected\yii\web\filters\CInlineFilter.php(59): GoController->filterAccessControl()

#24 [app-path]\protected\yii\web\filters\CFilterChain.php(130): CInlineFilter->filter()

#25 [app-path]\protected\yii\web\CController.php(283): CFilterChain->run()

#26 [app-path]\protected\yii\web\CController.php(257): GoController->runActionWithFilters()

#27 [app-path]\protected\yii\web\CWebApplication.php(324): GoController->run()

#28 [app-path]\protected\yii\web\CWebApplication.php(121): CWebApplication->runController()

#29 [app-path]\protected\yii\base\CApplication.php(135): CWebApplication->processRequest()

#30 [app-path]\index.php(17): CWebApplication->run()

as an addition an error message is displayed from line 44 of file [app-path]\protected\yii\views\pl\exception.php.

Any idea, what can be done with this and why it happens like this?

http://www.yiiframework.com/doc/api/1.1/CGridView/

the comments paragraph there exists your anwser ; the reason is this: when using CActiveDataProvider

in the CButtonColumn , CDataColumn,…xxColumn . they all depend on the "$data" variable . if you use CActiveDataProvider the $data represents the CActiveRecord subclass instance, when you replace it to CSqlDataProvider the "$data" represents a row of an resultSet from the sql query . so the statement such as

$data->xxx cause the error :lol:

only the CDataColumn has little effect:




 protected function renderDataCellContent($row,$data)

        {

                if($this->value!==null)

                        $value=$this->evaluateExpression($this->value,array('data'=>$data,'row'=>$row));

                else if($this->name!==null)

                        $value=CHtml::value($data,$this->name);

                echo $value===null ? $this->grid->nullDisplay : $this->grid->getFormatter()->format($value,$this->type);

        }


// you  read the CHtml::value  implements:

public static function value($model,$attribute,$defaultValue=null)

{

    foreach(explode('.',$attribute) as $name)

    {

        if(is_object($model))

            $model=$model->$name;

        else if(is_array($model) && isset($model[$name]))

            $model=$model[$name];

        else

            return $defaultValue;

    }

    return $model;

}

//  this function can handle both  object and array situation !






other XXXColumn only assume that the $data represent a instance of some CModel class . so this cause the error you met : trying to get property of non-object

just change $data->xx to $data[‘xx’]

Thanks for a detailed explanation.

But isn’t that a nasty bug? After all, CDetailView works perfectly with both model (CActiveDataProvider) and associative array (CSqlDataProvider) passed as data source. So, what causes CGridView to be unable to mimic the very same behaviour?

Of course… I can… hm… mimic the behaviour of CButtonColumn with CDataColumn.type set to raw and with huge, enormous code to be evaluated in value. But, somehow I’m feeling that this is not the way this should be solved.

EDIT: Combining this thread with your answer to my another question, about converting between CSqlDataProvider and CActiveDataProvider, we got conclusion that making CButtonColumn work also on CSqlDataProvider is just a few extra lines of code, that is --> use populateRecord fuction.

No, it isn’t, because the problem is not in CButtonColumn’s code. It’s in your code (a button url expression or the value of “visible” property), that passed as a string to CButtonColumn and evaluated in CButtonColumn::evaluateExpression().

EDIT: False, sorry. Button url expressions have default values, and this default values use object-syntax. You can avoid the error by explicitly specifying viewButtonUrl/updateButtonUrl/deleteButtonUrl in CButtonColumn options.

Well… I’m getting this exception thrown, if CGridView is fed with CSqlDataProvider and my CButtonColumn definition is like that:


array('class'=>'CButtonColumn')

Nothing else than that! So, yes – I’m pretty sure that the problem is inside CButtonColumn’s code and repeat that this seems to be some kind of bug! :]

OK, that is not a big problem.

When you use the CSqlDataProvider you must only change the public urls for the buttons or you can extend the CButtonColumn.

Here an example for changing the urls:




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

	'id'=>'ad-grid',

	'dataProvider'=>$dataProvider,

	//'filter'=>$model,

	'columns'=>array(

		'id',

    array(

     'name'=>'Title',

     'type'=>'raw',

     'value'=>'CHtml::link(CHtml::encode($data["title"]), array("ad/".$data["id"]))',

    ),

		'title',

		'uid',

    array(

      'name'=>'Erstellt',

      'value'=>'date("d.m.Y", $data["created"])',

    ),

    array(

			'class'=>'CButtonColumn',

      'viewButtonUrl'=>'Yii::app()->controller->createUrl("/ad/view",array("id"=>$data["id"]))',

      'updateButtonUrl'=>'Yii::app()->controller->createUrl("/ad/update",array("id"=>$data["id"]))',

      'deleteButtonUrl'=>'Yii::app()->controller->createUrl("/ad/delete",array("id"=>$data["id"]))',

    

		),






If you are in a custom area from where you manage the posts then you leave the controller from the url like so:




'viewButtonUrl'=>'Yii::app()->createUrl("/ad/view",array("id"=>$data["id"]))',



it is important to write the "/" before the path to get the right module in this case the application.

If you want to extend the CButton Column I have done it so:




/**

 * EButtonColumn modifies the button urls so you can use it with CSqlDataProvider

 * 

 */

class EButtonColumn extends CButtonColumn

{

  public $controllerPath='';

  public $viewButtonUrl='Yii::app()->createUrl("$this->controllerPath/view",array("id"=>$data["id"]))';

  public $updateButtonUrl='Yii::app()->createUrl("$this->controllerPath/update",array("id"=>$data["id"]))';

  public $deleteButtonUrl='Yii::app()->createUrl("$this->controllerPath/delete",array("id"=>$data["id"]))';

  

}



Then you can do simply this in your widget:




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

	'id'=>'ad-grid',

	'dataProvider'=>$dataProvider,

	//'filter'=>$model,

	'columns'=>array(

		'id',

    array(

     'name'=>'Title',

     'type'=>'raw',

     'value'=>'CHtml::link(CHtml::encode($data["title"]), array("ad/".$data["id"]))',

    ),

		'title',

		'uid',

    array(

      'name'=>'Erstellt',

      'value'=>'date("d.m.Y", $data["created"])',

    ),

    array(

			'class'=>'ext.grid.EButtonColumn',

      'controllerPath'=>'ad',

    ),

),

));




klaus66, sorry for very late reply and thank you for sharing your solution.

But then again, it is not the way (in my opinion) how such things should be solved here. If there is a bug in base framework code and no one is able to prove that it is in user code, then it has to be fixed in base code, not in user code.

Simple rule, I think.

In my situation, I am using CSqlDataProvider - so the only way I can get a $data variable is if I defined it with

[size="2"]$data=$dataProvider->getData - which then provides an array of ALL the results, not just a single row…[/size]

Im trying to define my own actions column so users can just click a link in any columne that can call a controller function ie index&id=7,

any ideas how to do this with CGridView using a CSqlDataProvider?

Figured it out!!




'columns' =>array( 

   array(

   'name'=>'actions',

   'type'=>'raw',

   'value'=>'CHtml::link("MyController/MyAction","$data[id]")',

   ),

)