Yii 1.1: exportablegridbehavior

Export filtered GridView as CSV
26 followers

This behaviour allows you to add an 'Export' button near your gridview, that will download the grid filtered data as CSV.

Changelog

  • v2.0
    • Totally rewritten code
    • Allows export of arrays, single model and CActiveDataProvider ("search" result)
    • Allows export of multiple models on same csv
    • New method (better and cleaner) for generation of export url with grid search filters
  • v1.1
    • Cleaner method of file generation allows to handle huge datasets.
    • Export data is now generated on php output. No more session temporal storage.
    • CSV downloaded is now done without redirections.
    • GridView id automatically fetch from widget

Requirements

  • Yii Framework v1.1.13 or greater (Dependecy with system.web.CDataProviderIterator)
  • An explicit rule matching export action on urlManger like:
'<controller:\w+>/<action:\w+>' => '<controller>/<action>'

Available behavior params

  • csvDelimiter Delimiter used on the csv generated rows. Default ','
  • csvEnclosure Enclosure used on the csv generated rows. Default '"'
  • filename Filename poposed to the browser when downloading the file. Default 'export.csv'
  • buttonId Id for the export button. Default 'export-button'
  • exportParam GET param added to the grid url, representing the 'export' command. Default 'exportCSV'

Available behavior methods

  • public exportCSV(mixed $data, $attributes = array(), $endApplication = true, $endLineCount = 0)
    mixed $data Data to be exported. CModel, CDataProvider or Array.
    Array $attributes Attributes of $data to be exported.
    boolean $endApplication True to end CApplication excecution.
    integer $endLineCount Number of empty rows to add after this data.
  • public isExportRequest()
    returns boolean True if the request have exportParam, False otherwise.
  • public renderExportGridButton(CGridView $gridWidget, $label = 'Export', $htmlOptions = array())
    CGridView $gridWidget CGridView widget.
    String $label Export button label.
    Array $htmlOptions Export button additional HTML options.

Usage

Step 1: Place file

Place ExportableGridBehavior.php file on application components directory.

Step 2: Add the behavior to the controller

Add the behavior ExportableGridBehavior to your Controller

public function behaviors() {
    return array(
        'exportableGrid' => array(
            'class' => 'application.components.ExportableGridBehavior',
            'filename' => 'PostsWithUsers.csv',
            'csvDelimiter' => ';', //i.e. Excel friendly csv delimiter
            ));
}

Step 3: Some magic

Call the exportCSV() method on your 'admin' Action, just before the 'render' method call. Like This:

class PostController extends CController {
    . . .
    public function actionAdmin() {
        $model = new Post('search');
        $model->unsetAttributes();
        if (isset($_GET['Post']))
            $model->attributes = $_GET['Post'];
        if ($this->isExportRequest()) { //<==== [[ADD THIS BLOCK BEFORE RENDER]]
            //set_time_limit(0); //Uncomment to export lage datasets
            //Add to the csv a single line of text
            $this->exportCSV(array('POSTS WITH FILTER:'), null, false);
            //Add to the csv a single model data with 3 empty rows after the data
            $this->exportCSV($model, array_keys($model->attributeLabels()), false, 3);
            //Add to the csv a lot of models from a CDataProvider
            $this->exportCSV($model->search(), array('id', 'title', 'user.id', 'user.name'));
        }
        $this->render('admin', array(
            'model' => $model,
        ));
    }
    . . .
}

Step 4: Render export button

Add the export button to the View that renders the CGridView.

//Capture your CGridView widget on a variable
//$gridWidget=$this->widget('bootstrap.widgets.TbGridView', array( . . .
$this->renderExportGridButton($gridWidget,'Export Grid Results',array('class'=>'btn btn-info pull-right'));

Resources

Total 20 comments

#19124 report it
Hipogea at 2015/03/24 10:33am
Doesn't work , since 10,000 rows

Please, I need some Help; I worked fine, with this extension but , when the CDataProvider , returns up to 10 000 records, the extension does'n t work, Only ignore the function. What I doing wrong?, Could be a bad Configuration PHP ini?

#19116 report it
Todd Anstis at 2015/03/20 06:40pm
Got it

