Passed
Push — master ( 2301b1...386ec7 )
by Alexander
14:22 queued 11:36
created

ButtonDropdown::run()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 15
c 1
b 0
f 0
nc 5
nop 0
dl 0
loc 28
ccs 16
cts 16
cp 1
crap 4
rs 9.7666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap5;
6
7
use Yiisoft\Arrays\ArrayHelper;
8
use Yiisoft\Definitions\Exception\InvalidConfigException;
9
use Yiisoft\Html\Html;
10
11
/**
12
 * ButtonDropdown renders a group or split button dropdown bootstrap component.
13
 *
14
 * For example,
15
 *
16
 * ```php
17
 * // a button group using Dropdown widget
18
 * echo ButtonDropdown::widget()
19
 *     ->label('Action')
20
 *     ->dropdown'([
21
 *         'items' => [
22
 *             ['label' => 'DropdownA', 'url' => '/'],
23
 *             ['label' => 'DropdownB', 'url' => '#'],
24
 *         ],
25
 *     ]);
26
 * ```
27
 */
28
final class ButtonDropdown extends Widget
29
{
30
    /**
31
     * The css class part of dropdown
32
     */
33
    public const DIRECTION_DOWN = 'down';
34
35
    /**
36
     * The css class part of dropleft
37
     */
38
    public const DIRECTION_LEFT = 'left';
39
40
    /**
41
     * The css class part of dropright
42
     */
43
    public const DIRECTION_RIGHT = 'right';
44
45
    /**
46
     * The css class part of dropup
47
     */
48
    public const DIRECTION_UP = 'up';
49
50
    private string $label = 'Button';
51
    private array $options = [];
52
    private array $buttonOptions = [];
53
    private array $dropdown = [];
54
    private string $direction = self::DIRECTION_DOWN;
55
    private bool $split = false;
56
    /** @psalm-var non-empty-string */
57
    private string $tagName = 'button';
58
    private bool $encodeLabels = true;
59
    private bool $encodeTags = false;
60
    /** @psalm-var class-string */
61
    private string $dropdownClass = Dropdown::class;
62
    private bool $renderContainer = true;
63
64 10
    public function render(): string
65
    {
66 10
        if (!isset($this->dropdown['items'])) {
67 1
            return '';
68
        }
69
70
        /** Set options id to button options id to ensure correct css selector in plugin initialisation */
71 9
        if (empty($this->options['id'])) {
72 9
            $id = $this->getId();
73
74 9
            $this->options['id'] = "{$id}-button-dropdown";
75 9
            $this->buttonOptions['id'] = "{$id}-button";
76
        }
77
78 9
        $html = $this->renderButton() . "\n" . $this->renderDropdown();
79
80 9
        if ($this->renderContainer) {
81
            /** @psalm-suppress InvalidArgument */
82 8
            Html::addCssClass($this->options, ['widget' => 'drop' . $this->direction, 'btn-group']);
83
84 8
            $options = $this->options;
85 8
            $tag = ArrayHelper::remove($options, 'tag', 'div');
86 8
            $html = Html::tag($tag, $html, $options)
87 8
                ->encode($this->encodeTags)
88 8
                ->render();
89
        }
90
91 9
        return $html;
92
    }
93
94
    /**
95
     * The HTML attributes of the button.
96
     *
97
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
98
     *
99
     * @param array $value
100
     */
101 1
    public function buttonOptions(array $value): self
102
    {
103 1
        $new = clone $this;
104 1
        $new->buttonOptions = $value;
105
106 1
        return $new;
107
    }
108
109
    /**
110
     * The drop-direction of the widget.
111
     *
112
     * Possible values are 'left', 'right', 'up', or 'down' (default)
113
     *
114
     * @param string $value
115
     */
116 2
    public function direction(string $value): self
117
    {
118 2
        $new = clone $this;
119 2
        $new->direction = $value;
120
121 2
        return $new;
122
    }
123
124
    /**
125
     * The configuration array for example:
126
     *
127
     * ```php
128
     *    [
129
     *        'items' => [
130
     *            ['label' => 'DropdownA', 'url' => '/'],
131
     *            ['label' => 'DropdownB', 'url' => '#'],
132
     *        ],
133
     *    ]
134
     * ```
135
     *
136
     * {@see Dropdown}
137
     *
138
     * @param array $value
139
     */
140 9
    public function dropdown(array $value): self
141
    {
142 9
        $new = clone $this;
143 9
        $new->dropdown = $value;
144
145 9
        return $new;
146
    }
147
148
    /**
149
     * Name of a class to use for rendering dropdowns withing this widget. Defaults to {@see Dropdown}.
150
     *
151
     * @psalm-param class-string $value
152
     */
153 1
    public function dropdownClass(string $value): self
154
    {
155 1
        $new = clone $this;
156 1
        $new->dropdownClass = $value;
157
158 1
        return $new;
159
    }
160
161
    /**
162
     * When tags Labels HTML should not be encoded.
163
     */
164 1
    public function withoutEncodeLabels(): self
165
    {
166 1
        $new = clone $this;
167 1
        $new->encodeLabels = false;
168
169 1
        return $new;
170
    }
171
172
    /**
173
     * The button label.
174
     */
175 2
    public function label(string $value): self
176
    {
177 2
        $new = clone $this;
178 2
        $new->label = $value;
179
180 2
        return $new;
181
    }
182
183
    /**
184
     * The HTML attributes for the container tag. The following special options are recognized.
185
     *
186
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
187
     *
188
     * @param array $value
189
     */
190 1
    public function options(array $value): self
191
    {
192 1
        $new = clone $this;
193 1
        $new->options = $value;
194
195 1
        return $new;
196
    }
197
198
    /**
199
     * Whether to render the container using the {@see options} as HTML attributes. If set to `false`, the container
200
     * element enclosing the button and dropdown will NOT be rendered.
201
     */
202 1
    public function withoutRenderContainer(): self
203
    {
204 1
        $new = clone $this;
205 1
        $new->renderContainer = false;
206
207 1
        return $new;
208
    }
209
210
    /**
211
     * Whether to display a group of split-styled button group.
212
     */
213 1
    public function split(): self
214
    {
215 1
        $new = clone $this;
216 1
        $new->split = true;
217
218 1
        return $new;
219
    }
220
221
    /**
222
     * The tag to use to render the button.
223
     *
224
     * @psalm-param non-empty-string $value
225
     */
226 1
    public function tagName(string $value): self
227
    {
228 1
        $new = clone $this;
229 1
        $new->tagName = $value;
230
231 1
        return $new;
232
    }
233
234
    /**
235
     * Generates the button dropdown.
236
     *
237
     * @throws InvalidConfigException
238
     *
239
     * @return string the rendering result.
240
     */
241 9
    private function renderButton(): string
242
    {
243 9
        Html::addCssClass($this->buttonOptions, ['buttonOptions' => 'btn']);
244
245 9
        $label = $this->label;
246
247 9
        if ($this->encodeLabels !== false) {
248 9
            $label = Html::encode($label);
249
        }
250
251 9
        $buttonOptions = $this->buttonOptions;
252
253 9
        if ($this->split) {
254 1
            $this->buttonOptions['data-bs-toggle'] = 'dropdown';
255 1
            $this->buttonOptions['aria-haspopup'] = 'true';
256 1
            $this->buttonOptions['aria-expanded'] = 'false';
257
258 1
            Html::addCssClass($this->buttonOptions, ['toggle' => 'dropdown-toggle dropdown-toggle-split']);
259
260 1
            unset($buttonOptions['id']);
261
262 1
            $splitButton = Button::widget()
263 1
                ->label('<span class="sr-only">Toggle Dropdown</span>')
0 ignored issues
show
Bug introduced by
The method label() 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\Button or Yiisoft\Yii\Bootstrap5\Progress or Yiisoft\Yii\Bootstrap5\ButtonDropdown. ( Ignorable by Annotation )

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

263
                ->/** @scrutinizer ignore-call */ label('<span class="sr-only">Toggle Dropdown</span>')
Loading history...
264 1
                ->options($this->buttonOptions)
265 1
                ->withoutEncodeLabels()
266 1
                ->render();
267
        } else {
268 8
            Html::addCssClass($buttonOptions, ['toggle' => 'dropdown-toggle']);
269
270 8
            $buttonOptions['data-bs-toggle'] = 'dropdown';
271 8
            $buttonOptions['aria-haspopup'] = 'true';
272 8
            $buttonOptions['aria-expanded'] = 'false';
273 8
            $splitButton = '';
274
        }
275
276 9
        if (!isset($buttonOptions['href']) && ($this->tagName === 'a')) {
277 1
            $buttonOptions['href'] = '#';
278 1
            $buttonOptions['role'] = 'button';
279
        }
280
281 9
        $button = Button::widget()
282 9
            ->label($label)
283 9
            ->options($buttonOptions)
284 9
            ->tagName($this->tagName);
285
286 9
        if ($this->encodeLabels === false) {
287 1
            $button = $button->withoutEncodeLabels();
288
        }
289
290 9
        return $button->render() . "\n" . $splitButton;
291
    }
292
293
    /**
294
     * Generates the dropdown menu.
295
     *
296
     * @return string the rendering result.
297
     */
298 9
    private function renderDropdown(): string
299
    {
300 9
        $dropdownClass = $this->dropdownClass;
301
302 9
        $dropdown = $dropdownClass::widget()->items($this->dropdown['items']);
303
304 9
        if ($this->encodeLabels === false) {
305 1
            $dropdown = $dropdown->withoutEncodeLabels();
306
        }
307
308 9
        return $dropdown->render();
309
    }
310
}
311