Completed
Pull Request — master (#48)
by Martin
03:00
created

DataGrid::render()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 68
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 1 Features 1
Metric Value
c 8
b 1
f 1
dl 0
loc 68
rs 8.5748
cc 6
eloc 31
nc 10
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Ublaboo\DataGrid\Utils\ArraysHelper;
13
use Nette\Application\UI\Form;
14
15
class DataGrid extends Nette\Application\UI\Control
16
{
17
18
	/**
19
	 * @var string
20
	 * @todo Tell about this on github
21
	 */
22
	public static $icon_prefix = 'fa fa-';
23
24
	/**
25
	 * When set to TRUE, datagrid throws an exception
26
	 * 	when tring to get related entity within join and entity does not exist
27
	 * @var bool
28
	 */
29
	public $strict_entity_property = FALSE;
30
31
	/**
32
	 * @var int
33
	 * @persistent
34
	 */
35
	public $page = 1;
36
37
	/**
38
	 * @var int
39
	 * @persistent
40
	 */
41
	public $per_page;
42
43
	/**
44
	 * @var array
45
	 * @persistent
46
	 */
47
	public $sort = [];
48
49
	/**
50
	 * @var array
51
	 * @persistent
52
	 */
53
	public $filter = [];
54
55
	/**
56
	 * @var Callable[]
57
	 */
58
	public $onRender = [];
59
60
	/**
61
	 * @var array
62
	 */
63
	protected $items_per_page_list = [10, 20, 50];
64
65
	/**
66
	 * @var string
67
	 */
68
	protected $template_file;
69
70
	/**
71
	 * @var Column\IColumn[]
72
	 */
73
	protected $columns = [];
74
75
	/**
76
	 * @var Column\Action[]
77
	 */
78
	protected $actions = [];
79
80
	/**
81
	 * @var GroupAction\GroupActionCollection
82
	 */
83
	protected $group_action_collection;
84
85
	/**
86
	 * @var Filter\Filter[]
87
	 */
88
	protected $filters = [];
89
90
	/**
91
	 * @var Export\Export[]
92
	 */
93
	protected $exports = [];
94
95
	/**
96
	 * @var DataModel
97
	 */
98
	protected $dataModel;
99
100
	/**
101
	 * @var DataFilter
102
	 */
103
	protected $dataFilter;
104
105
	/**
106
	 * @var string
107
	 */
108
	protected $primary_key = 'id';
109
110
	/**
111
	 * @var bool
112
	 */
113
	protected $do_paginate = TRUE;
114
115
	/**
116
	 * @var bool
117
	 */
118
	protected $csv_export = TRUE;
119
120
	/**
121
	 * @var bool
122
	 */
123
	protected $csv_export_filtered = TRUE;
124
125
	/**
126
	 * @var bool
127
	 */
128
	protected $sortable = FALSE;
129
130
	/**
131
	 * @var string
132
	 */
133
	protected $original_template;
134
135
	/**
136
	 * @var array
137
	 */
138
	protected $redraw_item;
139
140
	/**
141
	 * @var mixed
142
	 */
143
	protected $translator;
144
145
	/**
146
	 * @var bool
147
	 */
148
	protected $force_filter_active;
149
150
	/**
151
	 * @var callable
152
	 */
153
	protected $tree_view_children_callback;
154
155
	/**
156
	 * @var string
157
	 */
158
	protected $tree_view_has_children_column;
159
160
	/**
161
	 * @var bool
162
	 */
163
	protected $outer_filter_rendering = FALSE;
164
165
	/**
166
	 * @var bool
167
	 */
168
	private $remember_state = TRUE;
169
170
	/**
171
	 * @var bool
172
	 */
173
	private $refresh_url = TRUE;
174
175
	/**
176
	 * @var Nette\Http\SessionSection
177
	 */
178
	private $grid_session;
179
180
	/**
181
	 * @var array
182
	 */
183
	private $items_detail = [];
184
185
	/**
186
	 * @var array
187
	 */
188
	private $row_conditions = [
189
		'group_action' => FALSE,
190
		'action' => []
191
	];
192
193
194
	/**
195
	 * @param Nette\ComponentModel\IContainer|NULL $parent
196
	 * @param string                               $name
197
	 */
198
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
199
	{
200
		parent::__construct($parent, $name);
201
202
		$this->monitor('Nette\Application\UI\Presenter');
203
	}
204
205
206
	/**
207
	 * {inheritDoc}
208
	 * @return void
209
	 */
210
	public function attached($presenter)
211
	{
212
		parent::attached($presenter);
213
214
		if ($presenter instanceof Nette\Application\UI\Presenter) {
215
			/**
216
			 * Get session
217
			 */
218
			$this->grid_session = $this->getPresenter()->getSession($this->getSessionSectionName());
219
220
			/**
221
			 * Try to find previous filters/pagination/sort in session
222
			 */
223
			$this->findSessionFilters();
224
		}
225
	}
226
227
228
	/**
229
	 * Find some unique session key name
230
	 * @return string
231
	 */
232
	public function getSessionSectionName()
233
	{
234
		return $this->getPresenter()->getName().':'.$this->getName();
235
	}
236
237
238
	/**
239
	 * Render template
240
	 * @return void
241
	 */
242
	public function render()
243
	{
244
		/**
245
		 * Check whether datagrid has set some columns, initiated data source, etc
246
		 */
247
		if (!($this->dataModel instanceof DataModel)) {
248
			throw new DataGridException('You have to set a data source first.');
249
		}
250
251
		if (empty($this->columns)) {
252
			throw new DataGridException('You have to add at least one column.');
253
		}
254
255
		$this->template->setTranslator($this->getTranslator());
256
257
		/**
258
		 * Invoke some possible events
259
		 */
260
		$this->onRender($this);
261
262
		/**
263
		 * Prepare data for rendering (datagrid may render just one item)
264
		 */
265
		$rows = [];
266
267
		if (!empty($this->redraw_item)) {
268
			$items = $this->dataModel->filterRow($this->redraw_item);
269
		} else {
270
			$items = Nette\Utils\Callback::invokeArgs(
271
				[$this->dataModel, 'filterData'],
272
				[
273
					$this->getPaginator(),
274
					$this->sort,
275
					$this->assableFilters()
276
				]
277
			);
278
		}
279
280
		foreach ($items as $item) {
281
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
282
		}
283
284
		if ($this->isTreeView()) {
285
			$this->template->tree_view_has_children_column = $this->tree_view_has_children_column;
286
		}
287
288
		$this->template->rows = $rows;
289
290
		$this->template->columns = $this->columns;
291
		$this->template->actions = $this->actions;
292
		$this->template->exports = $this->exports;
293
		$this->template->filters = $this->filters;
294
295
		$this->template->filter_active = $this->isFilterActive();
296
		$this->template->original_template = $this->getOriginalTemplateFile();
297
		$this->template->icon_prefix = static::$icon_prefix;
298
		$this->template->items_detail = $this->items_detail;
299
300
		/**
301
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
302
		 */
303
		$this->template->filter = $this['filter'];
304
305
		/**
306
		 * Set template file and render it
307
		 */
308
		$this->template->setFile($this->getTemplateFile())->render();
309
	}
310
311
312
	/**
313
	 * Return current paginator class
314
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
315
	 */
316
	public function getPaginator()
317
	{
318
		if ($this->isPaginated() && $this->per_page !== 'all') {
319
			return $this['paginator'];
320
		}
321
322
		return NULL;
323
	}
324
325
326
	/**
327
	 * @param string $primary_key
328
	 */
329
	public function setPrimaryKey($primary_key)
330
	{
331
		$this->primary_key = $primary_key;
332
333
		return $this;
334
	}
335
336
337
	/**
338
	 * Set Grid data source
339
	 * @param DataSource\IDataSource|array|\DibiFluent $source
340
	 * @return DataGrid
341
	 */
342
	public function setDataSource($source)
343
	{
344
		if ($source instanceof DataSource\IDataSource) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
345
			// $source is ready for interact
346
347
		} else if (is_array($source)) {
348
			$data_source = new DataSource\ArrayDataSource($source);
349
350
		} else if ($source instanceof \DibiFluent) {
351
			$driver = $source->getConnection()->getDriver();
352
353
			if ($driver instanceof \DibiOdbcDriver) {
354
				$data_source = new DataSource\DibiFluentMssqlDataSource($source, $this->primary_key);
355
356
			} else if ($driver instanceof \DibiMsSqlDriver) {
357
				$data_source = new DataSource\DibiFluentMssqlDataSource($source, $this->primary_key);
358
359
			} else {
360
				$data_source = new DataSource\DibiFluentDataSource($source, $this->primary_key);
361
			}
362
363
		} else if ($source instanceof Nette\Database\Table\Selection) {
364
			$data_source = new DataSource\NetteDatabaseTableDataSource($source, $this->primary_key);
365
366
		} else if ($source instanceof \Kdyby\Doctrine\QueryBuilder) {
367
			$data_source = new DataSource\DoctrineDataSource($source, $this->primary_key);
368
369
		} else {
370
			$data_source_class = $source ? get_class($source) : 'NULL';
371
			throw new DataGridException("DataGrid can not take [$data_source_class] as data source.");
372
		}
373
374
		$this->dataModel = new DataModel($data_source);
375
376
		return $this;
377
	}
378
379
380
	/**
381
	 * Is filter active?
382
	 * @return boolean
383
	 */
384
	public function isFilterActive()
385
	{
386
		$is_filter = ArraysHelper::testTruthy($this->filter);
387
388
		return ($is_filter) || $this->force_filter_active;
389
	}
390
391
392
	/**
393
	 * Tell that filter is active from whatever reasons
394
	 * return self
395
	 */
396
	public function setFilterActive()
397
	{
398
		$this->force_filter_active = TRUE;
399
400
		return $this;
401
	}
402
403
404
	/**
405
	 * If we want to sent some initial filter
406
	 * @param array $filter
407
	 */
408
	public function setFilter(array $filter)
409
	{
410
		$this->filter = $filter;
411
412
		return $this;
413
	}
414
415
416
	/**
417
	 * Set options of select "items_per_page"
418
	 * @param array $items_per_page_list
419
	 */
420
	public function setItemsPerPageList(array $items_per_page_list)
421
	{
422
		$this->items_per_page_list = $items_per_page_list;
423
424
		return $this;
425
	}
426
427
428
	/**
429
	 * Set custom template file to render
430
	 * @param string $template_file
431
	 */
432
	public function setTemplateFile($template_file)
433
	{
434
		$this->template_file = $template_file;
435
436
		return $this;
437
	}
438
439
440
	/**
441
	 * Get DataGrid template file
442
	 * @return string
443
	 */
444
	public function getTemplateFile()
445
	{
446
		return $this->template_file ?: $this->getOriginalTemplateFile();
447
	}
448
449
450
	/**
451
	 * Get DataGrid original template file
452
	 * @return string
453
	 */
454
	public function getOriginalTemplateFile()
455
	{
456
		return __DIR__.'/templates/datagrid.latte';
457
	}
458
459
460
	/**
461
	 * Order Grid to "be paginated"
462
	 * @param bool $do
463
	 */
464
	public function setPagination($do)
465
	{
466
		$this->do_paginate = (bool) $do;
467
468
		return $this;
469
	}
470
471
472
	/**
473
	 * Tell whether Grid is paginated
474
	 * @return bool
475
	 */
476
	public function isPaginated()
477
	{
478
		return $this->do_paginate;
479
	}
480
481
482
	/**
483
	 * Set grido to be sortable
484
	 * @param bool $sortable
485
	 */
486
	public function setSortable($sortable = TRUE)
487
	{
488
		if ($this->getItemsDetail()) {
489
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
490
		}
491
492
		$this->sortable = (bool) $sortable;
493
494
		return $this;
495
	}
496
497
498
	/**
499
	 * Tell whether DataGrid is sortable
500
	 * @return bool
501
	 */
502
	public function isSortable()
503
	{
504
		return $this->sortable;
505
	}
506
507
508
	/**
509
	 * Is tree view set?
510
	 * @return boolean
511
	 */
512
	public function isTreeView()
513
	{
514
		return (bool) $this->tree_view_children_callback;
515
	}
516
517
518
	/**
519
	 * Setting tree view
520
	 * @param callable $get_children_callback
521
	 * @param string   $tree_view_has_children_column
522
	 */
523
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
524
	{
525
		if (!is_callable($get_children_callback)) {
526
			throw new DataGridException(
527
				'Parameters to method DataGrid::setTreeView must be of type callable'
528
			);
529
		}
530
531
		$this->tree_view_children_callback = $get_children_callback;
532
		$this->tree_view_has_children_column = $tree_view_has_children_column;
533
534
		/**
535
		 * TUrn off pagination
536
		 */
537
		$this->setPagination(NULL);
538
539
		/**
540
		 * Set tree view template file
541
		 */
542
		if (!$this->template_file) {
543
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
544
		}
545
546
		return $this;
547
	}
548
549
550
	/********************************************************************************
551
	 *                                    Columns                                   *
552
	 ********************************************************************************/
553
554
555
	/**
556
	 * Add text column with no other formating
557
	 * @param  string      $key
558
	 * @param  string      $name
559
	 * @param  string|null $column
560
	 * @return Column\Column
561
	 */
562
	public function addColumnText($key, $name, $column = NULL)
563
	{
564
		$this->addColumnCheck($key);
565
		$column = $column ?: $key;
566
567
		return $this->columns[$key] = new Column\ColumnText($column, $name);
568
	}
569
570
571
	/**
572
	 * Add column with link
573
	 * @param  string      $key
574
	 * @param  string      $name
575
	 * @param  string|null $column
576
	 * @return Column\Column
577
	 */
578
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
579
	{
580
		$this->addColumnCheck($key);
581
		$column = $column ?: $key;
582
		$href = $href ?: $key;
583
584
		if (NULL === $params) {
585
			$params = [$this->primary_key];
586
		}
587
588
		return $this->columns[$key] = new Column\ColumnLink($this, $column, $name, $href, $params);
589
	}
590
591
592
	/**
593
	 * Add column with possible number formating
594
	 * @param  string      $key
595
	 * @param  string      $name
596
	 * @param  string|null $column
597
	 * @return Column\Column
598
	 */
599
	public function addColumnNumber($key, $name, $column = NULL)
600
	{
601
		$this->addColumnCheck($key);
602
		$column = $column ?: $key;
603
604
		return $this->columns[$key] = new Column\ColumnNumber($column, $name);
605
	}
606
607
608
	/**
609
	 * Add column with date formating
610
	 * @param  string      $key
611
	 * @param  string      $name
612
	 * @param  string|null $column
613
	 * @return Column\Column
614
	 */
615
	public function addColumnDateTime($key, $name, $column = NULL)
616
	{
617
		$this->addColumnCheck($key);
618
		$column = $column ?: $key;
619
620
		return $this->columns[$key] = new Column\ColumnDateTime($column, $name);
621
	}
622
623
624
	/**
625
	 * Return existing column
626
	 * @param  string $key
627
	 * @return Column\Column
628
	 * @throws DataGridException
629
	 */
630
	public function getColumn($key)
631
	{
632
		if (!isset($this->columns[$key])) {
633
			throw new DataGridException("There is no column at key [$key] defined.");
634
		}
635
636
		return $this->columns[$key];
637
	}
638
639
640
	/**
641
	 * Remove column
642
	 * @param string $key
643
	 * @return void
644
	 */
645
	public function removeColumn($key)
646
	{
647
		unset($this->columns[$key]);
648
	}
649
650
651
	/**
652
	 * Check whether given key already exists in $this->columns
653
	 * @param  string $key
654
	 * @throws DataGridException
655
	 */
656
	protected function addColumnCheck($key)
657
	{
658
		if (isset($this->columns[$key])) {
659
			throw new DataGridException("There is already column at key [$key] defined.");
660
		}
661
	}
662
663
664
	/********************************************************************************
665
	 *                                    Actions                                   *
666
	 ********************************************************************************/
667
668
669
	/**
670
	 * Create action
671
	 * @param string     $key
672
	 * @param string     $name
673
	 * @param string     $href
674
	 * @param array|null $params
675
	 */
676
	public function addAction($key, $name = '', $href = NULL, array $params = NULL)
677
	{
678
		$this->addActionCheck($key);
679
		$href = $href ?: $key;
680
681
		if (NULL === $params) {
682
			$params = [$this->primary_key];
683
		}
684
685
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
686
	}
687
688
689
	/**
690
	 * Get existing action
691
	 * @param  string       $key
692
	 * @return Column\Action
693
	 * @throws DataGridException
694
	 */
695
	public function getAction($key)
696
	{
697
		if (!isset($this->actions[$key])) {
698
			throw new DataGridException("There is no action at key [$key] defined.");
699
		}
700
701
		return $this->actions[$key];
702
	}
703
704
705
	/**
706
	 * Remove action
707
	 * @param string $key
708
	 * @return void
709
	 */
710
	public function removeAction($key)
711
	{
712
		unset($this->actions[$key]);
713
	}
714
715
716
	/**
717
	 * Check whether given key already exists in $this->filters
718
	 * @param  string $key
719
	 * @throws DataGridException
720
	 */
721
	protected function addActionCheck($key)
722
	{
723
		if (isset($this->actions[$key])) {
724
			throw new DataGridException("There is already action at key [$key] defined.");
725
		}
726
	}
727
728
729
	/********************************************************************************
730
	 *                                    Filters                                   *
731
	 ********************************************************************************/
732
733
734
	/**
735
	 * Add filter fot text search
736
	 * @param string       $key
737
	 * @param string       $name
738
	 * @param array|string $columns
739
	 * @throws DataGridException
740
	 */
741
	public function addFilterText($key, $name, $columns = NULL)
742
	{
743
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
744
745
		if (!is_array($columns)) {
746
			throw new DataGridException("Filter Text can except only array or string.");
747
		}
748
749
		$this->addFilterCheck($key);
750
751
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
752
	}
753
754
755
	/**
756
	 * Add select box filter
757
	 * @param string $key
758
	 * @param string $name
759
	 * @param array  $options
760
	 * @param string $column
761
	 * @throws DataGridException
762
	 */
763
	public function addFilterSelect($key, $name, $options, $column = NULL)
764
	{
765
		$column = $column ?: $key;
766
767
		if (!is_string($column)) {
768
			throw new DataGridException("Filter Select can only filter through one column.");
769
		}
770
771
		$this->addFilterCheck($key);
772
773
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
774
	}
775
776
777
	/**
778
	 * Add datepicker filter
779
	 * @param string $key
780
	 * @param string $name
781
	 * @param string $column
782
	 * @throws DataGridException
783
	 */
784
	public function addFilterDate($key, $name, $column = NULL)
785
	{
786
		$column = $column ?: $key;
787
788
		if (!is_string($column)) {
789
			throw new DataGridException("FilterDate can only filter through one column.");
790
		}
791
792
		$this->addFilterCheck($key);
793
794
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
795
	}
796
797
798
	/**
799
	 * Add range filter (from - to)
800
	 * @param string $key
801
	 * @param string $name
802
	 * @param string $column
803
	 * @throws DataGridException
804
	 */
805
	public function addFilterRange($key, $name, $column = NULL, $name_second = '-')
806
	{
807
		$column = $column ?: $key;
808
809
		if (!is_string($column)) {
810
			throw new DataGridException("FilterRange can only filter through one column.");
811
		}
812
813
		$this->addFilterCheck($key);
814
815
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
816
	}
817
818
819
	/**
820
	 * Add datepicker filter (from - to)
821
	 * @param string $key
822
	 * @param string $name
823
	 * @param string $column
824
	 * @throws DataGridException
825
	 */
826
	public function addFilterDateRange($key, $name, $column = NULL, $name_second = '-')
827
	{
828
		$column = $column ?: $key;
829
830
		if (!is_string($column)) {
831
			throw new DataGridException("FilterDateRange can only filter through one column.");
832
		}
833
834
		$this->addFilterCheck($key);
835
836
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
837
	}
838
839
840
	/**
841
	 * Check whether given key already exists in $this->filters
842
	 * @param  string $key
843
	 * @throws DataGridException
844
	 */
845
	protected function addFilterCheck($key)
846
	{
847
		if (isset($this->filters[$key])) {
848
			throw new DataGridException("There is already action at key [$key] defined.");
849
		}
850
	}
851
852
853
	/**
854
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
855
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
856
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
857
	 */
858
	public function assableFilters()
859
	{
860
		foreach ($this->filter as $key => $value) {
861
			if (!isset($this->filters[$key])) {
862
				$this->deleteSesssionData($key);
863
864
				continue;
865
			}
866
867
			if (is_array($value) || $value instanceof \Traversable) {
868
				if (!ArraysHelper::testEmpty($value)) {
869
					$this->filters[$key]->setValue($value);
870
				}
871
			} else {
872
				if ($value !== '' && $value !== NULL) {
873
					$this->filters[$key]->setValue($value);
874
				}
875
			}
876
		}
877
878
		foreach ($this->columns as $column) {
879
			if (isset($this->sort[$column->getColumnName()])) {
880
				$column->setSort($this->sort);
881
			}
882
		}
883
884
		return $this->filters;
885
	}
886
887
888
	/**
889
	 * Try to restore session stuff
890
	 * @return void
891
	 */
892
	public function findSessionFilters()
893
	{
894
		if ($this->filter || ($this->page != 1) || $this->sort || $this->per_page) {
895
			return;
896
		}
897
898
		if (!$this->remember_state) {
899
			return;
900
		}
901
902
		if ($page = $this->getSessionData('_grid_page')) {
903
			$this->page = $page;
904
		}
905
906
		if ($per_page = $this->getSessionData('_grid_per_page')) {
907
			$this->per_page = $per_page;
908
		}
909
910
		if ($sort = $this->getSessionData('_grid_sort')) {
911
			$this->sort = $sort;
912
		}
913
914
		foreach ($this->getSessionData() as $key => $value) {
915
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
916
				$this->filter[$key] = $value;
917
			}
918
		}
919
	}