Nevermind, I got it. Thanks!

#19115 report it
Todd Anstis at 2015/03/20 12:12pm
PHP version

I'm using PHP 5.5

#19114 report it
Clem at 2015/03/20 11:59am
PHP > 5.3

Todd Anstis, you need PHP 5.3 or above to use anonymous functions. Hope it helps you.

#19113 report it
Todd Anstis at 2015/03/20 11:44am
Export related field data?

Clem, I implemented your adjustments so that I could export related data but I'm getting an error:

Undefined variable: data

How are you resolving the $data variable in your controller?

#19108 report it
Clem at 2015/03/19 09:13am
Some modification

I have modified this great extension to display different label as declarated in Model file. I can further personnalize values as in CGridView Or CDetailView.

Example in your Controller Action:

$this->exportCSV($model->search(), array(
    array(
        'label' => 'ID Column',
        'name' => 'id',
    ),
    'name',
    array(
        'label' => Yii::t('app', "Date"), 
        'value' => function ($data) {
            return Yii::app()->dateFormatter->formatDateTime($data->date,'medium',null);
        }
    ),
    ...
));

The modification:

private function csvRowHeaders($fileHandle, $attributes, CModel $model)
    {
        $row = array();
        foreach ($attributes as $attr) {
            if (is_array($attr)) {
                if (isset($attr['label'])) {
                    $row[] = $attr['label'];
                } elseif (isset($attr['name'])) {
                    $row[] = $model->getAttributeLabel($attr['name']);
                } else {
                    $row[] = '';
                }
            } else {
                $row[] = $model->getAttributeLabel($attr);
            }
        }
        fputcsv($fileHandle, $row, $this->csvDelimiter, $this->csvEnclosure);
    }
 
    private function csvRowModels($fileHandle, Traversable $cModels, $attributes)
    {
        foreach ($cModels as $cModel) {
            $row = array();
            foreach ($attributes as $attr) {
                if (is_array($attr)) {
                    if (isset($attr['name'])) {
                        $row[] = CHtml::value($cModel, $attr['name']);
                    } elseif (isset($attr['value'])) {
                        $row[] = is_object($attr['value']) && get_class($attr['value']) === 'Closure' ? call_user_func($attr['value'], $cModel) : $attr['value'];
                    } else {
                        $row[] = '';
                    }
                } else {
                    $row[] = CHtml::value($cModel, $attr);
                }
            }
            fputcsv($fileHandle, $row, $this->csvDelimiter, $this->csvEnclosure);
        }
    }
#18029 report it
Maxxer at 2014/08/28 04:01am
Problems when search attributes is an array

In my CGridView I have an advanced search function which can filter for more than one value (checkboxes). The view is filtered correctly but when you try to export with the filter values

$this->exportCSV($model, $model->attributeNames(), false, 3);

this resulted in an error:

Array to string conversion (protected/components/Export
ableGridBehavior.php:102)

I don't know if there's a better solution but I solved with:

@@ -98,7 +98,8 @@ class ExportableGridBehavior extends CBehavior {
         foreach ($attributes as $attr) {
             $row = array();
             $row[] = $cModel->getAttributeLabel($attr);
-            $row[] = CHtml::value($cModel, $attr);
+            $value = CHtml::value($cModel, $attr);
+            $row[] = is_array($value) ? implode(", ",$value) : $value;
             fputcsv($fileHandle, $row, $this->csvDelimiter, $this->csvEnclosure);
         }
     }
#17878 report it
vijay p s at 2014/08/05 06:44am
Need more !

How can we work foreign key values..

#16462 report it
abundance at 2014/02/26 07:04am
Using CArrayDataProvider as data source

Is it possible to use this extension with a CArrayDataProvider such as below. What should my syntax in the Controller look like? Below is the definition of my data provider. But my Controller code is not giving me a valid CSV - it provides blank data.

Model Code: searchProfitCurrency method

$rawData=Yii::app()->db->createCommand($selectStatment)->queryAll();
 
        return new CArrayDataProvider($rawData, array(
            'id'=>'id',
            'sort'=>array(
                'attributes'=>array(
                    'Currency',
                    'profitMarginCurrency'
                ),
            ),
            'pagination'=>array(
                'pageSize'=>50,
            ),
        ));

