Test Failed
Pull Request — master (#192)
by Alexander
12:02 queued 09:06
created

Checkbox::replaceInputLabelAttributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Field;
6
7
use InvalidArgumentException;
8
use Stringable;
9
use Yiisoft\Form\Field\Base\InputField;
10
use Yiisoft\Form\Field\Base\ValidationClass\ValidationClassInterface;
11
use Yiisoft\Form\Field\Base\ValidationClass\ValidationClassTrait;
12
use Yiisoft\Html\Html;
13
14
use function is_bool;
15
use function is_object;
16
use function is_string;
17
18
/**
19
 * The input element with a type attribute whose value is "checkbox" represents a state or option that can be toggled.
20
 *
21
 * @link https://html.spec.whatwg.org/multipage/input.html#checkbox-state-(type=checkbox)
22
 * @link https://developer.mozilla.org/docs/Web/HTML/Element/input/checkbox
23
 */
24
final class Checkbox extends InputField implements ValidationClassInterface
25
{
26
    use ValidationClassTrait;
27
28
    private ?string $uncheckValue = '0';
29
    private bool $enclosedByLabel = true;
30
    private ?string $inputLabel = null;
31
    private array $inputLabelAttributes = [];
32
    private bool $inputLabelEncode = true;
33
    private ?string $inputValue = null;
34
35
    /**
36
     * Identifies the element (or elements) that describes the object.
37
     *
38
     * @link https://w3c.github.io/aria/#aria-describedby
39
     */
40
    public function ariaDescribedBy(?string $value): self
41
    {
42
        $new = clone $this;
43
        $new->inputAttributes['aria-describedby'] = $value;
44
        return $new;
45
    }
46
47
    /**
48
     * Defines a string value that labels the current element.
49
     *
50
     * @link https://w3c.github.io/aria/#aria-label
51
     */
52
    public function ariaLabel(?string $value): self
53
    {
54
        $new = clone $this;
55
        $new->inputAttributes['aria-label'] = $value;
56
        return $new;
57
    }
58
59
    /**
60
     * Focus on the control (put cursor into it) when the page loads. Only one form element could be in focus
61
     * at the same time.
62
     *
63
     * @link https://html.spec.whatwg.org/multipage/interaction.html#attr-fe-autofocus
64
     */
65
    public function autofocus(bool $value = true): self
66
    {
67
        $new = clone $this;
68
        $new->inputAttributes['autofocus'] = $value;
69
        return $new;
70
    }
71
72
    /**
73
     * @link https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
74
     */
75
    public function disabled(bool $disabled = true): self
76
    {
77
        $new = clone $this;
78
        $new->inputAttributes['disabled'] = $disabled;
79
        return $new;
80
    }
81
82
    /**
83
     * If the input should be enclosed by label.
84
     *
85
     * @param bool $value If the input should be en closed by label.
86
     */
87
    public function enclosedByLabel(bool $value): self
88
    {
89
        $new = clone $this;
90
        $new->enclosedByLabel = $value;
91
        return $new;
92
    }
93
94
    /**
95
     * Label displayed next to the checkbox.
96
     *
97
     * When this option is specified, the checkbox will be enclosed by a label tag.
98
     *
99
     * @param string|null $value
100
     *
101
     * @return self
102
     *
103
     * @link https://www.w3.org/TR/html52/sec-forms.html#the-label-element
104
     */
105
    public function inputLabel(?string $value): self
106
    {
107
        $new = clone $this;
108
        $new->inputLabel = $value;
109
        return $new;
110
    }
111
112
    /**
113
     * HTML attributes for the label tag.
114
     *
115
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
116
     */
117
    public function inputLabelAttributes(array $attributes): self
118
    {
119
        $new = clone $this;
120
        $new->inputLabelAttributes = array_merge($new->inputLabelAttributes, $attributes);
121
        return $new;
122
    }
123
124
    public function replaceInputLabelAttributes(array $attributes): self
125
    {
126
        $new = clone $this;
127
        $new->inputLabelAttributes = $attributes;
128
        return $new;
129
    }
130
131
    /**
132
     * Set enclosed label tag ID.
133
     *
134
     * @param string|null $id Label tag ID.
135
     */
136
    public function inputLabelId(?string $id): self
137
    {
138
        $new = clone $this;
139
        $new->inputLabelAttributes['id'] = $id;
140
        return $new;
141
    }
142
143
    /**
144
     * Add one or more CSS classes to the enclosed label tag.
145
     *
146
     * @param string|null ...$class One or many CSS classes.
147
     */
148
    public function inputLabelClass(?string ...$class): self
149
    {
150
        $new = clone $this;
151
        Html::addCssClass(
152
            $new->inputLabelAttributes,
153
            array_filter($class, static fn ($c) => $c !== null),
154
        );
155
        return $new;
156
    }
157
158
    /**
159
     * Replace enclosed label tag CSS classes with a new set of classes.
160
     *
161
     * @param string|null ...$class One or many CSS classes.
162
     */
163
    public function replaceInputLabelClass(?string ...$class): self
164
    {
165
        $new = clone $this;
166
        $new->inputLabelAttributes['class'] = array_filter($class, static fn ($c) => $c !== null);
167
        return $new;
168
    }
169
170
    public function inputLabelEncode(bool $encode): self
171
    {
172
        $new = clone $this;
173
        $new->inputLabelEncode = $encode;
174
        return $new;
175
    }
176
177
    public function inputValue(bool|float|int|string|Stringable|null $value): self
178
    {
179
        $new = clone $this;
180
        $new->inputValue = $this->prepareValue($value);
181
        return $new;
182
    }
183
184
    /**
185
     * The `tabindex` attribute indicates that its element can be focused, and where it participates in sequential
186
     * keyboard navigation (usually with the Tab key, hence the name).
187
     *
188
     * It accepts an integer as a value, with different results depending on the integer's value:
189
     *
190
     * - A negative value (usually `tabindex="-1"`) means that the element is not reachable via sequential keyboard
191
     *   navigation, but could be focused with Javascript or visually. It's mostly useful to create accessible widgets
192
     *   with JavaScript.
193
     * - `tabindex="0"` means that the element should be focusable in sequential keyboard navigation, but its order is
194
     *   defined by the document's source order.
195
     * - A positive value means the element should be focusable in sequential keyboard navigation, with its order
196
     *   defined by the value of the number. That is, `tabindex="4"` is focused before `tabindex="5"`, but after
197
     *   `tabindex="3"`.
198
     *
199
     * @link https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex
200
     */
201
    public function tabIndex(?int $value): self
202
    {
203
        $new = clone $this;
204
        $new->inputAttributes['tabindex'] = $value;
205
        return $new;
206
    }
207
208
    /**
209
     * @param bool|float|int|string|Stringable|null $value Value that corresponds to "unchecked" state of the input.
210
     */
211
    public function uncheckValue(bool|float|int|string|Stringable|null $value): self
212
    {
213
        $new = clone $this;
214
        $new->uncheckValue = $this->prepareValue($value);
215
        return $new;
216
    }
217
218
    protected function generateInput(): string
219
    {
220
        $value = $this->getFormAttributeValue();
221
222
        if (!is_bool($value)
223
            && !is_string($value)
224
            && !is_numeric($value)
225
            && $value !== null
226
            && (!is_object($value) || !method_exists($value, '__toString'))
227
        ) {
228
            throw new InvalidArgumentException(
229
                'Checkbox widget requires a string, numeric, bool, Stringable or null value.'
230
            );
231
        }
232
233
        $value = $this->prepareValue($value);
234
235
        $inputAttributes = $this->getInputAttributes();
236
237
        $inputValue = $this->inputValue;
238
        $inputValue = $inputValue ?? $this->prepareValue($inputAttributes['value'] ?? null);
239
        unset($inputAttributes['value']);
240
        $inputValue = $inputValue ?? '1';
241
242
        $checkbox = Html::checkbox($this->getInputName(), $inputValue, $inputAttributes);
243
244
        $label = $this->inputLabel ?? $this->getFormAttributeLabel();
245
246
        if ($this->enclosedByLabel) {
247
            $checkbox = $checkbox
248
                ->label($label, $this->inputLabelAttributes)
249
                ->labelEncode($this->inputLabelEncode);
250
        }
251
252
        $html = $checkbox
253
            ->checked($inputValue === $value)
254
            ->uncheckValue($this->uncheckValue)
255
            ->render();
256
257
        if (!$this->enclosedByLabel && $this->inputLabel !== null) {
258
            $html .= ' ' . ($this->inputLabelEncode ? Html::encode($this->inputLabel) : $this->inputLabel);
259
        }
260
261
        return $html;
262
    }
263
264
    protected function shouldHideLabel(): bool
265
    {
266
        return $this->enclosedByLabel;
267
    }
268
269
    private function prepareValue(mixed $value): ?string
270
    {
271
        if ($value === null) {
272
            return null;
273
        }
274
275
        if (is_bool($value)) {
276
            return $value ? '1' : '0';
277
        }
278
279
        return (string) $value;
280
    }
281
282
    protected function prepareContainerAttributes(array &$attributes): void
283
    {
284
        if ($this->hasFormModelAndAttribute()) {
285
            $this->addValidationClassToAttributes(
286
                $attributes,
287
                $this->getFormModel(),
288
                $this->getFormAttributeName(),
289
            );
290
        }
291
    }
292
}
293