920
921
922
	/**
923
	 * Remove filter
924
	 * @param string $key
925
	 * @return void
926
	 */
927
	public function removeFilter($key)
928
	{
929
		unset($this->filters[$key]);
930
	}
931
932
933
	/********************************************************************************
934
	 *                                    Exports                                   *
935
	 ********************************************************************************/
936
937
938
	/**
939
	 * Add export of type callback
940
	 * @param string   $text
941
	 * @param callable $callback
942
	 * @param boolean  $filtered
943
	 */
944
	public function addExportCallback($text, $callback, $filtered = FALSE)
945
	{
946
		if (!is_callable($callback)) {
947
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
948
		}
949
950
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
951
	}
952
953
954
	/**
955
	 * Add already implemented csv export
956
	 * @param string $text
957
	 * @param string $csv_file_name
958
	 */
959
	public function addExportCsv($text, $csv_file_name)
960
	{
961
		return $this->addToExports(new Export\ExportCsv($text, $csv_file_name, FALSE));
962
	}
963
964
965
	/**
966
	 * Add already implemented csv export, but for filtered data
967
	 * @param string $text
968
	 * @param string $csv_file_name
969
	 */
970
	public function addExportCsvFiltered($text, $csv_file_name)
971
	{
972
		return $this->addToExports(new Export\ExportCsv($text, $csv_file_name, TRUE));
973
	}
