Passed
Pull Request — master (#35)
by Wilmer
02:20
created

Modal::footerOptions()   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
use function array_merge;
12
13
/**
14
 * Modal renders a modal window that can be toggled by clicking on a button.
15
 *
16
 * The following example will show the content enclosed between the {@see begin()} and {@see end()} calls within the
17
 * modal window:
18
 *
19
 * ```php
20
 * Modal::widget()
21
 *     ->withTitle('<h2>Hello world</h2>')
22
 *     ->withToggleButton(['label' => 'click me'])
23
 *     ->begin();
24
 *
25
 * echo 'Say hello...';
26
 *
27
 * echo Modal::end();
28
 * ```
29
 */
30
final class Modal extends Widget
31
{
32
    /**
33
     * The additional css class of large modal
34
     */
35
    public const SIZE_LARGE = 'modal-lg';
36
37
    /**
38
     * The additional css class of small modal
39
     */
40
    public const SIZE_SMALL = 'modal-sm';
41
42
    /**
43
     * The additional css class of default modal
44
     */
45
    public const SIZE_DEFAULT = '';
46
47
    private string $title = '';
48
    private array $titleOptions = [];
49
    private array $headerOptions = [];
50
    private array $bodyOptions = [];
51
    private string $footer = '';
52
    private array $footerOptions = [];
53
    private string $size = '';
54
    private array $closeButton = [];
55
    private bool $closeButtonEnabled = true;
56
    private array $toggleButton = [];
57
    private bool $toggleButtonEnabled = true;
58
    private array $options = [];
59
    private bool $encodeTags = false;
60
61 13
    public function begin(): string
62
    {
63 13
        parent::begin();
64
65 13
        if (!isset($this->options['id'])) {
66 13
            $this->options['id'] = "{$this->getId()}-modal";
67
        }
68
69 13
        if ($this->encodeTags === false) {
70 12
            $this->options = array_merge($this->options, ['encode' => false]);
71
        }
72
73 13
        $this->initOptions();
74
75
        return
76 13
            $this->renderToggleButton() . "\n" .
77 13
            Html::beginTag('div', $this->options) . "\n" .
78 13
            Html::beginTag('div', ['class' => 'modal-dialog ' . $this->size]) . "\n" .
79 13
            Html::beginTag('div', ['class' => 'modal-content']) . "\n" .
80 13
            $this->renderHeader() . "\n" .
81 13
            $this->renderBodyBegin() . "\n";
82
    }
83
84 13
    protected function run(): string
85
    {
86
        return
87 13
            "\n" . $this->renderBodyEnd() .
88 13
            "\n" . $this->renderFooter() .
89 13
            "\n" . Html::endTag('div') . // modal-content
90 13
            "\n" . Html::endTag('div') . // modal-dialog
91 13
            "\n" . Html::endTag('div');
92
    }
93
94
    /**
95
     * Body options.
96
     *
97
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
98
     *
99
     * @param array $value
100
     *
101
     * @return $this
102
     */
103 1
    public function withBodyOptions(array $value): self
104
    {
105 1
        $new = clone $this;
106 1
        $new->bodyOptions = $value;
107
108 1
        return $new;
109
    }
110
111
    /**
112
     * The options for rendering the close button tag.
113
     *
114
     * The close button is displayed in the header of the modal window. Clicking on the button will hide the modal
115
     * window. If {@see closeButtonEnabled} is false, no close button will be rendered.
116
     *
117
     * The following special options are supported:
118
     *
119
     * - tag: string, the tag name of the button. Defaults to 'button'.
120
     * - label: string, the label of the button. Defaults to '&times;'.
121
     *
122
     * The rest of the options will be rendered as the HTML attributes of the button tag. Please refer to the
123
     * [Modal plugin help](http://getbootstrap.com/javascript/#modals) for the supported HTML attributes.
124
     *
125
     * @param array $value
126
     *
127
     * @return $this
128
     */
129 1
    public function withCloseButton(array $value): self
130
    {
131 1
        $new = clone $this;
132 1
        $new->closeButton = $value;
133
134 1
        return $new;
135
    }
136
137
    /**
138
     * Disable close button.
139
     *
140
     * @param bool $value
141
     *
142
     * @return $this
143
     */
144 1
    public function withoutCloseButton(): self
145
    {
146 1
        $new = clone $this;
147 1
        $new->closeButtonEnabled = false;
148
149 1
        return $new;
150
    }
151
152
    /**
153
     * The footer content in the modal window.
154
     *
155
     * @param string $value
156
     *
157
     * @return $this
158
     */
159 2
    public function withFooter(string $value): self
160
    {
161 2
        $new = clone $this;
162 2
        $new->footer = $value;
163
164 2
        return $new;
165
    }
166
167
    /**
168
     * Additional footer options.
169
     *
170
     * @param array $value
171
     *
172
     * @return $this
173
     *
174
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
175
     */
176 1
    public function withFooterOptions(array $value): self
177
    {
178 1
        $new = clone $this;
179 1
        $new->footerOptions = $value;
180
181 1
        return $new;
182
    }
183
184
    /**
185
     * Additional header options.
186
     *
187
     * @param array $value
188
     *
189
     * @return $this
190
     *
191
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
192
     */
193 1
    public function withHeaderOptions(array $value): self
194
    {
195 1
        $new = clone $this;
196 1
        $new->headerOptions = $value;
197
198 1
        return $new;
199
    }
200
201
    /**
202
     * @param array $value the HTML attributes for the widget container tag. The following special options are
203
     * recognized.
204
     *
205
     * @return $this
206
     *
207
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
208
     */
209 1
    public function withOptions(array $value): self
210
    {
211 1
        $new = clone $this;
212 1
        $new->options = $value;
213
214 1
        return $new;
215
    }
216
217
    /**
218
     * The title content in the modal window.
219
     *
220
     * @param string $value
221
     *
222
     * @return $this
223
     */
224 2
    public function withTitle(string $value): self
225
    {
226 2
        $new = clone $this;
227 2
        $new->title = $value;
228
229 2
        return $new;
230
    }
231
232
    /**
233
     * Additional title options.
234
     *
235
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
236
     *
237
     * @param array $value
238
     *
239
     * @return $this
240
     */
241 1
    public function withTitleOptions(array $value): self
242
    {
243 1
        $new = clone $this;
244 1
        $new->titleOptions = $value;
245
246 1
        return $new;
247
    }
248
249
    /**
250
     * The options for rendering the toggle button tag.
251
     *
252
     * The toggle button is used to toggle the visibility of the modal window. If {@see toggleButtonEnabled} is false,
253
     * no toggle button will be rendered.
254
     *
255
     * The following special options are supported:
256
     *
257
     * - tag: string, the tag name of the button. Defaults to 'button'.
258
     * - label: string, the label of the button. Defaults to 'Show'.
259
     *
260
     * The rest of the options will be rendered as the HTML attributes of the button tag. Please refer to the
261
     * [Modal plugin help](http://getbootstrap.com/javascript/#modals) for the supported HTML attributes.
262
     *
263
     * @param array $value
264
     *
265
     * @return $this
266
     */
267 1
    public function withToggleButton(array $value): self
268
    {
269 1
        $new = clone $this;
270 1
        $new->toggleButton = $value;
271
272 1
        return $new;
273
    }
274
275
    /**
276
     * Disable toggle button.
277
     *
278
     * @return $this
279
     */
280 1
    public function withoutToggleButton(): self
281
    {
282 1
        $new = clone $this;
283 1
        $new->toggleButtonEnabled = false;
284
285 1
        return $new;
286
    }
287
288
    /**
289
     * The modal size. Can be {@see SIZE_LARGE} or {@see SIZE_SMALL}, or null for default.
290
     *
291
     * @param string $value
292
     *
293
     * @return $this
294
     */
295 1
    public function withSize(string $value): self
296
    {
297 1
        $new = clone $this;
298 1
        $new->size = $value;
299
300 1
        return $new;
301
    }
302
303
    /**
304
     * Allows you to enable or disable the encoding tags html.
305
     *
306
     * @return self
307
     */
308 1
    public function withEncodeTags(): self
309
    {
310 1
        $new = clone $this;
311 1
        $new->encodeTags = true;
312
313 1
        return $new;
314
    }
315
316
    /**
317
     * Renders the header HTML markup of the modal.
318
     *
319
     * @throws JsonException
320
     *
321
     * @return string the rendering result
322
     */
323 13
    private function renderHeader(): string
324
    {
325 13
        $button = $this->renderCloseButton();
326
327 13
        if ($this->title !== '') {
328 2
            Html::addCssClass($this->titleOptions, ['titleOptions' => 'modal-title']);
329
        }
330
331 13
        $header = ($this->title === '') ? '' : Html::tag('h5', $this->title, $this->titleOptions);
332
333 13
        if ($button !== null) {
334 12
            $header .= "\n" . $button;
335 1
        } elseif ($header === '') {
336 1
            return '';
337
        }
338
339 12
        Html::addCssClass($this->headerOptions, ['headerOptions' => 'modal-header']);
340
341 12
        if ($this->encodeTags === false) {
342 11
            $this->headerOptions = array_merge($this->headerOptions, ['encode' => false]);
343
        }
344
345 12
        return Html::div($header, $this->headerOptions);
346
    }
347
348
    /**
349
     * Renders the opening tag of the modal body.
350
     *
351
     * @throws JsonException
352
     *
353
     * @return string the rendering result
354
     */
355 13
    private function renderBodyBegin(): string
356
    {
357 13
        Html::addCssClass($this->bodyOptions, ['widget' => 'modal-body']);
358
359 13
        if ($this->encodeTags === false) {
360 12
            $this->bodyOptions = array_merge($this->bodyOptions, ['encode' => false]);
361
        }
362
363 13
        return Html::beginTag('div', $this->bodyOptions);
364
    }
365
366
    /**
367
     * Renders the closing tag of the modal body.
368
     *
369
     * @return string the rendering result
370
     */
371 13
    private function renderBodyEnd(): string
372
    {
373 13
        return Html::endTag('div');
374
    }
375
376
    /**
377
     * Renders the HTML markup for the footer of the modal.
378
     *
379
     * @throws JsonException
380
     *
381
     * @return string the rendering result
382
     */
383 13
    private function renderFooter(): string
384
    {
385 13
        if ($this->footer === '') {
386 11
            return '';
387
        }
388
389 2
        Html::addCssClass($this->footerOptions, ['widget' => 'modal-footer']);
390
391 2
        if ($this->encodeTags === false) {
392 2
            $this->footerOptions = array_merge($this->footerOptions, ['encode' => false]);
393
        }
394
395 2
        return Html::div($this->footer, $this->footerOptions);
396
    }
397
398
    /**
399
     * Renders the toggle button.
400
     *
401
     * @throws JsonException
402
     *
403
     * @return string the rendering result
404
     */
405 13
    private function renderToggleButton(): ?string
406
    {
407 13
        if ($this->toggleButtonEnabled === false) {
408 1
            return null;
409
        }
410
411 12
        $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button');
412 12
        $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show');
413
414 12
        if ($this->encodeTags === false) {
415 11
            $this->toggleButton = array_merge($this->toggleButton, ['encode' => false]);
416
        }
417
418 12
        return Html::tag($tag, $label, $this->toggleButton);
419
    }
420
421
    /**
422
     * Renders the close button.
423
     *
424
     * @throws JsonException
425
     *
426
     * @return string the rendering result
427
     */
428 13
    private function renderCloseButton(): ?string
429
    {
430 13
        if ($this->closeButtonEnabled === false) {
431 1
            return null;
432
        }
433
434 12
        $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
435 12
        $label = ArrayHelper::remove($this->closeButton, 'label', Html::span('&times;', [
436 12
            'aria-hidden' => 'true',
437
        ]));
438
439 12
        if ($this->encodeTags === false) {
440 11
            $this->closeButton = array_merge($this->closeButton, ['encode' => false]);
441
        }
442
443 12
        return Html::tag($tag, $label, $this->closeButton);
444
    }
445
446
    /**
447
     * Initializes the widget options.
448
     *
449
     * This method sets the default values for various options.
450
     */
451 13
    private function initOptions(): void
452
    {
453 13
        $this->options = array_merge([
454 13
            'class' => 'fade',
455
            'role' => 'dialog',
456
            'tabindex' => -1,
457
            'aria-hidden' => 'true',
458 13
        ], $this->options);
459
460
        /** @psalm-suppress InvalidArgument */
461 13
        Html::addCssClass($this->options, ['widget' => 'modal']);
462
463 13
        $this->titleOptions = array_merge([
464 13
            'id' => $this->options['id'] . '-label',
465 13
        ], $this->titleOptions);
466
467 13
        if (!isset($this->options['aria-label'], $this->options['aria-labelledby']) && $this->title !== '') {
468 2
            $this->options['aria-labelledby'] = $this->titleOptions['id'];
469
        }
470
471 13
        if ($this->closeButtonEnabled !== false) {
472 12
            Html::addCssClass($this->closeButton, ['closeButton' => 'close']);
473
474 12
            $this->closeButton = array_merge([
475 12
                'data-bs-dismiss' => 'modal',
476
                'type' => 'button',
477 12
            ], $this->closeButton);
478
        }
479
480 13
        if ($this->toggleButton !== []) {
481 1
            $this->toggleButton = array_merge([
482 1
                'data-bs-toggle' => 'modal',
483
                'type' => 'button',
484 1
            ], $this->toggleButton);
485 1
            if (!isset($this->toggleButton['data-bs-target']) && !isset($this->toggleButton['href'])) {
486 1
                $this->toggleButton['data-bs-target'] = '#' . $this->options['id'];
487
            }
488
        }
489 13
    }
490
}
491