Passed
Pull Request — master (#47)
by Wilmer
02:14
created

NavBar::togglerOptions()   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 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
 *        ->brandText('My Application Basic')
52
 *        ->brandLink('/')
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
final class NavBar extends Widget
111
{
112
    private array $collapseOptions = [];
113
    private string $brandText = '';
114
    private string $brandImage = '';
115
    private string $brandLink = '/';
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
125 13
    public function begin(): string
126
    {
127 13
        parent::begin();
128
129 13
        if (!isset($this->options['id'])) {
130 13
            $this->options['id'] = "{$this->getId()}-navbar";
131 13
            $this->collapseOptions['id'] = "{$this->getId()}-collapse";
132
        }
133
134 13
        if (empty($this->options['class'])) {
135 12
            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 13
        $navTag = ArrayHelper::remove($this->options, 'tag', 'nav');
141 13
        $brand = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $brand is dead and can be removed.
Loading history...
142
143 13
        if (!isset($this->innerContainerOptions['class'])) {
144 12
            Html::addCssClass($this->innerContainerOptions, ['innerContainerOptions' => 'container']);
145
        }
146
147 13
        Html::addCssClass($this->collapseOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
148
149 13
        $collapseTag = ArrayHelper::remove($this->collapseOptions, 'tag', 'div');
150
151 13
        $htmlStart = Html::openTag($navTag, $this->options) . "\n";
152
153 13
        if ($this->renderInnerContainer) {
154 12
            $htmlStart .= Html::openTag('div', $this->innerContainerOptions) . "\n";
155
        }
156
157 13
        $htmlStart .= $this->renderBrand() . "\n";
158 13
        $htmlStart .= $this->renderToggleButton() . "\n";
159 13
        $htmlStart .= Html::openTag($collapseTag, $this->collapseOptions) . "\n";
160
161 13
        return $htmlStart;
162
    }
163
164 13
    protected function run(): string
165
    {
166 13
        $tag = ArrayHelper::remove($this->collapseOptions, 'tag', 'div');
167
168 13
        $htmlRun = Html::closeTag($tag) . "\n";
169
170 13
        if ($this->renderInnerContainer) {
171 12
            $htmlRun .= Html::closeTag('div') . "\n";
172
        }
173
174 13
        $tag = ArrayHelper::remove($this->options, 'tag', 'nav');
175
176 13
        $htmlRun .= Html::closeTag($tag);
177
178 13
        return $htmlRun;
179
    }
180
181
    /**
182
     * The HTML attributes for the container tag. The following special options are recognized.
183
     *
184
     * @param array $value
185
     *
186
     * @return $this
187
     *
188
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
189
     */
190 1
    public function collapseOptions(array $value): self
191
    {
192 1
        $new = clone $this;
193 1
        $new->collapseOptions = $value;
194
195 1
        return $new;
196
    }
197
198
    /**
199
     * The text of the brand or empty if it's not used. Note that this is not HTML-encoded.
200
     *
201
     * @param string $value
202
     *
203
     * @return $this
204
     *
205
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#text
206
     */
207 6
    public function brandText(string $value): self
208
    {
209 6
        $new = clone $this;
210 6
        $new->brandText = $value;
211
212 6
        return $new;
213
    }
214
215
    /**
216
     * Src of the brand image or empty if it's not used. Note that this param will override `$this->brandText` param.
217
     *
218
     * @param string $value
219
     *
220
     * @return $this
221
     *
222
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#image
223
     */
224 2
    public function brandImage(string $value): self
225
    {
226 2
        $new = clone $this;
227 2
        $new->brandImage = $value;
228
229 2
        return $new;
230
    }
231
232
    /**
233
     * The Link for the brand's hyperlink tag and will be used for the "href" attribute of the brand link. Default value
234
     * is '/' will be used. You may set it to `` if you want to have no link at all.
235
     *
236
     * @param string $value
237
     *
238
     * @return $this
239
     *
240
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#text
241
     */
242 5
    public function brandLink(string $value): self
243
    {
244 5
        $new = clone $this;
245 5
        $new->brandLink = $value;
246
247 5
        return $new;
248
    }
249
250
    /**
251
     * The HTML attributes of the brand link.
252
     *
253
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
254
     *
255
     * @param array $value
256
     *
257
     * @return $this
258
     */
259 1
    public function brandOptions(array $value): self
260
    {
261 1
        $new = clone $this;
262 1
        $new->brandOptions = $value;
263
264 1
        return $new;
265
    }
266
267
    /**
268
     * Text to show for screen readers for the button to toggle the navbar.
269
     *
270
     * @param string $value
271
     *
272
     * @return $this
273
     */
274 1
    public function screenReaderToggleText(string $value): self
275
    {
276 1
        $new = clone $this;
277 1
        $new->screenReaderToggleText = $value;
278
279 1
        return $new;
280
    }
281
282
    /**
283
     * The toggle button content. Defaults to bootstrap 4 default `<span class="navbar-toggler-icon"></span>`.
284
     *
285
     * @param string $value
286
     *
287
     * @return $this
288
     */
289 1
    public function togglerContent(string $value): self
290
    {
291 1
        $new = clone $this;
292 1
        $new->togglerContent = $value;
293
294 1
        return $new;
295
    }
296
297
    /**
298
     * The HTML attributes of the navbar toggler button.
299
     *
300
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
301
     *
302
     * @param array $value
303
     *
304
     * @return $this
305
     */
306 1
    public function togglerOptions(array $value): self
307
    {
308 1
        $new = clone $this;
309 1
        $new->togglerOptions = $value;
310
311 1
        return $new;
312
    }
313
314
    /**
315
     * This for a 100% width navbar.
316
     *
317
     * @return $this
318
     */
319 1
    public function withoutRenderInnerContainer(): self
320
    {
321 1
        $new = clone $this;
322 1
        $new->renderInnerContainer = false;
323
324 1
        return $new;
325
    }
326
327
    /**
328
     * The HTML attributes of the inner container.
329
     *
330
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
331
     *
332
     * @param array $value
333
     *
334
     * @return $this
335
     */
336 1
    public function innerContainerOptions(array $value): self
337
    {
338 1
        $new = clone $this;
339 1
        $new->innerContainerOptions = $value;
340
341 1
        return $new;
342
    }
343
344
    /**
345
     * The HTML attributes for the widget container tag. The following special options are recognized.
346
     *
347
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
348
     *
349
     * @param array $value
350
     *
351
     * @return $this
352
     */
353 1
    public function options(array $value): self
354
    {
355 1
        $new = clone $this;
356 1
        $new->options = $value;
357
358 1
        return $new;
359
    }
360
361 13
    private function renderBrand(): string
362
    {
363 13
        $brand = '';
364 13
        $brandImage = '';
365
366 13
        Html::addCssClass($this->brandOptions, ['widget' => 'navbar-brand']);
367
368 13
        if ($this->brandImage !== '') {
369 2
            $brandImage = Html::img($this->brandImage)->render();
370 2
            $brand = Html::a($brandImage, $this->brandLink, $this->brandOptions)
371 2
                ->encode($this->encodeTags)
372 2
                ->render();
373
        }
374
375 13
        if ($this->brandText !== '') {
376 6
            $brandText = $this->brandText;
377
378 6
            if ($brandImage !== '') {
379 1
                $brandText = $brandImage . $this->brandText;
380
            }
381
382 6
            if (empty($this->brandLink)) {
383 1
                $brand = Html::span($brandText, $this->brandOptions)->render();
384
            } else {
385 5
                $brand = Html::a($brandText, $this->brandLink, $this->brandOptions)
386 5
                    ->encode($this->encodeTags)
387 5
                    ->render();
388
            }
389
        }
390
391 13
        return $brand;
392
    }
393
394
    /**
395
     * Renders collapsible toggle button.
396
     *
397
     * @throws JsonException
398
     *
399
     * @return string the rendering toggle button.
400
     *
401
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#toggler
402
     */
403 13
    private function renderToggleButton(): string
404
    {
405 13
        Html::addCssClass($this->togglerOptions, ['widget' => 'navbar-toggler']);
406
407 13
        return Html::button(
408 13
            $this->togglerContent,
409 13
            array_merge(
410 13
                $this->togglerOptions,
411
                [
412 13
                    'type' => 'button',
413
                    'data' => [
414 13
                        'bs-toggle' => 'collapse',
415 13
                        'bs-target' => '#' . $this->collapseOptions['id'],
416
                    ],
417 13
                    'aria-controls' => $this->collapseOptions['id'],
418 13
                    'aria-expanded' => 'false',
419 13
                    'aria-label' => $this->screenReaderToggleText,
420
                ]
421
            )
422 13
        )->encode($this->encodeTags)->render();
423
    }
424
}
425