974
975
976
	/**
977
	 * Add export to array
978
	 * @param Export\Export $export
979
	 */
980
	protected function addToExports(Export\Export $export)
981
	{
982
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
983
984
		$export->setLink($this->link('export!', ['id' => $id]));
985
986
		return $this->exports[$id] = $export;
987
	}
988
989
990
	/********************************************************************************
991
	 *                                 Group actions                                *
992
	 ********************************************************************************/
993
994
995
	/**
996
	 * Add group actino
997
	 * @param string $title
998
	 * @param array  $options
999
	 */
1000
	public function addGroupAction($title, $options = [])
1001
	{
1002
		return $this->getGroupActionCollection()->addGroupAction($title, $options);
1003
	}
1004
1005
1006
	/**
1007
	 * Get collection of all group actions
1008
	 * @return GroupAction\GroupActionCollection
1009
	 */
1010
	public function getGroupActionCollection()
1011
	{
1012
		if (!$this->group_action_collection) {
1013
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1014
		}
1015
1016
		return $this->group_action_collection;
1017
	}
1018
1019
1020
	/********************************************************************************
1021
	 *                                    Signals                                   *
1022
	 ********************************************************************************/
1023
1024
1025
	/**
1026
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1027
	 * @param  int  $page
1028
	 * @return void
1029
	 */
