PageRenderTime 148ms CodeModel.GetById 60ms app.highlight 49ms RepoModel.GetById 31ms app.codeStats 1ms

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