Passed
Pull Request — master (#78)
by
unknown
11:11
created

NavBar::innerContainerOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap5;
6
7
use Stringable;
8
use JsonException;
9
use Yiisoft\Arrays\ArrayHelper;
10
use Yiisoft\Html\Html;
11
12
/**
13
 * NavBar renders a navbar HTML component.
14
 *
15
 * Any content enclosed between the {@see begin()} and {@see end()} calls of NavBar is treated as the content of the
16
 * navbar. You may use widgets such as {@see Nav} or {@see \Yiisoft\Widget\Menu} to build up such content. For example,
17
 *
18
 * ```php
19
 *    if ($user->getId() !== null) {
20
 *        $menuItems = [
21
 *            [
22
 *                'label' => 'About',
23
 *                'url' => '/about',
24
 *            ],
25
 *            [
26
 *                'label' => 'Contact',
27
 *                'url' => '/contact',
28
 *            ],
29
 *            [
30
 *                'label' => 'Logout' . ' ' . '(' . $user->getUsername() . ')',
31
 *                'url' => '/logout'
32
 *            ],
33
 *        ];
34
 *    } else {
35
 *        $menuItems = [
36
 *            [
37
 *                'label' => 'About',
38
 *                'url' => '/about',
39
 *            ],
40
 *            [
41
 *                'label' => 'Contact',
42
 *                'url' => '/contact',
43
 *            ],
44
 *            [
45
 *                'label' => 'Login',
46
 *                'url' => '/login',
47
 *            ],
48
 *        ];
49
 *    }
50
 *
51
 *    <?php NavBar::widget()
52
 *        ->brandText('My Application Basic')
53
 *        ->brandUrl('/')
54
 *        ->options([
55
 *            'class' => 'navbar navbar-dark bg-dark navbar-expand-lg text-white',
56
 *        ])
57
 *        ->begin();
58
 *
59
 *        echo Nav::widget()
60
 *            ->currentPath($currentPath)
61
 *            ->items($menuItems)
62
 *            ->options([
63
 *                'class' => 'navbar-nav float-right ml-auto'
64
 *            ]);
65
 *
66
 *    echo NavBar::end(); ?>
67
 * ```
68
 * Note: $currentPath it must be injected from each controller to the main controller.
69
 *
70
 * SiteController.php
71
 *
72
 * ```php
73
 *
74
 *    public function index(ServerRequestInterface $request): ResponseInterface
75
 *    {
76
 *        $response = $this->responseFactory->createResponse();
77
 *        $currentPath = $request->getUri()->getPath();
78
 *        $output = $this->render('index', ['currentPath' => $currentPath]);
79
 *        $response->getBody()->write($output);
80
 *
81
 *        return $response;
82
 *    }
83
 * ```
84
 *
85
 * Controller.php
86
 *
87
 * ```php
88
 *    private function renderContent($content, array $parameters = []): string
89
 *    {
90
 *        $user = $this->user->getIdentity();
91
 *        $layout = $this->findLayoutFile($this->layout);
92
 *
93
 *        if ($layout !== null) {
94
 *            return $this->view->renderFile(
95
 *                $layout,
96
 *                    [
97
 *                        'aliases' => $this->aliases,
98
 *                        'content' => $content,
99
 *                        'user' => $user,
100
 *                        'params' => $this->params,
101
 *                        'currentPath' => !isset($parameters['currentPath']) ?: $parameters['currentPath']
102
 *                    ],
103
 *                $this
104
 *            );
105
 *        }
106
 *
107
 *        return $content;
108
 *    }
109
 * ```
110
 */
111
final class NavBar extends Widget
112
{
113
    public const EXPAND_SM = 'navbar-expand-sm';
114
    public const EXPAND_MD = 'navbar-expand-md';
115
    public const EXPAND_LG = 'navbar-expand-lg';
116
    public const EXPAND_XL = 'navbar-expand-xl';
117
    public const EXPAND_XXL = 'navbar-expand-xxl';
118
119
    private array $collapseOptions = [];
120
    private ?string $brandText = null;
121
    private ?string $brandImage = null;
122
    private array $brandImageAttributes = [];
123
    private ?string $brandUrl = '/';
124
    private array $brandOptions = [];
125
    private string $screenReaderToggleText = 'Toggle navigation';
126
    private string $togglerContent = '<span class="navbar-toggler-icon"></span>';
127
    private array $togglerOptions = [];
128
    private bool $renderInnerContainer = true;
129
    private array $innerContainerOptions = [];
130
    private array $options = [];
131
    private bool $encodeTags = false;
132
    private ?string $expandSize = self::EXPAND_LG;
133
134 16
    public function getId(?string $suffix = '-navbar'): ?string
135
    {
136 16
        return $this->options['id'] ?? parent::getId($suffix);
137
    }
138
139 16
    public function begin(): string
140
    {
141 16
        parent::begin();
142
143 16
        $options = $this->options;
144 16
        $options['id'] = $this->getId();
145 16
        $navTag = ArrayHelper::remove($options, 'tag', 'nav');
146 16
        $classNames = ['widget' => 'navbar'];
147
148 16
        if ($this->expandSize) {
149 15
            $classNames['size'] = $this->expandSize;
150
        }
151
152 16
        if (empty($options['class'])) {
153 15
            $classNames = array_merge($classNames, ['navbar-light', 'bg-light']);
154
        }
155
156 16
        Html::addCssClass($options, $classNames);
157
158 16
        if (!isset($this->innerContainerOptions['class'])) {
159 15
            Html::addCssClass($this->innerContainerOptions, ['innerContainerOptions' => 'container']);
160
        }
161
162 16
        $htmlStart = Html::openTag($navTag, $options) . "\n";
163
164 16
        if ($this->renderInnerContainer) {
165 15
            $htmlStart .= Html::openTag('div', $this->innerContainerOptions) . "\n";
166
        }
167
168 16
        $htmlStart .= $this->renderBrand() . "\n";
169
170 16
        if ($this->expandSize) {
171 15
            $collapseOptions = $this->collapseOptions;
172 15
            $collapseTag = ArrayHelper::remove($collapseOptions, 'tag', 'div');
173
174 15
            if (!isset($collapseOptions['id'])) {
175 15
                $collapseOptions['id'] = $options['id'] . '-collapse';
176
            }
177
178 15
            Html::addCssClass($collapseOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
179
180 15
            $htmlStart .= $this->renderToggleButton($collapseOptions) . "\n";
181 15
            $htmlStart .= Html::openTag($collapseTag, $collapseOptions) . "\n";
182
        }
183
184 16
        return $htmlStart;
185
    }
186
187 16
    protected function run(): string
188
    {
189 16
        $htmlRun = '';
190
191 16
        if ($this->expandSize) {
192 15
            $tag = ArrayHelper::getValue($this->collapseOptions, 'tag', 'div');
193 15
            $htmlRun .= Html::closeTag($tag) . "\n";
194
        }
195
196 16
        if ($this->renderInnerContainer) {
197 15
            $htmlRun .= Html::closeTag('div') . "\n";
198
        }
199
200 16
        $tag = ArrayHelper::getValue($this->options, 'tag', 'nav');
201
202 16
        $htmlRun .= Html::closeTag($tag);
203
204 16
        return $htmlRun;
205
    }
206
207
    /**
208
     * Set size before then content will be expanded
209
     *
210
     * @param string|null $size
211
     *
212
     * @return self
213
     */
214 2
    public function expandSize(?string $size): self
215
    {
216 2
        $new = clone $this;
217 2
        $new->expandSize = $size;
218
219 2
        return $new;
220
    }
221
222
    /**
223
     * The HTML attributes for the container tag. The following special options are recognized.
224
     *
225
     * @param array $value
226
     *
227
     * @return self
228
     *
229
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
230
     */
231 1
    public function collapseOptions(array $value): self
232
    {
233 1
        $new = clone $this;
234 1
        $new->collapseOptions = $value;
235
236 1
        return $new;
237
    }
238
239
    /**
240
     * The text of the brand or empty if it's not used. Note that this is not HTML-encoded.
241
     *
242
     * @param string|null $value
243
     *
244
     * @return self
245
     *
246
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#text
247
     */
248 7
    public function brandText(?string $value): self
249
    {
250 7
        $new = clone $this;
251 7
        $new->brandText = $value;
252
253 7
        return $new;
254
    }
255
256
    /**
257
     * Src of the brand image or empty if it's not used. Note that this param will override `$this->brandText` param.
258
     *
259
     * @param string|null $value
260
     *
261
     * @return self
262
     *
263
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#image
264
     */
265 3
    public function brandImage(?string $value): self
266
    {
267 3
        $new = clone $this;
268 3
        $new->brandImage = $value;
269
270 3
        return $new;
271
    }
272
273
    /**
274
     * Set attributes for brandImage
275
     *
276
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
277
     *
278
     * @param array $attributes
279
     *
280
     * @return self
281
     */
282 1
    public function brandImageAttributes(array $attributes): self
283
    {
284 1
        $new = clone $this;
285 1
        $new->brandImageAttributes = $attributes;
286
287 1
        return $new;
288
    }
289
290
    /**
291
     * The URL for the brand's hyperlink tag and will be used for the "href" attribute of the brand link. Default value
292
     * is "/". You may set it to empty string if you want no link at all.
293
     *
294
     * @param string|null $value
295
     *
296
     * @return self
297
     *
298
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#text
299
     */
300 6
    public function brandUrl(?string $value): self
301
    {
302 6
        $new = clone $this;
303 6
        $new->brandUrl = $value;
304
305 6
        return $new;
306
    }
307
308
    /**
309
     * The HTML attributes of the brand link.
310
     *
311
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
312
     *
313
     * @param array $value
314
     *
315
     * @return self
316
     */
317 1
    public function brandOptions(array $value): self
318
    {
319 1
        $new = clone $this;
320 1
        $new->brandOptions = $value;
321
322 1
        return $new;
323
    }
324
325
    /**
326
     * Text to show for screen readers for the button to toggle the navbar.
327
     *
328
     * @param string $value
329
     *
330
     * @return self
331
     */
332 1
    public function screenReaderToggleText(string $value): self
333
    {
334 1
        $new = clone $this;
335 1
        $new->screenReaderToggleText = $value;
336
337 1
        return $new;
338
    }
339
340
    /**
341
     * The toggle button content. Defaults to bootstrap 4 default `<span class="navbar-toggler-icon"></span>`.
342
     *
343
     * @param string $value
344
     *
345
     * @return self
346
     */
347 1
    public function togglerContent(string $value): self
348
    {
349 1
        $new = clone $this;
350 1
        $new->togglerContent = $value;
351
352 1
        return $new;
353
    }
354
355
    /**
356
     * The HTML attributes of the navbar toggler button.
357
     *
358
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
359
     *
360
     * @param array $value
361
     *
362
     * @return self
363
     */
364 1
    public function togglerOptions(array $value): self
365
    {
366 1
        $new = clone $this;
367 1
        $new->togglerOptions = $value;
368
369 1
        return $new;
370
    }
371
372
    /**
373
     * This for a 100% width navbar.
374
     *
375
     * @return self
376
     */
377 1
    public function withoutRenderInnerContainer(): self
378
    {
379 1
        $new = clone $this;
380 1
        $new->renderInnerContainer = false;
381
382 1
        return $new;
383
    }
384
385
    /**
386
     * The HTML attributes of the inner container.
387
     *
388
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
389
     *
390
     * @param array $value
391
     *
392
     * @return self
393
     */
394 1
    public function innerContainerOptions(array $value): self
395
    {
396 1
        $new = clone $this;
397 1
        $new->innerContainerOptions = $value;
398
399 1
        return $new;
400
    }
401
402
    /**
403
     * The HTML attributes for the widget container tag. The following special options are recognized.
404
     *
405
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
406
     *
407
     * @param array $value
408
     *
409
     * @return self
410
     */
411 2
    public function options(array $value): self
412
    {
413 2
        $new = clone $this;
414 2
        $new->options = $value;
415
416 2
        return $new;
417
    }
418
419 16
    private function renderBrand(): string
420
    {
421 16
        if (empty($this->brandImage) && empty($this->brandText)) {
422 7
            return '';
423
        }
424
425 9
        $content = '';
426 9
        $options = $this->brandOptions;
427 9
        $encode = ArrayHelper::remove($options, 'encode', $this->encodeTags);
428
429 9
        Html::addCssClass($options, ['widget' => 'navbar-brand']);
430
431 9
        if (!empty($this->brandImage)) {
432 3
            $encode = false;
433 3
            $content = Html::img($this->brandImage)->attributes($this->brandImageAttributes);
434
        }
435
436 9
        if (!empty($this->brandText)) {
437 7
            $content .= $this->brandText;
438
        }
439
        /** @var Stringable|string $content */
440 9
        if (empty($this->brandUrl)) {
441 1
            $brand = Html::span($content, $options);
442
        } else {
443 8
            $brand = Html::a($content, $this->brandUrl, $options);
444
        }
445
446 9
        return $brand->encode($encode)->render();
447
    }
448
449
    /**
450
     * Renders collapsible toggle button.
451
     *
452
     * @throws JsonException
453
     *
454
     * @return string the rendering toggle button.
455
     *
456
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#toggler
457
     */
458 15
    private function renderToggleButton(array $collapseOptions): string
459
    {
460 15
        $options = $this->togglerOptions;
461 15
        $encode = ArrayHelper::remove($options, 'encode', $this->encodeTags);
462 15
        Html::addCssClass($options, ['widget' => 'navbar-toggler']);
463
464 15
        return Html::button(
465 15
            $this->togglerContent,
466 15
            array_merge(
467 15
                $options,
468
                [
469 15
                    'type' => 'button',
470
                    'data' => [
471 15
                        'bs-toggle' => 'collapse',
472 15
                        'bs-target' => '#' . $collapseOptions['id'],
473
                    ],
474 15
                    'aria-controls' => $collapseOptions['id'],
475 15
                    'aria-expanded' => 'false',
476 15
                    'aria-label' => $this->screenReaderToggleText,
477
                ]
478
            )
479 15
        )->encode($encode)->render();
480
    }
481
}
482