1030
	public function handlePage($page)
1031
	{
1032
		/**
1033
		 * Session stuff
1034
		 */
1035
		$this->page = $page;
1036
		$this->saveSessionData('_grid_page', $page);
1037
1038
		$this->reload(['table']);
1039
	}
1040
1041
1042
	/**
1043
	 * Handler for sorting
1044
	 * @return void
1045
	 */
1046
	public function handleSort(array $sort)
1047
	{
1048
		/**
1049
		 * Session stuff
1050
		 */
1051
		$this->sort = $sort;
1052
		$this->saveSessionData('_grid_sort', $this->sort);
1053
1054
		$this->reload(['table']);
1055
	}
1056
1057
1058
	/**
1059
	 * handler for reseting the filter
1060
	 * @return void
1061
	 */
1062
	public function handleResetFilter()
1063
	{
1064
		/**
1065
		 * Session stuff
1066
		 */
1067
		$this->deleteSesssionData('_grid_page');
1068
1069
		foreach ($this->getSessionData() as $key => $value) {
1070
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1071
				$this->deleteSesssionData($key);
1072
				if ($value instanceof \Traversable) {
1073
					foreach ($value as $key2 => $value2) {
1074
						$this->filter[$key][$key2] = NULL;
1075
					}
1076
				} else {
1077
					$this->filter[$key] = NULL;
1078
				}
1079
			}
1080
		}
