Passed
Push — master ( 566566...d0f53d )
by Alexander
02:18
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 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
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
125 13
    public function begin(): string
126
    {
127 13
        parent::begin();
128
129 13
        if (!isset($this->options['id'])) {
130 13
            $id = $this->getId();
131 13
            $this->options['id'] = "{$id}-navbar";
132 13
            $this->collapseOptions['id'] = "{$id}-collapse";
133
        }
134
135 13
        if (empty($this->options['class'])) {
136
            /** @psalm-suppress InvalidArgument */
137 12
            Html::addCssClass($this->options, ['widget' => 'navbar', 'navbar-expand-lg', 'navbar-light', 'bg-light']);
138
        } else {
139
            /** @psalm-suppress InvalidArgument */
140 1
            Html::addCssClass($this->options, ['widget' => 'navbar']);
141
        }
142
143 13
        if ($this->encodeTags === false) {
144 12
            $this->collapseOptions['encode'] = false;
145 12
            $this->brandOptions['encode'] = false;
146 12
            $this->options['encode'] = false;
147 12
            $this->togglerOptions['encode'] = false;
148
        }
149
150 13
        $navOptions = $this->options;
151 13
        $navTag = ArrayHelper::remove($navOptions, 'tag', 'nav');
152 13
        $brand = '';
153
154 13
        if (!isset($this->innerContainerOptions['class'])) {
155 12
            Html::addCssClass($this->innerContainerOptions, ['innerContainerOptions' => 'container']);
156
        }
157
158 13
        if ($this->brandImage !== '') {
159 1
            $this->brandLabel = Html::img($this->brandImage);
160
        }
161
162 13
        if ($this->brandLabel !== '') {
163 6
            Html::addCssClass($this->brandOptions, ['widget' => 'navbar-brand']);
164 6
            if (empty($this->brandUrl)) {
165 1
                $brand = Html::span($this->brandLabel, $this->brandOptions);
166
            } else {
167 5
                $brand = Html::a(
168 5
                    $this->brandLabel,
169 5
                    $this->brandUrl,
170 5
                    $this->brandOptions
171
                );
172
            }
173
        }
174
175 13
        Html::addCssClass($this->collapseOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
176 13
        $collapseOptions = $this->collapseOptions;
177 13
        $collapseTag = ArrayHelper::remove($collapseOptions, 'tag', 'div');
178
179 13
        $htmlStart = Html::beginTag($navTag, $navOptions) . "\n";
180
181 13
        if ($this->renderInnerContainer) {
182 12
            $htmlStart .= Html::beginTag('div', $this->innerContainerOptions) . "\n";
183
        }
184
185 13
        $htmlStart .= $brand . "\n";
186 13
        $htmlStart .= $this->renderToggleButton() . "\n";
187
188 13
        $htmlStart .= Html::beginTag($collapseTag, $collapseOptions) . "\n";
189
190 13
        return $htmlStart;
191
    }
192
193 13
    protected function run(): string
194
    {
195 13
        $tag = ArrayHelper::remove($this->collapseOptions, 'tag', 'div');
196
197 13
        $htmlRun = Html::endTag($tag) . "\n";
198
199 13
        if ($this->renderInnerContainer) {
200 12
            $htmlRun .= Html::endTag('div') . "\n";
201
        }
202
203 13
        $tag = ArrayHelper::remove($this->options, 'tag', 'nav');
204
205 13
        $htmlRun .= Html::endTag($tag);
206
207 13
        return $htmlRun;
208
    }
209
210
    /**
211
     * The HTML attributes for the container tag. The following special options are recognized.
212
     *
213
     * @param array $value
214
     *
215
     * @return $this
216
     *
217
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
218
     */
219 1
    public function collapseOptions(array $value): self
220
    {
221 1
        $new = clone $this;
222 1
        $new->collapseOptions = $value;
223
224 1
        return $new;
225
    }
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 5
    public function brandLabel(string $value): self
237
    {
238 5
        $new = clone $this;
239 5
        $new->brandLabel = $value;
240
241 5
        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 1
    public function brandImage(string $value): self
254
    {
255 1
        $new = clone $this;
256 1
        $new->brandImage = $value;
257
258 1
        return $new;
259
    }
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 5
    public function brandUrl(string $value): self
270
    {
271 5
        $new = clone $this;
272 5
        $new->brandUrl = $value;
273
274 5
        return $new;
275
    }
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 1
    public function brandOptions(array $value): self
287
    {
288 1
        $new = clone $this;
289 1
        $new->brandOptions = $value;
290
291 1
        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 1
    public function screenReaderToggleText(string $value): self
302
    {
303 1
        $new = clone $this;
304 1
        $new->screenReaderToggleText = $value;
305
306 1
        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 1
    public function togglerContent(string $value): self
317
    {
318 1
        $new = clone $this;
319 1
        $new->togglerContent = $value;
320
321 1
        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 1
    public function togglerOptions(array $value): self
334
    {
335 1
        $new = clone $this;
336 1
        $new->togglerOptions = $value;
337
338 1
        return $new;
339
    }
340
341
    /**
342
     * This for a 100% width navbar.
343
     *
344
     * @return $this
345
     */
346 1
    public function withoutRenderInnerContainer(): self
347
    {
348 1
        $new = clone $this;
349 1
        $new->renderInnerContainer = false;
350
351 1
        return $new;
352
    }
353
354
    /**
355
     * The HTML attributes of the inner container.
356
     *
357
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
358
     *
359
     * @param array $value
360
     *
361
     * @return $this
362
     */
363 1
    public function innerContainerOptions(array $value): self
364
    {
365 1
        $new = clone $this;
366 1
        $new->innerContainerOptions = $value;
367
368 1
        return $new;
369
    }
370
371
    /**
372
     * The HTML attributes for the widget container tag. The following special options are recognized.
373
     *
374
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
375
     *
376
     * @param array $value
377
     *
378
     * @return $this
379
     */
380 1
    public function options(array $value): self
381
    {
382 1
        $new = clone $this;
383 1
        $new->options = $value;
384
385 1
        return $new;
386
    }
387
388
    /**
389
     * Allows you to enable the encoding tags html.
390
     *
391
     * @return self
392
     */
393 1
    public function encodeTags(): self
394
    {
395 1
        $new = clone $this;
396 1
        $new->encodeTags = true;
397
398 1
        return $new;
399
    }
400
401
    /**
402
     * Renders collapsible toggle button.
403
     *
404
     * @throws JsonException
405
     *
406
     * @return string the rendering toggle button.
407
     */
408 13
    private function renderToggleButton(): string
409
    {
410 13
        $options = $this->togglerOptions;
411
412 13
        Html::addCssClass($options, ['widget' => 'navbar-toggler']);
413
414 13
        return Html::button(
415 13
            $this->togglerContent,
416 13
            ArrayHelper::merge($options, [
417 13
                'type' => 'button',
418
                'data' => [
419 13
                    'bs-toggle' => 'collapse',
420 13
                    'bs-target' => '#' . $this->collapseOptions['id'],
421
                ],
422 13
                'aria-controls' => $this->collapseOptions['id'],
423 13
                'aria-expanded' => 'false',
424 13
                'aria-label' => $this->screenReaderToggleText,
425
            ])
426
        );
427
    }
428
}
429