Passed
Pull Request — master (#79)
by Wilmer
02:24
created

ModalCard   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 673
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

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