From dfb8600d05860b282c97f3a65fd0db710be18e70 Mon Sep 17 00:00:00 2001 From: AntikCz Date: Fri, 26 Feb 2016 20:44:05 +0100 Subject: [PATCH] Support for multiple export (Closes #262) --- src/Components/Container.php | 56 +++++++- src/Components/Export.php | 180 ------------------------ src/Components/Exports/BaseExport.php | 154 ++++++++++++++++++++ src/Components/Exports/CsvExport.php | 78 ++++++++++ src/templates/default.latte | 7 +- tests/Components/Export.phpt | 22 +-- tests/DataSources/ArraySource.phpt | 3 +- tests/DataSources/DibiFluent.phpt | 3 +- tests/DataSources/Doctrine.phpt | 3 +- tests/DataSources/NetteDatabase.phpt | 3 +- tests/DataSources/TestCase.php | 2 +- tests/DataSources/files/render.expect | 2 +- tests/Grid/files/render.editable.expect | 2 +- tests/Grid/files/render.expect | 5 +- tests/Grid/render.editable.phpt | 3 +- tests/Grid/render.phpt | 4 +- 16 files changed, 320 insertions(+), 207 deletions(-) delete mode 100644 src/Components/Export.php create mode 100644 src/Components/Exports/BaseExport.php create mode 100644 src/Components/Exports/CsvExport.php diff --git a/src/Components/Container.php b/src/Components/Container.php index 2d7198b4..45058622 100644 --- a/src/Components/Container.php +++ b/src/Components/Container.php @@ -11,6 +11,8 @@ namespace Grido\Components; +use Grido\Components\Exports\BaseExport; +use Grido\Components\Exports\CsvExport; use Grido\Grid; use Grido\Helpers; use Grido\Components\Actions\Action; @@ -94,12 +96,37 @@ public function getOperation($need = TRUE) /** * Returns export component. + * @param string $name * @param bool $need - * @return Export + * @return CsvExport + */ + public function getExport($name = NULL, $need = TRUE) + { + if (is_bool($name) || $name === NULL) { // deprecated + trigger_error('This usage of ' . __METHOD__ . '() is deprecated, + please write name of export to first parameter.', E_USER_DEPRECATED); + $export = $this->getComponent(BaseExport::ID, $name); + if ($export) { + $export = $export->getComponent(CsvExport::CSV_ID, is_bool($name) ? $name : TRUE); + } + return $export; + } + return $this->hasExport() + ? $this->getComponent(BaseExport::ID)->getComponent(Helpers::formatColumnName($name), $need) + : NULL; + } + + /** + * @param bool $need + * @return BaseExport[] */ - public function getExport($need = TRUE) + public function getExports($need = TRUE) { - return $this->getComponent(Export::ID, $need); + $export = $this->getComponent(BaseExport::ID, $need); + if ($export) { + $export = $export->getComponents(); + } + return $export; } /**********************************************************************************************/ @@ -185,7 +212,7 @@ public function hasExport($useCache = TRUE) $hasExport = $this->hasExport; if ($hasExport === NULL || $useCache === FALSE) { - $hasExport = (bool) $this->getComponent(Export::ID, FALSE); + $hasExport = (bool) $this->getExports(FALSE); $this->hasExport = $useCache ? $hasExport : NULL; } @@ -361,10 +388,29 @@ public function setOperation(array $operations, $onSubmit) /** * @param string $label of exporting file * @return Export + * + * @deprecated */ public function setExport($label = NULL) { - return new Export($this, $label); + trigger_error(__METHOD__ . '() is deprecated; use addExport instead.', E_USER_DEPRECATED); + return $this->addExport(new CsvExport($label), CsvExport::CSV_ID); + } + + /** + * @param BaseExport $export + * @param string $name Component name + * @return BaseExport + */ + public function addExport(BaseExport $export, $name) + { + $container = $this->getComponent(BaseExport::ID, FALSE); + if (!$container) { + $container = new \Nette\ComponentModel\Container(); + $this->addComponent($container, BaseExport::ID); + } + $container->addComponent($export, $name); + return $export; } /** diff --git a/src/Components/Export.php b/src/Components/Export.php deleted file mode 100644 index ca4fb713..00000000 --- a/src/Components/Export.php +++ /dev/null @@ -1,180 +0,0 @@ -grid = $grid; - $this->label = $label; - - $grid->addComponent($this, self::ID); - } - - /** - * @return void - */ - protected function printCsv() - { - $escape = function($value) { - return preg_match("~[\"\n,;\t]~", $value) || $value === "" - ? '"' . str_replace('"', '""', $value) . '"' - : $value; - }; - - $print = function(array $row) { - print implode(',', $row) . "\n"; - }; - - $columns = $this->grid[Column::ID]->getComponents(); - - $header = []; - $headerItems = $this->header ? $this->header : $columns; - foreach ($headerItems as $column) { - $header[] = $this->header - ? $escape($column) - : $escape($column->getLabel()); - } - - $print($header); - - $datasource = $this->grid->getData(FALSE, FALSE, FALSE); - $iterations = ceil($datasource->getCount() / $this->fetchLimit); - for ($i = 0; $i < $iterations; $i++) { - $datasource->limit($i * $this->fetchLimit, $this->fetchLimit); - $data = $this->customData - ? call_user_func_array($this->customData, [$datasource]) - : $datasource->getData(); - - foreach ($data as $items) { - $row = []; - - $columns = $this->customData - ? $items - : $columns; - - foreach ($columns as $column) { - $row[] = $this->customData - ? $escape($column) - : $escape($column->renderExport($items)); - } - - $print($row); - } - } - } - - /** - * Sets a limit which will be used in order to retrieve data from datasource. - * @param int $limit - * @return \Grido\Components\Export - */ - public function setFetchLimit($limit) - { - $this->fetchLimit = (int) $limit; - return $this; - } - - /** - * @return int - */ - public function getFetchLimit() - { - return $this->fetchLimit; - } - - /** - * Sets a custom header of result CSV file (list of field names). - * @param array $header - * @return \Grido\Components\Export - */ - public function setHeader(array $header) - { - $this->header = $header; - return $this; - } - - /** - * Sets a callback to modify output data. This callback must return a list of items. (array) function($datasource) - * DEBUG? You probably need to comment lines started with $httpResponse->setHeader in Grido\Components\Export.php - * @param callable $callback - * @return \Grido\Components\Export - */ - public function setCustomData($callback) - { - $this->customData = $callback; - return $this; - } - - /** - * @internal - */ - public function handleExport() - { - !empty($this->grid->onRegistered) && $this->grid->onRegistered($this->grid); - $this->grid->presenter->sendResponse($this); - } - - /*************************** interface \Nette\Application\IResponse ***************************/ - - /** - * Sends response to output. - * @param \Nette\Http\IRequest $httpRequest - * @param \Nette\Http\IResponse $httpResponse - * @return void - */ - public function send(\Nette\Http\IRequest $httpRequest, \Nette\Http\IResponse $httpResponse) - { - $encoding = 'utf-8'; - $label = $this->label - ? ucfirst(Strings::webalize($this->label)) - : ucfirst($this->grid->getName()); - - $httpResponse->setHeader('Content-Encoding', $encoding); - $httpResponse->setHeader('Content-Type', "text/csv; charset=$encoding"); - $httpResponse->setHeader('Content-Disposition', "attachment; filename=\"$label.csv\""); - - print chr(0xEF) . chr(0xBB) . chr(0xBF); //UTF-8 BOM - $this->printCsv(); - } -} diff --git a/src/Components/Exports/BaseExport.php b/src/Components/Exports/BaseExport.php new file mode 100644 index 00000000..a516d4bd --- /dev/null +++ b/src/Components/Exports/BaseExport.php @@ -0,0 +1,154 @@ +label = $label; + $this->monitor('Grido\Grid'); + } + + protected function attached($presenter) + { + parent::attached($presenter); + if ($presenter instanceof Grid) { + $this->grid = $presenter; + } + } + + /** + * @return void + */ + abstract protected function printData(); + + /** + * @param \Nette\Http\IResponse $httpResponse + * @param string $label + * @return void + */ + abstract protected function setHttpHeaders(\Nette\Http\IResponse $httpResponse, $label); + + /** + * @param string $title + * @return self + */ + public function setTitle($title) + { + $this->title = $title; + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Sets a limit which will be used in order to retrieve data from datasource. + * @param int $limit + * @return \Grido\Components\Export + */ + public function setFetchLimit($limit) + { + $this->fetchLimit = (int) $limit; + return $this; + } + + /** + * @return int + */ + public function getFetchLimit() + { + return $this->fetchLimit; + } + + /** + * Sets a custom header of result CSV file (list of field names). + * @param array $header + * @return \Grido\Components\Export + */ + public function setHeader(array $header) + { + $this->header = $header; + return $this; + } + + /** + * Sets a callback to modify output data. This callback must return a list of items. (array) function($datasource) + * DEBUG? You probably need to comment lines started with $httpResponse->setHeader in Grido\Components\Export.php + * @param callable $callback + * @return \Grido\Components\Export + */ + public function setCustomData($callback) + { + $this->customData = $callback; + return $this; + } + + /** + * @internal + */ + public function handleExport() + { + !empty($this->grid->onRegistered) && $this->grid->onRegistered($this->grid); + $this->grid->presenter->sendResponse($this); + } + + /*************************** interface \Nette\Application\IResponse ***************************/ + + /** + * Sends response to output. + * @param \Nette\Http\IRequest $httpRequest + * @param \Nette\Http\IResponse $httpResponse + * @return void + */ + public function send(\Nette\Http\IRequest $httpRequest, \Nette\Http\IResponse $httpResponse) + { + $label = $this->label + ? ucfirst(Strings::webalize($this->label)) + : ucfirst($this->grid->name); + + $this->setHttpHeaders($httpResponse, $label); + + print chr(0xEF) . chr(0xBB) . chr(0xBF); //UTF-8 BOM + $this->printData(); + } +} diff --git a/src/Components/Exports/CsvExport.php b/src/Components/Exports/CsvExport.php new file mode 100644 index 00000000..8d8fc50f --- /dev/null +++ b/src/Components/Exports/CsvExport.php @@ -0,0 +1,78 @@ +grid[Column::ID]->getComponents(); + + $header = []; + $headerItems = $this->header ? $this->header : $columns; + foreach ($headerItems as $column) { + $header[] = $this->header + ? $escape($column) + : $escape($column->getLabel()); + } + + $print($header); + + $datasource = $this->grid->getData(FALSE, FALSE, FALSE); + $iterations = ceil($datasource->getCount() / $this->fetchLimit); + for ($i = 0; $i < $iterations; $i++) { + $datasource->limit($i * $this->fetchLimit, $this->fetchLimit); + $data = $this->customData + ? call_user_func_array($this->customData, [$datasource]) + : $datasource->getData(); + + foreach ($data as $items) { + $row = []; + + $columns = $this->customData + ? $items + : $columns; + + foreach ($columns as $column) { + $row[] = $this->customData + ? $escape($column) + : $escape($column->renderExport($items)); + } + + $print($row); + } + } + } + + /** + * @param IResponse $httpResponse + * @param string $label + */ + protected function setHttpHeaders(IResponse $httpResponse, $label) + { + $encoding = 'utf-8'; + $httpResponse->setHeader('Content-Encoding', $encoding); + $httpResponse->setHeader('Content-Type', "text/csv; charset=$encoding"); + $httpResponse->setHeader('Content-Disposition', "attachment; filename=\"$label.csv\""); + } +} diff --git a/src/templates/default.latte b/src/templates/default.latte index 86109cc6..891fed08 100755 --- a/src/templates/default.latte +++ b/src/templates/default.latte @@ -144,9 +144,12 @@ {/block} {input $form[buttons][perPage], class => 'hide'} - - {_'Grido.Export'} + {if $control->hasExport()} + + {var $title = $export->title ? : $template->translate('Grido.ExportAllItems')} + {_'Grido.Export'} + {/if} {input $form[buttons][reset]} diff --git a/tests/Components/Export.phpt b/tests/Components/Export.phpt index 9a33aec6..5c1cd92d 100644 --- a/tests/Components/Export.phpt +++ b/tests/Components/Export.phpt @@ -9,6 +9,8 @@ namespace Grido\Tests; +use Grido\Components\Exports\BaseExport; +use Grido\Components\Exports\CsvExport; use Tester\Assert, Grido\Grid, Grido\Tests\Helper, @@ -47,7 +49,7 @@ class ExportTest extends \Tester\TestCase $grid = new Grid; Assert::false($grid->hasExport()); - $grid->setExport(); + $grid->addExport(new CsvExport(), 'csv'); Assert::false($grid->hasExport()); Assert::true($grid->hasExport(FALSE)); } @@ -57,15 +59,15 @@ class ExportTest extends \Tester\TestCase $grid = new Grid; $label = 'export'; - $grid->setExport($label); - $component = $grid->getExport(); - Assert::type('\Grido\Components\Export', $component); + $grid->addExport(new CsvExport($label), 'csv'); + $component = $grid->getExport('csv'); + Assert::type('\Grido\Components\Exports\BaseExport', $component); Assert::same($label, $component->label); - unset($grid[Export::ID]); + $grid[BaseExport::ID]->removeComponent($grid->getExport('csv')); // getter Assert::exception(function() use ($grid) { - $grid->getExport(); + $grid->getExport('csv'); }, 'Nette\InvalidArgumentException'); } @@ -95,11 +97,11 @@ class ExportTest extends \Tester\TestCase ->setSortable(); $grid->addColumnText('country', 'Country') ->setFilterText(); - $grid->setExport($label); + $grid->addExport(new CsvExport($label), 'csv'); }); $params = [ - 'do' => 'grid-export-export', + 'do' => 'grid-export-csv-export', 'grid-sort' => ['name' => \Grido\Components\Columns\Column::ORDER_DESC], 'grid-filter' => ['country' => 'Switzerland'], 'grid-page' => 2 @@ -134,7 +136,7 @@ class ExportTest extends \Tester\TestCase $grid->addColumnText('firstname', 'Name') ->setSortable(); - $grid->setExport() + $grid->addExport(new CsvExport(), 'csv') ->setHeader(['"Jméno"', "Příjmení\t", "Karta\n", 'Jméno,Příjmení']) ->setCustomData(function(ArraySource $source) { $data = $source->getData(); @@ -151,7 +153,7 @@ class ExportTest extends \Tester\TestCase }); }); - $params = ['do' => 'grid-export-export']; + $params = ['do' => 'grid-export-csv-export']; ob_start(); Helper::request($params)->send(mock('\Nette\Http\IRequest'), new Response); diff --git a/tests/DataSources/ArraySource.phpt b/tests/DataSources/ArraySource.phpt index 48fcb762..16565308 100644 --- a/tests/DataSources/ArraySource.phpt +++ b/tests/DataSources/ArraySource.phpt @@ -9,6 +9,7 @@ namespace Grido\Tests; +use Grido\Components\Exports\CsvExport; use Tester\Assert, Grido\Grid, Grido\Components\Filters\Condition; @@ -58,7 +59,7 @@ class ArraySourceTest extends DataSourceTestCase return $row['centimeters'] >= 180; }); - $grid->setExport(); + $grid->addExport(new CsvExport(), 'csv'); })->run(); } diff --git a/tests/DataSources/DibiFluent.phpt b/tests/DataSources/DibiFluent.phpt index 93642f65..58437c05 100644 --- a/tests/DataSources/DibiFluent.phpt +++ b/tests/DataSources/DibiFluent.phpt @@ -9,6 +9,7 @@ namespace Grido\Tests; +use Grido\Components\Exports\CsvExport; use Tester\Assert, Grido\Grid, Grido\Components\Filters\Condition; @@ -60,7 +61,7 @@ class DibiFluentTest extends DataSourceTestCase }); $limit = 100; - $export = $grid->setExport()->setFetchLimit($limit); + $export = $grid->addExport(new CsvExport(), 'csv')->setFetchLimit($limit); Assert::same($limit, $export->getFetchLimit()); })->run(); diff --git a/tests/DataSources/Doctrine.phpt b/tests/DataSources/Doctrine.phpt index 7bfbeb1a..715583d1 100644 --- a/tests/DataSources/Doctrine.phpt +++ b/tests/DataSources/Doctrine.phpt @@ -9,6 +9,7 @@ namespace Grido\Tests; +use Grido\Components\Exports\CsvExport; use Tester\Assert; use Grido\Grid; use Grido\Components\Filters\Condition; @@ -65,7 +66,7 @@ class DoctrineTest extends DataSourceTestCase $qb->andWhere("a.centimeters >= :height")->setParameter('height', 180); }); - $grid->setExport(); + $grid->addExport(new CsvExport(), 'csv'); })->run(); } diff --git a/tests/DataSources/NetteDatabase.phpt b/tests/DataSources/NetteDatabase.phpt index 3dbb4dc3..ed5c3123 100644 --- a/tests/DataSources/NetteDatabase.phpt +++ b/tests/DataSources/NetteDatabase.phpt @@ -9,6 +9,7 @@ namespace Grido\Tests; +use Grido\Components\Exports\CsvExport; use Tester\Assert, Grido\Grid, Grido\Components\Filters\Condition; @@ -55,7 +56,7 @@ class NetteDatabaseTest extends DataSourceTestCase $fluent->where('[centimeters] >= ?', 180); }); - $grid->setExport(); + $grid->addExport(new CsvExport(), 'csv'); })->run(); } diff --git a/tests/DataSources/TestCase.php b/tests/DataSources/TestCase.php index 43677dd0..ec87d0c9 100644 --- a/tests/DataSources/TestCase.php +++ b/tests/DataSources/TestCase.php @@ -105,7 +105,7 @@ function testNullableForeignKey() function testExport() { Helper::$presenter->forceAjaxMode = FALSE; - $params = $this->params + ['do' => 'grid-export-export']; + $params = $this->params + ['do' => 'grid-export-csv-export']; ob_start(); Helper::request($params)->send(mock('\Nette\Http\IRequest'), new \Nette\Http\Response); diff --git a/tests/DataSources/files/render.expect b/tests/DataSources/files/render.expect index 3a1bdb8a..628c3dfd 100644 --- a/tests/DataSources/files/render.expect +++ b/tests/DataSources/files/render.expect @@ -68,7 +68,7 @@ - Export + Export diff --git a/tests/Grid/files/render.editable.expect b/tests/Grid/files/render.editable.expect index 68e46c1d..21759899 100644 --- a/tests/Grid/files/render.editable.expect +++ b/tests/Grid/files/render.editable.expect @@ -72,7 +72,7 @@ - Export + Export diff --git a/tests/Grid/files/render.expect b/tests/Grid/files/render.expect index e16cf7cf..4f1781de 100644 --- a/tests/Grid/files/render.expect +++ b/tests/Grid/files/render.expect @@ -72,7 +72,10 @@ - Export + Export + + + Export diff --git a/tests/Grid/render.editable.phpt b/tests/Grid/render.editable.phpt index a4932d58..1b2afefd 100644 --- a/tests/Grid/render.editable.phpt +++ b/tests/Grid/render.editable.phpt @@ -9,6 +9,7 @@ namespace Grido\Tests; +use Grido\Components\Exports\CsvExport; use Tester\Assert, Grido\Grid; @@ -58,7 +59,7 @@ test(function() ->elementPrototype = \Nette\Utils\Html::el('button'); $grid->setOperation(['print' => 'Print'], function(){}); - $grid->setExport(); + $grid->addExport(new CsvExport(), 'csv'); })->run(); diff --git a/tests/Grid/render.phpt b/tests/Grid/render.phpt index 5c48c57d..604601c8 100644 --- a/tests/Grid/render.phpt +++ b/tests/Grid/render.phpt @@ -9,6 +9,7 @@ namespace Grido\Tests; +use Grido\Components\Exports\CsvExport; use Tester\Assert, Grido\Grid; @@ -55,7 +56,8 @@ test(function() ->elementPrototype = \Nette\Utils\Html::el('button'); $grid->setOperation(['print' => 'Print'], function(){}); - $grid->setExport(); + $grid->addExport(new CsvExport('myLabel'), 'csv'); + $grid->addExport(new CsvExport('myLabel'), 'csv2'); })->run();