1081
1082
		$this->reload(['grid']);
1083
	}
1084
1085
1086
	/**
1087
	 * Handler for export
1088
	 * @param  int $id Key for particular export class in array $this->exports
1089
	 * @return void
1090
	 */
1091
	public function handleExport($id)
1092
	{
1093
		if (!isset($this->exports[$id])) {
1094
			throw new Nette\Application\ForbiddenRequestException;
1095
		}
1096
1097
		$export = $this->exports[$id];
1098
1099
		if ($export->isFiltered()) {
1100
			$sort      = $this->sort;
1101
			$filter    = $this->assableFilters();
1102
		} else {
1103
			$sort      = $this->primary_key;
1104
			$filter    = [];
1105
		}
1106
1107
		if (NULL === $this->dataModel) {
1108
			throw new DataGridException('You have to set a data source first.');
1109
		}
1110
1111
		$rows = [];
1112
1113
		$items = Nette\Utils\Callback::invokeArgs(
1114
			[$this->dataModel, 'filterData'], [NULL, $sort, $filter]
1115
		);
1116
1117
		foreach ($items as $item) {
1118
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1119
		}
1120
1121
		if ($export instanceof Export\ExportCsv) {
1122
			$export->invoke($rows, $this);
1123
		} else {
1124
			$export->invoke($items, $this);
1125
		}
1126
1127
		if ($export->isAjax()) {
1128
			$this->reload();
1129
		}
1130
	}
