Quantcast
Channel: Live News for Yii Framework
Viewing all articles
Browse latest Browse all 3361

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

$
0
0

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 I want to explain boils down to using just one model function, which reads through the related records just once - and then returns all required virtual attribute data in an array that can be shared between 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 a single 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 returning both values in an array.

Column-2 receives this array and extracts 'amount' before storing the array in the view - which is the CGridView's parent scope. From here column-3 can retrieve the same array and extract 'tax', without needing to call another model function again.

The model function:

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

The view:

// Array in view to store received virtual attribute values
$virtAttrbArray = array();
 
// The CGridView
$this->widget('zii.widgets.grid.CGridView', array(
...
'columns'=>array(
    ...
    array(
        'name'  => 'column_2',
        'value'=>function($data,$row) use (&$virtAttrbArray){
        // Populate array
        $virtAttrbArray = $data->getVirtAttrbArray($data->invoice_id);
 
        // Extract value from array
        return $virtAttrbArray['amount'];
        },
        ...
    ),
 
    array(
        'name'  => 'column_3',
        'value'=>function($data,$row) use (&$virtAttrbArray){
        // Extract value from array
        return $virtAttrbArray['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 ($virtAttrbArray) 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 add the ampersand inside the function as well).

Obviously 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.

Hope this helps.


Viewing all articles
Browse latest Browse all 3361

Trending Articles