Multiple CGridView virtual attributes, retrieved from a single model function. Also communication between CGridView columns and rows; and between CGridView and its own parent view.

You are viewing revision #10 of this wiki article.
This is the latest version of this article.
You may want to see the changes made in this revision.

« previous (#9)

We probably have all used a virtual attribute (model function) to retrieve complex or related data for a CGridView column:

array(
	'name'   => ... ,
	'value'  => '$data->getModelFunction()',
	'header' => ... ,
	'filter' => ... ,
),

But usually such model function only returns a single value for a single CGridView column. This means that we need to use separate model functions if we need virtual attribute data for multiple CGridView columns.

The problem is that these model functions often have to perform lengthy tasks - such as reading through all related child records. Having multiple virtual attributes of this kind will thus mean a lot of db access for each CGridView column.

The method explained here, boils down to using just one model function, which reads through the related records just once. It then stores all required virtual attribute data in an array, which is stored in the same model that was used to call the search() function, from where it can be read/updated by all the CGridView columns (requires php 5.3 for the "use" keyword).

Suppose you have invoice_tbl and invoice_lines_tbl (one-many). In the gridview you want the following in each row:

column-1: invoice numbers from invoice_tbl (included in dataprovider as usual)

column-2: total amount of each invoice's lines from invoice_lines_tbl (virtual attribute)

column-3: total tax of each invoice's lines from invoice_lines_tbl (virtual attribute)

The function needs to be called by the first column that requires virtual attribute data (column-2). The function reads through the related invoice_lines_tbl only once and performs the necessary calculations for both $amount and $tax, before storing both values in the array.

Column-2 then extracts 'amount' from the array. Column-3 can retrieve the same array and extract 'tax', without needing to call another model function again.

The model function:

public $valuesArray = array();
public function getAmountTax($invoice_id)
{
	// Read related records and calculate $amount and $tax
	...
	$this->valuesArray = array(
		'amount'=> $amount,
		'tax' 	=> $tax
	);
	return 'Everything OK';
}

The view:

// The CGridView
$this->widget('zii.widgets.grid.CGridView', array(
...
'columns'=>array(
	...
	array(
		'name'	=> 'column_2',
		'value'=>function($data,$row) use (&$model){
			// Populate array
			$result = $model->getAmountTax($data->invoice_id);
	
			// Extract value from array
			return $model->valuesArray['amount'];
		},
		...
	),

	array(
		'name'	=> 'column_3',
		'value'=>function($data,$row) use (&$model){
			// Extract value from array
			return $model->valuesArray['tax'];
		},
		...
	),
IMPORTANT

Just like normal function parameters, the parameter provided to the column's function via the "use" keyword is passed "by value". To be able to update such parameter ($model) in the parent scope (view), it must be passed "by reference". To pass the parameter "by reference", simply add an ampersand (&) in front of the parameter when passed to the "use" language construct (do not use the ampersand inside the function as well).

Obviously, since $model can be updated by each column, this feature allows for more possibilities pertaining to communication between gridview columns, the gridview and its parent view, and maybe also between different widgets in the view.

TIPS

If you want to calculate a virtual attribute only once and use it in all your gridview rows, then store it in $model (the main $model passed to the view which was used to call the search() function to get the dataprovider).

If the virtual attributes need to be calculated for each gridview row, you can store it in $data (the dataprovider contains a model for each gridview row - called $data).

In the gridview column, 'cssClassExpression' is executed before 'value'. So if your headings also need virtual attributes, then call the function in 'cssClassExpression'.

'cssClassExpression' => '$data->getAmountTax($data->invoice_id) ? "" : ""',

It might be handy sometimes to update the models in the dataprovider AFTER it was generated in the search() function.

public $myAttribute;
public function search()
{
	...
	$dataProvider = new ...

	foreach($dataProvider->getData() as $data)
	{
		$data->myAttribute = 'Updated';
	}

	return $dataProvider;
}

// In the gridview column you can access it by using $data->myAttribute.

Hope this helps.

0 0
4 followers
Viewed: 13 659 times
Version: Unknown (update)
Category: How-tos
Written by: Gerhard Liebenberg
Last updated by: Gerhard Liebenberg
Created on: Mar 23, 2015
Last updated: 9 years ago
Update Article

Revisions

View all history