Passed
Pull Request — master (#79)
by Wilmer
02:24
created

Breadcrumbs::render()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 5
nop 0
dl 0
loc 28
ccs 18
cts 18
cp 1
crap 4
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use InvalidArgumentException;
8
use Yiisoft\Html\Html;
9
use Yiisoft\Html\Tag\A;
10
use Yiisoft\Html\Tag\CustomTag;
11
use Yiisoft\Html\Tag\I;
12
use Yiisoft\Html\Tag\Span;
13
use Yiisoft\Widget\Widget;
14
15
use function array_key_exists;
16
use function implode;
17
use function is_array;
18
use function strtr;
19
20
/**
21
 * The Bulma breadcrumb is a simple navigation component.
22
 *
23
 * ```php
24
 * echo Breadcrumbs::widget()->items([
25
 *     ['label' => 'Info'],
26
 *     ['label' => 'Contacts'],
27
 * ]);
28
 * ```
29
 *
30
 * @link https://bulma.io/documentation/components/breadcrumb/
31
 */
32
final class Breadcrumbs extends Widget
33
{
34
    private string $activeItemTemplate = "<li class=\"is-active\"><a aria-current=\"page\">{link}</a></li>\n";
35
    private array $attributes = [];
36
    private string $autoIdPrefix = 'w';
37
    private bool $encode = false;
38
    private ?array $homeItem = ['label' => 'Home', 'url' => '/'];
39
    private array $items = [];
40
    private array $itemsAttributes = [];
41
    private string $itemTemplate = "<li>{link}</li>\n";
42
43
    /**
44
     * Returns a new instance with the specified active item template.
45
     *
46
     * @param string $value The template used to render each active item in the breadcrumbs. The token `{link}` will be
47
     * replaced with the actual HTML link for each active item.
48
     *
49
     * @return self
50
     */
51 2
    public function activeItemTemplate(string $value): self
52
    {
53 2
        $new = clone $this;
54 2
        $new->activeItemTemplate = $value;
55 2
        return $new;
56
    }
57
58
    /**
59
     * Returns a new instance with the specified `aria-label` attribute for the current element.
60
     *
61
     * @param string $value The value of the aria-label attribute.
62
     *
63
     * @return self
64
     *
65
     * @link https://www.w3.org/TR/wai-aria/#aria-label
66
     */
67 2
    public function ariaLabel(string $value): self
68
    {
69 2
        $new = clone $this;
70 2
        $new->attributes['aria-label'] = $value;
71 2
        return $new;
72
    }
73
74
    /**
75
     * Returns a new instance with the specified HTML attributes for widget.
76
     *
77
     * @param array $values Attribute values indexed by attribute names.
78
     *
79
     * @return self
80
     *
81
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} For details on how attributes are being rendered.
82
     */
83 3
    public function attributes(array $values): self
84
    {
85 3
        $new = clone $this;
86 3
        $new->attributes = $values;
87 3
        return $new;
88
    }
89
90
    /**
91
     * Returns a new instance with the specified prefix to the automatically generated widget IDs.
92
     *
93
     * @param string $value The prefix to the automatically generated widget IDs.
94
     *
95
     * @return self
96
     */
97 1
    public function autoIdPrefix(string $value): self
98
    {
99 1
        $new = clone $this;
100 1
        $new->autoIdPrefix = $value;
101 1
        return $new;
102
    }
103
104
    /**
105
     * Returns a new instance with the specified whether the tags for the breadcrumbs are encoded.
106
     *
107
     * @param bool $value Whether to encode the output.
108
     *
109
     * @return self
110
     */
111 2
    public function encode(bool $value): self
112
    {
113 2
        $new = clone $this;
114 2
        $new->encode = $value;
115 2
        return $new;
116
    }
117
118
    /**
119
     * Returns a new instance with the specified first item in the breadcrumbs (called home link).
120
     *
121
     * If a `null` is specified, the home item will not be rendered.
122
     *
123
     * @param array|null $value Please refer to {@see items()} on the format.
124
     *
125
     * @throws InvalidArgumentException If an empty array is specified.
126
     *
127
     * @return self
128
     */
129 10
    public function homeItem(?array $value): self
130
    {
131 10
        if ($value === []) {
132 1
            throw new InvalidArgumentException(
133 1
                'The home item cannot be an empty array. To disable rendering of the home item, specify null.',
134 1
            );
135
        }
136
137 9
        $new = clone $this;
138 9
        $new->homeItem = $value;
139 9
        return $new;
140
    }
141
142
    /**
143
     * Returns a new instance with the specified ID of the widget.
144
     *
145
     * @param string $value The ID of the widget.
146
     *
147
     * @return self
148
     */
149 1
    public function id(string $value): self
150
    {
151 1
        $new = clone $this;
152 1
        $new->attributes['id'] = $value;
153 1
        return $new;
154
    }
155
156
    /**
157
     * Returns a new instance with the specified list of items.
158
     *
159
     * @param array $value List of items to appear in the breadcrumbs. If this property is empty, the widget will not
160
     * render anything. Each array element represents a single item in the breadcrumbs with the following structure:
161
     *
162
     * ```php
163
     * [
164
     *     'label' => 'label of the link', // required
165
     *     'url' => 'url of the link', // optional, will be processed by Url::to()
166
     *     'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
167
     *     'encode' => true/false, // optional, is encoded is `true`, the tags will be encoded
168
     *     'icon' => 'icon css class', // optional, icon css class
169
     *     'iconAttributes' => [], // the html attributes for icon container
170
     * ]
171
     * ```
172
     *
173
     * @return self
174
     */
175 15
    public function items(array $value): self
176
    {
177 15
        $new = clone $this;
178 15
        $new->items = $value;
179 15
        return $new;
180
    }
181
182
    /**
183
     * Returns a new instance with the specified items HTML attributes.
184
     *
185
     * @param array $value The HTML attributes for the item's widget.
186
     *
187
     * @return self
188
     *
189
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
190
     */
191 2
    public function itemsAttributes(array $value): self
192
    {
193 2
        $new = clone $this;
194 2
        $new->itemsAttributes = $value;
195 2
        return $new;
196
    }
197
198
    /**
199
     * Returns a new instance with the specified item template.
200
     *
201
     * @param string $value The template used to render each inactive item in the breadcrumbs. The token `{link}` will
202
     * be replaced with the actual HTML link for each inactive item.
203
     *
204
     * @return self
205
     */
206 2
    public function itemTemplate(string $value): self
207
    {
208 2
        $new = clone $this;
209 2
        $new->itemTemplate = $value;
210 2
        return $new;
211
    }
212
213 14
    public function render(): string
214
    {
215 14
        if (empty($this->items)) {
216 1
            return '';
217
        }
218
219 13
        $attributes = $this->attributes;
220 13
        $customTag = CustomTag::name('nav');
221
222 13
        Html::addCssClass($attributes, 'breadcrumb');
223
224 13
        if (!array_key_exists('aria-label', $attributes)) {
225 12
            $customTag = $customTag->attribute('aria-label', 'breadcrumbs');
226
        }
227
228 13
        if (!array_key_exists('id', $attributes)) {
229 13
            $customTag = $customTag->id(Html::generateId($this->autoIdPrefix) . '-breadcrumbs');
230
        }
231
232 13
        $content = PHP_EOL . Html::openTag('ul', $this->itemsAttributes) . PHP_EOL .
233 13
            implode('', $this->renderItems()) .
234 13
            Html::closeTag('ul') . PHP_EOL;
235
236 12
        return $customTag
237 12
            ->addAttributes($attributes)
238 12
            ->content($content)
239 12
            ->encode(false)
240 12
            ->render();
241
    }
242
243 13
    private function renderIcon(?string $icon, array $iconAttributes): string
244
    {
245 13
        $html = '';
246
247 13
        if ($icon !== null) {
248 1
            $html = Span::tag()
249 1
                ->attributes($iconAttributes)
250 1
                ->content(I::tag()
251 1
                    ->attributes(['class' => $icon, 'aria-hidden' => 'true'])
252 1
                    ->render())
253 1
                ->encode($this->encode)
254 1
                ->render();
255
        }
256
257 13
        return $html;
258
    }
259
260
    /**
261
     * Renders a single breadcrumb item.
262
     *
263
     * @param array $item The item to be rendered. It must contain the "label" element. The "url" element is optional.
264
     * @param string $template The template to be used to render the link. The token "{link}" will be replaced by the
265
     * link.
266
     *
267
     * @throws InvalidArgumentException If `$item` does not have "label" element.
268
     *
269
     * @return string The rendering result.
270
     */
271 13
    private function renderItem(array $item, string $template): string
272
    {
273 13
        if (!array_key_exists('label', $item)) {
274 1
            throw new InvalidArgumentException('The "label" element is required for each link.');
275
        }
276
277
        /** @var bool */
278 13
        $encode = $item['encode'] ?? $this->encode;
279 13
        unset($item['encode']);
280
281
        /** @var string|null */
282 13
        $icon = $item['icon'] ?? null;
283 13
        unset($item['icon']);
284
285
        /** @var array */
286 13
        $iconAttributes = $item['iconAttributes'] ?? [];
287 13
        unset($item['iconAttributes']);
288
289
        /** @var string */
290 13
        $template = $item['template'] ?? $template;
291 13
        unset($item['template']);
292
293
        /** @var string|null */
294 13
        $url = $item['url'] ?? null;
295 13
        unset($item['url']);
296
297
        /** @var string */
298 13
        $label = $item['label'];
299 13
        unset($item['label']);
300
301 13
        $icon = $this->renderIcon($icon, $iconAttributes);
302
303 13
        if ($icon !== '') {
304 1
            $label = $icon . Span::tag()
305 1
                    ->content($label)
306 1
                    ->render();
307
        }
308
309 13
        $link = $url !== null
310 13
            ? A::tag()
311 13
                ->attributes($item)
312 13
                ->content($label)
313 13
                ->url($url)
314 13
                ->encode($encode)
315 13
                ->render() : $label;
316
317 13
        return strtr($template, ['{link}' => $link, '{label}' => $label, '{icon}' => $icon]);
318
    }
319
320
    /**
321
     * @psalm-return string[]
322
     */
323 13
    private function renderItems(): array
324
    {
325 13
        $renderItems = [];
326
327 13
        if ($this->homeItem !== null) {
328 12
            $renderItems[] = $this->renderItem($this->homeItem, $this->itemTemplate);
329
        }
330
331
        /** @psalm-var string[]|string $item */
332 13
        foreach ($this->items as $item) {
333 13
            if (!is_array($item)) {
334 1
                $item = ['label' => $item];
335
            }
336
337 13
            $renderItems[] = $this->renderItem(
338 13
                $item,
339 13
                isset($item['url']) ? $this->itemTemplate : $this->activeItemTemplate,
340 13
            );
341
        }
342
343 12
        return $renderItems;
344
    }
345
}
346