diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 420458e..e86c0c2 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -47,6 +47,18 @@ public function getConfigTreeBuilder() ->end() ->end() ->end() + ->arrayNode('export') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('enabled') + ->defaultValue(true) + ->end() + ->scalarNode('path') + ->defaultValue('/tmp/') + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->end(); diff --git a/Grid/Column.php b/Grid/Column.php index 616ca49..7b2f9f8 100644 --- a/Grid/Column.php +++ b/Grid/Column.php @@ -47,6 +47,11 @@ class Column */ protected $renderType = 'text'; + /** + * @var bool + */ + protected $exportOnly = false; + /** * @var \PedroTeixeira\Bundle\GridBundle\Grid\Render\RenderAbstract */ @@ -266,6 +271,26 @@ public function getRenderType() return $this->renderType; } + /** + * @param boolean $exportOnly + * + * @return Column + */ + public function setExportOnly($exportOnly) + { + $this->exportOnly = $exportOnly; + + return $this; + } + + /** + * @return boolean + */ + public function getExportOnly() + { + return $this->exportOnly; + } + /** * Return render * diff --git a/Grid/GridAbstract.php b/Grid/GridAbstract.php index 0c3cefe..ee44de4 100644 --- a/Grid/GridAbstract.php +++ b/Grid/GridAbstract.php @@ -54,6 +54,21 @@ abstract class GridAbstract */ protected $ajax; + /** + * @var bool + */ + protected $exportable; + + /** + * @var bool + */ + protected $export; + + /** + * @var string + */ + protected $fileHash; + /** * @var string */ @@ -79,8 +94,16 @@ public function __construct(\Symfony\Component\DependencyInjection\Container $co $this->columns = array(); $this->url = null; + $this->ajax = $this->request->isXmlHttpRequest() ? true : false; + $this->exportable = $this->container->getParameter('pedro_teixeira_grid.export.enabled'); + $this->export = $this->request->query->get('export', false); + $this->fileHash = $this->request->query->get('file_hash', null); + if (is_null($this->fileHash)) { + $this->fileHash = uniqid(); + } + $now = new \DateTime(); $this->name = md5($now->format('Y-m-d H:i:s:u')); } @@ -101,6 +124,22 @@ public function isAjax() return $this->ajax; } + /** + * @return bool Check if it's an export call + */ + public function isExport() + { + return $this->export; + } + + /** + * @return bool + */ + public function isResponseAnswer() + { + return ($this->isAjax() || $this->isExport()); + } + /** * @param string $name * @@ -145,6 +184,26 @@ public function getUrl() return $this->url; } + /** + * @param boolean $exportable + * + * @return GridAbstract + */ + public function setExportable($exportable) + { + $this->exportable = $exportable; + + return $this; + } + + /** + * @return boolean + */ + public function getExportable() + { + return $this->exportable; + } + /** * @param \Doctrine\ORM\QueryBuilder $queryBuilder * @@ -199,6 +258,17 @@ public function getColumnsCount() return count($this->getColumns()); } + /** + * @return string + */ + protected function getExportFileName() + { + $exportPath = $this->container->getParameter('pedro_teixeira_grid.export.path'); + $exportFile = $exportPath . $this->getName() . '_' . $this->fileHash . '.csv'; + + return $exportFile; + } + /** * Process the filters and return the result * @@ -253,26 +323,33 @@ public function getData() $this->getQueryBuilder()->orderBy($sortIndex, $sortOrder); } - $totalCount = Paginate::count($this->getQueryBuilder()->getQuery()); + // Don't process grid for export + if (!$this->isExport()) { + $totalCount = Paginate::count($this->getQueryBuilder()->getQuery()); - $totalPages = ceil($totalCount / $limit); - $totalPages = ($totalPages <= 0 ? 1 : $totalPages); + $totalPages = ceil($totalCount / $limit); + $totalPages = ($totalPages <= 0 ? 1 : $totalPages); - $page = ($page > $totalPages ? $totalPages : $page); + $page = ($page > $totalPages ? $totalPages : $page); - $queryOffset = (($page * $limit) - $limit); + $queryOffset = (($page * $limit) - $limit); - $this->getQueryBuilder() - ->setFirstResult($queryOffset) - ->setMaxResults($limit); + $this->getQueryBuilder() + ->setFirstResult($queryOffset) + ->setMaxResults($limit); - $response = array( - 'page' => $page, - 'page_count' => $totalPages, - 'page_limit' => $limit, - 'row_count' => $totalCount, - 'rows' => array() - ); + $response = array( + 'page' => $page, + 'page_count' => $totalPages, + 'page_limit' => $limit, + 'row_count' => $totalCount, + 'rows' => array() + ); + } else { + $response = array( + 'rows' => array() + ); + } foreach ($this->getQueryBuilder()->getQuery()->getResult() as $key => $row) { @@ -281,6 +358,10 @@ public function getData() /** @var Column $column */ foreach ($this->columns as $column) { + if ($column->getExportOnly() && !$this->isExport()) { + continue; + } + $rowColumn = ' '; // Array @@ -316,7 +397,10 @@ public function getData() ); } - $rowValue[$column->getField()] = $column->getRender()->setValue($rowColumn)->render(); + $rowValue[$column->getField()] = $column->getRender() + ->setValue($rowColumn) + ->setStringOnly($this->isExport()) + ->render(); } $response['rows'][$key] = $rowValue; @@ -325,25 +409,103 @@ public function getData() return $response; } + /** + * @return array + */ + public function processGrid() + { + return $this->getData(); + } + + /** + * @return array + */ + public function processExport() + { + set_time_limit(0); + + $exportFile = $this->getExportFileName(); + + $fileHandler = fopen($exportFile, 'w'); + + $columnsHeader = array(); + + /** @var Column $column */ + foreach ($this->getColumns() as $column) { + if (!$column->getTwig()) { + $columnsHeader[$column->getField()] = $column->getName(); + } + } + + fputcsv($fileHandler, $columnsHeader); + + $data = $this->getData(); + + foreach ($data['rows'] as $row) { + + $rowContent = array(); + + foreach ($row as $key => $column) { + if (isset($columnsHeader[$key])) { + $rowContent[] = $column; + } + } + + fputcsv($fileHandler, $rowContent); + } + + fclose($fileHandler); + + return array( + 'file_hash' => $this->fileHash + ); + } + /** * Returns the an array with a GridView instance for normal requests and json for AJAX requests * + * @throws \Exception * @return GridView | \Symfony\Component\HttpFoundation\Response */ public function render() { if ($this->isAjax()) { + if ($this->isExport()) { + + if (!$this->getExportable()) { + throw new \Exception('Export not allowed'); + } + + $data = $this->processExport(); + } else { + $data = $this->processGrid(); + } - $data = $this->getData(); $json = json_encode($data); $response = new Response(); - $response->setContent($json); $response->headers->set('Content-Type', 'application/json'); + $response->setContent($json); return $response; } else { - return new GridView($this, $this->container); + if ($this->isExport()) { + + if (!$this->getExportable()) { + throw new \Exception('Export not allowed'); + } + + $exportFile = $this->getExportFileName(); + + $response = new Response(); + $response->headers->set('Content-Type', 'text/csv'); + $response->headers->set('Content-Disposition', 'attachment; filename="' . basename($exportFile) . '"'); + $response->setContent(file_get_contents($exportFile)); + + return $response; + } else { + return new GridView($this, $this->container); + } } } } \ No newline at end of file diff --git a/Grid/Render/RenderAbstract.php b/Grid/Render/RenderAbstract.php index 093a612..e4bdd92 100644 --- a/Grid/Render/RenderAbstract.php +++ b/Grid/Render/RenderAbstract.php @@ -17,6 +17,11 @@ abstract class RenderAbstract */ protected $value; + /** + * @var bool + */ + protected $stringOnly = false; + /** * @return string */ @@ -25,7 +30,7 @@ abstract public function render(); /** * @param \Symfony\Component\DependencyInjection\Container $container * - * @return \PedroTeixeira\Bundle\GridBundle\Grid\Filter\RenderAbstract + * @return \PedroTeixeira\Bundle\GridBundle\Grid\Render\RenderAbstract */ public function __construct(\Symfony\Component\DependencyInjection\Container $container) { @@ -51,4 +56,24 @@ public function getValue() { return $this->value; } + + /** + * @param boolean $stringOnly + * + * @return RenderAbstract + */ + public function setStringOnly($stringOnly) + { + $this->stringOnly = $stringOnly; + + return $this; + } + + /** + * @return boolean + */ + public function getStringOnly() + { + return $this->stringOnly; + } } \ No newline at end of file diff --git a/Grid/Render/Url.php b/Grid/Render/Url.php index 53a7931..7e2ee28 100644 --- a/Grid/Render/Url.php +++ b/Grid/Render/Url.php @@ -12,6 +12,10 @@ class Url extends RenderAbstract */ public function render() { - return '' . $this->getValue() . ''; + if ($this->getStringOnly()) { + return $this->getValue(); + } else { + return '' . $this->getValue() . ''; + } } } \ No newline at end of file diff --git a/Grid/Render/YesNo.php b/Grid/Render/YesNo.php index 0d7de31..313d41f 100644 --- a/Grid/Render/YesNo.php +++ b/Grid/Render/YesNo.php @@ -22,10 +22,14 @@ class YesNo extends RenderAbstract */ public function render() { - if ($this->getValue() && $this->getShowYes()) { - return ''; - } else if ($this->getShowNo()) { - return ''; + if ($this->getStringOnly()) { + return (int) $this->getValue(); + } else { + if ($this->getValue() && $this->getShowYes()) { + return ''; + } else if ($this->getShowNo()) { + return ''; + } } return null; diff --git a/README.md b/README.md index 9299c95..1573ac7 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ Requirements 1. [Doctrine Extensions](https://github.com/beberlei/DoctrineExtensions) 2. [Twitter Bootstrap](http://twitter.github.com/bootstrap/) (not mandatory) - * If you choose to don't use Twitter Bootstrap, it'll be necessary to create your own style. + * If you choose to don't use Twitter Bootstrap, it'll be necessary to create your own style. 3. [jQuery Bootstrap Datepicker](http://www.eyecon.ro/bootstrap-datepicker/) (not mandatory) - * If you choose to don't use Bootstrap Datepicker, please disable the datepicker as default in the configuration, "use_datepicker". + * If you choose to don't use Bootstrap Datepicker, please disable the datepicker as default in the configuration, "use_datepicker". Installation @@ -65,19 +65,22 @@ Installation 5. **(optional) Adjust configurations** - ```yml - # application/config/config.yml - pedro_teixeira_grid: - defaults: - date: - use_datepicker: true - date_format: 'dd/MM/yy' - date_time_format: 'dd/MM/yy HH:mm:ss' - pagination: - limit: 20 - ``` - - The configuration "use_datepicker" will set the input type as "text" and attach a jQuery plugin "datepicker()" to the filter. + ```yml + # application/config/config.yml + pedro_teixeira_grid: + defaults: + date: + use_datepicker: true + date_format: 'dd/MM/yy' + date_time_format: 'dd/MM/yy HH:mm:ss' + pagination: + limit: 20 + export: + enabled true + path: '/tmp/' + ``` + + The configuration "use_datepicker" will set the input type as "text" and attach a jQuery plugin "datepicker()" to the filter. Create your grid @@ -104,6 +107,11 @@ Create your grid */ public function setupGrid() { + $this->addColumn('Hidden Field') + ->setField('hidden') + ->setIndex('r.hidden') + ->setExportOnly(true); + $this->addColumn('ID') ->setField('id') ->setIndex('r.id') @@ -172,7 +180,7 @@ Create your grid $grid = $this->get('pedroteixeira.grid')->createGrid('\PedroTeixeira\Bundle\TestBundle\Grid\TestGrid'); $grid->setQueryBuilder($queryBuilder); - if ($grid->isAjax()) { + if ($grid->isResponseAnswer()) { return $grid->render(); } diff --git a/Resources/doc/column.md b/Resources/doc/column.md index ca83336..309dc47 100644 --- a/Resources/doc/column.md +++ b/Resources/doc/column.md @@ -84,6 +84,27 @@ public function setupGrid() } ``` +protected $exportOnly +------------ + +Just show the field in the export. + +Default: + +``` +false +``` + +Usage: + +```php +public function setupGrid() +{ + $this->addColumn() + ->setExportOnly(true); +} +``` + protected $filterType ------------ diff --git a/Resources/doc/grid.md b/Resources/doc/grid.md index 90da0b0..d582b5e 100644 --- a/Resources/doc/grid.md +++ b/Resources/doc/grid.md @@ -24,6 +24,22 @@ Usage: $grid->setName('My Grid'); ``` +protected $exportable +------------ +Enable export in the grid. + +Default: + +``` +true +``` + +Usage: + +```php +$grid->setExportable(false); +``` + abstract public function setupGrid() ------------ Use this method to define your grid. diff --git a/Resources/public/js/grid.js b/Resources/public/js/grid.js index 5d68438..9da23d9 100644 --- a/Resources/public/js/grid.js +++ b/Resources/public/js/grid.js @@ -41,16 +41,22 @@ 'limit':this.limit, 'sort': this.sortIndex, 'sort_order': this.sortOrder, - 'export': this.exportFlag, + 'export': (this.exportFlag ? 1 : 0), 'filters': filters }, dataType:'json', + timeout: (this.exportFlag ? (5*60*1000) : (10 * 1000)), beforeSend:function (data) { thisClass.gridLock() }, success:function (data) { thisClass.gridUnlock() + if (data.file_hash) { + window.location = thisClass.ajaxUrl + '?export=1&file_hash=' + data.file_hash + return + } + thisClass.page = data.page thisClass.limit = data.page_limit thisClass.totalRows = data.row_count @@ -131,6 +137,7 @@ , export:function () { this.exportFlag = true this.ajax() + this.exportFlag = false return this } diff --git a/Resources/views/block.html.twig b/Resources/views/block.html.twig index 92fcba8..7cc6236 100644 --- a/Resources/views/block.html.twig +++ b/Resources/views/block.html.twig @@ -6,12 +6,16 @@ {% for column in view.grid.columns %} - {{ column.name }} + {% if column.exportOnly == false %} + {{ column.name }} + {% endif %} {% endfor %} {% for column in view.grid.columns %} - {{ column.renderFilter | raw }} + {% if column.exportOnly == false %} + {{ column.renderFilter | raw }} + {% endif %} {% endfor %} @@ -33,10 +37,11 @@ {% trans %}Refresh Filters{% endtrans %} - {# @todo Implement #} - {##} + {% if view.grid.exportable %} + + {% endif %}