Passed
Pull Request — master (#22)
by Mr.
02:11
created

Dropdown   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Test Coverage

Coverage 98.63%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 76
c 2
b 0
f 0
dl 0
loc 188
ccs 72
cts 73
cp 0.9863
rs 10
wmc 20

6 Methods

Rating   Name   Duplication   Size   Complexity  
A run() 0 11 2
A items() 0 5 1
A encodeLabels() 0 5 1
A options() 0 5 1
A submenuOptions() 0 5 1
C renderItems() 0 85 14
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap5;
6
7
use JsonException;
8
use RuntimeException;
9
use Yiisoft\Arrays\ArrayHelper;
10
use Yiisoft\Html\Html;
11
12
use function array_key_exists;
13
use function array_merge;
14
use function array_merge_recursive;
15
use function is_string;
16
17
/**
18
 * Dropdown renders a Bootstrap dropdown menu component.
19
 *
20
 * For example,
21
 *
22
 * ```php
23
 * <div class="dropdown">
24
 *     <?php
25
 *         echo Dropdown::widget()
26
 *             ->items([
27
 *                 ['label' => 'DropdownA', 'url' => '/'],
28
 *                 ['label' => 'DropdownB', 'url' => '#'],
29
 *             ]);
30
 *     ?>
31
 * </div>
32
 * ```
33
 */
34
class Dropdown extends Widget
35
{
36
    private array $items = [];
37
    private bool $encodeLabels = true;
38
    private array $submenuOptions = [];
39
    private array $options = [];
40
41 15
    protected function run(): string
42
    {
43 15
        if (!isset($this->options['id'])) {
44 14
            $this->options['id'] = "{$this->getId()}-dropdown";
45
        }
46
47 15
        Html::addCssClass($this->options, ['widget' => 'dropdown-menu']);
48
49 15
        $this->registerClientEvents($this->options['id']);
50
51 15
        return $this->renderItems($this->items, $this->options);
52
    }
53
54
    /**
55
     * Renders menu items.
56
     *
57
     * @param array $items the menu items to be rendered
58
     * @param array $options the container HTML attributes
59
     *
60
     * @throws JsonException|RuntimeException if the label option is not specified in one of the items.
61
     *
62
     * @return string the rendering result.
63
     */
64 15
    protected function renderItems(array $items, array $options = []): string
65
    {
66 15
        $lines = [];
67
68 15
        foreach ($items as $item) {
69 15
            if (is_string($item)) {
70 3
                $item = ['label' => $item, 'encode' => false, 'enclose' => false];
71
            }
72
73 15
            if (isset($item['visible']) && !$item['visible']) {
74 3
                continue;
75
            }
76
77 15
            if (!array_key_exists('label', $item)) {
78
                throw new RuntimeException("The 'label' option is required.");
79
            }
80
81 15
            $encodeLabel = $item['encode'] ?? $this->encodeLabels;
82 15
            $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
83 15
            $itemOptions = ArrayHelper::getValue($item, 'options', []);
84 15
            $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
85 15
            $active = ArrayHelper::getValue($item, 'active', false);
86 15
            $disabled = ArrayHelper::getValue($item, 'disabled', false);
87 15
            $enclose = ArrayHelper::getValue($item, 'enclose', true);
88
89 15
            Html::addCssClass($linkOptions, 'dropdown-item');
90
91 15
            if ($disabled) {
92 1
                ArrayHelper::setValue($linkOptions, 'tabindex', '-1');
93 1
                ArrayHelper::setValue($linkOptions, 'aria-disabled', 'true');
94 1
                Html::addCssClass($linkOptions, 'disabled');
95 15
            } elseif ($active) {
96 2
                Html::addCssClass($linkOptions, 'active');
97
            }
98
99 15
            $url = $item['url'] ?? null;
100
101 15
            if (empty($item['items'])) {
102 15
                if ($label === '-') {
103 3
                    $content = Html::div('', ['class' => 'dropdown-divider']);
104 15
                } elseif ($enclose === false) {
105 1
                    $content = $label;
106 15
                } elseif ($url === null) {
107 7
                    $content = Html::tag('h6', $label, ['class' => 'dropdown-header']);
108
                } else {
109 9
                    $content = Html::a($label, $url, $linkOptions);
110
                }
111
112 15
                $lines[] = $content;
113
            } else {
114 3
                $submenuOptions = $this->submenuOptions;
115
116 3
                if (isset($item['submenuOptions'])) {
117 1
                    $submenuOptions = array_merge($submenuOptions, $item['submenuOptions']);
118
                }
119
120 3
                Html::addCssClass($submenuOptions, ['dropdown-menu']);
121 3
                Html::addCssClass($linkOptions, ['dropdown-toggle']);
122
123 3
                $lines[] = Html::a($label, $url, array_merge([
124 3
                    'data-bs-toggle' => 'dropdown',
125
                    'aria-haspopup' => 'true',
126
                    'aria-expanded' => 'false',
127
                    'role' => 'button',
128 3
                ], $linkOptions))
129
130 3
                . Html::tag(
131 3
                    'ul',
132 3
                    self::widget()
133 3
                        ->items($item['items'])
0 ignored issues
show
Bug introduced by
The method items() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Yii\Bootstrap5\Nav or Yiisoft\Yii\Bootstrap5\Carousel or Yiisoft\Yii\Bootstrap5\Dropdown or Yiisoft\Yii\Bootstrap5\Tabs or Yiisoft\Yii\Bootstrap5\Accordion. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

133
                        ->/** @scrutinizer ignore-call */ items($item['items'])
Loading history...
134 3
                        ->options($submenuOptions)
135 3
                        ->submenuOptions($submenuOptions)
136 3
                        ->encodeLabels($this->encodeLabels)
137 3
                        ->render(),
138 3
                    array_merge_recursive(
139 3
                        ['aria-expanded' => 'false', 'class' => ['dropdown'], 'encode' => false],
140 3
                        $itemOptions
141
                    )
142
                );
143
            }
144
        }
145
146 15
        return Html::ul(
147 15
            $lines,
148 15
            array_merge_recursive(['aria-expanded' => 'false', 'encode' => false], $options)
149
        );
150
    }
151
152
    /**
153
     * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a
154
     * single menu with the following structure:
155
     *
156
     * - label: string, required, the label of the item link.
157
     * - encode: bool, optional, whether to HTML-encode item label.
158
     * - url: string|array, optional, the URL of the item link. This will be processed by {@see currentPath}.
159
     *   If not set, the item will be treated as a menu header when the item has no sub-menu.
160
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
161
     * - linkOptions: array, optional, the HTML attributes of the item link.
162
     * - options: array, optional, the HTML attributes of the item.
163
     * - items: array, optional, the submenu items. The structure is the same as this property.
164
     *   Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
165
     * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be
166
     *   merged with {@see submenuOptions}.
167
     *
168
     * To insert divider use `-`.
169
     *
170
     * @param array $value
171
     *
172
     * @return $this
173
     */
174 15
    public function items(array $value): self
175
    {
176 15
        $this->items = $value;
177
178 15
        return $this;
179
    }
180
181
    /**
182
     * Whether the labels for header items should be HTML-encoded.
183
     *
184
     * @param bool $value
185
     *
186
     * @return $this
187
     */
188 11
    public function encodeLabels(bool $value): self
189
    {
190 11
        $this->encodeLabels = $value;
191
192 11
        return $this;
193
    }
194
195
    /**
196
     * The HTML attributes for sub-menu container tags.
197
     *
198
     * @param array $value
199
     *
200
     * @return $this
201
     */
202 3
    public function submenuOptions(array $value): self
203
    {
204 3
        $this->submenuOptions = $value;
205
206 3
        return $this;
207
    }
208
209
    /**
210
     * @param array $value the HTML attributes for the widget container tag. The following special options are
211
     * recognized.
212
     *
213
     * @return $this
214
     *
215
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
216
     */
217 11
    public function options(array $value): self
218
    {
219 11
        $this->options = $value;
220
221 11
        return $this;
222
    }
223
}
224