Passed
Branch master (fbd372)
by Wilmer
02:37
created

ModalCard   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 659
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 204
dl 0
loc 659
ccs 193
cts 193
cp 1
rs 8.5599
c 1
b 0
f 0
wmc 48

37 Methods

Rating   Name   Duplication   Size   Complexity  
A attributes() 0 5 1
A autoIdPrefix() 0 5 1
A footerClass() 0 6 1
A toggleButtonLabel() 0 6 1
A closeButtonAttributes() 0 5 1
A bodyAttributes() 0 5 1
A renderFooter() 0 12 2
A bodyClass() 0 5 1
A headerAttributes() 0 5 1
A footer() 0 5 1
A headerClass() 0 6 1
A renderBodyBegin() 0 7 1
A id() 0 5 1
A withoutToggleButton() 0 5 1
A renderHeader() 0 20 2
A toggleButtonSize() 0 11 2
A renderToggleButton() 0 22 4
A closeButtonSize() 0 10 2
A toggleButtonAttributes() 0 6 1
A cardAttributes() 0 5 1
A footerAttributes() 0 5 1
A title() 0 6 1
A titleAttributes() 0 6 1
A contentClass() 0 6 1
A backgroundClass() 0 5 1
A withoutCloseButton() 0 5 1
A begin() 0 29 3
A renderCloseButton() 0 12 2
A buttonClass() 0 5 1
A toggleButtonId() 0 5 1
A closeButtonCssClass() 0 5 1
A toggleButtonColor() 0 11 2
A titleClass() 0 6 1
A renderBackgroundTransparentOverlay() 0 3 1
A run() 0 8 1
A renderBodyEnd() 0 3 1
A cardClass() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like ModalCard often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ModalCard, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use InvalidArgumentException;
8
use Yiisoft\Html\Html;
9
use Yiisoft\Html\Tag\Button;
10
use Yiisoft\Html\Tag\CustomTag;
11
use Yiisoft\Html\Tag\Div;
12
use Yiisoft\Html\Tag\P;
13
use Yiisoft\Widget\Widget;
14
15
use function implode;
16
use function in_array;
17
18
/**
19
 * ModalCard renders a modal window that can be toggled by clicking on a button.
20
 *
21
 * The following example will show the content enclosed between the {@see Widget::begin()} and {@see Widget::end()}
22
 * calls within the modal window:
23
 *
24
 * ```php
25
 * echo ModalCard::widget()
26
 *     ->title('Modal title')
27
 *     ->footer(
28
 *         Html::button('Cancel', ['class' => 'button'])
29
 *     )
30
 *     ->begin();
31
 *
32
 * echo 'Say hello...';
33
 *
34
 * echo ModalCard::end();
35
 * ```
36
 *
37
 * @link https://bulma.io/documentation/components/modal/
38
 */