1131
1132
1133
	/**
1134
	 * Handler for getting children of parent item (e.g. category)
1135
	 * @param  int $parent
1136
	 * @return void
1137
	 */
1138
	public function handleGetChildren($parent)
1139
	{
1140
		$this->setDataSource(
1141
			call_user_func($this->tree_view_children_callback, $parent)
1142
		);
1143
1144
		if ($this->getPresenter()->isAjax()) {
1145
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
1146
			$this->getPresenter()->payload->_datagrid_tree = $parent;
1147
1148
			$this->redrawControl('items');
1149
		} else {
1150
			$this->getPresenter()->redirect('this');
1151
		}
1152
	}
1153
1154
1155
	/**
1156
	 * Handler for getting item detail
1157
	 * @param  mixed $id
1158
	 * @return void
1159
	 */
1160
	public function handleGetItemDetail($id)
1161
	{
1162
		$this->template->toggle_detail = $id;
1163
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1164
1165
		if ($this->getPresenter()->isAjax()) {
1166
			$this->getPresenter()->payload->_datagrid_toggle_detail = $id;
1167
			$this->redrawControl('items');
1168
		} else {
1169
			$this->getPresenter()->redirect('this');
1170
		}
1171
	}
1172
1173
1174
	/**
1175
	 * Handler for inline editing
1176
	 * @param  mixed $id
1177
	 * @param  mixed $key
1178
	 * @return void
1179
	 */
1180
	public function handleEdit($id, $key)
1181
	{
1182
		$column = $this->getColumn($key);
1183
		$value = $this->getPresenter()->getRequest()->getPost('value');
1184
1185
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1186
	}
1187
1188
1189
	/**
1190
	 * Redraw $this
1191
	 * @return void
1192
	 */
1193
	public function reload($snippets = [])
1194
	{
1195
		if ($this->getPresenter()->isAjax()) {
1196
			$this->redrawControl('tbody');
1197
			$this->redrawControl('pagination');
1198
1199
			/**
1200
			 * manualy reset exports links...
1201
			 */
1202
			$this->resetExportsLinks();
1203
			$this->redrawControl('exports');
1204
1205
			foreach ($snippets as $snippet) {
1206
				$this->redrawControl($snippet);
1207
			}
1208
1209
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
1210
		} else {
1211
			$this->getPresenter()->redirect('this');
1212
		}
1213
	}
1214
1215
1216
	/**
1217
	 * Redraw just one row via ajax
1218
	 * @param  int   $id
1219
	 * @param  mixed $primary_where_column
1220
	 * @return void
1221
	 */
1222
	public function redrawItem($id, $primary_where_column = NULL)
1223
	{
1224
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1225
1226
		$this->redrawControl('items');
1227
		$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
1228
	}
1229
1230
1231
	/********************************************************************************
1232
	 *                                  Components                                  *
1233
	 ********************************************************************************/
1234
1235
1236
	/**
1237
	 * Paginator factory
1238
	 * @return Components\DataGridPaginator\DataGridPaginator
1239
	 */
1240
	public function createComponentPaginator()
1241
	{
1242
		/**
1243
		 * Init paginator
1244
		 */
1245
		$component = new Components\DataGridPaginator\DataGridPaginator;
1246
		$paginator = $component->getPaginator();
1247
1248
		$paginator->setPage($this->page);
1249
		$paginator->setItemsPerPage($this->getPerPage());
1250
1251
		return $component;
1252
	}
1253
1254
1255
	/**
1256
	 * PerPage form factory
1257
	 * @return Form
1258
	 */
1259
	public function createComponentPerPage()
