Test Failed
Push — extract-attributes ( 6e6144...c9c1de )
by Dmitriy
07:22 queued 12s
created

ListInput::unselect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Widget;
6
7
use Closure;
8
use RuntimeException;
9
use Yiisoft\Arrays\ArrayHelper;
10
use Yiisoft\Form\FormModelInterface;
11
use Yiisoft\Form\Helper\HtmlForm;
12
use Yiisoft\Html\Html;
13
use Yiisoft\Html\Widget\CheckboxList\CheckboxItem;
14
use Yiisoft\Html\Widget\RadioList\RadioItem;
15
use Yiisoft\Widget\Widget;
16
17
final class ListInput extends Widget
18
{
19
    private ?string $id = null;
20
    private FormModelInterface $data;
21
    private string $attribute;
22
    private array $options = [];
23
    private string $charset = 'UTF-8';
24
    private array $items = [];
25
    private bool $noUnselect = false;
26
    private string $unselect = '';
27
    private string $type;
28
29
    /**
30
     * @psalm-var Closure(CheckboxItem):string|Closure(RadioItem):string|null
31
     */
32
    private ?Closure $itemFormatter = null;
33
34
    /**
35
     * Generates a list of input fields.
36
     *
37
     * This method is mainly called by {@see ListBox()}, {@see RadioList()} and {@see CheckboxList()}.
38
     *
39
     * @return string the generated input list
40
     */
41
    public function run(): string
42
    {
43
        $new = clone $this;
44
        $type = strtolower($new->type);
45
46
        $uncheckValue = ArrayHelper::remove($new->options, 'unselect');
47
        if (!$new->noUnselect) {
48
            $uncheckValue ??= $new->unselect;
49
        }
50
51
        if (!empty($new->getId())) {
52
            $new->options['id'] = $new->getId();
53
        }
54
55
        $containerTag = ArrayHelper::remove($new->options, 'tag', 'div');
56
        $separator = ArrayHelper::remove($new->options, 'separator', "\n");
57
        $encode = ArrayHelper::remove($new->options, 'encode', true);
58
        $disabled = ArrayHelper::remove($new->options, 'disabled', false);
59
60
        switch ($type) {
61
            case 'checkboxlist':
62
                $checkboxAttributes = ArrayHelper::remove($new->options, 'itemOptions', []);
63
                $encodeLabels = ArrayHelper::remove($checkboxAttributes, 'encode', true);
64
65
                /** @psalm-var Closure(CheckboxItem):string|null $itemFormatter */
66
                $itemFormatter = $this->itemFormatter;
67
68
                $value = $new->getValue();
69
                /** @psalm-suppress PossiblyInvalidArgument */
70
                return Html::checkboxList($new->getName())
71
                    ->values(!is_iterable($value) ? [$value] : $value)
72
                    ->uncheckValue($uncheckValue)
73
                    ->items($new->items, $encodeLabels)
74
                    ->itemFormatter($itemFormatter)
75
                    ->separator($separator)
76
                    ->containerTag($containerTag)
77
                    ->containerAttributes($new->options)
78
                    ->checkboxAttributes($checkboxAttributes)
79
                    ->disabled($disabled)
80
                    ->render();
81
82
            case 'radiolist':
83
                $radioAttributes = ArrayHelper::remove($new->options, 'itemOptions', []);
84
                $encodeLabels = ArrayHelper::remove($radioAttributes, 'encode', true);
85
86
                /** @psalm-var Closure(RadioItem):string|null $itemFormatter */
87
                $itemFormatter = $this->itemFormatter;
88
89
                $value = $new->getValue();
90
                return Html::radioList($new->getName())
91
                    ->value($value)
92
                    ->uncheckValue($uncheckValue)
93
                    ->items($new->items, $encodeLabels)
94
                    ->itemFormatter($itemFormatter)
95
                    ->separator($separator)
96
                    ->containerTag($containerTag)
97
                    ->containerAttributes($new->options)
98
                    ->radioAttributes($radioAttributes)
99
                    ->disabled($disabled)
100
                    ->render();
101
102
            case 'listbox':
103
            case 'dropdownlist':
104
                $groups = ArrayHelper::remove($new->options, 'groups', []);
105
                $optionsAttributes = ArrayHelper::remove($new->options, 'options', []);
106
107
                $items = [];
108
                foreach ($new->items as $value => $content) {
109
                    if (is_array($content)) {
110
                        $groupAttrs = $groups[$value] ?? [];
111
                        $groupAttrs['encode'] = false;
112
                        if (!isset($groupAttrs['label'])) {
113
                            $groupAttrs['label'] = $value;
114
                        }
115
                        $options = [];
116
                        foreach ($content as $v => $c) {
117
                            $options[] = Html::option($c, $v)
118
                                ->attributes($optionsAttributes[$v] ?? [])
119
                                ->encode($encode);
120
                        }
121
                        $items[] = Html::optgroup()
122
                            ->options(...$options)
123
                            ->attributes($groupAttrs);
124
                    } else {
125
                        $items[] = Html::option($content, $value)
126
                            ->attributes($optionsAttributes[$value] ?? [])
127
                            ->encode($encode);
128
                    }
129
                }
130
131
                $promptOption = null;
132
                $prompt = ArrayHelper::remove($new->options, 'prompt');
133
                if ($prompt) {
134
                    $promptText = $prompt['text'] ?? '';
135
                    if ($promptText) {
136
                        $promptOption = Html::option($promptText)
137
                            ->attributes($prompt['options'] ?? []);
138
                    }
139
                }
140
141
                if ($type === 'listbox') {
142
                    $new->options['size'] ??= 4;
143
                }
144
145
                $value = $new->getValue();
146
                /** @psalm-suppress PossiblyInvalidArgument */
147
                return Html::select($new->getName())
148
                    ->values(!is_iterable($value) ? [$value] : $value)
149
                    ->unselectValue($type === 'listbox' ? $uncheckValue : null)
150
                    ->promptOption($promptOption)
151
                    ->items(...$items)
152
                    ->attributes($new->options)
153
                    ->disabled($disabled)
154
                    ->render();
155
        }
156
157
        throw new RuntimeException('Unknown type: ' . $type);
158
    }
159
160
    /**
161
     * Set form model, name and options for the widget.
162
     *
163
     * @param FormModelInterface $data Form model.
164
     * @param string $attribute Form model property this widget is rendered for.
165
     * @param array $options The HTML attributes for the widget container tag.
166
     * See {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
167
     *
168
     * @return self
169
     */
170
    public function config(FormModelInterface $data, string $attribute, array $options = []): self
171
    {
172
        $new = clone $this;
173
        $new->data = $data;
174
        $new->attribute = $attribute;
175
        $new->options = $options;
176
        return $new;
177
    }
178
179
    /**
180
     * Set the character set used to generate the widget id. See {@see HtmlForm::getInputId()}.
181
     *
182
     * @param string $value
183
     *
184
     * @return self
185
     */
186
    public function charset(string $value): self
187
    {
188
        $new = clone $this;
189
        $new->charset = $value;
190
        return $new;
191
    }
192
193
    /**
194
     * Set the Id of the widget.
195
     *
196
     * @param string|null $value
197
     *
198
     * @return self
199
     */
200
    public function id(?string $value): self
201
    {
202
        $new = clone $this;
203
        $new->id = $value;
204
        return $new;
205
    }
206
207
    /**
208
     * Callable, a callback that can be used to customize the generation of the HTML code corresponding to a single
209
     * item in $items.
210
     *
211
     * The signature of this callback must be:
212
     *
213
     * ```php
214
     * function ($index, $label, $name, $checked, $value)
215
     * ```
216
     *
217
     * @param Closure|null $formatter
218
     *
219
     * @return self
220
     */
221
    public function item(?Closure $formatter): self
222
    {
223
        $new = clone $this;
224
        $new->itemFormatter = $formatter;
225
        return $new;
226
    }
227
228
    /**
229
     * The option data items.
230
     *
231
     * The array keys are option values, and the array values are the corresponding option labels. The array can also
232
     * be nested (i.e. some array values are arrays too). For each sub-array, an option group will be generated whose
233
     * label is the key associated with the sub-array. If you have a list of data {@see FormModel}, you may convert
234
     * them into the format described above using {@see \Yiisoft\Arrays\ArrayHelper::map()}
235
     *
236
     * Example:
237
     * ```php
238
     * [
239
     *     '1' => 'Santiago',
240
     *     '2' => 'Concepcion',
241
     *     '3' => 'Chillan',
242
     *     '4' => 'Moscu'
243
     *     '5' => 'San Petersburgo',
244
     *     '6' => 'Novosibirsk',
245
     *     '7' => 'Ekaterinburgo'
246
     * ];
247
     * ```
248
     *
249
     * Example with options groups:
250
     * ```php
251
     * [
252
     *     '1' => [
253
     *         '1' => 'Santiago',
254
     *         '2' => 'Concepcion',
255
     *         '3' => 'Chillan',
256
     *     ],
257
     *     '2' => [
258
     *         '4' => 'Moscu',
259
     *         '5' => 'San Petersburgo',
260
     *         '6' => 'Novosibirsk',
261
     *         '7' => 'Ekaterinburgo'
262
     *     ],
263
     * ];
264
     * ```
265
     *
266
     * @param array $value
267
     *
268
     * @return self
269
     */
270
    public function items(array $value): self
271
    {
272
        $new = clone $this;
273
        $new->items = $value;
274
        return $new;
275
    }
276
277
    /**
278
     * Allows you to disable the widgets hidden input tag.
279
     *
280
     * @param bool $value
281
     *
282
     * @return self
283
     */
284
    public function noUnselect(bool $value = true): self
285
    {
286
        $new = clone $this;
287
        $new->noUnselect = $value;
288
        return $new;
289
    }
290
291
    /**
292
     * Type of the input control to use.
293
     *
294
     * @param string $value
295
     *
296
     * @return self
297
     */
298
    public function type(string $value): self
299
    {
300
        $new = clone $this;
301
        $new->type = $value;
302
        return $new;
303
    }
304
305
    /**
306
     * The value that should be submitted when none of the listbox is selected.
307
     *
308
     * You may set this option to be null to prevent default value submission. If this option is not set, an empty
309
     * string will be submitted.
310
     *
311
     * @param string $value
312
     *
313
     * @return self
314
     */
315
    public function unselect(string $value = ''): self
316
    {
317
        $new = clone $this;
318
        $new->unselect = $value;
319
        return $new;
320
    }
321
322
    private function getId(): string
323
    {
324
        $id = $this->options['id'] ?? $this->id;
325
326
        if ($id === null) {
327
            $id = HtmlForm::getInputId($this->data, $this->attribute, $this->charset);
328
        }
329
330
        return $id !== false ? (string)$id : '';
331
    }
332
333
    private function getName(): string
334
    {
335
        return ArrayHelper::remove($this->options, 'name', HtmlForm::getInputName($this->data, $this->attribute));
336
    }
337
338
    private function getValue()
339
    {
340
        $value = HtmlForm::getAttributeValue($this->data, $this->attribute);
341
342
        if ($value !== null && is_scalar($value)) {
343
            $value = (string)$value;
344
        }
345
346
        return ArrayHelper::remove(
347
            $this->options,
348
            'value',
349
            $value
350
        );
351
    }
352
}
353