Controller Code:

$search_dataProvider = $model->searchProfitCurrency();
$this->exportCSV($search_dataProvider, 
      array(
            $search_dataProvider['Currency'],
            $search_dataProvider['profitMarginCurrency'],
 
      ));
#16116 report it
acy at 2014/01/21 09:04am
404 on export

Hey there! I get "File not found" when I click on the export button.

Does it have to do with my url structure?

/user/admin?exportCSV=1

Please assist

#14993 report it
stromgol at 2013/09/26 09:06pm
Re: Eport has_many relation

Thank you Gero!! I tried like you said and it worked. I just have a lot to learn in programming, particulary OOP in PHP. But Yii is great for that. Thanks again!

#14992 report it
Gero at 2013/09/26 05:15pm
Re: Eport has_many relation

stromgol

If your model has a getProducts() method, im pretty sure you should be able to get the method return value by adding its name without the 'get' part to the attributes array of exportCSV..

Somethig like this:

$this->exportCSV($model->search(), array('id', 'products'));
#14989 report it
stromgol at 2013/09/26 04:14pm
Eport has_many relation

I just installed the last version (2.0) and it's a great extension, really easy to use. I modified it a little bit to show the date at the end of the filename. I have one question though. I have some classes with has_many relations and to access the related products in cgridview, I have to use a line like this 'value'=>'$data->getproducts()'. getproducts() is declared in my model. It's a loop with an implode function. The products are shown in cgridview, separated by a comma, but I can't find a way to export them with this behavior. Is there a way? thank you!

#14838 report it
stromgol at 2013/09/13 03:02pm
Works like a charm

I just installed the new version (1.1) and I've been able to export huge csv files (50 000 rows). For the benefits of newbies like me, I want to add that to make it work, you have to add the $gridWidget variable at the beginning of the cgridview line in the admin page, as well as in the export button line. I mention it because it wasn't like that in the previous version and this option is greyed out in the above example(step 4, render export).

Thanks a lot!

#14623 report it
abundance at 2013/08/29 10:03am
Error

I am getting this error:

MyController and its behaviors do not have a method or closure named "renderExportGridButton".

#13813 report it
Gustavo Andrade at 2013/06/27 09:18pm
Replace the button for a link?

How to replace the button for a link?

$this->renderExportGridButton('Link here',array('class'=>'btn btn-info pull-right'));

#13456 report it
deez at 2013/05/29 12:03pm
2 Grids in same controller?

Will this work if the same controller is responsible for multiple grids from multiple models?

#13349 report it
danschmidt5189 at 2013/05/23 05:34pm
Detect loading class & customize pagination

Hi there,

Nice behavior! I'd just add two features and one modification:

1) Auto-detect the loading CSS class that's added / removed from the gridView. You can get the CSS class by modifying the code in renderExportGridButton():

var grid         = $('#" . $this->gridId . "'),
    loadingClass = grid.yiiGridView.settings.loadingClass,
    dataVar      = grid.yiiGridView('getUrl'),
    idx          = dataVar.indexOf('?');
 
... more javascript...
 
success: function() {
  grid.removeClass(loadingClass);
  window.location = '".$fileDownloadUrl."';
},

2) Add an onclick prompt that asks users whether they want to export all records, just the current page, or just the selected rows. This would require a bit more customization but all in all isn't hard.

3) Change exportGrid() and generateFileOnMem() to use a dataProvider instance of a model instance. You can always extract the model's attribute info from the dataProvider, anyway, and doing this reduces coupling with the model's search() implementation.

#12774 report it
Gustavo Andrade at 2013/04/11 09:37am
I Like

It worked great! Very simple to use

CONGRATULATIONS! :D

#12258 report it
Jeffrico El Exotico at 2013/03/10 07:39am
I like this, but...

I do like your work as it is easy and just what I was looking for.

Beware however, the extension assumes the grid is populated using $model->search(). As one must provide input such as the grid-id, I incorrectly assumed it would magically figure use the same dataprovider as the one actually used by the grid.

It would be awesome if this could be overwritten through parameter.

Thanks

Leave a comment

Please to leave your comment.

Create extension