1260
	{
1261
		$form = new Form;
1262
1263
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1264
			->setValue($this->getPerPage());
1265
1266
		$form->addSubmit('submit', '');
1267
1268
		$saveSessionData = [$this, 'saveSessionData'];
1269
1270
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1271
			/**
1272
			 * Session stuff
1273
			 */
1274
			$saveSessionData('_grid_per_page', $values->per_page);
1275
1276
			/**
1277
			 * Other stuff
1278
			 */
1279
			$this->per_page = $values->per_page;
1280
			$this->reload();
1281
		};
1282
1283
		return $form;
1284
	}
1285
1286
1287
	/**
1288
	 * FilterAndGroupAction form factory
1289
	 * @return Form
1290
	 */
1291
	public function createComponentFilter()
1292
	{
1293
		$form = new Form($this, 'filter');
1294
1295
		$form->setMethod('get');
1296
1297
		/**
1298
		 * Filter part
1299
		 */
1300
		$filter_container = $form->addContainer('filter');
1301
1302
		foreach ($this->filters as $filter) {
1303
			$filter->addToFormContainer($filter_container, $filter_container);
1304
		}
1305
1306
		/**
1307
		 * Group action part
1308
		 */
1309
		$group_action_container = $form->addContainer('group_action');
1310
1311
		if ($this->hasGroupActions()) {
1312
			$this->getGroupActionCollection()->addToFormContainer($group_action_container, $form, $this->getTranslator());
1313
		}
1314
1315
		$form->setDefaults(['filter' => $this->filter]);
1316
1317
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1318
1319
		return $form;
1320
	}
1321
1322
1323
	/**
1324
	 * Set $this->filter values after filter form submitted
1325
	 * @param  Form $form
1326
	 * @return void
1327
	 */
1328
	public function filterSucceeded(Form $form)
1329
	{
1330
		$values = $form->getValues();
1331
1332
		if ($this->getPresenter()->isAjax()) {
1333
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1334
				return;
1335
			}
1336
		}
1337
1338
		$values = $values['filter'];
1339
1340
		foreach ($values as $key => $value) {
1341
			/**
1342
			 * Session stuff
1343
			 */
1344
			$this->saveSessionData($key, $value);
1345
1346
			/**
1347
			 * Other stuff
1348
			 */
1349
			$this->filter[$key] = $value;
1350
		}
1351
1352
		$this->reload();
1353
	}
1354
1355
1356
	/********************************************************************************
1357
	 *                               Support functions                              *
1358
	 ********************************************************************************/
1359
1360
1361
	public function resetExportsLinks()
1362
	{
1363
		foreach ($this->exports as $id => $export) {
1364
			$export->setLink($this->link('export!', ['id' => $id]));
1365
		}
1366
	}
1367
1368
1369
	/**
1370
	 * Get parameter per_page
1371
	 * @return int
1372
	 */
1373
	public function getPerPage()
1374
	{
1375
		$per_page = $this->per_page ?: reset($this->items_per_page_list);
1376
1377
		if ($per_page !== 'all' && !in_array($this->per_page, $this->items_per_page_list)) {
1378
			$per_page = reset($this->items_per_page_list);
1379
		}
1380
1381
		return $per_page;
1382
	}
1383
1384
1385
	/**
1386
	 * Get associative array of items_per_page_list
1387
	 * @return array
1388
	 */
1389
	public function getItemsPerPageList()
1390
	{
1391
		$list = array_flip($this->items_per_page_list);
1392
1393
		foreach ($list as $key => $value) {
1394
			$list[$key] = $key;
1395
		}
1396
1397
		$list['all'] = $this->getTranslator()->translate('Vše');
1398
1399
		return $list;
1400
	}
1401
1402
1403
	/**
1404
	 * Get primary key of datagrid data source
1405
	 * @return string
1406
	 */
1407
	public function getPrimaryKey()
1408
	{
1409
		return $this->primary_key;
1410
	}
1411
1412
1413
	/**
1414
	 * Get set of set columns
1415
	 * @return Column\IColumn[]
1416
	 */
1417
	public function getColumns()
1418
	{
1419
		return $this->columns;
1420
	}
1421
1422
1423
	/**
1424
	 * Has datagrid some group actions?
1425
	 * @return boolean
1426
	 */
1427
	public function hasGroupActions()
1428
	{
1429
		return (bool) $this->group_action_collection;
1430
	}
1431
1432
1433
	/**
1434
	 * Get translator for datagrid
1435
	 * @return Nette\Localization\ITranslator
1436
	 */
1437
	public function getTranslator()
1438
	{
1439
		if (!$this->translator) {
1440
			$this->translator = new Localization\SimpleTranslator;
1441
		}
1442
1443
		return $this->translator;
1444
	}
1445
1446
1447
	/**
1448
	 * Set datagrid translator
1449
	 * @param Nette\Localization\ITranslator $translator
1450
	 */
1451
	public function setTranslator(Nette\Localization\ITranslator $translator)
1452
	{
1453
		$this->translator = $translator;
1454
1455
		return $this;
1456
	}
1457
1458
1459
	/**
1460
	 * Should be datagrid filters rendered separately?
1461
	 * @param boolean $out
1462
	 */
1463
	public function setOuterFilterRendering($out = TRUE)
1464
	{
1465
		$this->outer_filter_rendering = (bool) $out;
1466
	}
1467
1468
1469
	/**
1470
	 * Are datagrid filters rendered separately?
1471
	 * @return boolean
1472
	 */
