Passed
Push — master ( ce957f...2562fd )
by Alexander
02:19
created

NavBar::renderBrand()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5

Importance

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