PageRenderTime 59ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/www/app/AdminModule/components/Datagrid/DataGridRenderer.php

https://github.com/bazo/Mokuji
PHP | 643 lines | 434 code | 109 blank | 100 comment | 63 complexity | 407e06614b3088b437b37a9469d6e1ea MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
  1. <?php
  2. require_once dirname(__FILE__) . '/IDataGridRenderer.php';
  3. /**
  4. * Converts a data grid into the HTML output.
  5. *
  6. * @author Roman Sklenář
  7. * @copyright Copyright (c) 2009 Roman Sklenář (http://romansklenar.cz)
  8. * @license New BSD License
  9. * @example http://nettephp.com/extras/datagrid
  10. * @package Nette\Extras\DataGrid
  11. * @version $Id$
  12. */
  13. class DataGridRenderer extends Object implements IDataGridRenderer
  14. {
  15. /** @var array of HTML tags */
  16. public $wrappers = array(
  17. 'datagrid' => array(
  18. 'container' => 'table class=datagrid',
  19. ),
  20. 'form' => array(
  21. '.class' => 'datagrid',
  22. ),
  23. 'error' => array(
  24. 'container' => 'ul class=error',
  25. 'item' => 'li',
  26. ),
  27. 'row.header' => array(
  28. 'container' => 'tr class=header',
  29. 'cell' => array(
  30. 'container' => 'th', // .checker, .action
  31. '.active' => 'active',
  32. ),
  33. ),
  34. 'row.filter' => array(
  35. 'container' => 'tr class=filters',
  36. 'cell' => array(
  37. 'container' => 'td', // .action
  38. ),
  39. 'control' => array(
  40. '.input' => 'text',
  41. '.select' => 'select',
  42. '.submit' => 'button',
  43. ),
  44. ),
  45. 'row.content' => array(
  46. 'container' => 'tr', // .even, .selected
  47. '.even' => 'even',
  48. 'cell' => array(
  49. 'container' => 'td', // .checker, .action
  50. ),
  51. ),
  52. 'row.footer' => array(
  53. 'container' => 'tr class=footer',
  54. 'cell' => array(
  55. 'container' => 'td',
  56. ),
  57. ),
  58. 'paginator' => array(
  59. 'container' => 'span class=paginator',
  60. 'button' => array(
  61. 'first' => 'span class="paginator-first"',
  62. 'prev' => 'span class="paginator-prev"',
  63. 'next' => 'span class="paginator-next"',
  64. 'last' => 'span class="paginator-last"',
  65. ),
  66. 'controls' => array(
  67. 'container' => 'span class=paginator-controls',
  68. ),
  69. ),
  70. 'operations' => array(
  71. 'container' => 'span class=operations',
  72. ),
  73. 'info' => array(
  74. 'container' => 'span class=grid-info',
  75. ),
  76. );
  77. /** @var string */
  78. public $footerFormat = '%operations% %paginator% %info%';
  79. /** @var string */
  80. public $paginatorFormat = '%label% %input% of %count%';
  81. /** @var string */
  82. public $infoFormat = 'Items %from% - %to% of %count% | Display: %selectbox% | %reset%';
  83. /** @var string template file*/
  84. public $file;
  85. /** @var DataGrid */
  86. protected $dataGrid;
  87. /** @var array of function(Html $row, DibiRow $data) */
  88. public $onRowRender;
  89. /** @var array of function(Html $cell, string $column, mixed $value) */
  90. public $onCellRender;
  91. /** @var array of function(Html $action, DibiRow $data) */
  92. public $onActionRender;
  93. /**
  94. * Data grid renderer constructor.
  95. * @return void
  96. */
  97. public function __construct()
  98. {
  99. $this->file = dirname(__FILE__) . '/grid.phtml';
  100. }
  101. /**
  102. * Provides complete datagrid rendering.
  103. * @param DataGrid
  104. * @param string
  105. * @return string
  106. */
  107. public function render(DataGrid $dataGrid, $mode = NULL)
  108. {
  109. if ($this->dataGrid !== $dataGrid) {
  110. $this->dataGrid = $dataGrid;
  111. }
  112. if (!$dataGrid->dataSource instanceof DibiDataSource) {
  113. throw new InvalidArgumentException("Data source was not setted. You must set data source to data grid before rendering.");
  114. }
  115. if ($mode !== NULL) {
  116. return call_user_func_array(array($this, 'render' . $mode), array());
  117. }
  118. $template = $this->dataGrid->getTemplate();
  119. $template->setFile($this->file);
  120. return $template->__toString(TRUE);
  121. }
  122. /**
  123. * Renders datagrid form begin.
  124. * @return string
  125. */
  126. public function renderBegin()
  127. {
  128. $form = $this->dataGrid->getForm(TRUE);
  129. foreach ($form->getControls() as $control) {
  130. $control->setOption('rendered', FALSE);
  131. }
  132. $form->getElementPrototype()->addClass($this->getValue('form .class'));
  133. return $form->getElementPrototype()->startTag();
  134. }
  135. /**
  136. * Renders datagrid form end.
  137. * @return string
  138. */
  139. public function renderEnd()
  140. {
  141. $form = $this->dataGrid->getForm(TRUE);
  142. return $form->getElementPrototype()->endTag() . "\n";
  143. }
  144. /**
  145. * Renders validation errors.
  146. * @return string
  147. */
  148. public function renderErrors()
  149. {
  150. $form = $this->dataGrid->getForm(TRUE);
  151. $errors = $form->getErrors();
  152. if (count($errors)) {
  153. $ul = $this->getWrapper('error container');
  154. $li = $this->getWrapper('error item');
  155. foreach ($errors as $error) {
  156. $item = clone $li;
  157. if ($error instanceof Html) {
  158. $item->add($error);
  159. } else {
  160. $item->setText($error);
  161. }
  162. $ul->add($item);
  163. }
  164. return "\n" . $ul->render(0);
  165. }
  166. }
  167. /**
  168. * Renders data grid body.
  169. * @return string
  170. */
  171. public function renderBody()
  172. {
  173. $container = $this->getWrapper('datagrid container');
  174. // headers
  175. $header = Html::el($container->getName() == 'table' ? 'thead' : NULL);
  176. $header->add($this->generateHeaderRow());
  177. if ($this->dataGrid->hasFilters()) {
  178. $header->add($this->generateFilterRow());
  179. }
  180. // footer
  181. $footer = Html::el($container->getName() == 'table' ? 'tfoot' : NULL);
  182. $footer->add($this->generateFooterRow());
  183. // body
  184. $body = Html::el($container->getName() == 'table' ? 'tbody' : NULL);
  185. $iterator = new SmartCachingIterator($this->dataGrid->getRows());
  186. foreach ($iterator as $data) {
  187. $row = $this->generateContentRow($data);
  188. $row->addClass($iterator->isEven() ? $this->getValue('row.content .even') : NULL);
  189. $body->add($row);
  190. }
  191. if ($container->getName() == 'table') {
  192. $container->add($header);
  193. $container->add($footer);
  194. $container->add($body);
  195. } else {
  196. $container->add($header);
  197. $container->add($body);
  198. $container->add($footer);
  199. }
  200. return $container->render(0);
  201. }
  202. /**
  203. * Renders data grid paginator.
  204. * @return string
  205. */
  206. public function renderPaginator()
  207. {
  208. $paginator = $this->dataGrid->paginator;
  209. if ($paginator->pageCount <= 1) return '';
  210. $container = $this->getWrapper('paginator container');
  211. $translator = $this->dataGrid->getTranslator();
  212. $a = Html::el('a');
  213. $a->addClass(DataGridAction::$ajaxClass);
  214. // to-first button
  215. $first = $this->getWrapper('paginator button first');
  216. $title = $this->dataGrid->translate('First');
  217. $link = clone $a->href($this->dataGrid->link('page', 1));
  218. if ($first instanceof Html) {
  219. if ($paginator->isFirst()) $first->addClass('inactive');
  220. else $first = $link->add($first);
  221. $first->title($title);
  222. } else {
  223. $first = $link->setText($title);
  224. }
  225. $container->add($first);
  226. // previous button
  227. $prev = $this->getWrapper('paginator button prev');
  228. $title = $this->dataGrid->translate('Previous');
  229. $link = clone $a->href($this->dataGrid->link('page', $paginator->page - 1));
  230. if ($prev instanceof Html) {
  231. if ($paginator->isFirst()) $prev->addClass('inactive');
  232. else $prev = $link->add($prev);
  233. $prev->title($title);
  234. } else {
  235. $prev = $link->setText($title);
  236. }
  237. $container->add($prev);
  238. // page input
  239. $controls = $this->getWrapper('paginator controls container');
  240. $form = $this->dataGrid->getForm(TRUE);
  241. $format = $this->dataGrid->translate($this->paginatorFormat);
  242. $html = str_replace(
  243. array('%label%', '%input%', '%count%'),
  244. array($form['page']->label, $form['page']->control, $paginator->pageCount),
  245. $format
  246. );
  247. $controls->add(Html::el()->setHtml($html));
  248. $container->add($controls);
  249. // next button
  250. $next = $this->getWrapper('paginator button next');
  251. $title = $this->dataGrid->translate('Next');
  252. $link = clone $a->href($this->dataGrid->link('page', $paginator->page + 1));
  253. if ($next instanceof Html) {
  254. if ($paginator->isLast()) $next->addClass('inactive');
  255. else $next = $link->add($next);
  256. $next->title($title);
  257. } else {
  258. $next = $link->setText($title);
  259. }
  260. $container->add($next);
  261. // to-last button
  262. $last = $this->getWrapper('paginator button last');
  263. $title = $this->dataGrid->translate('Last');
  264. $link = clone $a->href($this->dataGrid->link('page', $paginator->pageCount));
  265. if ($last instanceof Html) {
  266. if ($paginator->isLast()) $last->addClass('inactive');
  267. else $last = $link->add($last);
  268. $last->title($title);
  269. } else {
  270. $last = $link->setText($title);
  271. }
  272. $container->add($last);
  273. // page change submit
  274. $control = $form['pageSubmit']->control;
  275. $control->title = $control->value;
  276. $container->add($control);
  277. unset($first, $prev, $next, $last, $button, $paginator, $link, $a, $form);
  278. return $container->render();
  279. }
  280. /**
  281. * Renders data grid operation controls.
  282. * @return string
  283. */
  284. public function renderOperations()
  285. {
  286. if (!$this->dataGrid->hasOperations()) return '';
  287. $container = $this->getWrapper('operations container');
  288. $form = $this->dataGrid->getForm(TRUE);
  289. $container->add($form['operations']->label);
  290. $container->add($form['operations']->control);
  291. $container->add($form['operationSubmit']->control->title($form['operationSubmit']->control->value));
  292. return $container->render();
  293. }
  294. /**
  295. * Renders info about data grid.
  296. * @return string
  297. */
  298. public function renderInfo()
  299. {
  300. $container = $this->getWrapper('info container');
  301. $paginator = $this->dataGrid->paginator;
  302. $form = $this->dataGrid->getForm(TRUE);
  303. $stateSubmit = $form['resetSubmit']->control;
  304. $stateSubmit->title($stateSubmit->value);
  305. $this->infoFormat = $this->dataGrid->translate($this->infoFormat);
  306. $html = str_replace(
  307. array(
  308. '%from%',
  309. '%to%',
  310. '%count%',
  311. '%selectbox%',
  312. '%reset%'
  313. ),
  314. array(
  315. $paginator->itemCount != 0 ? $paginator->offset + 1 : $paginator->offset,
  316. $paginator->offset + $paginator->length,
  317. $paginator->itemCount,
  318. $form['items']->control . $form['itemsSubmit']->control->title($form['itemsSubmit']->control->value),
  319. ($this->dataGrid->rememberState ? $stateSubmit : ''),
  320. ),
  321. $this->infoFormat
  322. );
  323. $container->setHtml(trim($html, ' | '));
  324. return $container->render();
  325. }
  326. /**
  327. * Generates datagrid headrer.
  328. * @return Html
  329. */
  330. protected function generateHeaderRow()
  331. {
  332. $row = $this->getWrapper('row.header container');
  333. // checker
  334. if ($this->dataGrid->hasOperations()) {
  335. $cell = $this->getWrapper('row.header cell container');
  336. $cell->addClass('checker');
  337. if ($this->dataGrid->hasFilters()) {
  338. $cell->rowspan(2);
  339. }
  340. $row->add($cell);
  341. }
  342. // headers
  343. foreach ($this->dataGrid->getColumns() as $column) {
  344. $value = $text = $column->caption;
  345. if ($column->isOrderable()) {
  346. $i = 1;
  347. parse_str($this->dataGrid->order, $list);
  348. foreach ($list as $field => $dir) {
  349. $list[$field] = array($dir, $i++);
  350. }
  351. if (isset($list[$column->getName()])) {
  352. $a = $list[$column->getName()][0] === 'a';
  353. $d = $list[$column->getName()][0] === 'd';
  354. } else {
  355. $a = $d = FALSE;
  356. }
  357. if (count($list) > 1 && isset($list[$column->getName()])) {
  358. $text .= Html::el('span')->setHtml($list[$column->getName()][1]);
  359. }
  360. $up = clone $down = Html::el('a')->addClass(DataGridColumn::$ajaxClass);
  361. $up->addClass($a ? 'active' : '')->href($column->getOrderLink('a'))
  362. ->add(Html::el('span')->class('up'));
  363. $down->addClass($d ? 'active' : '')->href($column->getOrderLink('d'))
  364. ->add(Html::el('span')->class('down'));
  365. $positioner = Html::el('span')->class('positioner')->add($up)->add($down);
  366. $active = $a || $d;
  367. $value = (string) Html::el('a')->href($column->getOrderLink())
  368. ->addClass(DataGridColumn::$ajaxClass)->setHtml($text) . $positioner;
  369. } else {
  370. $value = (string) Html::el('p')->setText($value);
  371. }
  372. $cell = $this->getWrapper('row.header cell container')->setHtml($value);
  373. $cell->attrs = $column->getHeaderPrototype()->attrs;
  374. $cell->addClass(isset($active) && $active == TRUE ? $this->getValue('row.header cell .active') : NULL);
  375. if ($column instanceof ActionColumn) $cell->addClass('actions');
  376. $row->add($cell);
  377. }
  378. return $row;
  379. }
  380. /**
  381. * Generates datagrid filter.
  382. * @return Html
  383. */
  384. protected function generateFilterRow()
  385. {
  386. $row = $this->getWrapper('row.filter container');
  387. $form = $this->dataGrid->getForm(TRUE);
  388. $submitControl = $form['filterSubmit']->control;
  389. $submitControl->addClass($this->getValue('row.filter control .submit'));
  390. $submitControl->title = $submitControl->value;
  391. foreach ($this->dataGrid->getColumns() as $column) {
  392. $cell = $this->getWrapper('row.filter cell container');
  393. // TODO: set on filters too?
  394. $cell->attrs = $column->getCellPrototype()->attrs;
  395. if ($column instanceof ActionColumn) {
  396. $value = (string) $submitControl;
  397. $cell->addClass('actions');
  398. } else {
  399. if ($column->hasFilter()) {
  400. $filter = $column->getFilter();
  401. if ($filter instanceof SelectboxFilter) {
  402. $class = $this->getValue('row.filter control .select');
  403. } else {
  404. $class = $this->getValue('row.filter control .input');
  405. }
  406. $control = $filter->getFormControl()->control;
  407. $control->addClass($class);
  408. $value = (string) $control;
  409. } else {
  410. $value = '';
  411. }
  412. }
  413. $cell->setHtml($value);
  414. $row->add($cell);
  415. }
  416. if (!$this->dataGrid->hasActions()) {
  417. $submitControl->addStyle('display: none');
  418. $row->add($submitControl);
  419. }
  420. return $row;
  421. }
  422. /**
  423. * Generates datagrid row content.
  424. * @param DibiRow data
  425. * @return Html
  426. */
  427. protected function generateContentRow($data)
  428. {
  429. $form = $this->dataGrid->getForm(TRUE);
  430. $row = $this->getWrapper('row.content container');
  431. if ($this->dataGrid->hasOperations() || $this->dataGrid->hasActions()) {
  432. $primary = $this->dataGrid->keyName;
  433. if (!isset($data[$primary])) {
  434. throw new InvalidArgumentException("Invalid name of key for group operations or actions. Column '" . $primary . "' does not exist in data source.");
  435. }
  436. }
  437. // checker
  438. if ($this->dataGrid->hasOperations()) {
  439. $value = $form['checker'][$data[$primary]]->getControl();
  440. $cell = $this->getWrapper('row.content cell container')->setHtml((string)$value);
  441. $cell->addClass('checker');
  442. $row->add($cell);
  443. }
  444. // content
  445. foreach ($this->dataGrid->getColumns() as $column) {
  446. $cell = $this->getWrapper('row.content cell container');
  447. $cell->attrs = $column->getCellPrototype()->attrs;
  448. if ($column instanceof ActionColumn) {
  449. $value = '';
  450. foreach ($this->dataGrid->getActions() as $action) {
  451. $html = $action->getHtml();
  452. $html->title($this->dataGrid->translate($html->title));
  453. $action->generateLink(array($primary => $data[$primary]));
  454. $this->onActionRender($html, $data);
  455. $value .= $html->render() . ' ';
  456. }
  457. $cell->addClass('actions');
  458. } else {
  459. if (!isset($data[$column->getName()])) {
  460. throw new InvalidArgumentException("Non-existing column '" . $column->getName() . "' in datagrid '" . $this->dataGrid->getName() . "'");
  461. }
  462. $value = $column->formatContent($data[$column->getName()], $data);
  463. }
  464. $cell->setHtml((string)$value);
  465. $this->onCellRender($cell, $column->getName(), !($column instanceof ActionColumn) ? $data[$column->getName()] : NULL);
  466. $row->add($cell);
  467. }
  468. unset($form, $primary, $cell, $value, $action);
  469. $this->onRowRender($row, $data);
  470. return $row;
  471. }
  472. /**
  473. * Generates datagrid footer.
  474. * @return Html
  475. */
  476. protected function generateFooterRow()
  477. {
  478. $form = $this->dataGrid->getForm(TRUE);
  479. $paginator = $this->dataGrid->paginator;
  480. $row = $this->getWrapper('row.footer container');
  481. $count = count($this->dataGrid->getColumns());
  482. if ($this->dataGrid->hasOperations()) $count++;
  483. $cell = $this->getWrapper('row.footer cell container');
  484. $cell->colspan($count);
  485. $this->footerFormat = $this->dataGrid->translate($this->footerFormat);
  486. $html = str_replace(
  487. array(
  488. '%operations%',
  489. '%paginator%',
  490. '%info%',
  491. ),
  492. array(
  493. $this->renderOperations(),
  494. $this->renderPaginator(),
  495. $this->renderInfo(),
  496. ),
  497. $this->footerFormat
  498. );
  499. $cell->setHtml($html);
  500. $row->add($cell);
  501. return $row;
  502. }
  503. /**
  504. * @param string
  505. * @return Html
  506. */
  507. protected function getWrapper($name)
  508. {
  509. $data = $this->getValue($name);
  510. return $data instanceof Html ? clone $data : Html::el($data);
  511. }
  512. /**
  513. * @param string
  514. * @return string
  515. */
  516. protected function getValue($name)
  517. {
  518. $name = explode(' ', $name);
  519. if (count($name) == 3) {
  520. $data = & $this->wrappers[$name[0]][$name[1]][$name[2]];
  521. } else {
  522. $data = & $this->wrappers[$name[0]][$name[1]];
  523. }
  524. return $data;
  525. }
  526. /**
  527. * Returns DataGrid.
  528. * @return DataGrid
  529. */
  530. public function getDataGrid()
  531. {
  532. return $this->dataGrid;
  533. }
  534. }