1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace Yiisoft\Yii\DataView; |
||||
6 | |||||
7 | use Yiisoft\Data\Paginator\KeysetPaginator; |
||||
8 | use Yiisoft\Data\Paginator\OffsetPaginator; |
||||
9 | use Yiisoft\Data\Paginator\PaginatorInterface; |
||||
10 | use Yiisoft\Data\Reader\ReadableDataInterface; |
||||
11 | use Yiisoft\Definitions\Exception\CircularReferenceException; |
||||
12 | use Yiisoft\Definitions\Exception\InvalidConfigException; |
||||
13 | use Yiisoft\Definitions\Exception\NotInstantiableException; |
||||
14 | use Yiisoft\Factory\NotFoundException; |
||||
15 | use Yiisoft\Html\Tag\Div; |
||||
16 | use Yiisoft\Html\Tag\Td; |
||||
17 | use Yiisoft\Router\UrlGeneratorInterface; |
||||
18 | use Yiisoft\Translator\CategorySource; |
||||
19 | use Yiisoft\Translator\IdMessageReader; |
||||
20 | use Yiisoft\Translator\IntlMessageFormatter; |
||||
21 | use Yiisoft\Translator\SimpleMessageFormatter; |
||||
22 | use Yiisoft\Translator\Translator; |
||||
23 | use Yiisoft\Translator\TranslatorInterface; |
||||
24 | use Yiisoft\Widget\Widget; |
||||
25 | use Yiisoft\Yii\DataView\Exception\DataReaderNotSetException; |
||||
26 | |||||
27 | abstract class BaseListView extends Widget |
||||
28 | { |
||||
29 | /** |
||||
30 | * A name for {@see CategorySource} used with translator ({@see TranslatorInterface}) by default. |
||||
31 | */ |
||||
32 | public const DEFAULT_TRANSLATION_CATEGORY = 'yii-dataview'; |
||||
33 | |||||
34 | /** |
||||
35 | * @var TranslatorInterface A translator instance used for translations of messages. If it was not set |
||||
36 | * explicitly in the constructor, a default one created automatically in {@see createDefaultTranslator()}. |
||||
37 | */ |
||||
38 | private TranslatorInterface $translator; |
||||
39 | |||||
40 | private array $attributes = []; |
||||
41 | protected ?string $emptyText = null; |
||||
42 | private array $emptyTextAttributes = []; |
||||
43 | private string $header = ''; |
||||
44 | private array $headerAttributes = []; |
||||
45 | private string $layout = "{header}\n{toolbar}"; |
||||
46 | private string $layoutGridTable = "{items}\n{summary}\n{pager}"; |
||||
47 | private string $pagination = ''; |
||||
48 | protected ?ReadableDataInterface $dataReader = null; |
||||
49 | protected array $sortLinkAttributes = []; |
||||
50 | private ?string $summary = null; |
||||
51 | private array $summaryAttributes = []; |
||||
52 | private string $toolbar = ''; |
||||
53 | protected array $urlArguments = []; |
||||
54 | protected array $urlQueryParameters = []; |
||||
55 | private bool $withContainer = true; |
||||
56 | |||||
57 | 99 | public function __construct( |
|||
58 | TranslatorInterface|null $translator = null, |
||||
59 | private UrlGeneratorInterface|null $urlGenerator = null, |
||||
60 | private string $translationCategory = self::DEFAULT_TRANSLATION_CATEGORY, |
||||
61 | ) { |
||||
62 | 99 | $this->translator = $translator ?? $this->createDefaultTranslator(); |
|||
63 | } |
||||
64 | |||||
65 | /** |
||||
66 | * Renders the data models. |
||||
67 | * |
||||
68 | * @return string the rendering result. |
||||
69 | */ |
||||
70 | abstract protected function renderItems(): string; |
||||
71 | |||||
72 | /** |
||||
73 | * Returns a new instance with the HTML attributes. The following special options are recognized. |
||||
74 | * |
||||
75 | * @param array $values Attribute values indexed by attribute names. |
||||
76 | */ |
||||
77 | 1 | public function attributes(array $values): static |
|||
78 | { |
||||
79 | 1 | $new = clone $this; |
|||
80 | 1 | $new->attributes = $values; |
|||
81 | |||||
82 | 1 | return $new; |
|||
83 | } |
||||
84 | |||||
85 | /** |
||||
86 | * Return a new instance with the empty text. |
||||
87 | * |
||||
88 | * @param string $emptyText the HTML content to be displayed when {@see dataProvider} does not have any data. |
||||
89 | * |
||||
90 | * The default value is the text "No results found." which will be translated to the current application language. |
||||
91 | * |
||||
92 | * {@see notShowOnEmpty()} |
||||
93 | * {@see emptyTextAttributes()} |
||||
94 | */ |
||||
95 | 2 | public function emptyText(?string $emptyText): static |
|||
96 | { |
||||
97 | 2 | $new = clone $this; |
|||
98 | 2 | $new->emptyText = $emptyText; |
|||
99 | |||||
100 | 2 | return $new; |
|||
101 | } |
||||
102 | |||||
103 | /** |
||||
104 | * Returns a new instance with the HTML attributes for the empty text. |
||||
105 | * |
||||
106 | * @param array $values Attribute values indexed by attribute names. |
||||
107 | */ |
||||
108 | 1 | public function emptyTextAttributes(array $values): static |
|||
109 | { |
||||
110 | 1 | $new = clone $this; |
|||
111 | 1 | $new->emptyTextAttributes = $values; |
|||
112 | |||||
113 | 1 | return $new; |
|||
114 | } |
||||
115 | |||||
116 | 93 | public function getDataReader(): ReadableDataInterface |
|||
117 | { |
||||
118 | 93 | if ($this->dataReader === null) { |
|||
119 | 1 | throw new DataReaderNotSetException(); |
|||
120 | } |
||||
121 | |||||
122 | 92 | return $this->dataReader; |
|||
123 | } |
||||
124 | |||||
125 | 1 | public function getUrlGenerator(): UrlGeneratorInterface |
|||
126 | { |
||||
127 | 1 | if ($this->urlGenerator === null) { |
|||
128 | 1 | throw new Exception\UrlGeneratorNotSetException(); |
|||
129 | } |
||||
130 | |||||
131 | return $this->urlGenerator; |
||||
132 | } |
||||
133 | |||||
134 | /** |
||||
135 | * Return new instance with the header for the grid. |
||||
136 | * |
||||
137 | * @param string $value The header of the grid. |
||||
138 | * |
||||
139 | * {@see headerAttributes} |
||||
140 | */ |
||||
141 | 3 | public function header(string $value): self |
|||
142 | { |
||||
143 | 3 | $new = clone $this; |
|||
144 | 3 | $new->header = $value; |
|||
145 | |||||
146 | 3 | return $new; |
|||
147 | } |
||||
148 | |||||
149 | /** |
||||
150 | * Return new instance with the HTML attributes for the header. |
||||
151 | * |
||||
152 | * @param array $values Attribute values indexed by attribute names. |
||||
153 | */ |
||||
154 | 1 | public function headerAttributes(array $values): self |
|||
155 | { |
||||
156 | 1 | $new = clone $this; |
|||
157 | 1 | $new->headerAttributes = $values; |
|||
158 | |||||
159 | 1 | return $new; |
|||
160 | } |
||||
161 | |||||
162 | /** |
||||
163 | * Returns a new instance with the id of the grid view, detail view, or list view. |
||||
164 | * |
||||
165 | * @param string $value The id of the grid view, detail view, or list view. |
||||
166 | */ |
||||
167 | 85 | public function id(string $value): static |
|||
168 | { |
||||
169 | 85 | $new = clone $this; |
|||
170 | 85 | $new->attributes['id'] = $value; |
|||
171 | |||||
172 | 85 | return $new; |
|||
173 | } |
||||
174 | |||||
175 | /** |
||||
176 | * Returns a new instance with the layout of the grid view, and list view. |
||||
177 | * |
||||
178 | * @param string $value The template that determines how different sections of the grid view, list view. Should be |
||||
179 | * organized. |
||||
180 | * |
||||
181 | * The following tokens will be replaced with the corresponding section contents: |
||||
182 | * |
||||
183 | * - `{header}`: The header section. |
||||
184 | * - `{toolbar}`: The toolbar section. |
||||
185 | */ |
||||
186 | 2 | public function layout(string $value): static |
|||
187 | { |
||||
188 | 2 | $new = clone $this; |
|||
189 | 2 | $new->layout = $value; |
|||
190 | |||||
191 | 2 | return $new; |
|||
192 | } |
||||
193 | |||||
194 | /** |
||||
195 | * Returns a new instance with the layout grid table. |
||||
196 | * |
||||
197 | * @param string $value The layout that determines how different sections of the grid view, list view. Should be |
||||
198 | * organized. |
||||
199 | * |
||||
200 | * The following tokens will be replaced with the corresponding section contents: |
||||
201 | * |
||||
202 | * - `{items}`: The items section. |
||||
203 | * - `{summary}`: The summary section. |
||||
204 | * - `{pager}`: The pager section. |
||||
205 | */ |
||||
206 | 3 | public function layoutGridTable(string $value): static |
|||
207 | { |
||||
208 | 3 | $new = clone $this; |
|||
209 | 3 | $new->layoutGridTable = $value; |
|||
210 | |||||
211 | 3 | return $new; |
|||
212 | } |
||||
213 | |||||
214 | /** |
||||
215 | * Returns a new instance with the pagination of the grid view, detail view, or list view. |
||||
216 | * |
||||
217 | * @param string $value The pagination of the grid view, detail view, or list view. |
||||
218 | */ |
||||
219 | 4 | public function pagination(string $value): static |
|||
220 | { |
||||
221 | 4 | $new = clone $this; |
|||
222 | 4 | $new->pagination = $value; |
|||
223 | |||||
224 | 4 | return $new; |
|||
225 | } |
||||
226 | |||||
227 | /** |
||||
228 | * Returns a new instance with the paginator interface of the grid view, detail view, or list view. |
||||
229 | * |
||||
230 | * @param ReadableDataInterface $dataReader The paginator interface of the grid view, detail view, or list view. |
||||
231 | */ |
||||
232 | 93 | public function dataReader(ReadableDataInterface $dataReader): static |
|||
233 | { |
||||
234 | 93 | $new = clone $this; |
|||
235 | 93 | $new->dataReader = $dataReader; |
|||
236 | 93 | return $new; |
|||
237 | } |
||||
238 | |||||
239 | /** |
||||
240 | * Return new instance with the HTML attributes for widget link sort. |
||||
241 | * |
||||
242 | * @param array $values Attribute values indexed by attribute names. |
||||
243 | */ |
||||
244 | 1 | public function sortLinkAttributes(array $values): static |
|||
245 | { |
||||
246 | 1 | $new = clone $this; |
|||
247 | 1 | $new->sortLinkAttributes = $values; |
|||
248 | |||||
249 | 1 | return $new; |
|||
250 | } |
||||
251 | |||||
252 | /** |
||||
253 | * Returns a new instance with the summary of the grid view, detail view, and list view. |
||||
254 | * |
||||
255 | * @param string $value the HTML content to be displayed as the summary of the list view. |
||||
256 | * |
||||
257 | * If you do not want to show the summary, you may set it with an empty string. |
||||
258 | * |
||||
259 | * The following tokens will be replaced with the corresponding values: |
||||
260 | * |
||||
261 | * - `{begin}`: the starting row number (1-based) currently being displayed. |
||||
262 | * - `{end}`: the ending row number (1-based) currently being displayed. |
||||
263 | * - `{count}`: the number of rows currently being displayed. |
||||
264 | * - `{totalCount}`: the total number of rows available. |
||||
265 | * - `{page}`: the page number (1-based) current being displayed. |
||||
266 | * - `{pageCount}`: the number of pages available. |
||||
267 | */ |
||||
268 | 1 | public function summary(?string $value): static |
|||
269 | { |
||||
270 | 1 | $new = clone $this; |
|||
271 | 1 | $new->summary = $value; |
|||
272 | |||||
273 | 1 | return $new; |
|||
274 | } |
||||
275 | |||||
276 | /** |
||||
277 | * Returns a new instance with the HTML attributes for summary of grid view, detail view, and list view. |
||||
278 | * |
||||
279 | * @param array $values Attribute values indexed by attribute names. |
||||
280 | */ |
||||
281 | 1 | public function summaryAttributes(array $values): static |
|||
282 | { |
||||
283 | 1 | $new = clone $this; |
|||
284 | 1 | $new->summaryAttributes = $values; |
|||
285 | |||||
286 | 1 | return $new; |
|||
287 | } |
||||
288 | |||||
289 | /** |
||||
290 | * Return new instance with toolbar content. |
||||
291 | * |
||||
292 | * @param string $value The toolbar content. |
||||
293 | * |
||||
294 | * @psalm-param array<array-key,array> $toolbar |
||||
295 | */ |
||||
296 | 1 | public function toolbar(string $value): self |
|||
297 | { |
||||
298 | 1 | $new = clone $this; |
|||
299 | 1 | $new->toolbar = $value; |
|||
300 | |||||
301 | 1 | return $new; |
|||
302 | } |
||||
303 | |||||
304 | /** |
||||
305 | * Return a new instance with arguments of the route. |
||||
306 | * |
||||
307 | * @param array $value Arguments of the route. |
||||
308 | */ |
||||
309 | 1 | public function urlArguments(array $value): static |
|||
310 | { |
||||
311 | 1 | $new = clone $this; |
|||
312 | 1 | $new->urlArguments = $value; |
|||
313 | |||||
314 | 1 | return $new; |
|||
315 | } |
||||
316 | |||||
317 | /** |
||||
318 | * Return a new instance with query parameters of the route. |
||||
319 | * |
||||
320 | * @param array $value The query parameters of the route. |
||||
321 | */ |
||||
322 | 1 | public function urlQueryParameters(array $value): static |
|||
323 | { |
||||
324 | 1 | $new = clone $this; |
|||
325 | 1 | $new->urlQueryParameters = $value; |
|||
326 | |||||
327 | 1 | return $new; |
|||
328 | } |
||||
329 | |||||
330 | /** |
||||
331 | * Returns a new instance whether container is enabled or not. |
||||
332 | * |
||||
333 | * @param bool $value Whether container is enabled or not. |
||||
334 | */ |
||||
335 | 1 | public function withContainer(bool $value = true): static |
|||
336 | { |
||||
337 | 1 | $new = clone $this; |
|||
338 | 1 | $new->withContainer = $value; |
|||
339 | |||||
340 | 1 | return $new; |
|||
341 | } |
||||
342 | |||||
343 | 7 | protected function renderEmpty(int $colspan): Td |
|||
344 | { |
||||
345 | 7 | $emptyTextAttributes = $this->emptyTextAttributes; |
|||
346 | 7 | $emptyTextAttributes['colspan'] = $colspan; |
|||
347 | |||||
348 | 7 | $emptyText = $this->translator->translate( |
|||
349 | 7 | $this->emptyText ?? 'No results found.', |
|||
350 | 7 | category: $this->translationCategory |
|||
351 | 7 | ); |
|||
352 | |||||
353 | 7 | return Td::tag()->attributes($emptyTextAttributes)->content($emptyText); |
|||
354 | } |
||||
355 | |||||
356 | /** |
||||
357 | * @throws InvalidConfigException |
||||
358 | * @throws NotFoundException |
||||
359 | * @throws NotInstantiableException |
||||
360 | * @throws CircularReferenceException |
||||
361 | */ |
||||
362 | protected function renderLinkSorter(string $attribute, string $label): string |
||||
363 | { |
||||
364 | $dataReader = $this->getDataReader(); |
||||
365 | if (!$dataReader instanceof PaginatorInterface) { |
||||
366 | return ''; |
||||
367 | } |
||||
368 | |||||
369 | $sort = $dataReader->getSort(); |
||||
370 | if ($sort === null) { |
||||
371 | return ''; |
||||
372 | } |
||||
373 | |||||
374 | $linkSorter = $dataReader instanceof OffsetPaginator |
||||
375 | ? LinkSorter::widget()->currentPage($dataReader->getCurrentPage()) |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
376 | : LinkSorter::widget(); |
||||
377 | |||||
378 | return $linkSorter |
||||
379 | ->attribute($attribute) |
||||
0 ignored issues
–
show
The method
attribute() does not exist on Yiisoft\Widget\Widget . It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Yii\DataView\LinkSorter .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
380 | ->attributes($sort->getCriteria()) |
||||
381 | ->directions($sort->getOrder()) |
||||
382 | ->iconAscClass('bi bi-sort-alpha-up') |
||||
383 | ->iconDescClass('bi bi-sort-alpha-down') |
||||
384 | ->label($label) |
||||
385 | ->linkAttributes($this->sortLinkAttributes) |
||||
386 | ->pageSize($dataReader->getPageSize()) |
||||
387 | ->urlArguments($this->urlArguments) |
||||
388 | ->urlQueryParameters($this->urlQueryParameters) |
||||
389 | ->render(); |
||||
390 | } |
||||
391 | |||||
392 | 94 | public function render(): string |
|||
393 | { |
||||
394 | 94 | if ($this->dataReader === null) { |
|||
395 | 2 | throw new DataReaderNotSetException(); |
|||
396 | } |
||||
397 | |||||
398 | 92 | return $this->renderGrid(); |
|||
399 | } |
||||
400 | |||||
401 | /** |
||||
402 | * @psalm-return array<array-key, array|object> |
||||
403 | */ |
||||
404 | 92 | protected function getItems(): array |
|||
405 | { |
||||
406 | 92 | $data = $this->getDataReader()->read(); |
|||
407 | 92 | return is_array($data) ? $data : iterator_to_array($data); |
|||
408 | } |
||||
409 | |||||
410 | 90 | private function renderPagination(): string |
|||
411 | { |
||||
412 | 90 | $dataReader = $this->getDataReader(); |
|||
413 | 90 | if (!$dataReader instanceof PaginatorInterface) { |
|||
414 | return ''; |
||||
415 | } |
||||
416 | |||||
417 | 90 | return $dataReader->isRequired() ? $this->pagination : ''; |
|||
418 | } |
||||
419 | |||||
420 | 90 | private function renderSummary(): string |
|||
421 | { |
||||
422 | 90 | if ($this->getDataReader() instanceof KeysetPaginator) { |
|||
423 | 2 | return ''; |
|||
424 | } |
||||
425 | |||||
426 | /** @var OffsetPaginator $paginator */ |
||||
427 | 88 | $paginator = $this->getDataReader(); |
|||
428 | |||||
429 | 88 | $data = iterator_to_array($paginator->read()); |
|||
430 | 88 | $pageCount = count($data); |
|||
431 | |||||
432 | 88 | if ($pageCount <= 0) { |
|||
433 | 6 | return ''; |
|||
434 | } |
||||
435 | |||||
436 | 82 | $summary = $this->translator->translate( |
|||
437 | 82 | $this->summary ?? 'Page <b>{currentPage}</b> of <b>{totalPages}</b>', |
|||
438 | 82 | [ |
|||
439 | 82 | 'currentPage' => $paginator->getCurrentPage(), |
|||
440 | 82 | 'totalPages' => $paginator->getTotalPages(), |
|||
441 | 82 | ], |
|||
442 | 82 | $this->translationCategory, |
|||
443 | 82 | ); |
|||
444 | |||||
445 | 82 | return Div::tag()->attributes($this->summaryAttributes)->content($summary)->encode(false)->render(); |
|||
446 | } |
||||
447 | |||||
448 | 92 | private function renderGrid(): string |
|||
449 | { |
||||
450 | 92 | $attributes = $this->attributes; |
|||
451 | 92 | $contentGrid = ''; |
|||
452 | |||||
453 | 92 | if ($this->layout !== '') { |
|||
454 | 91 | $contentGrid = trim( |
|||
455 | 91 | strtr($this->layout, ['{header}' => $this->renderHeader(), '{toolbar}' => $this->toolbar]) |
|||
456 | 91 | ); |
|||
457 | } |
||||
458 | |||||
459 | 92 | return match ($this->withContainer) { |
|||
460 | 92 | true => trim( |
|||
461 | 92 | $contentGrid . PHP_EOL . Div::tag() |
|||
462 | 92 | ->attributes($attributes) |
|||
463 | 92 | ->content(PHP_EOL . $this->renderGridTable() . PHP_EOL) |
|||
464 | 92 | ->encode(false) |
|||
465 | 92 | ->render() |
|||
466 | 92 | ), |
|||
467 | 92 | false => trim($contentGrid . PHP_EOL . $this->renderGridTable()), |
|||
468 | 92 | }; |
|||
469 | } |
||||
470 | |||||
471 | 92 | private function renderGridTable(): string |
|||
472 | { |
||||
473 | 92 | return trim( |
|||
474 | 92 | strtr( |
|||
475 | 92 | $this->layoutGridTable, |
|||
476 | 92 | [ |
|||
477 | 92 | '{header}' => $this->renderHeader(), |
|||
478 | 92 | '{toolbar}' => $this->toolbar, |
|||
479 | 92 | '{items}' => $this->renderItems(), |
|||
480 | 92 | '{summary}' => $this->renderSummary(), |
|||
481 | 92 | '{pager}' => $this->renderPagination(), |
|||
482 | 92 | ], |
|||
483 | 92 | ) |
|||
484 | 92 | ); |
|||
485 | } |
||||
486 | |||||
487 | 92 | private function renderHeader(): string |
|||
488 | { |
||||
489 | 92 | return match ($this->header) { |
|||
490 | 92 | '' => '', |
|||
491 | 92 | default => Div::tag() |
|||
492 | 92 | ->attributes($this->headerAttributes) |
|||
493 | 92 | ->content($this->header) |
|||
494 | 92 | ->encode(false) |
|||
495 | 92 | ->render(), |
|||
496 | 92 | }; |
|||
497 | } |
||||
498 | |||||
499 | /** |
||||
500 | * Creates default translator to use if {@see $translator} was not set explicitly in the constructor. Depending on |
||||
501 | * "intl" extension availability, either {@see IntlMessageFormatter} or {@see SimpleMessageFormatter} is used as |
||||
502 | * formatter. |
||||
503 | * |
||||
504 | * @return Translator Translator instance used for translations of messages. |
||||
505 | */ |
||||
506 | 93 | private function createDefaultTranslator(): Translator |
|||
507 | { |
||||
508 | 93 | $categorySource = new CategorySource( |
|||
509 | 93 | $this->translationCategory, |
|||
510 | 93 | new IdMessageReader(), |
|||
511 | 93 | extension_loaded('intl') ? new IntlMessageFormatter() : new SimpleMessageFormatter(), |
|||
512 | 93 | ); |
|||
513 | 93 | $translator = new Translator(); |
|||
514 | 93 | $translator->addCategorySources($categorySource); |
|||
515 | |||||
516 | 93 | return $translator; |
|||
517 | } |
||||
518 | } |
||||
519 |