Test Failed
Pull Request — master (#35)
by Wilmer
02:25
created

NavBar::innerContainerOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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