39
final class ModalCard extends Widget
40
{
41
    public const SIZE_SMALL = 'is-small';
42
    public const SIZE_MEDIUM = 'is-medium';
43
    public const SIZE_LARGE = 'is-large';
44
    private const SIZE_ALL = [
45
        self::SIZE_SMALL,
46
        self::SIZE_MEDIUM,
47
        self::SIZE_LARGE,
48
    ];
49
    public const COLOR_PRIMARY = 'is-primary';
50
    public const COLOR_LINK = 'is-link';
51
    public const COLOR_INFO = 'is-info';
52
    public const COLOR_SUCCESS = 'is-success';
53
    public const COLOR_WARNING = 'is-warning';
54
    public const COLOR_DANGER = 'is-danger';
55
    public const COLOR_DARK = 'is-dark';
56
    private const COLOR_ALL = [
57
        self::COLOR_PRIMARY,
58
        self::COLOR_LINK,
59
        self::COLOR_INFO,
60
        self::COLOR_SUCCESS,
61
        self::COLOR_WARNING,
62
        self::COLOR_DANGER,
63
        self::COLOR_DARK,
64
    ];
65
    private array $attributes = [];
66
    private string $autoIdPrefix = 'w';
67
    private string $backgroundClass = 'modal-background';
68
    private array $bodyAttributes = [];
69
    private string $bodyClass = 'modal-card-body';
70
    private string $buttonClass = 'button modal-button';
71
    private array $cardAttributes = [];
72
    private string $cardClass = 'modal';
73
    private array $closeButtonAttributes = [];
74
    private string $closeButtonCssClass = 'button delete';
75
    private string $closeButtonSize = '';
76
    private string $contentClass = 'modal-card';
77
    private string $footer = '';
78
    private array $headerAttributes = [];
79
    private string $headerClass = 'modal-card-head';
80
    private array $footerAttributes = [];
81
    private string $footerClass = 'modal-card-foot';
82
    private string $title = '';
83
    private string $titleClass = 'modal-card-title';
84
    private array $titleAttributes = [];
85
    private array $toggleButtonAttributes = [];
86
    private string $toggleButtonColor = '';
87
    private string $toggleButtonLabel = 'Toggle button';
88
    private string $toggleButtonSize = '';
89
    private bool $withoutCloseButton = false;
90
    private bool $withoutToggleButton = false;
91
92
    /**
93
     * The HTML attributes.
94
     *
95
     * @param array $values Attribute values indexed by attribute names.
96
     *
97
     * @return self
98
     *
99
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} For details on how attributes are being rendered.
100
     */
101 2
    public function attributes(array $values): self
102
    {
103 2
        $new = clone $this;
104 2
        $new->attributes = $values;
105 2
        return $new;
106
    }
107
108
    /**
109
     * Returns a new instance with the specified prefix to the automatically generated widget IDs.
110
     *
111
     * @param string $value The prefix to the automatically generated widget IDs.
112
     *
113
     * @return self
114
     */
115 1
    public function autoIdPrefix(string $value): self
116
    {
117 1
        $new = clone $this;
118 1
        $new->autoIdPrefix = $value;
119 1
        return $new;
120
    }
121
122
    /**
123
     * Returns a new instance with the specified modal card background class.
124
     *
125
     * @param string $value The modal card background class.
126
     *
127
     * @return self
128
     */
129 1
    public function backgroundClass(string $value): self
130
    {
131 1
        $new = clone $this;
132 1
        $new->backgroundClass = $value;
133 1
        return $new;
134
    }
135
136
    /**
137
     * Returns a new instance with the specified body attributes.
138
     *
139
     * @param array $value The body attributes.
140
     *
141
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
142
     *
143
     * @return self
144
     */
145 3
    public function bodyAttributes(array $value): self
146
    {
147 3
        $new = clone $this;
148 3
        $new->bodyAttributes = $value;
149 3
        return $new;
150
    }
151
152
    /**
153
     * Returns a new instance with the specified modal card body class.
154
     *
155
     * @param string $value The modal card body class.
156
     *
157
     * @return self
158
     */
159 1
    public function bodyClass(string $value): self
160
    {
161 1
        $new = clone $this;
162 1
        $new->bodyClass = $value;
163 1
        return $new;
164
    }
165
166
    /**
167
     * Returns a new instance with the specified modal card button class.
168
     *
169
     * @param string $value The modal card button class.
170
     *
171
     * @return self
172
     */
173 1
    public function buttonClass(string $value): self
174
    {
175 1
        $new = clone $this;
176 1
        $new->buttonClass = $value;
177 1
        return $new;
178
    }
179
180
    /**
181
     * Returns a new instance with the specified card attributes.
182
     *
183
     * @param array $value The content attributes.
184
     *
185
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
186
     *
187
     * @return self
188
     */
189 2
    public function cardAttributes(array $value): self
190
    {
191 2
        $new = clone $this;
192 2
        $new->cardAttributes = $value;
193 2
        return $new;
194
    }
195
196
    /**
197
     * Returns a new instance with the specified modal card class.
198
     *
199
     * @param string $value The modal card class.
200
     *
201
     * @return self
202
     */
203 1
    public function cardClass(string $value): self
204
    {
205 1
        $new = clone $this;
206 1
        $new->cardClass = $value;
207
208 1
        return $new;
209
    }
210
211
    /**
212
     * Returns a new instance with the specified close button attributes.
213
     *
214
     * @param array $value The close button attributes.
215
     *
216
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
217
     *
218
     * @return self
219
     */
220 3
    public function closeButtonAttributes(array $value): self
221
    {
222 3
        $new = clone $this;
223 3
        $new->closeButtonAttributes = $value;
224 3
        return $new;
225
    }
226
227
    /**
228
     * Returns a new instance with the specified close button class.
229
     *
230
     * @param string $value The close button class.
231
     *
232
     * @return self
233
     */
234 1
    public function closeButtonCssClass(string $value): self
235
    {
236 1
        $new = clone $this;
237 1
        $new->closeButtonCssClass = $value;
238 1
        return $new;
239
    }
240
241
    /**
242
     * Returns a new instance with the specified close button size.
243
     *
244
     * @param string $value The close button size. Default setting is "normal".
245
     * Possible values are: ModalCard::SIZE_SMALL, ModalCard::SIZE_MEDIUM, ModalCard::SIZE_LARGE.
246
     *
247
     * @return self
248
     */
249 3
    public function closeButtonSize(string $value): self
250
    {
251 3
        if (!in_array($value, self::SIZE_ALL, true)) {
252 1
            $values = implode('", "', self::SIZE_ALL);
253 1
            throw new InvalidArgumentException("Invalid size. Valid values are: \"$values\".");
254
        }
255
256 2
        $new = clone $this;
257 2
        $new->closeButtonSize = $value;
258 2
        return $new;
259
    }
260
261
    /**
262
     * Returns a new instance with the specified modal card content class.
263
     *
264
     * @param string $value The modal card content class.
265
     *
266
     * @return self
267
     */
268 1
    public function contentClass(string $value): self
269
    {
270 1
        $new = clone $this;
271 1
        $new->contentClass = $value;
272
273 1
        return $new;
274
    }
275
276
    /**
277
     * Returns a new instance with the specified footer content.
278
     *
279
     * @param string $value The footer content in the modal window.
280
     *
281
     * @return self
282
     */
283 17
    public function footer(string $value): self
284
    {
285 17
        $new = clone $this;
286 17
        $new->footer = $value;
287 17
        return $new;
288
    }
289
290
    /**
291
     * Returns a new instance with the specified footer attributes.
292
     *
293
     * @param array $value The footer attributes.
294
     *
295
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
296
     *
297
     * @return self
298
     */
299 2
    public function footerAttributes(array $value): self
300
    {
301 2
        $new = clone $this;
302 2
        $new->footerAttributes = $value;
303 2
        return $new;
304
    }
305
306
    /**
307
     * Returns a new instance with the specified header attributes.
308
     *
309
     * @param array $value The header attributes.
310
     *
311
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
312
     *
313
     * @return self
314
     */
315 2
    public function headerAttributes(array $value): self
316
    {
317 2
        $new = clone $this;
318 2
        $new->headerAttributes = $value;
319 2
        return $new;
320
    }
321
322
    /**
323
     * Returns a new instance with the specified ID of the widget.
324
     *
325
     * @param string|null $value The ID of the widget.
326
     *
327
     * @return self
328
     */
329 1
    public function id(?string $value): self
330
    {
331 1
        $new = clone $this;
332 1
        $new->attributes['id'] = $value;
333 1
        return $new;
334
    }
335
336
    /**
337
     * Returns a new instance with the specified modal card footer class.
338
     *
339
     * @param string $value The modal card footer class.
340
     *
341
     * @return self
342
     */
343 1
    public function footerClass(string $value): self
344
    {
345 1
        $new = clone $this;
346 1
        $new->footerClass = $value;
347
348 1
        return $new;
349
    }
350
351
    /**
352
     * Returns a new instance with the specified modal card header class.
353
     *
354
     * @param string $value The modal card head class.
355
     *
356
     * @return self
357
     */
358 1
    public function headerClass(string $value): self
359
    {
360 1
        $new = clone $this;
361 1
        $new->headerClass = $value;
362
363 1
        return $new;
364
    }
365
366
    /**
367
     * Returns a new instance with the specified modal card title class.
368
     *
369
     * @param string $value The modal card title class.
370
     *
371
     * @return self
372
     */
373 1
    public function titleClass(string $value): self
374
    {
375 1
        $new = clone $this;
376 1
        $new->titleClass = $value;
377
378 1
        return $new;
379
    }
380
381
    /**
382
     * Returns a new instance with the specified title content.
383
     *
384
     * @param string $value The title content in the modal window.
385
     *
386
     * @return self
387
     */
388 17
    public function title(string $value): self
389
    {
390 17
        $new = clone $this;
391 17
        $new->title = $value;
392
393 17
        return $new;
394
    }
395
396
    /**
397
     * Returns a new instance with the specified title attributes.
398
     *
399
     * @param array $value The title attributes.
400
     *
401
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
402
     *
403
     * @return self
404
     */
405 2
    public function titleAttributes(array $value): self
406
    {
407 2
        $new = clone $this;
408 2
        $new->titleAttributes = $value;
409
410 2
        return $new;
411
    }
412
413
    /**
414
     * Returns a new instance with the specified toggle button attributes.
415
     *
416
     * @param array $value The toggle button attributes.
417
     *
418
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
419
     *
420
     * @return self
421
     */
422 2
    public function toggleButtonAttributes(array $value): self
423
    {
424 2
        $new = clone $this;
425 2
        $new->toggleButtonAttributes = $value;
426
427 2
        return $new;
428
    }
429
430
    /**
431
     * Returns a new instance with the specified toggle button color.
432
     *
433
     * @param string $value The toggle button color. By default, there's no color set.
434
     * Possible values are: ModalCard::COLOR_PRIMARY, ModalCard::COLOR_INFO, ModalCard::COLOR_SUCCESS,
435
     * ModalCard::COLOR_WARNING, ModalCard::COLOR_DANGER, ModalCard::COLOR_DARK
436
     *
437
     * @return self
438
     */
439 3
    public function toggleButtonColor(string $value): self
440
    {
441 3
        if (!in_array($value, self::COLOR_ALL, true)) {
442 1
            $values = implode('", "', self::COLOR_ALL);
443 1
            throw new InvalidArgumentException("Invalid color. Valid values are: \"$values\".");
444
        }
445
446 2
        $new = clone $this;
447 2
        $new->toggleButtonColor = $value;
448
449 2
        return $new;
450
    }
451
452
    /**
453
     * Returns a new instance with the specified ID of the toggle button.
454
     *
455
     * @param string|null $value The ID of the widget.
456
     *
457
     * @return self
458
     */
459 1
    public function toggleButtonId(?string $value): self
460
    {
461 1
        $new = clone $this;
462 1
        $new->toggleButtonAttributes['id'] = $value;
463 1
        return $new;
464
    }
465
466
    /**
467
     * Returns a new instance with the specified toggle button label.
468
     *
469
     * @param string $value The toggle button label.
470
     *
471
     * @return self
472
     */
473 2
    public function toggleButtonLabel(string $value): self
474
    {
475 2
        $new = clone $this;
476 2
        $new->toggleButtonLabel = $value;
477
478 2
        return $new;
479
    }
480
481
    /**
482
     * Returns a new instance with the specified toggle button size.
483
     *
484
     * @param string $value The toggle button size.
485
     *
486
     * @return self
487
     */
488 3
    public function toggleButtonSize(string $value): self
489
    {
490 3
        if (!in_array($value, self::SIZE_ALL, true)) {
491 1
            $values = implode('", "', self::SIZE_ALL);
492 1
            throw new InvalidArgumentException("Invalid size. Valid values are: \"$values\".");
493
        }
494
495 2
        $new = clone $this;
496 2
        $new->toggleButtonSize = $value;
497
498 2
        return $new;
499
    }
500
501
    /**
502
     * Returns a new instance with the specified options for rendering the close button tag.
503
     *
504
     * @param bool $value Whether the close button is disabled.
505
     *
506
     * @return self
507
     */
508 2
    public function withoutCloseButton(bool $value): self
509
    {
510 2
        $new = clone $this;
511 2
        $new->withoutCloseButton = $value;
512 2
        return $new;
513
    }
514
515
    /**
516
     * Returns a new instance with the disabled toggle button.
517
     *
518
     * @param bool $value Whether the toggle button is disabled.
519
     *
520
     * @return self
521
     */
522 2
    public function withoutToggleButton(bool $value): self
523
    {
524 2
        $new = clone $this;
525 2
        $new->withoutToggleButton = $value;
526 2
        return $new;
527
    }
528
529 16
    public function begin(): ?string
530
    {
531 16
        parent::begin();
532
533 16
        $attributes = $this->attributes;
534 16
        $cardAttributes = $this->cardAttributes;
535 16
        $html = '';
536
537 16
        if (!array_key_exists('id', $attributes)) {
538 16
            $attributes['id'] = Html::generateId($this->autoIdPrefix) . '-modal';
539
        }
540
541
        /** @var string */
542 16
        $id = $attributes['id'];
543
544 16
        if ($this->withoutToggleButton === false) {
545 15
            $html .= $this->renderToggleButton($id) . "\n";
546
        }
547
548 16
        Html::addCssClass($attributes, $this->cardClass);
549 16
        Html::addCssClass($cardAttributes, $this->contentClass);
550
551 16
        $html .= Html::openTag('div', $attributes) . "\n"; // .modal
552 16
        $html .= $this->renderBackgroundTransparentOverlay() . "\n"; // .modal-background
553 16
        $html .= Html::openTag('div', $cardAttributes) . "\n"; // .modal-card
554 16
        $html .= $this->renderHeader();
555 16
        $html .= $this->renderBodyBegin() . "\n";
556
557 16
        return $html;
558
    }
559
560 16
    protected function run(): string
561
    {
562 16
        $html = $this->renderBodyEnd() . "\n";
563 16
        $html .= $this->renderFooter() . "\n";
564 16
        $html .= Html::closeTag('div') . "\n"; // .modal-card
565 16
        $html .= Html::closeTag('div'); // .modal
566
567 16
        return $html;
568
    }
569
570
    /**
571
     * Renders the background transparent overlay.
572
     *
573
     * @return string
574
     */
575 16
    private function renderBackgroundTransparentOverlay(): string
576
    {
577 16
        return Div::tag()->class($this->backgroundClass)->render();
578
    }
579
580
    /**
581
     * Renders begin body tag.
582
     *
583
     * @return string
584
     */
585 16
    private function renderBodyBegin(): string
586
    {
587 16
        $bodyAttributes = $this->bodyAttributes;
588
589 16
        Html::addCssClass($bodyAttributes, $this->bodyClass);
590
591 16
        return Html::openTag('section', $bodyAttributes);
592
    }
593
594
    /**
595
     * Renders end body tag.
596
     *
597
     * @return string
598
     */
599 16
    private function renderBodyEnd(): string
600
    {
601 16
        return Html::closeTag('section');
602
    }
603
604
    /**
605
     * Renders the close button.
606
     *
607
     * @return string
608
     */
609 15
    private function renderCloseButton(): string
610
    {
611 15
        $closeButtonAttributes = $this->closeButtonAttributes;
612 15
        $closeButtonAttributes['aria-label'] = 'close';
613
614 15
        Html::addCssClass($closeButtonAttributes, $this->closeButtonCssClass);
615
616 15
        if ($this->closeButtonSize !== '') {
617 1
            Html::addCssClass($closeButtonAttributes, $this->closeButtonSize);
618
        }
619
620 15
        return Button::tag()->attributes($closeButtonAttributes)->render() . PHP_EOL;
621
    }
622
623
    /**
624
     * Renders the footer.
625
     *
626
     * @return string
627
     */
628 16
    private function renderFooter(): string
629
    {
630 16
        $footer = $this->footer;
631 16
        $footerAttributes = $this->footerAttributes;
632
633 16
        if ($footer !== '') {
634 16
            $footer = PHP_EOL . $footer . PHP_EOL;
635
        }
636
637 16
        Html::addCssClass($footerAttributes, $this->footerClass);
638
639 16
        return CustomTag::name('footer')->attributes($footerAttributes)->content($footer)->encode(false)->render();
640
    }
641
642
    /**
643
     * Renders header.
644
     *
645
     * @return string
646
     */
647 16
    private function renderHeader(): string
648
    {
649 16
        $content = '';
650 16
        $headerAttributes = $this->headerAttributes;
651 16
        $titleAttributes = $this->titleAttributes;
652
653 16
        Html::addCssClass($headerAttributes, $this->headerClass);
654 16
        Html::addCssClass($titleAttributes, $this->titleClass);
655
656 16
        $content .= P::tag()->attributes($titleAttributes)->content($this->title)->render() . PHP_EOL;
657
658 16
        if ($this->withoutCloseButton === false) {
659 15
            $content .= $this->renderCloseButton();
660
        }
661
662 16
        return CustomTag::name('header')
663 16
            ->attributes($headerAttributes)
664 16
            ->content(PHP_EOL . $content)
665 16
            ->encode(false)
666 16
            ->render() . PHP_EOL;
667
    }
668
669
    /**
670
     * Renders the toggle button.
671
     *
672
     * @param string $id
673
     *
674
     * @return string
675
     */
676 15
    private function renderToggleButton(string $id): string
677
    {
678 15
        $toggleButtonAttributes = $this->toggleButtonAttributes;
679
680 15
        if (!array_key_exists('id', $toggleButtonAttributes)) {
681 15
            $toggleButtonAttributes['id'] = Html::generateId($this->autoIdPrefix) . '-button';
682
        }
683
684 15
        $toggleButtonAttributes['data-target'] = '#' . $id;
685 15
        $toggleButtonAttributes['aria-haspopup'] = 'true';
686
687 15
        if ($this->toggleButtonSize !== '') {
688 1
            Html::addCssClass($toggleButtonAttributes, $this->toggleButtonSize);
689
        }
690
691 15
        if ($this->toggleButtonColor !== '') {
692 1
            Html::addCssClass($toggleButtonAttributes, $this->toggleButtonColor);
693
        }
694
695 15
        Html::addCssClass($toggleButtonAttributes, $this->buttonClass);
696
697 15
        return Button::tag()->attributes($toggleButtonAttributes)->content($this->toggleButtonLabel)->render();
698
    }
699
}
700