1473
	public function hasOuterFilterRendering()
1474
	{
1475
		return $this->outer_filter_rendering;
1476
	}
1477
1478
1479
	/**
1480
	 * Set order of datagrid columns
1481
	 * @param array $order
1482
	 */
1483
	public function setColumnsOrder($order)
1484
	{
1485
		$new_order = [];
1486
1487
		foreach ($order as $key) {
1488
			if (isset($this->columns[$key])) {
1489
				$new_order[$key] = $this->columns[$key];
1490
			}
1491
		}
1492
1493
		if (sizeof($new_order) === sizeof($this->columns)) {
1494
			$this->columns = $new_order;
1495
		} else {
1496
			throw new DataGridException('When changing columns order, you have to specify all columns');
1497
		}
1498
	}
1499
1500
1501
	/**
1502
	 * Should datagrid remember its filters/pagination/etc using session?
1503
	 * @param bool $remember
1504
	 */
1505
	public function setRememberState($remember = TRUE)
1506
	{
1507
		$this->remember_state = (bool) $remember;
1508
1509
		return $this
1510
	}
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected '}', expecting ';'
Loading history...
1511
1512
1513
	/**
1514
	 * Should datagrid refresh url using history API?
1515
	 * @param bool $refresh
1516
	 */
1517
	public function setRefreshUrl($refresh = TRUE)
1518
	{
1519
		$this->refresh_url = (bool) $refresh;
1520
	}
1521
1522
1523
	/**
1524
	 * Get session data if functionality is enabled
1525
	 * @param  string $key
1526
	 * @return mixed
1527
	 */
1528
	public function getSessionData($key = NULL)
1529
	{
1530
		if (!$this->remember_state) {
1531
			return NULL;
1532
		}
1533
1534
		return $key ? $this->grid_session->{$key} : $this->grid_session;
1535
	}
1536
1537
1538
	/**
1539
	 * Save session data - just if it is enabled
1540
	 * @param  string $key
1541
	 * @param  mixed  $value
1542
	 * @return void
1543
	 */
1544
	public function saveSessionData($key, $value)
1545
	{
1546
1547
		if ($this->remember_state) {
1548
			$this->grid_session->{$key} = $value;
1549
		}
1550
	}
1551
1552
1553
	/**
1554
	 * Delete session data
1555
	 * @return void
1556
	 */
1557
	public function deleteSesssionData($key)
1558
	{
1559
		unset($this->grid_session->{$key});
1560
	}
1561
1562
1563
	/**
1564
	 * Get items detail parameters
1565
	 * @return array
1566
	 */
1567
	public function getItemsDetail()
1568
	{
1569
		return $this->items_detail;
1570
	}
1571
1572
1573
	/**
1574
	 * Items can have thair detail - toggled
1575
	 * @param mixed $detail callable|string|bool
1576
	 */
1577
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
1578
	{
1579
		if ($this->isSortable()) {
1580
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
1581
		}
1582
1583
		$this->items_detail = new Column\ItemDetail(
1584
			$this,
1585
			$primary_where_column ?: $this->primary_key
1586
		);
1587
1588
		if (is_string($detail)) {
1589
			/**
1590
			 * Item detail will be in separate template
1591
			 */
1592
			$this->items_detail->setType('template');
1593
			$this->items_detail->setTemplate($detail);
1594
1595
		} else if (is_callable($detail)) {
1596
			/**
1597
			 * Item detail will be rendered via custom callback renderer
1598
			 */
1599
			$this->items_detail->setType('renderer');
1600
			$this->items_detail->setRenderer($detail);
1601
1602
		} else if (TRUE === $detail) {
1603
			/**
1604
			 * Item detail will be rendered probably via block #detail
1605
			 */
1606
			$this->items_detail->setType('block');
1607
1608
		} else {
1609
			throw new DataGridException(
1610
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
1611
			);
1612
		}
1613
1614
		return $this->items_detail;
1615
	}
1616
1617
1618
	/**
1619
	 * Get cont of columns
1620
	 * @return int
1621
	 */
1622
	public function getColumnsCount()
1623
	{
1624
		$count = sizeof($this->columns);
1625
1626
		if ($this->actions || $this->isSortable() || $this->getItemsDetail()) {
1627
			$count++;
1628
		}
1629
1630
		if ($this->hasGroupActions()) {
1631
			$count++;
1632
		}
1633
1634
		return $count;
1635
	}
1636
1637
1638
	public function allowRowsGroupAction(callable $condition)
1639
	{
1640
		$this->row_conditions['group_action'] = $condition;
1641
	}
1642
1643
1644
	public function allowRowsAction($key, callable $condition)
1645
	{
1646
		$this->row_conditions['action'][$key] = $condition;
1647
	}
1648
1649
1650
	public function getRowCondition($name, $key = NULL)
1651
	{
1652
		if (!isset($this->row_conditions[$name])) {
1653
			return FALSE;
1654
		}
1655
1656
		$condition = $this->row_conditions[$name];
1657
1658
		if (!$key) {
1659
			return $condition;
1660
		}
1661
1662
		return isset($condition[$key]) ? $condition[$key] : FALSE;
1663
	}
1664
1665
}
1666
1667
1668
class DataGridException extends \Exception
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
1669
{
1670
}
1671