NavBar   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Test Coverage

Coverage 75.86%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 88
c 1
b 0
f 0
dl 0
loc 286
ccs 66
cts 87
cp 0.7586
rs 10
wmc 22

14 Methods

Rating   Name   Duplication   Size   Complexity  
B begin() 0 57 8
A togglerContent() 0 5 1
A collapseOptions() 0 5 1
A innerContainerOptions() 0 5 1
A brandLabel() 0 5 1
A brandUrl() 0 5 1
A togglerOptions() 0 5 1
A brandImage() 0 5 1
A renderInnerContainer() 0 5 1
A brandOptions() 0 5 1
A renderToggleButton() 0 17 1
A options() 0 5 1
A screenReaderToggleText() 0 5 1
A run() 0 15 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap4;
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
 *        ->brandLabel('My Application Basic')
52
 *        ->brandUrl('/')
53
 *        ->options([
54
 *            'class' => 'navbar navbar-dark bg-dark navbar-expand-lg text-white',
55
 *        ])
56
 *        ->begin();
57
 *
58
 *        echo Nav::widget()
59
 *            ->currentPath($currentPath)
60
 *            ->items($menuItems)
61
 *            ->options([
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
class NavBar extends Widget
111
{
112
    private array $collapseOptions = [];
113
    private ?string $brandLabel = null;
114
    private ?string $brandImage = null;
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
124 5
    public function begin(): ?string
125
    {
126 5
        parent::begin();
127
128 5
        if (!isset($this->options['id'])) {
129 5
            $id = $this->getId();
130 5
            $this->options['id'] = "{$id}-navbar";
131 5
            $this->collapseOptions['id'] = "{$id}-collapse";
132
        }
133
134 5
        if (empty($this->options['class'])) {
135 4
            Html::addCssClass($this->options, ['widget' => 'navbar', 'navbar-expand-lg', 'navbar-light', 'bg-light']);
136
        } else {
137 1
            Html::addCssClass($this->options, ['widget' => 'navbar']);
138
        }
139
140 5
        $navOptions = $this->options;
141 5
        $navTag = ArrayHelper::remove($navOptions, 'tag', 'nav');
142 5
        $brand = '';
143
144 5
        if (!isset($this->innerContainerOptions['class'])) {
145 5
            Html::addCssClass($this->innerContainerOptions, 'container');
146
        }
147
148 5
        if ($this->brandImage !== null) {
149 1
            $this->brandLabel = Html::img($this->brandImage);
150
        }
151
152 5
        if ($this->brandLabel !== null) {
153 5
            Html::addCssClass($this->brandOptions, ['widget' => 'navbar-brand']);
154 5
            if (empty($this->brandUrl)) {
155 1
                $brand = Html::tag('span', $this->brandLabel, $this->brandOptions);
156
            } else {
157 4
                $brand = Html::a(
158 4
                    $this->brandLabel,
159 4
                    $this->brandUrl,
160 4
                    $this->brandOptions
161
                );
162
            }
163
        }
164
165 5
        Html::addCssClass($this->collapseOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
166 5
        $collapseOptions = $this->collapseOptions;
167 5
        $collapseTag = ArrayHelper::remove($collapseOptions, 'tag', 'div');
168
169 5
        $htmlStart = Html::beginTag($navTag, $navOptions) . "\n";
170
171 5
        if ($this->renderInnerContainer) {
172 5
            $htmlStart .= Html::beginTag('div', $this->innerContainerOptions) . "\n";
173
        }
174
175 5
        $htmlStart .= $brand . "\n";
176 5
        $htmlStart .= $this->renderToggleButton() . "\n";
177
178 5
        $htmlStart .= Html::beginTag($collapseTag, $collapseOptions) . "\n";
179
180 5
        return $htmlStart;
181
    }
182
183 5
    protected function run(): string
184
    {
185 5
        $tag = ArrayHelper::remove($this->collapseOptions, 'tag', 'div');
186
187 5
        $htmlRun = Html::endTag($tag) . "\n";
188
189 5
        if ($this->renderInnerContainer) {
190 5
            $htmlRun .= Html::endTag('div') . "\n";
191
        }
192
193 5
        $tag = ArrayHelper::remove($this->options, 'tag', 'nav');
194
195 5
        $htmlRun .= Html::endTag($tag);
196
197 5
        return $htmlRun;
198
    }
199
200
    /**
201
     * Renders collapsible toggle button.
202
     *
203
     * @throws JsonException
204
     *
205
     * @return string the rendering toggle button.
206
     */
207 5
    protected function renderToggleButton(): string
208
    {
209 5
        $options = $this->togglerOptions;
210
211 5
        Html::addCssClass($options, ['widget' => 'navbar-toggler']);
212
213 5
        return Html::button(
214 5
            $this->togglerContent,
215 5
            ArrayHelper::merge($options, [
216 5
                'type' => 'button',
217
                'data' => [
218 5
                    'toggle' => 'collapse',
219 5
                    'target' => '#' . $this->collapseOptions['id'],
220
                ],
221 5
                'aria-controls' => $this->collapseOptions['id'],
222 5
                'aria-expanded' => 'false',
223 5
                'aria-label' => $this->screenReaderToggleText,
224
            ])
225
        );
226
    }
227
228
    /**
229
     * The HTML attributes for the container tag. The following special options are recognized.
230
     *
231
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
232
     *
233
     * @param array $value
234
     *
235
     * @return $this
236
     */
237
    public function collapseOptions(array $value): self
238
    {
239
        $this->collapseOptions = $value;
240
241
        return $this;
242
    }
243
244
    /**
245
     * The text of the brand or empty if it's not used. Note that this is not HTML-encoded.
246
     *
247
     * @param string|null $value
248
     *
249
     * @return $this
250
     *
251
     * {@see https://getbootstrap.com/docs/4.2/components/navbar/}
252
     */
253 4
    public function brandLabel(?string $value): self
254
    {
255 4
        $this->brandLabel = $value;
256
257 4
        return $this;
258
    }
259
260
    /**
261
     * Src of the brand image or empty if it's not used. Note that this param will override `$this->brandLabel` param.
262
     *
263
     * @param string|null $value
264
     *
265
     * @return $this
266
     *
267
     * {@see https://getbootstrap.com/docs/4.2/components/navbar/}
268
     */
269 1
    public function brandImage(?string $value): self
270
    {
271 1
        $this->brandImage = $value;
272
273 1
        return $this;
274
    }
275
276
    /**
277
     * The URL for the brand's hyperlink tag and will be used for the "href" attribute of the brand link. Default value
278
     * is '/' will be used. You may set it to `null` if you want to have no link at all.
279
     *
280
     * @param string|null $value
281
     *
282
     * @return $this
283
     */
284 5
    public function brandUrl(?string $value): self
285
    {
286 5
        $this->brandUrl = $value;
287
288 5
        return $this;
289
    }
290
291
    /**
292
     * The HTML attributes of the brand link.
293
     *
294
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
295
     *
296
     * @param array $value
297
     *
298
     * @return $this
299
     */
300
    public function brandOptions(array $value): self
301
    {
302
        $this->brandOptions = $value;
303
304
        return $this;
305
    }
306
307
    /**
308
     * Text to show for screen readers for the button to toggle the navbar.
309
     *
310
     * @param string $value
311
     *
312
     * @return $this
313
     */
314
    public function screenReaderToggleText(string $value): self
315
    {
316
        $this->screenReaderToggleText = $value;
317
318
        return $this;
319
    }
320
321
    /**
322
     * The toggle button content. Defaults to bootstrap 4 default `<span class="navbar-toggler-icon"></span>`.
323
     *
324
     * @param string $value
325
     *
326
     * @return $this
327
     */
328
    public function togglerContent(string $value): self
329
    {
330
        $this->togglerContent = $value;
331
332
        return $this;
333
    }
334
335
    /**
336
     * The HTML attributes of the navbar toggler button.
337
     *
338
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
339
     *
340
     * @param array $value
341
     *
342
     * @return $this
343
     */
344
    public function togglerOptions(array $value): self
345
    {
346
        $this->togglerOptions = $value;
347
348
        return $this;
349
    }
350
351
    /**
352
     * Whether the navbar content should be included in an inner div container which by default adds left and right
353
     * padding. Set this to false for a 100% width navbar.
354
     *
355
     * @param bool $value
356
     *
357
     * @return $this
358
     */
359
    public function renderInnerContainer(bool $value): self
360
    {
361
        $this->renderInnerContainer = $value;
362
363
        return $this;
364
    }
365
366
    /**
367
     * The HTML attributes of the inner container.
368
     *
369
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
370
     *
371
     * @param array $value
372
     *
373
     * @return $this
374
     */
375
    public function innerContainerOptions(array $value): self
376
    {
377
        $this->innerContainerOptions = $value;
378
379
        return $this;
380
    }
381
382
    /**
383
     * The HTML attributes for the widget container tag. The following special options are recognized.
384
     *
385
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
386
     *
387
     * @param array $value
388
     *
389
     * @return $this
390
     */
391 1
    public function options(array $value): self
392
    {
393 1
        $this->options = $value;
394
395 1
        return $this;
396
    }
397
}
398