Overview

Namespaces

  • Budovy
  • Kdyby
    • BootstrapFormRenderer
      • DI
      • Latte
  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • Nextras
    • Datagrid
  • None
  • PHP
  • Tester
    • CodeCoverage
    • Runner
      • Output
  • Vodacek
    • Forms
      • Controls
  • WebLoader
    • Filter
    • Nette

Classes

  • Column
  • Datagrid
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nextras community extensions of Nette Framework
  5:  *
  6:  * @license    MIT
  7:  * @link       https://github.com/nextras
  8:  * @author     Jan Skrasek
  9:  */
 10: 
 11: namespace Nextras\Datagrid;
 12: 
 13: use Nette;
 14: use Nette\Application\UI;
 15: use Nette\Utils\Paginator;
 16: use Nette\Localization\ITranslator;
 17: 
 18: 
 19: 
 20: class Datagrid extends UI\Control
 21: {
 22:     /** @var string */
 23:     const ORDER_ASC = 'asc';
 24: 
 25:     /** @var string */
 26:     const ORDER_DESC = 'desc';
 27: 
 28:     /** @persistent */
 29:     public $filter = array();
 30: 
 31:     /** @persistent */
 32:     public $orderColumn;
 33: 
 34:     /** @persistent */
 35:     public $orderType = self::ORDER_ASC;
 36: 
 37:     /** @persistent */
 38:     public $page = 1;
 39: 
 40:     /** @var array */
 41:     protected $filterDataSource = array();
 42: 
 43:     /** @var array */
 44:     protected $columns = array();
 45: 
 46:     /** @var Nette\Callback */
 47:     protected $columnGetterCallback;
 48: 
 49:     /** @var Nette\Callback */
 50:     protected $dataSourceCallback;
 51: 
 52:     /** @var mixed */
 53:     protected $editFormFactory;
 54: 
 55:     /** @var mixed */
 56:     protected $editFormCallback;
 57: 
 58:     /** @var Nette\Callback */
 59:     protected $filterFormFactory;
 60: 
 61:     /** @var Nette\Utils\Paginator */
 62:     protected $paginator;
 63: 
 64:     /** @var Nette\Localization\ITranslator */
 65:     protected $translator;
 66: 
 67:     /** @var Nette\Callback */
 68:     protected $paginatorItemsCountCallback;
 69: 
 70:     /** @var mixed */
 71:     protected $editRowKey;
 72: 
 73:     /** @var string */
 74:     protected $rowPrimaryKey;
 75: 
 76:     /** @var mixed */
 77:     protected $data;
 78: 
 79:     /** @var array */
 80:     protected $cellsTemplates = array();
 81: 
 82: 
 83: 
 84:     /**
 85:      * Adds column
 86:      * @param  string
 87:      * @param  string
 88:      * @return Column
 89:      */
 90:     public function addColumn($name, $label = NULL)
 91:     {
 92:         if (!$this->rowPrimaryKey) {
 93:             $this->rowPrimaryKey = $name;
 94:         }
 95: 
 96:         $label = $label ? $this->translate($label) : ucfirst($name);
 97:         return $this->columns[] = new Column($name, $label, $this);
 98:     }
 99: 
100: 
101: 
102:     public function setRowPrimaryKey($columnName)
103:     {
104:         $this->rowPrimaryKey = (string) $columnName;
105:     }
106: 
107: 
108: 
109:     public function getRowPrimaryKey()
110:     {
111:         return $this->rowPrimaryKey;
112:     }
113: 
114: 
115: 
116:     public function setColumnGetterCallback($getterCallback)
117:     {
118:         $this->columnGetterCallback = new Nette\Callback($getterCallback);
119:     }
120: 
121: 
122: 
123:     public function getColumnGetterCallback()
124:     {
125:         return $this->columnGetterCallback;
126:     }
127: 
128: 
129: 
130:     public function setDataSourceCallback($dataSourceCallback)
131:     {
132:         $this->dataSourceCallback = new Nette\Callback($dataSourceCallback);
133:     }
134: 
135: 
136: 
137:     public function getDataSourceCallback()
138:     {
139:         return $this->dataSourceCallback;
140:     }
141: 
142: 
143: 
144:     public function setEditFormFactory($editFormFactory)
145:     {
146:         $this->editFormFactory = $editFormFactory;
147:     }
148: 
149: 
150: 
151:     public function getEditFormFactory()
152:     {
153:         return $this->editFormFactory;
154:     }
155: 
156: 
157: 
158:     public function setEditFormCallback($editFormCallback)
159:     {
160:         $this->editFormCallback = $editFormCallback;
161:     }
162: 
163: 
164: 
165:     public function getEditFormCallback()
166:     {
167:         return $this->editFormCallback;
168:     }
169: 
170: 
171: 
172:     public function setFilterFormFactory($filterFormFactory)
173:     {
174:         $this->filterFormFactory = new Nette\Callback($filterFormFactory);
175:     }
176: 
177: 
178: 
179:     public function getFilterFormFactory()
180:     {
181:         return $this->filterFormFactory;
182:     }
183: 
184: 
185: 
186:     public function setPagination($itemsPerPage, $itemsCountCallback = NULL)
187:     {
188:         if ($itemsPerPage === FALSE) {
189:             $this->paginator = NULL;
190:             $this->paginatorItemsCountCallback = NULL;
191:         } else {
192:             if ($itemsCountCallback === NULL) {
193:                 throw new \InvalidArgumentException('Items count callback must be set.');
194:             }
195: 
196:             $this->paginator = new Paginator();
197:             $this->paginator->itemsPerPage = $itemsPerPage;
198:             $this->paginatorItemsCountCallback = new Nette\Callback($itemsCountCallback);
199:         }
200:     }
201: 
202: 
203: 
204:     public function addCellsTemplate($path)
205:     {
206:         $this->cellsTemplates[] = $path;
207:     }
208: 
209: 
210: 
211:     public function getCellsTemplate()
212:     {
213:         return $this->cellsTemplates;
214:     }
215: 
216: 
217: 
218:     public function setTranslator(ITranslator $translator)
219:     {
220:         $this->translator = $translator;
221:     }
222: 
223: 
224: 
225:     public function getTranslator()
226:     {
227:         return $this->translator;
228:     }
229: 
230: 
231: 
232:     public function translate($s, $count = NULL)
233:     {
234:         $translator = $this->getTranslator();
235:         return $translator === NULL || $s == NULL || $s instanceof Html // intentionally ==
236:             ? $s
237:             : $translator->translate((string) $s, $count);
238:     }
239: 
240: 
241: 
242:     /*******************************************************************************/
243: 
244: 
245: 
246:     public function render()
247:     {
248:         if ($this->filterFormFactory) {
249:             $this['form']['filter']->setDefaults($this->filter);
250:         }
251: 
252:         $this->template->data = $this->getData();
253:         $this->template->columns = $this->columns;
254:         $this->template->editRowKey = $this->editRowKey;
255:         $this->template->rowPrimaryKey = $this->rowPrimaryKey;
256:         $this->template->paginator = $this->paginator;
257: 
258:         foreach ($this->cellsTemplates as &$cellsTemplate) {
259:             if ($cellsTemplate instanceof Nette\Templating\IFileTemplate) {
260:                 $cellsTemplate = $cellsTemplate->getFile();
261:             }
262:             if (!file_exists($cellsTemplate)) {
263:                 throw new \RuntimeException("Cells template '{$cellsTemplate}' does not exist.");
264:             }
265:         }
266: 
267:         $this->template->cellsTemplates = $this->cellsTemplates;
268:         $this->template->setFile(__DIR__ . '/Datagrid.latte');
269:         $this->template->render();
270:     }
271: 
272: 
273: 
274:     public function invalidateRow($primaryValue)
275:     {
276:         if ($this->presenter->isAjax()) {
277:             if (isset($this->filterDataSource[$this->rowPrimaryKey]) && is_string($this->filterDataSource[$this->rowPrimaryKey])) {
278:                 $this->filterDataSource[$this->rowPrimaryKey] = array($this->filterDataSource[$this->rowPrimaryKey]);
279:             }
280: 
281:             $this->filterDataSource[$this->rowPrimaryKey][] = $primaryValue;
282:             parent::invalidateControl('rows');
283:             $this->invalidateControl('rows-' . $primaryValue);
284:         }
285:     }
286: 
287: 
288: 
289:     public function invalidateControl($snippet = NULL)
290:     {
291:         parent::invalidateControl($snippet);
292:         if ($snippet === NULL || $snippet === 'rows') {
293:             $this->template->echoSnippets = TRUE;
294:         }
295:     }
296: 
297: 
298: 
299:     /*******************************************************************************/
300: 
301: 
302: 
303:     protected function attached($presenter)
304:     {
305:         parent::attached($presenter);
306:         $this->filterDataSource = $this->filter;
307:     }
308: 
309: 
310: 
311:     protected function getData($key = NULL)
312:     {
313:         if (!$this->data) {
314:             $onlyRow = $key && $this->presenter->isAjax();
315:             if (!$onlyRow && $this->paginator) {
316:                 $itemsCount = $this->paginatorItemsCountCallback->invokeArgs(array(
317:                     $this->filter,
318:                     $this->orderColumn ? array($this->orderColumn, strtoupper($this->orderType)) : NULL,
319:                 ));
320: 
321:                 $this->paginator->setItemCount($itemsCount);
322:                 if ($this->paginator->page !== $this->page) {
323:                     $this->paginator->page = $this->page = 1;
324:                 }
325:             }
326: 
327:             $this->data = $this->dataSourceCallback->invokeArgs(array(
328:                 $this->filterDataSource,
329:                 $this->orderColumn ? array($this->orderColumn, strtoupper($this->orderType)) : NULL,
330:                 $onlyRow ? NULL : $this->paginator,
331:             ));
332:         }
333: 
334:         if ($key === NULL) {
335:             return $this->data;
336:         }
337: 
338:         foreach ($this->data as $row) {
339:             if ($this->getter($row, $this->rowPrimaryKey) == $key) {
340:                 return $row;
341:             }
342:         }
343: 
344:         throw new \Exception('Row not found');
345:     }
346: 
347: 
348: 
349:     /**
350:      * @internal
351:      * @ignore
352:      */
353:     public function getter($row, $column, $need = TRUE)
354:     {
355:         if ($this->columnGetterCallback) {
356:             return $this->columnGetterCallback->invokeArgs(array($row, $column));
357:         } else {
358:             if (!isset($row->$column)) {
359:                 if ($need) {
360:                     throw new \InvalidArgumentException("Result row does not have '{$column}' column.");
361:                 } else {
362:                     return NULL;
363:                 }
364:             }
365: 
366:             return $row->$column;
367:         }
368:     }
369: 
370: 
371: 
372:     public function handleEdit($primaryValue, $cancelEditPrimaryValue = NULL)
373:     {
374:         $this->editRowKey = $primaryValue;
375:         $this->invalidateRow($primaryValue);
376:         if ($cancelEditPrimaryValue) {
377:             foreach (explode(',', $cancelEditPrimaryValue) as $pv) {
378:                 $this->invalidateRow($pv);
379:             }
380:         }
381:     }
382: 
383: 
384: 
385:     public function handleSort()
386:     {
387:         if ($this->presenter->isAjax()) {
388:             $this->invalidateControl('rows');
389:         } else {
390:             $this->redirect('this');
391:         }
392:     }
393: 
394: 
395: 
396:     public function createComponentForm()
397:     {
398:         $form = new UI\Form;
399: 
400:         if ($this->editFormFactory && ($this->editRowKey || !empty($_POST['edit']))) {
401:             $data = $this->editRowKey && empty($_POST) ? $this->getData($this->editRowKey) : NULL;
402:             $form['edit'] = Nette\Callback::create($this->editFormFactory)->invokeArgs(array($data));
403: 
404:             if (!isset($form['edit']['save']))
405:                 $form['edit']->addSubmit('save', 'Save');
406:             if (!isset($form['edit']['cancel']))
407:                 $form['edit']->addSubmit('cancel', 'Cancel');
408:             if (!isset($form['edit'][$this->rowPrimaryKey]))
409:                 $form['edit']->addHidden($this->rowPrimaryKey);
410: 
411:             $form['edit'][$this->rowPrimaryKey]
412:                 ->setDefaultValue($this->editRowKey)
413:                 ->setOption('rendered', TRUE);
414:         }
415: 
416:         if ($this->filterFormFactory) {
417:             $form['filter'] = $this->filterFormFactory->invoke();
418:             if (!isset($form['filter']['filter'])) {
419:                 $form['filter']->addSubmit('filter', 'Filter');
420:             }
421:             if (!isset($form['filter']['cancel'])) {
422:                 $form['filter']->addSubmit('cancel', 'Cancel');
423:             }
424:         }
425: 
426:         $form->onSuccess[] = function() {}; // fix for Nette Framework 2.0.x
427:         $form->onSubmit[] = $this->processForm;
428:         return $form;
429:     }
430: 
431: 
432: 
433:     public function processForm(UI\Form $form)
434:     {
435:         $allowRedirect = TRUE;
436:         if (isset($form['edit'])) {
437:             if ($form['edit']['save']->isSubmittedBy()) {
438:                 if ($form['edit']->isValid()) {
439:                     Nette\Callback::create($this->editFormCallback)->invokeArgs(array(
440:                         $form['edit']
441:                     ));
442:                 } else {
443:                     $this->editRowKey = $form['edit'][$this->rowPrimaryKey]->getValue();
444:                     $allowRedirect = FALSE;
445:                 }
446:             }
447:             if ($form['edit']['cancel']->isSubmittedBy() || ($form['edit']['save']->isSubmittedBy() && $form['edit']->isValid())) {
448:                 $editRowKey = $form['edit'][$this->rowPrimaryKey]->getValue();
449:                 $this->invalidateRow($editRowKey);
450:                 $this->getData($editRowKey);
451:             }
452:             if ($this->editRowKey) {
453:                 $this->invalidateRow($this->editRowKey);
454:             }
455:         }
456: 
457:         if (isset($form['filter'])) {
458:             if ($form['filter']['filter']->isSubmittedBy()) {
459:                 $values = $form['filter']->getValues(TRUE);
460:                 unset($values['filter']);
461:                 $values = array_filter($values, function($val) {
462:                     return strlen($val) > 0;
463:                 });
464:                 if ($this->paginator) {
465:                     $this->page = $this->paginator->page = 1;
466:                 }
467:                 $this->filter = $this->filterDataSource = $values;
468:                 $this->invalidateControl('rows');
469:             } elseif ($form['filter']['cancel']->isSubmittedBy()) {
470:                 if ($this->paginator) {
471:                     $this->page = $this->paginator->page = 1;
472:                 }
473:                 $this->filter = $this->filterDataSource = array();
474:                 $form['filter']->setValues(array(), TRUE);
475:                 $this->invalidateControl('rows');
476:             }
477:         }
478: 
479:         if (!$this->presenter->isAjax() && $allowRedirect) {
480:             $this->redirect('this');
481:         }
482:     }
483: 
484: 
485: 
486:     public function loadState(array $params)
487:     {
488:         parent::loadState($params);
489:         if ($this->paginator) {
490:             $this->paginator->page = $this->page;
491:         }
492:     }
493: 
494: 
495: 
496:     protected function createTemplate($class = NULL)
497:     {
498:         $template = parent::createTemplate($class);
499:         if ($translator = $this->getTranslator()) {
500:             $template->setTranslator($translator);
501:         }
502:         return $template;
503:     }
504: 
505: 
506: 
507:     public function handlePaginate()
508:     {
509:         if ($this->presenter->isAjax()) {
510:             $this->invalidateControl('rows');
511:         } else {
512:             $this->redirect('this');
513:         }
514:     }
515: 
516: }
517: 
API documentation generated by ApiGen 2.8.0