1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Yiisoft\Yii\DataView; |
||
6 | |||
7 | use Closure; |
||
8 | use Psr\Container\ContainerInterface; |
||
9 | use Stringable; |
||
10 | use Yiisoft\Definitions\Exception\CircularReferenceException; |
||
11 | use Yiisoft\Definitions\Exception\InvalidConfigException; |
||
12 | use Yiisoft\Definitions\Exception\NotInstantiableException; |
||
13 | use Yiisoft\Factory\NotFoundException; |
||
14 | use Yiisoft\Html\Html; |
||
15 | use Yiisoft\Html\Tag\Tr; |
||
16 | use Yiisoft\Router\UrlGeneratorInterface; |
||
17 | use Yiisoft\Translator\TranslatorInterface; |
||
18 | use Yiisoft\Yii\DataView\Column\ActionColumn; |
||
19 | use Yiisoft\Yii\DataView\Column\Base\Cell; |
||
20 | use Yiisoft\Yii\DataView\Column\Base\GlobalContext; |
||
21 | use Yiisoft\Yii\DataView\Column\Base\DataContext; |
||
22 | use Yiisoft\Yii\DataView\Column\ColumnInterface; |
||
23 | use Yiisoft\Yii\DataView\Column\ColumnRendererInterface; |
||
24 | use Yiisoft\Yii\DataView\Column\DataColumn; |
||
25 | |||
26 | /** |
||
27 | * The GridView widget is used to display data in a grid. |
||
28 | * |
||
29 | * It provides features like {@see sorter|sorting}, and {@see filterModel|filtering} the data. |
||
30 | * |
||
31 | * The columns of the grid table are configured in terms of {@see Column} classes, which are configured via |
||
32 | * {@see columns}. |
||
33 | * |
||
34 | * The look and feel of a grid view can be customized using the large amount of properties. |
||
35 | */ |
||
36 | final class GridView extends BaseListView |
||
37 | { |
||
38 | public const FILTER_POS_HEADER = 'header'; |
||
39 | public const FILTER_POS_FOOTER = 'footer'; |
||
40 | public const FILTER_POS_BODY = 'body'; |
||
41 | |||
42 | private Closure|null $afterRow = null; |
||
43 | private Closure|null $beforeRow = null; |
||
44 | |||
45 | /** |
||
46 | * @var ColumnInterface[] |
||
47 | */ |
||
48 | private array $columns = []; |
||
49 | |||
50 | private bool $columnsGroupEnabled = false; |
||
51 | private string $emptyCell = ' '; |
||
52 | private ?string $filterModelName = null; |
||
53 | private string $filterPosition = self::FILTER_POS_BODY; |
||
54 | private array $filterRowAttributes = []; |
||
55 | private bool $footerEnabled = false; |
||
56 | private array $footerRowAttributes = []; |
||
57 | private bool $headerTableEnabled = true; |
||
58 | private array $headerRowAttributes = []; |
||
59 | private array $rowAttributes = []; |
||
60 | private array $tableAttributes = []; |
||
61 | private array $tbodyAttributes = []; |
||
62 | private array $headerCellAttributes = []; |
||
63 | private array $bodyCellAttributes = []; |
||
64 | |||
65 | 88 | public function __construct( |
|
66 | private ContainerInterface $columnRenderersContainer, |
||
67 | TranslatorInterface|null $translator = null, |
||
68 | UrlGeneratorInterface|null $urlGenerator = null |
||
69 | ) { |
||
70 | 88 | parent::__construct($translator, $urlGenerator); |
|
71 | } |
||
72 | |||
73 | /** |
||
74 | * Returns a new instance with anonymous function that is called once AFTER rendering each data. |
||
75 | * |
||
76 | * @param Closure|null $value The anonymous function that is called once AFTER rendering each data. |
||
77 | */ |
||
78 | 2 | public function afterRow(Closure|null $value): self |
|
79 | { |
||
80 | 2 | $new = clone $this; |
|
81 | 2 | $new->afterRow = $value; |
|
82 | |||
83 | 2 | return $new; |
|
84 | } |
||
85 | |||
86 | /** |
||
87 | * Return a new instance with anonymous function that is called once BEFORE rendering each data. |
||
88 | * |
||
89 | * @param Closure|null $value The anonymous function that is called once BEFORE rendering each data. |
||
90 | */ |
||
91 | 2 | public function beforeRow(Closure|null $value): self |
|
92 | { |
||
93 | 2 | $new = clone $this; |
|
94 | 2 | $new->beforeRow = $value; |
|
95 | |||
96 | 2 | return $new; |
|
97 | } |
||
98 | |||
99 | /** |
||
100 | * Return a new instance the specified columns. |
||
101 | * |
||
102 | * @param ColumnInterface ...$values The grid column configuration. Each array element represents the configuration |
||
103 | * for one particular grid column. For example, |
||
104 | * |
||
105 | * ```php |
||
106 | * [ |
||
107 | * SerialColumn::create(), |
||
108 | * DetailColumn::create() |
||
109 | * ->attribute('identity_id') |
||
110 | * ->filterAttribute('identity_id') |
||
111 | * ->filterValueDefault(0) |
||
112 | * ->filterAttributes(['class' => 'text-center', 'maxlength' => '5', 'style' => 'width:60px']), |
||
113 | * ActionColumn::create()->primaryKey('identity_id')->visibleButtons(['view' => true]), |
||
114 | * ] |
||
115 | * ``` |
||
116 | */ |
||
117 | 82 | public function columns(ColumnInterface ...$values): self |
|
118 | { |
||
119 | 82 | $new = clone $this; |
|
120 | 82 | $new->columns = $values; |
|
121 | 82 | return $new; |
|
122 | } |
||
123 | |||
124 | /** |
||
125 | * Returns a new instance with the specified column group enabled. |
||
126 | * |
||
127 | * @param bool $value Whether to enable the column group. |
||
128 | */ |
||
129 | 3 | public function columnsGroupEnabled(bool $value): self |
|
130 | { |
||
131 | 3 | $new = clone $this; |
|
132 | 3 | $new->columnsGroupEnabled = $value; |
|
133 | |||
134 | 3 | return $new; |
|
135 | } |
||
136 | |||
137 | /** |
||
138 | * Return new instance with the HTML display when the content is empty. |
||
139 | * |
||
140 | * @param string $value The HTML display when the content of a cell is empty. This property is used to render cells |
||
141 | * that have no defined content, e.g. empty footer or filter cells. |
||
142 | */ |
||
143 | 2 | public function emptyCell(string $value): self |
|
144 | { |
||
145 | 2 | $new = clone $this; |
|
146 | 2 | $new->emptyCell = $value; |
|
147 | |||
148 | 2 | return $new; |
|
149 | } |
||
150 | |||
151 | /** |
||
152 | * Return new instance with the filter model name. |
||
153 | * |
||
154 | * @param string|null $value The form model name that keeps the user-entered filter data. When this property is set, the |
||
155 | * grid view will enable column-based filtering. Each data column by default will display a text field at the top |
||
156 | * that users can fill in to filter the data. |
||
157 | * |
||
158 | * Note that in order to show an input field for filtering, a column must have its {@see DataColumn::attribute} |
||
159 | * property set and the attribute should be active in the current scenario of $filterModelName or have |
||
160 | * {@see DataColumn::filter} set as the HTML code for the input field. |
||
161 | */ |
||
162 | 11 | public function filterModelName(?string $value): self |
|
163 | { |
||
164 | 11 | $new = clone $this; |
|
165 | 11 | $new->filterModelName = $value; |
|
166 | |||
167 | 11 | return $new; |
|
168 | } |
||
169 | |||
170 | /** |
||
171 | * Return new instance with the filter position. |
||
172 | * |
||
173 | * @param string $filterPosition Whether the filters should be displayed in the grid view. Valid values include: |
||
174 | * |
||
175 | * - {@see FILTER_POS_HEADER}: The filters will be displayed on top of each column's header cell. |
||
176 | * - {@see FILTER_POS_BODY}: The filters will be displayed right below each column's header cell. |
||
177 | * - {@see FILTER_POS_FOOTER}: The filters will be displayed below each column's footer cell. |
||
178 | */ |
||
179 | 3 | public function filterPosition(string $filterPosition): self |
|
180 | { |
||
181 | 3 | $new = clone $this; |
|
182 | 3 | $new->filterPosition = $filterPosition; |
|
183 | |||
184 | 3 | return $new; |
|
185 | } |
||
186 | |||
187 | /** |
||
188 | * Returns a new instance with the HTML attributes for filter row. |
||
189 | * |
||
190 | * @param array $values Attribute values indexed by attribute names. |
||
191 | */ |
||
192 | 7 | public function filterRowAttributes(array $values): self |
|
193 | { |
||
194 | 7 | $new = clone $this; |
|
195 | 7 | $new->filterRowAttributes = $values; |
|
196 | |||
197 | 7 | return $new; |
|
198 | } |
||
199 | |||
200 | /** |
||
201 | * Return new instance whether to show the footer section of the grid. |
||
202 | * |
||
203 | * @param bool $value Whether to show the footer section of the grid. |
||
204 | */ |
||
205 | 4 | public function footerEnabled(bool $value): self |
|
206 | { |
||
207 | 4 | $new = clone $this; |
|
208 | 4 | $new->footerEnabled = $value; |
|
209 | |||
210 | 4 | return $new; |
|
211 | } |
||
212 | |||
213 | /** |
||
214 | * Returns a new instance with the HTML attributes for footer row. |
||
215 | * |
||
216 | * @param array $values Attribute values indexed by attribute names. |
||
217 | */ |
||
218 | 2 | public function footerRowAttributes(array $values): self |
|
219 | { |
||
220 | 2 | $new = clone $this; |
|
221 | 2 | $new->footerRowAttributes = $values; |
|
222 | |||
223 | 2 | return $new; |
|
224 | } |
||
225 | |||
226 | /** |
||
227 | * Return new instance whether to show the header table section of the grid. |
||
228 | * |
||
229 | * @param bool $value Whether to show the header table section of the grid. |
||
230 | */ |
||
231 | 2 | public function headerTableEnabled(bool $value): self |
|
232 | { |
||
233 | 2 | $new = clone $this; |
|
234 | 2 | $new->headerTableEnabled = $value; |
|
235 | |||
236 | 2 | return $new; |
|
237 | } |
||
238 | |||
239 | /** |
||
240 | * Return new instance with the HTML attributes for the header row. |
||
241 | * |
||
242 | * @param array $values Attribute values indexed by attribute names. |
||
243 | */ |
||
244 | 2 | public function headerRowAttributes(array $values): self |
|
245 | { |
||
246 | 2 | $new = clone $this; |
|
247 | 2 | $new->headerRowAttributes = $values; |
|
248 | |||
249 | 2 | return $new; |
|
250 | } |
||
251 | |||
252 | /** |
||
253 | * Return new instance with the HTML attributes for row of the grid. |
||
254 | * |
||
255 | * @param array $values Attribute values indexed by attribute names. |
||
256 | * |
||
257 | * This can be either an array specifying the common HTML attributes for all body rows. |
||
258 | */ |
||
259 | 2 | public function rowAttributes(array $values): self |
|
260 | { |
||
261 | 2 | $new = clone $this; |
|
262 | 2 | $new->rowAttributes = $values; |
|
263 | |||
264 | 2 | return $new; |
|
265 | } |
||
266 | |||
267 | /** |
||
268 | * Return new instance with the HTML attributes for the `table` tag. |
||
269 | * |
||
270 | * @param array $attributes The tag attributes in terms of name-value pairs. |
||
271 | */ |
||
272 | 2 | public function tableAttributes(array $attributes): self |
|
273 | { |
||
274 | 2 | $new = clone $this; |
|
275 | 2 | $new->tableAttributes = $attributes; |
|
276 | 2 | return $new; |
|
277 | } |
||
278 | |||
279 | /** |
||
280 | * Add one or more CSS classes to the `table` tag. |
||
281 | * |
||
282 | * @param string|null ...$class One or many CSS classes. |
||
283 | */ |
||
284 | public function addTableClass(?string ...$class): self |
||
285 | { |
||
286 | $new = clone $this; |
||
287 | Html::addCssClass($new->tableAttributes, $class); |
||
288 | return $new; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Replace current `table` tag CSS classes with a new set of classes. |
||
293 | * |
||
294 | * @param string|null ...$class One or many CSS classes. |
||
295 | */ |
||
296 | public function tableClass(?string ...$class): static |
||
297 | { |
||
298 | $new = clone $this; |
||
299 | $new->tableAttributes['class'] = array_filter($class, static fn ($c) => $c !== null); |
||
300 | return $new; |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Return new instance with the HTML attributes for the `tbody` tag. |
||
305 | * |
||
306 | * @param array $attributes The tag attributes in terms of name-value pairs. |
||
307 | */ |
||
308 | public function tbodyAttributes(array $attributes): self |
||
309 | { |
||
310 | $new = clone $this; |
||
311 | $new->tbodyAttributes = $attributes; |
||
312 | return $new; |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * Add one or more CSS classes to the `tbody` tag. |
||
317 | * |
||
318 | * @param string|null ...$class One or many CSS classes. |
||
319 | */ |
||
320 | public function addTbodyClass(?string ...$class): self |
||
321 | { |
||
322 | $new = clone $this; |
||
323 | Html::addCssClass($new->tbodyAttributes, $class); |
||
324 | return $new; |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * Replace current `tbody` tag CSS classes with a new set of classes. |
||
329 | * |
||
330 | * @param string|null ...$class One or many CSS classes. |
||
331 | */ |
||
332 | public function tbodyClass(?string ...$class): static |
||
333 | { |
||
334 | $new = clone $this; |
||
335 | $new->tbodyAttributes['class'] = array_filter($class, static fn ($c) => $c !== null); |
||
336 | return $new; |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * Return new instance with the HTML attributes for the `th` tag. |
||
341 | * |
||
342 | * @param array $attributes The tag attributes in terms of name-value pairs. |
||
343 | */ |
||
344 | public function headerCellAttributes(array $attributes): self |
||
345 | { |
||
346 | $new = clone $this; |
||
347 | $new->headerCellAttributes = $attributes; |
||
348 | return $new; |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * Return new instance with the HTML attributes for the `td` tag. |
||
353 | * |
||
354 | * @param array $attributes The tag attributes in terms of name-value pairs. |
||
355 | */ |
||
356 | public function bodyCellAttributes(array $attributes): self |
||
357 | { |
||
358 | $new = clone $this; |
||
359 | $new->bodyCellAttributes = $attributes; |
||
360 | return $new; |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * Renders the data active record classes for the grid view. |
||
365 | * |
||
366 | * @throws InvalidConfigException |
||
367 | * @throws NotFoundException |
||
368 | * @throws NotInstantiableException |
||
369 | * @throws CircularReferenceException |
||
370 | */ |
||
371 | 84 | protected function renderItems(): string |
|
372 | { |
||
373 | 84 | $columns = empty($this->columns) ? $this->guessColumns() : $this->columns; |
|
374 | 84 | $columns = array_filter( |
|
375 | 84 | $columns, |
|
376 | 84 | static fn(ColumnInterface $column) => $column->isVisible() |
|
377 | 84 | ); |
|
378 | |||
379 | 84 | $renderers = []; |
|
380 | 84 | foreach ($columns as $i => $column) { |
|
381 | 81 | $renderers[$i] = $this->getColumnRenderer($column); |
|
382 | } |
||
383 | |||
384 | 84 | $blocks = []; |
|
385 | |||
386 | 84 | $globalContext = new GlobalContext( |
|
387 | 84 | $this->getDataReader(), |
|
388 | 84 | $this->sortLinkAttributes, |
|
389 | 84 | $this->urlArguments, |
|
390 | 84 | $this->urlQueryParameters, |
|
391 | 84 | $this->filterModelName, |
|
392 | 84 | ); |
|
393 | |||
394 | 84 | if ($this->columnsGroupEnabled) { |
|
395 | 2 | $tags = []; |
|
396 | 2 | foreach ($columns as $i => $column) { |
|
397 | 2 | $cell = $renderers[$i]->renderColumn($column, new Cell(), $globalContext); |
|
398 | 2 | $tags[] = Html::col($cell->getAttributes()); |
|
399 | } |
||
400 | 2 | $blocks[] = Html::colgroup()->columns(...$tags)->render(); |
|
401 | } |
||
402 | |||
403 | 84 | if ($this->filterPosition === self::FILTER_POS_BODY |
|
404 | 2 | || $this->filterPosition === self::FILTER_POS_HEADER |
|
405 | 84 | || $this->filterPosition === self::FILTER_POS_FOOTER |
|
406 | ) { |
||
407 | 84 | $tags = []; |
|
408 | 84 | $hasFilters = false; |
|
409 | 84 | foreach ($columns as $i => $column) { |
|
410 | 81 | $baseCell = new Cell(encode: false, content: ' '); |
|
411 | 81 | $cell = $renderers[$i]->renderFilter($column, $baseCell, $globalContext); |
|
412 | 81 | if ($cell === null) { |
|
413 | 75 | $cell = $baseCell; |
|
414 | } else { |
||
415 | 18 | $hasFilters = true; |
|
416 | } |
||
417 | /** @var string|Stringable $content */ |
||
418 | 81 | $content = $cell->getContent(); |
|
419 | 81 | $tags[] = Html::td(attributes: $cell->getAttributes()) |
|
420 | 81 | ->content($content) |
|
421 | 81 | ->encode($cell->isEncode()) |
|
422 | 81 | ->doubleEncode($cell->isDoubleEncode()); |
|
423 | } |
||
424 | 84 | $filterRow = $hasFilters ? Html::tr($this->filterRowAttributes)->cells(...$tags) : null; |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
425 | } else { |
||
426 | $filterRow = null; |
||
427 | } |
||
428 | |||
429 | 84 | if ($this->headerTableEnabled) { |
|
430 | 83 | $tags = []; |
|
431 | 83 | foreach ($columns as $i => $column) { |
|
432 | 80 | $cell = $renderers[$i]->renderHeader($column, new Cell($this->headerCellAttributes), $globalContext); |
|
433 | /** @var string|Stringable $content */ |
||
434 | 80 | $content = $cell?->getContent(); |
|
435 | 80 | $tags[] = $cell === null |
|
436 | 5 | ? Html::th(' ')->encode(false) |
|
437 | 80 | : Html::th(attributes: $cell->getAttributes()) |
|
438 | 80 | ->content($content) |
|
439 | 80 | ->encode($cell->isEncode()) |
|
440 | 80 | ->doubleEncode($cell->isDoubleEncode()); |
|
441 | } |
||
442 | 83 | $headerRow = Html::tr($this->headerRowAttributes)->cells(...$tags); |
|
443 | |||
444 | 83 | if ($filterRow === null) { |
|
0 ignored issues
–
show
|
|||
445 | 65 | $rows = [$headerRow]; |
|
446 | 18 | } elseif ($this->filterPosition === self::FILTER_POS_HEADER) { |
|
447 | 1 | $rows = [$filterRow, $headerRow]; |
|
448 | 17 | } elseif ($this->filterPosition === self::FILTER_POS_BODY) { |
|
449 | 16 | $rows = [$headerRow, $filterRow]; |
|
450 | } else { |
||
451 | 1 | $rows = [$headerRow]; |
|
452 | } |
||
453 | |||
454 | 83 | $blocks[] = Html::thead()->rows(...$rows)->render(); |
|
455 | } |
||
456 | |||
457 | 84 | if ($this->footerEnabled) { |
|
458 | 3 | $tags = []; |
|
459 | 3 | foreach ($columns as $i => $column) { |
|
460 | 3 | $cell = $renderers[$i]->renderFooter( |
|
461 | 3 | $column, |
|
462 | 3 | (new Cell())->content(' ')->encode(false), |
|
463 | 3 | $globalContext |
|
464 | 3 | ); |
|
465 | /** @var string|Stringable $content */ |
||
466 | 3 | $content = $cell->getContent(); |
|
467 | 3 | $tags[] = Html::td(attributes: $cell->getAttributes()) |
|
468 | 3 | ->content($content) |
|
469 | 3 | ->encode($cell->isEncode()) |
|
470 | 3 | ->doubleEncode($cell->isDoubleEncode()); |
|
471 | } |
||
472 | 3 | $footerRow = Html::tr($this->footerRowAttributes)->cells(...$tags); |
|
473 | |||
474 | 3 | $rows = [$footerRow]; |
|
475 | 3 | if ($this->filterPosition === self::FILTER_POS_FOOTER) { |
|
476 | /** @var Tr */ |
||
477 | 1 | $rows[] = $filterRow; |
|
478 | } |
||
479 | |||
480 | 3 | $blocks[] = Html::tfoot()->rows(...$rows)->render(); |
|
481 | } |
||
482 | |||
483 | 84 | $rows = []; |
|
484 | 84 | $index = 0; |
|
485 | 84 | foreach ($this->getItems() as $key => $value) { |
|
486 | 77 | if ($this->beforeRow !== null) { |
|
487 | /** @var Tr|null $row */ |
||
488 | 1 | $row = call_user_func($this->beforeRow, $value, $key, $index, $this); |
|
489 | 1 | if (!empty($row)) { |
|
490 | 1 | $rows[] = $row; |
|
491 | } |
||
492 | } |
||
493 | |||
494 | 77 | $tags = []; |
|
495 | 77 | foreach ($columns as $i => $column) { |
|
496 | 76 | $context = new DataContext($column, $value, $key, $index); |
|
497 | 76 | $cell = $renderers[$i]->renderBody($column, new Cell(), $context); |
|
498 | 76 | $contentSource = $cell->getContent(); |
|
499 | /** @var string|Stringable $content */ |
||
500 | 76 | $content = $contentSource instanceof Closure |
|
501 | ? $contentSource($context) |
||
502 | 76 | : $contentSource; |
|
503 | 76 | $tags[] = empty($content) |
|
504 | 2 | ? Html::td()->content($this->emptyCell)->encode(false) |
|
505 | 75 | : Html::td(attributes: $this->prepareBodyAttributes($cell->getAttributes(), $context)) |
|
506 | 75 | ->content($content) |
|
507 | 75 | ->encode($cell->isEncode()) |
|
508 | 75 | ->doubleEncode($cell->isDoubleEncode()); |
|
509 | } |
||
510 | 77 | $rows[] = Html::tr($this->rowAttributes)->cells(...$tags); |
|
511 | |||
512 | 77 | if ($this->afterRow !== null) { |
|
513 | /** @var Tr|null $row */ |
||
514 | 1 | $row = call_user_func($this->afterRow, $value, $key, $index, $this); |
|
515 | 1 | if (!empty($row)) { |
|
516 | 1 | $rows[] = $row; |
|
517 | } |
||
518 | } |
||
519 | |||
520 | 77 | $index++; |
|
521 | } |
||
522 | 84 | $blocks[] = empty($rows) |
|
523 | 7 | ? Html::tbody($this->tbodyAttributes) |
|
524 | 7 | ->rows(Html::tr()->cells($this->renderEmpty(count($columns)))) |
|
525 | 7 | ->render() |
|
526 | 77 | : Html::tbody($this->tbodyAttributes)->rows(...$rows)->render(); |
|
527 | |||
528 | 84 | return Html::tag('table', attributes: $this->tableAttributes)->open() |
|
529 | 84 | . PHP_EOL |
|
530 | 84 | . implode(PHP_EOL, $blocks) |
|
531 | 84 | . PHP_EOL |
|
532 | 84 | . '</table>'; |
|
533 | } |
||
534 | |||
535 | /** |
||
536 | * This function tries to guess the columns to show from the given data if {@see columns} are not explicitly |
||
537 | * specified. |
||
538 | * |
||
539 | * @psalm-return list<ColumnInterface> |
||
540 | */ |
||
541 | 3 | private function guessColumns(): array |
|
542 | { |
||
543 | 3 | $items = $this->getItems(); |
|
544 | |||
545 | 3 | $columns = []; |
|
546 | 3 | foreach ($items as $item) { |
|
547 | /** |
||
548 | * @var string $name |
||
549 | * @var mixed $value |
||
550 | */ |
||
551 | 1 | foreach ($item as $name => $value) { |
|
552 | 1 | if ($value === null || is_scalar($value) || is_callable([$value, '__toString'])) { |
|
553 | 1 | $columns[] = new DataColumn(property: $name); |
|
554 | } |
||
555 | } |
||
556 | 1 | break; |
|
557 | } |
||
558 | |||
559 | 3 | if (!empty($items)) { |
|
560 | 1 | $columns[] = new ActionColumn(); |
|
561 | } |
||
562 | |||
563 | 3 | return $columns; |
|
564 | } |
||
565 | |||
566 | 75 | private function prepareBodyAttributes(array $attributes, DataContext $context): array |
|
567 | { |
||
568 | 75 | foreach ($attributes as $i => $attribute) { |
|
569 | 5 | if (is_callable($attribute)) { |
|
570 | 1 | $attributes[$i] = $attribute($context); |
|
571 | } |
||
572 | } |
||
573 | |||
574 | 75 | return $attributes; |
|
575 | } |
||
576 | |||
577 | 81 | private function getColumnRenderer(ColumnInterface $column): ColumnRendererInterface |
|
578 | { |
||
579 | /** @var ColumnRendererInterface */ |
||
580 | 81 | return $this->columnRenderersContainer->get($column->getRenderer()); |
|
581 | } |
||
582 | } |
||
583 |