1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of the Grido (https://github.com/o5/grido) |
5
|
|
|
* |
6
|
|
|
* Copyright (c) 2011 Petr Bugyík (http://petr.bugyik.cz) |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view |
9
|
|
|
* the file LICENSE.md that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Grido; |
13
|
|
|
|
14
|
|
|
use Grido\Exception; |
15
|
|
|
use Grido\Components\Button; |
16
|
|
|
use Grido\Components\Paginator; |
17
|
|
|
use Grido\Components\Columns\Column; |
18
|
|
|
use Grido\Components\Filters\Filter; |
19
|
|
|
use Grido\Components\Actions\Action; |
20
|
|
|
|
21
|
|
|
use Nette\Application\UI\Presenter; |
22
|
|
|
use Symfony\Component\PropertyAccess\PropertyAccessor; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Grido - DataGrid for Nette Framework. |
26
|
|
|
* |
27
|
|
|
* @package Grido |
28
|
|
|
* @author Petr Bugyík |
29
|
1 |
|
* |
30
|
|
|
* @property-read int $count |
31
|
|
|
* @property-read mixed $data |
32
|
|
|
* @property-read \Nette\Utils\Html $tablePrototype |
33
|
|
|
* @property-read PropertyAccessor $propertyAccessor |
34
|
|
|
* @property-read Customization $customization |
35
|
|
|
* @property-write string $templateFile |
36
|
|
|
* @property bool $rememberState |
37
|
|
|
* @property array $defaultPerPage |
38
|
|
|
* @property array $defaultFilter |
39
|
|
|
* @property array $defaultSort |
40
|
1 |
|
* @property array $perPageList |
41
|
|
|
* @property \Nette\Localization\ITranslator $translator |
42
|
1 |
|
* @property Paginator $paginator |
43
|
|
|
* @property string $primaryKey |
44
|
1 |
|
* @property string $filterRenderType |
45
|
|
|
* @property DataSources\IDataSource $model |
46
|
1 |
|
* @property callback $rowCallback |
47
|
|
|
* @property bool $strictMode |
48
|
|
|
* @method void onRegistered(Grid $grid) |
49
|
|
|
* @method void onRender(Grid $grid) |
50
|
1 |
|
* @method void onFetchData(Grid $grid) |
51
|
|
|
*/ |
52
|
1 |
|
class Grid extends Components\Container |
53
|
|
|
{ |
54
|
|
|
/***** DEFAULTS ****/ |
55
|
|
|
const BUTTONS = 'buttons'; |
56
|
|
|
|
57
|
|
|
const CLIENT_SIDE_OPTIONS = 'grido-options'; |
58
|
|
|
|
59
|
|
|
/** @var int @persistent */ |
60
|
|
|
public $page = 1; |
61
|
|
|
|
62
|
|
|
/** @var int @persistent */ |
63
|
|
|
public $perPage; |
64
|
|
|
|
65
|
|
|
/** @var array @persistent */ |
66
|
|
|
public $sort = []; |
67
|
1 |
|
|
68
|
|
|
/** @var array @persistent */ |
69
|
|
|
public $filter = []; |
70
|
|
|
|
71
|
|
|
/** @var array event on all grid's components registered */ |
72
|
|
|
public $onRegistered; |
73
|
|
|
|
74
|
|
|
/** @var array event on render */ |
75
|
|
|
public $onRender; |
76
|
|
|
|
77
|
|
|
/** @var array event for modifying data */ |
78
|
|
|
public $onFetchData; |
79
|
|
|
|
80
|
|
|
/** @var callback returns tr html element; function($row, Html $tr) */ |
81
|
|
|
protected $rowCallback; |
82
|
|
|
|
83
|
|
|
/** @var \Nette\Utils\Html */ |
84
|
|
|
protected $tablePrototype; |
85
|
|
|
|
86
|
|
|
/** @var bool */ |
87
|
|
|
protected $rememberState = FALSE; |
88
|
|
|
|
89
|
|
|
/** @var string */ |
90
|
|
|
protected $rememberStateSectionName; |
91
|
|
|
|
92
|
|
|
/** @var string */ |
93
|
|
|
protected $primaryKey = 'id'; |
94
|
|
|
|
95
|
|
|
/** @var string */ |
96
|
|
|
protected $filterRenderType; |
97
|
|
|
|
98
|
|
|
/** @var array */ |
99
|
|
|
protected $perPageList = [10, 20, 30, 50, 100]; |
100
|
|
|
|
101
|
|
|
/** @var int */ |
102
|
|
|
protected $defaultPerPage = 20; |
103
|
|
|
|
104
|
|
|
/** @var array */ |
105
|
|
|
protected $defaultFilter = []; |
106
|
|
|
|
107
|
|
|
/** @var array */ |
108
|
|
|
protected $defaultSort = []; |
109
|
|
|
|
110
|
|
|
/** @var DataSources\IDataSource */ |
111
|
|
|
protected $model; |
112
|
|
|
|
113
|
|
|
/** @var int total count of items */ |
114
|
|
|
protected $count; |
115
|
|
|
|
116
|
|
|
/** @var mixed */ |
117
|
|
|
protected $data; |
118
|
|
|
|
119
|
|
|
/** @var Paginator */ |
120
|
|
|
protected $paginator; |
121
|
|
|
|
122
|
|
|
/** @var \Nette\Localization\ITranslator */ |
123
|
|
|
protected $translator; |
124
|
|
|
|
125
|
|
|
/** @var PropertyAccessor */ |
126
|
|
|
protected $propertyAccessor; |
127
|
|
|
|
128
|
|
|
/** @var bool */ |
129
|
|
|
protected $strictMode = TRUE; |
130
|
|
|
|
131
|
|
|
/** @var array */ |
132
|
|
|
protected $options = [ |
133
|
|
|
self::CLIENT_SIDE_OPTIONS => [] |
134
|
|
|
]; |
135
|
|
|
|
136
|
|
|
/** @var Customization */ |
137
|
|
|
protected $customization; |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Grid constructor. |
141
|
|
|
*/ |
142
|
|
|
public function __construct() |
143
|
|
|
{ |
144
|
|
|
list($parent, $name) = func_get_args() + [NULL, NULL]; |
145
|
|
|
if ($parent !== NULL) { |
146
|
|
|
$parent->addComponent($this, $name); |
147
|
1 |
|
} elseif (is_string($name)) { |
148
|
1 |
|
$this->name = $name; |
149
|
1 |
|
} |
150
|
1 |
|
} |
151
|
1 |
|
|
152
|
|
|
/** |
153
|
|
|
* Sets a model that implements the interface Grido\DataSources\IDataSource or data-source object. |
154
|
|
|
* @param mixed $model |
155
|
|
|
* @param bool $forceWrapper |
156
|
|
|
* @throws Exception |
157
|
|
|
* @return Grid |
158
|
|
|
*/ |
159
|
|
|
public function setModel($model, $forceWrapper = FALSE) |
160
|
1 |
|
{ |
161
|
1 |
|
$this->model = $model instanceof DataSources\IDataSource && $forceWrapper === FALSE |
162
|
1 |
|
? $model |
163
|
|
|
: new DataSources\Model($model); |
164
|
1 |
|
|
165
|
1 |
|
return $this; |
166
|
1 |
|
} |
167
|
1 |
|
|
168
|
1 |
|
/** |
169
|
1 |
|
* Sets the default number of items per page. |
170
|
|
|
* @param int $perPage |
171
|
|
|
* @return Grid |
172
|
|
|
*/ |
173
|
|
|
public function setDefaultPerPage($perPage) |
174
|
|
|
{ |
175
|
|
|
$perPage = (int) $perPage; |
176
|
|
|
$this->defaultPerPage = $perPage; |
177
|
|
|
|
178
|
|
|
if (!in_array($perPage, $this->perPageList)) { |
179
|
1 |
|
$this->perPageList[] = $perPage; |
180
|
1 |
|
sort($this->perPageList); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
return $this; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
1 |
|
* Sets default filtering. |
188
|
|
|
* @param array $filter |
189
|
|
|
* @return Grid |
190
|
|
|
*/ |
191
|
1 |
|
public function setDefaultFilter(array $filter) |
192
|
|
|
{ |
193
|
1 |
|
$this->defaultFilter = array_merge($this->defaultFilter, $filter); |
194
|
1 |
|
return $this; |
195
|
1 |
|
} |
196
|
1 |
|
|
197
|
|
|
/** |
198
|
|
|
* Sets default sorting. |
199
|
1 |
|
* @param array $sort |
200
|
1 |
|
* @return Grid |
201
|
|
|
* @throws Exception |
202
|
1 |
|
*/ |
203
|
1 |
|
public function setDefaultSort(array $sort) |
204
|
|
|
{ |
205
|
|
|
static $replace = ['asc' => Column::ORDER_ASC, 'desc' => Column::ORDER_DESC]; |
206
|
|
|
|
207
|
|
|
foreach ($sort as $column => $dir) { |
208
|
1 |
|
$dir = strtr(strtolower($dir), $replace); |
209
|
|
|
if (!in_array($dir, $replace)) { |
210
|
|
|
throw new Exception("Dir '$dir' for column '$column' is not allowed."); |
211
|
|
|
} |
212
|
1 |
|
|
213
|
|
|
$this->defaultSort[$column] = $dir; |
214
|
1 |
|
} |
215
|
1 |
|
|
216
|
1 |
|
return $this; |
217
|
|
|
} |
218
|
1 |
|
|
219
|
|
|
/** |
220
|
1 |
|
* Sets items to per-page select. |
221
|
|
|
* @param array $perPageList |
222
|
|
|
* @return Grid |
223
|
|
|
*/ |
224
|
|
|
public function setPerPageList(array $perPageList) |
225
|
|
|
{ |
226
|
|
|
$this->perPageList = $perPageList; |
227
|
|
|
|
228
|
1 |
|
if ($this->hasFilters(FALSE) || $this->hasOperation(FALSE)) { |
229
|
1 |
|
$this['form']['count']->setItems($this->getItemsForCountSelect()); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
return $this; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
1 |
|
* Sets translator. |
237
|
|
|
* @param \Nette\Localization\ITranslator $translator |
238
|
|
|
* @return Grid |
239
|
|
|
*/ |
240
|
|
|
public function setTranslator(\Nette\Localization\ITranslator $translator) |
241
|
1 |
|
{ |
242
|
1 |
|
$this->translator = $translator; |
243
|
1 |
|
return $this; |
244
|
|
|
} |
245
|
|
|
|
246
|
1 |
|
/** |
247
|
1 |
|
* Sets type of filter rendering. |
248
|
|
|
* Defaults inner (Filter::RENDER_INNER) if column does not exist then outer filter (Filter::RENDER_OUTER). |
249
|
|
|
* @param string $type |
250
|
|
|
* @throws Exception |
251
|
|
|
* @return Grid |
252
|
|
|
*/ |
253
|
|
|
public function setFilterRenderType($type) |
254
|
|
|
{ |
255
|
|
|
$type = strtolower($type); |
256
|
|
|
if (!in_array($type, [Filter::RENDER_INNER, Filter::RENDER_OUTER])) { |
257
|
1 |
|
throw new Exception('Type must be Filter::RENDER_INNER or Filter::RENDER_OUTER.'); |
258
|
1 |
|
} |
259
|
|
|
|
260
|
|
|
$this->filterRenderType = $type; |
261
|
|
|
return $this; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Sets custom paginator. |
266
|
|
|
* @param Paginator $paginator |
267
|
|
|
* @return Grid |
268
|
|
|
*/ |
269
|
1 |
|
public function setPaginator(Paginator $paginator) |
270
|
1 |
|
{ |
271
|
|
|
$this->paginator = $paginator; |
272
|
|
|
return $this; |
273
|
|
|
} |
274
|
|
|
|
275
|
1 |
|
/** |
276
|
|
|
* Sets grid primary key. |
277
|
|
|
* Defaults is "id". |
278
|
|
|
* @param string $key |
279
|
|
|
* @return Grid |
280
|
1 |
|
*/ |
281
|
1 |
|
public function setPrimaryKey($key) |
282
|
1 |
|
{ |
283
|
1 |
|
$this->primaryKey = $key; |
284
|
|
|
return $this; |
285
|
1 |
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Sets file name of custom template. |
289
|
|
|
* @param string $file |
290
|
|
|
* @return Grid |
291
|
|
|
*/ |
292
|
|
|
public function setTemplateFile($file) |
293
|
|
|
{ |
294
|
|
|
$this->onRender[] = function() use ($file) { |
295
|
|
|
$this->getTemplate()->add('gridoTemplate', $this->getTemplate()->getFile()); |
296
|
1 |
|
$this->getTemplate()->setFile($file); |
297
|
1 |
|
}; |
298
|
1 |
|
|
299
|
1 |
|
return $this; |
300
|
|
|
} |
301
|
1 |
|
|
302
|
|
|
/** |
303
|
|
|
* Sets saving state to session. |
304
|
|
|
* @param bool $state |
305
|
|
|
* @param string $sectionName |
306
|
|
|
* @return Grid |
307
|
|
|
*/ |
308
|
|
|
public function setRememberState($state = TRUE, $sectionName = NULL) |
309
|
|
|
{ |
310
|
|
|
$this->getPresenter(); //component must be attached to presenter |
311
|
|
|
$this->getRememberSession(TRUE); //start session if not |
312
|
1 |
|
$this->rememberState = (bool) $state; |
313
|
1 |
|
$this->rememberStateSectionName = $sectionName; |
314
|
|
|
|
315
|
|
|
return $this; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Sets callback for customizing tr html object. |
320
|
|
|
* Callback returns tr html element; function($row, Html $tr). |
321
|
|
|
* @param $callback |
322
|
|
|
* @return Grid |
323
|
1 |
|
*/ |
324
|
1 |
|
public function setRowCallback($callback) |
325
|
|
|
{ |
326
|
|
|
$this->rowCallback = $callback; |
327
|
|
|
return $this; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Sets client-side options. |
332
|
|
|
* @param array $options |
333
|
|
|
* @return Grid |
334
|
1 |
|
*/ |
335
|
1 |
|
public function setClientSideOptions(array $options) |
336
|
|
|
{ |
337
|
|
|
$this->options[self::CLIENT_SIDE_OPTIONS] = $options; |
338
|
|
|
return $this; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Determines whether any user error will cause a notice. |
343
|
1 |
|
* @param bool $mode |
344
|
1 |
|
* @return \Grido\Grid |
345
|
|
|
*/ |
346
|
|
|
public function setStrictMode($mode) |
347
|
|
|
{ |
348
|
|
|
$this->strictMode = (bool) $mode; |
349
|
|
|
return $this; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* @param \Grido\Customization $customization |
354
|
1 |
|
*/ |
355
|
1 |
|
public function setCustomization(Customization $customization) |
356
|
1 |
|
{ |
357
|
|
|
$this->customization = $customization; |
358
|
1 |
|
} |
359
|
|
|
|
360
|
|
|
/**********************************************************************************************/ |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Returns total count of data. |
364
|
|
|
* @return int |
365
|
|
|
*/ |
366
|
|
|
public function getCount() |
367
|
1 |
|
{ |
368
|
1 |
|
if ($this->count === NULL) { |
369
|
1 |
|
$this->count = $this->getModel()->getCount(); |
370
|
|
|
} |
371
|
1 |
|
|
372
|
|
|
return $this->count; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Returns default per page. |
377
|
|
|
* @return int |
378
|
|
|
*/ |
379
|
|
|
public function getDefaultPerPage() |
380
|
1 |
|
{ |
381
|
|
|
if (!in_array($this->defaultPerPage, $this->perPageList)) { |
382
|
|
|
$this->defaultPerPage = $this->perPageList[0]; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
return $this->defaultPerPage; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
1 |
|
* Returns default filter. |
390
|
|
|
* @return array |
391
|
1 |
|
*/ |
392
|
|
|
public function getDefaultFilter() |
393
|
|
|
{ |
394
|
|
|
return $this->defaultFilter; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
1 |
|
* Returns default sort. |
399
|
|
|
* @return array |
400
|
|
|
*/ |
401
|
|
|
public function getDefaultSort() |
402
|
|
|
{ |
403
|
|
|
return $this->defaultSort; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
/** |
407
|
1 |
|
* Returns list of possible items per page. |
408
|
|
|
* @return array |
409
|
|
|
*/ |
410
|
|
|
public function getPerPageList() |
411
|
|
|
{ |
412
|
|
|
return $this->perPageList; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
1 |
|
* Returns primary key. |
417
|
|
|
* @return string |
418
|
|
|
*/ |
419
|
1 |
|
public function getPrimaryKey() |
420
|
|
|
{ |
421
|
|
|
return $this->primaryKey; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
1 |
|
* Returns remember state. |
426
|
|
|
* @return bool |
427
|
|
|
*/ |
428
|
|
|
public function getRememberState() |
429
|
|
|
{ |
430
|
|
|
return $this->rememberState; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
1 |
|
* Returns row callback. |
435
|
1 |
|
* @return callback |
436
|
1 |
|
*/ |
437
|
|
|
public function getRowCallback() |
438
|
|
|
{ |
439
|
|
|
return $this->rowCallback; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Returns items per page. |
444
|
|
|
* @return int |
445
|
|
|
*/ |
446
|
1 |
|
public function getPerPage() |
447
|
1 |
|
{ |
448
|
|
|
return $this->perPage === NULL |
449
|
|
|
? $this->getDefaultPerPage() |
450
|
|
|
: $this->perPage; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* Returns actual filter values. |
455
|
|
|
* @param string $key |
456
|
|
|
* @return mixed |
457
|
|
|
*/ |
458
|
|
|
public function getActualFilter($key = NULL) |
459
|
|
|
{ |
460
|
1 |
|
$filter = $this->filter ? $this->filter : $this->defaultFilter; |
461
|
1 |
|
return $key !== NULL && isset($filter[$key]) ? $filter[$key] : $filter; |
462
|
|
|
} |
463
|
|
|
|
464
|
1 |
|
/** |
465
|
1 |
|
* Returns fetched data. |
466
|
1 |
|
* @param bool $applyPaging |
467
|
1 |
|
* @param bool $useCache |
468
|
|
|
* @param bool $fetch |
469
|
1 |
|
* @throws Exception |
470
|
1 |
|
* @return array|DataSources\IDataSource|\Nette\Database\Table\Selection |
471
|
1 |
|
*/ |
472
|
|
|
public function getData($applyPaging = TRUE, $useCache = TRUE, $fetch = TRUE) |
473
|
1 |
|
{ |
474
|
1 |
|
if ($this->getModel() === NULL) { |
475
|
|
|
throw new Exception('Model cannot be empty, please use method $grid->setModel().'); |
476
|
|
|
} |
477
|
1 |
|
|
478
|
|
|
$data = $this->data; |
479
|
1 |
|
if ($data === NULL || $useCache === FALSE) { |
480
|
1 |
|
$this->applyFiltering(); |
481
|
1 |
|
$this->applySorting(); |
482
|
|
|
|
483
|
1 |
|
if ($applyPaging) { |
484
|
1 |
|
$this->applyPaging(); |
485
|
1 |
|
} |
486
|
1 |
|
|
487
|
|
|
if ($fetch === FALSE) { |
488
|
1 |
|
return $this->getModel(); |
489
|
|
|
} |
490
|
|
|
|
491
|
1 |
|
$data = $this->getModel()->getData(); |
492
|
|
|
|
493
|
1 |
|
if ($useCache === TRUE) { |
494
|
|
|
$this->data = $data; |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
if ($applyPaging && !empty($data) && !in_array($this->page, range(1, $this->getPaginator()->pageCount))) { |
498
|
|
|
$this->__triggerUserNotice("Page is out of range."); |
499
|
|
|
$this->page = 1; |
500
|
|
|
} |
501
|
|
|
|
502
|
1 |
|
if (!empty($this->onFetchData)) { |
503
|
1 |
|
$this->onFetchData($this); |
504
|
1 |
|
} |
505
|
|
|
} |
506
|
1 |
|
|
507
|
|
|
return $data; |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
/** |
511
|
|
|
* Returns translator. |
512
|
|
|
* @return Translations\FileTranslator |
513
|
|
|
*/ |
514
|
|
|
public function getTranslator() |
515
|
|
|
{ |
516
|
1 |
|
if ($this->translator === NULL) { |
517
|
1 |
|
$this->setTranslator(new Translations\FileTranslator); |
518
|
|
|
} |
519
|
1 |
|
|
520
|
1 |
|
return $this->translator; |
521
|
1 |
|
} |
522
|
|
|
|
523
|
1 |
|
/** |
524
|
1 |
|
* Returns remember session for set expiration, etc. |
525
|
1 |
|
* @param bool $forceStart - if TRUE, session will be started if not |
526
|
|
|
* @return \Nette\Http\SessionSection|NULL |
527
|
|
|
*/ |
528
|
|
|
public function getRememberSession($forceStart = FALSE) |
529
|
|
|
{ |
530
|
|
|
$presenter = $this->getPresenter(); |
531
|
|
|
$session = $presenter->getSession(); |
532
|
|
|
|
533
|
|
|
if (!$session->isStarted() && $forceStart) { |
534
|
1 |
|
$session->start(); |
535
|
1 |
|
} |
536
|
1 |
|
|
537
|
1 |
|
return $session->isStarted() |
538
|
|
|
? ($session->getSection($this->rememberStateSectionName ?: ($presenter->name . ':' . $this->getUniqueId()))) |
539
|
1 |
|
: NULL; |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* Returns table html element of grid. |
544
|
|
|
* @return \Nette\Utils\Html |
545
|
|
|
*/ |
546
|
|
|
public function getTablePrototype() |
547
|
|
|
{ |
548
|
1 |
|
if ($this->tablePrototype === NULL) { |
549
|
1 |
|
$this->tablePrototype = \Nette\Utils\Html::el('table'); |
550
|
|
|
$this->tablePrototype->id($this->getName()); |
551
|
|
|
} |
552
|
1 |
|
|
553
|
1 |
|
return $this->tablePrototype; |
554
|
1 |
|
} |
555
|
|
|
|
556
|
1 |
|
/** |
557
|
1 |
|
* @return string |
558
|
1 |
|
* @internal |
559
|
1 |
|
*/ |
560
|
1 |
|
public function getFilterRenderType() |
561
|
|
|
{ |
562
|
1 |
|
if ($this->filterRenderType !== NULL) { |
563
|
1 |
|
return $this->filterRenderType; |
564
|
|
|
} |
565
|
1 |
|
|
566
|
|
|
$this->filterRenderType = Filter::RENDER_OUTER; |
567
|
|
|
if ($this->hasColumns() && $this->hasFilters() && $this->hasActions()) { |
568
|
|
|
$this->filterRenderType = Filter::RENDER_INNER; |
569
|
|
|
|
570
|
|
|
$filters = $this[Filter::ID]->getComponents(); |
571
|
|
|
foreach ($filters as $filter) { |
572
|
|
|
if (!$this[Column::ID]->getComponent($filter->name, FALSE)) { |
573
|
1 |
|
$this->filterRenderType = Filter::RENDER_OUTER; |
574
|
|
|
break; |
575
|
|
|
} |
576
|
|
|
} |
577
|
|
|
} |
578
|
|
|
|
579
|
|
|
return $this->filterRenderType; |
580
|
|
|
} |
581
|
|
|
|
582
|
1 |
|
/** |
583
|
1 |
|
* @return DataSources\IDataSource |
584
|
1 |
|
*/ |
585
|
1 |
|
public function getModel() |
586
|
1 |
|
{ |
587
|
|
|
return $this->model; |
588
|
1 |
|
} |
589
|
|
|
|
590
|
|
|
/** |
591
|
|
|
* @return Paginator |
592
|
|
|
* @internal |
593
|
|
|
*/ |
594
|
|
|
public function getPaginator() |
595
|
|
|
{ |
596
|
|
|
if ($this->paginator === NULL) { |
597
|
|
|
$this->paginator = new Paginator; |
598
|
|
|
$this->paginator->setItemsPerPage($this->getPerPage()) |
599
|
|
|
->setGrid($this); |
600
|
1 |
|
} |
601
|
1 |
|
|
602
|
1 |
|
return $this->paginator; |
603
|
1 |
|
} |
604
|
1 |
|
|
605
|
1 |
|
/** |
606
|
1 |
|
* A simple wrapper around symfony/property-access with Nette Database dot notation support. |
607
|
|
|
* @param array|object $object |
608
|
1 |
|
* @param string $name |
609
|
|
|
* @return mixed |
610
|
|
|
* @internal |
611
|
1 |
|
*/ |
612
|
1 |
|
public function getProperty($object, $name) |
613
|
1 |
|
{ |
614
|
|
|
if ($object instanceof \Nette\Database\Table\IRow && \Nette\Utils\Strings::contains($name, '.')) { |
|
|
|
|
615
|
1 |
|
$parts = explode('.', $name); |
616
|
|
|
foreach ($parts as $item) { |
617
|
|
|
if (is_object($object)) { |
618
|
|
|
$object = $object->$item; |
619
|
|
|
} |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
return $object; |
623
|
|
|
} |
624
|
1 |
|
|
625
|
1 |
|
if (is_array($object)) { |
626
|
1 |
|
$name = "[$name]"; |
627
|
|
|
} |
628
|
1 |
|
|
629
|
|
|
return $this->getPropertyAccessor()->getValue($object, $name); |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
/** |
633
|
|
|
* @return PropertyAccessor |
634
|
|
|
* @internal |
635
|
|
|
*/ |
636
|
|
|
public function getPropertyAccessor() |
637
|
|
|
{ |
638
|
|
|
if ($this->propertyAccessor === NULL) { |
639
|
1 |
|
$this->propertyAccessor = new PropertyAccessor(TRUE, TRUE); |
640
|
1 |
|
} |
641
|
1 |
|
|
642
|
|
|
return $this->propertyAccessor; |
643
|
|
|
} |
644
|
1 |
|
|
645
|
1 |
|
/** |
646
|
|
|
* @param mixed $row item from db |
647
|
1 |
|
* @return \Nette\Utils\Html |
648
|
1 |
|
* @internal |
649
|
1 |
|
*/ |
650
|
|
|
public function getRowPrototype($row) |
651
|
1 |
|
{ |
652
|
|
|
try { |
653
|
|
|
$primaryValue = $this->getProperty($row, $this->getPrimaryKey()); |
654
|
|
|
} catch (\Exception $e) { |
655
|
|
|
$primaryValue = NULL; |
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
$tr = \Nette\Utils\Html::el('tr'); |
659
|
|
|
$primaryValue ? $tr->class[] = "grid-row-$primaryValue" : NULL; |
660
|
1 |
|
|
661
|
|
|
if ($this->rowCallback) { |
662
|
|
|
$tr = call_user_func_array($this->rowCallback, [$row, $tr]); |
663
|
|
|
} |
664
|
|
|
|
665
|
|
|
return $tr; |
666
|
|
|
} |
667
|
|
|
|
668
|
1 |
|
/** |
669
|
|
|
* Returns client-side options. |
670
|
|
|
* @return array |
671
|
|
|
*/ |
672
|
|
|
public function getClientSideOptions() |
673
|
|
|
{ |
674
|
|
|
return (array) $this->options[self::CLIENT_SIDE_OPTIONS]; |
675
|
|
|
} |
676
|
1 |
|
|
677
|
1 |
|
/** |
678
|
1 |
|
* @return bool |
679
|
|
|
*/ |
680
|
1 |
|
public function isStrictMode() |
681
|
|
|
{ |
682
|
|
|
return $this->strictMode; |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
/** |
686
|
|
|
* @return Customization |
687
|
|
|
*/ |
688
|
|
|
public function getCustomization() |
689
|
|
|
{ |
690
|
|
|
if ($this->customization === NULL) { |
691
|
|
|
$this->customization = new Customization($this); |
692
|
|
|
} |
693
|
1 |
|
|
694
|
1 |
|
return $this->customization; |
695
|
1 |
|
} |
696
|
1 |
|
|
697
|
1 |
|
/**********************************************************************************************/ |
698
|
1 |
|
|
699
|
|
|
/** |
700
|
1 |
|
* Loads state informations. |
701
|
1 |
|
* @param array $params |
702
|
|
|
* @internal |
703
|
|
|
*/ |
704
|
|
|
public function loadState(array $params): void |
705
|
|
|
{ |
706
|
|
|
//loads state from session |
707
|
|
|
$session = $this->getRememberSession(); |
708
|
|
|
if ($session && $this->getPresenter()->isSignalReceiver($this)) { |
709
|
|
|
$session->remove(); |
710
|
|
|
} elseif ($session && empty($params) && $session->params) { |
711
|
1 |
|
$params = (array) $session->params; |
712
|
1 |
|
} |
713
|
|
|
|
714
|
|
|
parent::loadState($params); |
715
|
|
|
} |
716
|
|
|
|
717
|
|
|
/** |
718
|
|
|
* Saves state informations for next request. |
719
|
|
|
* @param array $params |
720
|
|
|
* @param \Nette\Application\UI\PresenterComponentReflection $reflection (internal, used by Presenter) |
721
|
|
|
* @internal |
722
|
|
|
*/ |
723
|
|
|
public function saveState(array &$params, $reflection = NULL): void |
724
|
|
|
{ |
725
|
|
|
!empty($this->onRegistered) && $this->onRegistered($this); |
726
|
|
|
parent::saveState($params, $reflection); |
727
|
|
|
} |
728
|
|
|
|
729
|
|
|
/** |
730
|
1 |
|
* Ajax method. |
731
|
|
|
* @internal |
732
|
|
|
*/ |
733
|
|
|
public function handleRefresh() |
734
|
|
|
{ |
735
|
|
|
$this->reload(); |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
/** |
739
|
1 |
|
* @param int $page |
740
|
1 |
|
* @internal |
741
|
|
|
*/ |
742
|
|
|
public function handlePage($page) |
743
|
|
|
{ |
744
|
|
|
$this->reload(); |
745
|
|
|
} |
746
|
|
|
|
747
|
|
|
/** |
748
|
|
|
* @param array $sort |
749
|
1 |
|
* @internal |
750
|
1 |
|
*/ |
751
|
1 |
|
public function handleSort(array $sort) |
752
|
1 |
|
{ |
753
|
1 |
|
$this->page = 1; |
754
|
1 |
|
$this->reload(); |
755
|
|
|
} |
756
|
1 |
|
|
757
|
1 |
|
/** |
758
|
1 |
|
* @param \Nette\Forms\Controls\SubmitButton $button |
759
|
1 |
|
* @internal |
760
|
|
|
*/ |
761
|
|
|
public function handleFilter(\Nette\Forms\Controls\SubmitButton $button) |
762
|
1 |
|
{ |
763
|
|
|
$values = $button->form->values[Filter::ID]; |
764
|
1 |
|
$session = $this->rememberState //session filter |
765
|
1 |
|
? isset($this->getRememberSession(TRUE)->params['filter']) |
766
|
|
|
? $this->getRememberSession(TRUE)->params['filter'] |
767
|
|
|
: [] |
768
|
|
|
: []; |
769
|
|
|
|
770
|
|
|
foreach ($values as $name => $value) { |
771
|
|
|
if (is_numeric($value) || !empty($value) || isset($this->defaultFilter[$name]) || isset($session[$name])) { |
772
|
|
|
$this->filter[$name] = $this->getFilter($name)->changeValue($value); |
773
|
|
|
} elseif (isset($this->filter[$name])) { |
774
|
1 |
|
unset($this->filter[$name]); |
775
|
1 |
|
} |
776
|
1 |
|
} |
777
|
|
|
|
778
|
1 |
|
$this->page = 1; |
779
|
1 |
|
$this->reload(); |
780
|
1 |
|
} |
781
|
|
|
|
782
|
1 |
|
/** |
783
|
|
|
* @param \Nette\Forms\Controls\SubmitButton $button |
784
|
1 |
|
* @internal |
785
|
1 |
|
*/ |
786
|
|
|
public function handleReset(\Nette\Forms\Controls\SubmitButton $button) |
787
|
|
|
{ |
788
|
|
|
$this->sort = []; |
789
|
|
|
$this->filter = []; |
790
|
|
|
$this->perPage = NULL; |
791
|
|
|
|
792
|
|
|
if ($session = $this->getRememberSession()) { |
793
|
|
|
$session->remove(); |
794
|
1 |
|
} |
795
|
1 |
|
|
796
|
1 |
|
$button->form->setValues([Filter::ID => $this->defaultFilter], TRUE); |
797
|
|
|
|
798
|
|
|
$this->page = 1; |
799
|
1 |
|
$this->reload(); |
800
|
1 |
|
} |
801
|
|
|
|
802
|
|
|
/** |
803
|
|
|
* @param \Nette\Forms\Controls\SubmitButton $button |
804
|
|
|
* @internal |
805
|
|
|
*/ |
806
|
|
|
public function handlePerPage(\Nette\Forms\Controls\SubmitButton $button) |
807
|
|
|
{ |
808
|
|
|
$perPage = (int) $button->form['count']->value; |
809
|
|
|
$this->perPage = $perPage == $this->defaultPerPage |
810
|
1 |
|
? NULL |
811
|
|
|
: $perPage; |
812
|
|
|
|
813
|
|
|
$this->page = 1; |
814
|
1 |
|
$this->reload(); |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
/** |
818
|
|
|
* Refresh wrapper. |
819
|
|
|
* @return void |
820
|
|
|
* @internal |
821
|
|
|
*/ |
822
|
|
|
public function reload() |
823
|
|
|
{ |
824
|
|
|
if ($this->presenter->isAjax()) { |
825
|
|
|
$this->presenter->payload->grido = TRUE; |
826
|
1 |
|
$this->redrawControl(); |
827
|
1 |
|
} else { |
828
|
1 |
|
$this->redirect('this'); |
829
|
|
|
} |
830
|
1 |
|
} |
831
|
|
|
|
832
|
|
|
/**********************************************************************************************/ |
833
|
|
|
|
834
|
|
|
/** |
835
|
|
|
* @internal |
836
|
|
|
*/ |
837
|
|
|
public function createTemplate(): \Nette\Application\UI\ITemplate |
838
|
|
|
{ |
839
|
1 |
|
$template = parent::createTemplate(); |
840
|
|
|
$template->setFile($this->getCustomization()->getTemplateFiles()[Customization::TEMPLATE_DEFAULT]); |
841
|
|
|
$template->getLatte()->addFilter('translate', [$this->getTranslator(), 'translate']); |
842
|
|
|
|
843
|
1 |
|
return $template; |
844
|
1 |
|
} |
845
|
|
|
|
846
|
1 |
|
/** |
847
|
1 |
|
* @internal |
848
|
1 |
|
* @throws Exception |
849
|
|
|
*/ |
850
|
1 |
|
public function render() |
851
|
|
|
{ |
852
|
1 |
|
if (!$this->hasColumns()) { |
853
|
1 |
|
throw new Exception('Grid must have defined a column, please use method $grid->addColumn*().'); |
854
|
1 |
|
} |
855
|
1 |
|
|
856
|
1 |
|
$this->saveRememberState(); |
857
|
1 |
|
$data = $this->getData(); |
858
|
1 |
|
|
859
|
1 |
|
if (!empty($this->onRender)) { |
860
|
1 |
|
$this->onRender($this); |
861
|
|
|
} |
862
|
1 |
|
|
863
|
1 |
|
$form = $this['form']; |
864
|
1 |
|
|
865
|
1 |
|
$this->getTemplate()->add('data', $data); |
866
|
|
|
$this->getTemplate()->add('form', $form); |
867
|
1 |
|
$this->getTemplate()->add('paginator', $this->getPaginator()); |
868
|
1 |
|
$this->getTemplate()->add('customization', $this->getCustomization()); |
869
|
1 |
|
$this->getTemplate()->add('columns', $this->getComponent(Column::ID)->getComponents()); |
870
|
1 |
|
$this->getTemplate()->add('actions', $this->hasActions() |
871
|
|
|
? $this->getComponent(Action::ID)->getComponents() |
872
|
1 |
|
: [] |
873
|
|
|
); |
874
|
1 |
|
|
875
|
1 |
|
$this->getTemplate()->add('buttons', $this->hasButtons() |
876
|
1 |
|
? $this->getComponent(Button::ID)->getComponents() |
877
|
|
|
: [] |
878
|
1 |
|
); |
879
|
1 |
|
|
880
|
|
|
$this->getTemplate()->add('formFilters', $this->hasFilters() |
881
|
|
|
? $form->getComponent(Filter::ID)->getComponents() |
882
|
|
|
: [] |
883
|
1 |
|
); |
884
|
1 |
|
|
885
|
1 |
|
$form['count']->setValue($this->getPerPage()); |
886
|
1 |
|
|
887
|
1 |
|
if ($options = $this->options[self::CLIENT_SIDE_OPTIONS]) { |
888
|
1 |
|
$this->getTablePrototype()->setAttribute('data-' . self::CLIENT_SIDE_OPTIONS, json_encode($options)); |
889
|
1 |
|
} |
890
|
1 |
|
|
891
|
|
|
$this->getTemplate()->render(); |
892
|
|
|
} |
893
|
|
|
|
894
|
1 |
|
protected function saveRememberState() |
895
|
1 |
|
{ |
896
|
1 |
|
if ($this->rememberState) { |
897
|
|
|
$session = $this->getRememberSession(TRUE); |
898
|
|
|
$params = array_keys($this->getReflection()->getPersistentParams()); |
899
|
|
|
foreach ($params as $param) { |
900
|
|
|
$session->params[$param] = $this->$param; |
901
|
|
|
} |
902
|
|
|
} |
903
|
|
|
} |
904
|
|
|
|
905
|
1 |
|
protected function applyFiltering() |
906
|
1 |
|
{ |
907
|
|
|
$conditions = $this->__getConditions($this->getActualFilter()); |
908
|
1 |
|
$this->getModel()->filter($conditions); |
909
|
1 |
|
} |
910
|
|
|
|
911
|
|
|
/** |
912
|
|
|
* @param array $filter |
913
|
|
|
* @return array |
914
|
|
|
* @internal |
915
|
|
|
*/ |
916
|
|
|
public function __getConditions(array $filter) |
917
|
1 |
|
{ |
918
|
1 |
|
$conditions = []; |
919
|
1 |
|
if (!empty($filter)) { |
920
|
1 |
|
try { |
921
|
1 |
|
$this['form']->setDefaults([Filter::ID => $filter]); |
922
|
1 |
|
} catch (\Nette\InvalidArgumentException $e) { |
|
|
|
|
923
|
1 |
|
$this->__triggerUserNotice($e->getMessage()); |
924
|
|
|
$filter = []; |
925
|
1 |
|
if ($session = $this->getRememberSession()) { |
926
|
1 |
|
$session->remove(); |
927
|
|
|
} |
928
|
1 |
|
} |
929
|
|
|
|
930
|
|
|
foreach ($filter as $column => $value) { |
931
|
|
|
if ($component = $this->getFilter($column, FALSE)) { |
932
|
|
|
if ($condition = $component->__getCondition($value)) { |
933
|
1 |
|
$conditions[] = $condition; |
934
|
1 |
|
} |
935
|
|
|
} else { |
936
|
1 |
|
$this->__triggerUserNotice("Filter with name '$column' does not exist."); |
937
|
1 |
|
} |
938
|
1 |
|
} |
939
|
1 |
|
} |
940
|
1 |
|
|
941
|
1 |
|
return $conditions; |
942
|
|
|
} |
943
|
|
|
|
944
|
1 |
|
protected function applySorting() |
945
|
1 |
|
{ |
946
|
1 |
|
$sort = []; |
947
|
1 |
|
$this->sort = $this->sort ? $this->sort : $this->defaultSort; |
948
|
1 |
|
|
949
|
1 |
|
foreach ($this->sort as $column => $dir) { |
950
|
|
|
$component = $this->getColumn($column, FALSE); |
951
|
1 |
|
if (!$component) { |
952
|
|
|
if (!isset($this->defaultSort[$column])) { |
953
|
1 |
|
$this->__triggerUserNotice("Column with name '$column' does not exist."); |
954
|
1 |
|
break; |
955
|
|
|
} |
956
|
|
|
|
957
|
|
|
} elseif (!$component->isSortable()) { |
958
|
|
|
if (isset($this->defaultSort[$column])) { |
959
|
1 |
|
$component->setSortable(); |
960
|
1 |
|
} else { |
961
|
|
|
$this->__triggerUserNotice("Column with name '$column' is not sortable."); |
962
|
|
|
break; |
963
|
1 |
|
} |
964
|
1 |
|
} |
965
|
|
|
|
966
|
1 |
|
if (!in_array($dir, [Column::ORDER_ASC, Column::ORDER_DESC])) { |
967
|
1 |
|
if ($dir == '' && isset($this->defaultSort[$column])) { |
968
|
1 |
|
unset($this->sort[$column]); |
969
|
1 |
|
break; |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
$this->__triggerUserNotice("Dir '$dir' is not allowed."); |
973
|
1 |
|
break; |
974
|
1 |
|
} |
975
|
1 |
|
|
976
|
|
|
$sort[$component ? $component->column : $column] = $dir == Column::ORDER_ASC ? 'ASC' : 'DESC'; |
977
|
1 |
|
} |
978
|
1 |
|
|
979
|
1 |
|
if (!empty($sort)) { |
980
|
1 |
|
$this->getModel()->sort($sort); |
981
|
|
|
} |
982
|
1 |
|
} |
983
|
1 |
|
|
984
|
|
|
protected function applyPaging() |
985
|
|
|
{ |
986
|
|
|
$paginator = $this->getPaginator() |
987
|
1 |
|
->setItemCount($this->getCount()) |
988
|
1 |
|
->setPage($this->page); |
989
|
1 |
|
|
990
|
|
|
$perPage = $this->getPerPage(); |
991
|
1 |
|
if ($perPage !== NULL && !in_array($perPage, $this->perPageList)) { |
992
|
1 |
|
$this->__triggerUserNotice("The number '$perPage' of items per page is out of range."); |
993
|
1 |
|
} |
994
|
1 |
|
|
995
|
1 |
|
$this->getModel()->limit($paginator->getOffset(), $paginator->getLength()); |
996
|
1 |
|
} |
997
|
1 |
|
|
998
|
|
|
protected function createComponentForm($name) |
999
|
1 |
|
{ |
1000
|
1 |
|
$form = new \Nette\Application\UI\Form($this, $name); |
1001
|
1 |
|
$form->setTranslator($this->getTranslator()); |
1002
|
1 |
|
$form->setMethod($form::GET); |
1003
|
|
|
|
1004
|
|
|
$buttons = $form->addContainer(self::BUTTONS); |
1005
|
|
|
$buttons->addSubmit('search', 'Grido.Search') |
1006
|
|
|
->onClick[] = [$this, 'handleFilter']; |
1007
|
|
|
$buttons->addSubmit('reset', 'Grido.Reset') |
1008
|
|
|
->onClick[] = [$this, 'handleReset']; |
1009
|
1 |
|
$buttons->addSubmit('perPage', 'Grido.ItemsPerPage') |
1010
|
|
|
->onClick[] = [$this, 'handlePerPage']; |
1011
|
|
|
|
1012
|
|
|
$form->addSelect('count', 'Count', $this->getItemsForCountSelect()) |
1013
|
|
|
->setTranslator(NULL) |
1014
|
|
|
->controlPrototype->attrs['title'] = $this->getTranslator()->translate('Grido.ItemsPerPage'); |
1015
|
|
|
} |
1016
|
|
|
|
1017
|
|
|
/** |
1018
|
1 |
|
* @return array |
1019
|
1 |
|
*/ |
1020
|
1 |
|
protected function getItemsForCountSelect() |
1021
|
|
|
{ |
1022
|
1 |
|
return array_combine($this->perPageList, $this->perPageList); |
1023
|
1 |
|
} |
1024
|
|
|
|
1025
|
1 |
|
/** |
1026
|
|
|
* @internal |
1027
|
|
|
* @param string $message |
1028
|
|
|
*/ |
1029
|
|
|
public function __triggerUserNotice($message) |
1030
|
|
|
{ |
1031
|
|
|
if ($this->lookup(Presenter::class, FALSE) && $session = $this->getRememberSession()) { |
1032
|
|
|
$session->remove(); |
1033
|
|
|
} |
1034
|
|
|
|
1035
|
|
|
$this->strictMode && trigger_error($message, E_USER_NOTICE); |
1036
|
|
|
} |
1037
|
|
|
} |
1038
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.