OSDN Git Service

Добавлена возможность создавать новую запись предмета оборудования на основании уже...
[invent/invent.git] / vendor / yiisoft / yii2 / widgets / ActiveField.php
1 <?php
2 /**
3  * @link http://www.yiiframework.com/
4  * @copyright Copyright (c) 2008 Yii Software LLC
5  * @license http://www.yiiframework.com/license/
6  */
7
8 namespace yii\widgets;
9
10 use Yii;
11 use yii\base\Component;
12 use yii\base\ErrorHandler;
13 use yii\base\Model;
14 use yii\helpers\ArrayHelper;
15 use yii\helpers\Html;
16 use yii\web\JsExpression;
17
18 /**
19  * ActiveField represents a form input field within an [[ActiveForm]].
20  *
21  * For more details and usage information on ActiveField, see the [guide article on forms](guide:input-forms).
22  *
23  * @author Qiang Xue <qiang.xue@gmail.com>
24  * @since 2.0
25  */
26 class ActiveField extends Component
27 {
28     /**
29      * @var ActiveForm the form that this field is associated with.
30      */
31     public $form;
32     /**
33      * @var Model the data model that this field is associated with.
34      */
35     public $model;
36     /**
37      * @var string the model attribute that this field is associated with.
38      */
39     public $attribute;
40     /**
41      * @var array the HTML attributes (name-value pairs) for the field container tag.
42      * The values will be HTML-encoded using [[Html::encode()]].
43      * If a value is `null`, the corresponding attribute will not be rendered.
44      * The following special options are recognized:
45      *
46      * - `tag`: the tag name of the container element. Defaults to `div`. Setting it to `false` will not render a container tag.
47      *   See also [[\yii\helpers\Html::tag()]].
48      *
49      * If you set a custom `id` for the container element, you may need to adjust the [[$selectors]] accordingly.
50      *
51      * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
52      */
53     public $options = ['class' => 'form-group'];
54     /**
55      * @var string the template that is used to arrange the label, the input field, the error message and the hint text.
56      * The following tokens will be replaced when [[render()]] is called: `{label}`, `{input}`, `{error}` and `{hint}`.
57      */
58     public $template = "{label}\n{input}\n{hint}\n{error}";
59     /**
60      * @var array the default options for the input tags. The parameter passed to individual input methods
61      * (e.g. [[textInput()]]) will be merged with this property when rendering the input tag.
62      *
63      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
64      *
65      * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
66      */
67     public $inputOptions = ['class' => 'form-control'];
68     /**
69      * @var array the default options for the error tags. The parameter passed to [[error()]] will be
70      * merged with this property when rendering the error tag.
71      * The following special options are recognized:
72      *
73      * - `tag`: the tag name of the container element. Defaults to `div`. Setting it to `false` will not render a container tag.
74      *   See also [[\yii\helpers\Html::tag()]].
75      * - `encode`: whether to encode the error output. Defaults to `true`.
76      *
77      * If you set a custom `id` for the error element, you may need to adjust the [[$selectors]] accordingly.
78      *
79      * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
80      */
81     public $errorOptions = ['class' => 'help-block'];
82     /**
83      * @var array the default options for the label tags. The parameter passed to [[label()]] will be
84      * merged with this property when rendering the label tag.
85      * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
86      */
87     public $labelOptions = ['class' => 'control-label'];
88     /**
89      * @var array the default options for the hint tags. The parameter passed to [[hint()]] will be
90      * merged with this property when rendering the hint tag.
91      * The following special options are recognized:
92      *
93      * - `tag`: the tag name of the container element. Defaults to `div`. Setting it to `false` will not render a container tag.
94      *   See also [[\yii\helpers\Html::tag()]].
95      *
96      * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
97      */
98     public $hintOptions = ['class' => 'hint-block'];
99     /**
100      * @var bool whether to enable client-side data validation.
101      * If not set, it will take the value of [[ActiveForm::enableClientValidation]].
102      */
103     public $enableClientValidation;
104     /**
105      * @var bool whether to enable AJAX-based data validation.
106      * If not set, it will take the value of [[ActiveForm::enableAjaxValidation]].
107      */
108     public $enableAjaxValidation;
109     /**
110      * @var bool whether to perform validation when the value of the input field is changed.
111      * If not set, it will take the value of [[ActiveForm::validateOnChange]].
112      */
113     public $validateOnChange;
114     /**
115      * @var bool whether to perform validation when the input field loses focus.
116      * If not set, it will take the value of [[ActiveForm::validateOnBlur]].
117      */
118     public $validateOnBlur;
119     /**
120      * @var bool whether to perform validation while the user is typing in the input field.
121      * If not set, it will take the value of [[ActiveForm::validateOnType]].
122      * @see validationDelay
123      */
124     public $validateOnType;
125     /**
126      * @var int number of milliseconds that the validation should be delayed when the user types in the field
127      * and [[validateOnType]] is set `true`.
128      * If not set, it will take the value of [[ActiveForm::validationDelay]].
129      */
130     public $validationDelay;
131     /**
132      * @var array the jQuery selectors for selecting the container, input and error tags.
133      * The array keys should be `container`, `input`, and/or `error`, and the array values
134      * are the corresponding selectors. For example, `['input' => '#my-input']`.
135      *
136      * The container selector is used under the context of the form, while the input and the error
137      * selectors are used under the context of the container.
138      *
139      * You normally do not need to set this property as the default selectors should work well for most cases.
140      */
141     public $selectors = [];
142     /**
143      * @var array different parts of the field (e.g. input, label). This will be used together with
144      * [[template]] to generate the final field HTML code. The keys are the token names in [[template]],
145      * while the values are the corresponding HTML code. Valid tokens include `{input}`, `{label}` and `{error}`.
146      * Note that you normally don't need to access this property directly as
147      * it is maintained by various methods of this class.
148      */
149     public $parts = [];
150     /**
151      * @var bool adds aria HTML attributes `aria-required` and `aria-invalid` for inputs
152      * @since 2.0.11
153      */
154     public $addAriaAttributes = true;
155
156     /**
157      * @var string this property holds a custom input id if it was set using [[inputOptions]] or in one of the
158      * `$options` parameters of the `input*` methods.
159      */
160     private $_inputId;
161     /**
162      * @var bool if "for" field label attribute should be skipped.
163      */
164     private $_skipLabelFor = false;
165
166
167     /**
168      * PHP magic method that returns the string representation of this object.
169      * @return string the string representation of this object.
170      */
171     public function __toString()
172     {
173         // __toString cannot throw exception
174         // use trigger_error to bypass this limitation
175         try {
176             return $this->render();
177         } catch (\Exception $e) {
178             ErrorHandler::convertExceptionToError($e);
179             return '';
180         } catch (\Throwable $e) {
181             ErrorHandler::convertExceptionToError($e);
182             return '';
183         }
184     }
185
186     /**
187      * Renders the whole field.
188      * This method will generate the label, error tag, input tag and hint tag (if any), and
189      * assemble them into HTML according to [[template]].
190      * @param string|callable $content the content within the field container.
191      * If `null` (not set), the default methods will be called to generate the label, error tag and input tag,
192      * and use them as the content.
193      * If a callable, it will be called to generate the content. The signature of the callable should be:
194      *
195      * ```php
196      * function ($field) {
197      *     return $html;
198      * }
199      * ```
200      *
201      * @return string the rendering result.
202      */
203     public function render($content = null)
204     {
205         if ($content === null) {
206             if (!isset($this->parts['{input}'])) {
207                 $this->textInput();
208             }
209             if (!isset($this->parts['{label}'])) {
210                 $this->label();
211             }
212             if (!isset($this->parts['{error}'])) {
213                 $this->error();
214             }
215             if (!isset($this->parts['{hint}'])) {
216                 $this->hint(null);
217             }
218             $content = strtr($this->template, $this->parts);
219         } elseif (!is_string($content)) {
220             $content = call_user_func($content, $this);
221         }
222
223         return $this->begin() . "\n" . $content . "\n" . $this->end();
224     }
225
226     /**
227      * Renders the opening tag of the field container.
228      * @return string the rendering result.
229      */
230     public function begin()
231     {
232         if ($this->form->enableClientScript) {
233             $clientOptions = $this->getClientOptions();
234             if (!empty($clientOptions)) {
235                 $this->form->attributes[] = $clientOptions;
236             }
237         }
238
239         $inputID = $this->getInputId();
240         $attribute = Html::getAttributeName($this->attribute);
241         $options = $this->options;
242         $class = isset($options['class']) ? (array) $options['class'] : [];
243         $class[] = "field-$inputID";
244         if ($this->model->isAttributeRequired($attribute)) {
245             $class[] = $this->form->requiredCssClass;
246         }
247         $options['class'] = implode(' ', $class);
248         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_CONTAINER) {
249             $this->addErrorClassIfNeeded($options);
250         }
251         $tag = ArrayHelper::remove($options, 'tag', 'div');
252
253         return Html::beginTag($tag, $options);
254     }
255
256     /**
257      * Renders the closing tag of the field container.
258      * @return string the rendering result.
259      */
260     public function end()
261     {
262         return Html::endTag(ArrayHelper::keyExists('tag', $this->options) ? $this->options['tag'] : 'div');
263     }
264
265     /**
266      * Generates a label tag for [[attribute]].
267      * @param null|string|false $label the label to use. If `null`, the label will be generated via [[Model::getAttributeLabel()]].
268      * If `false`, the generated field will not contain the label part.
269      * Note that this will NOT be [[Html::encode()|encoded]].
270      * @param null|array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]].
271      * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
272      * using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
273      * @return $this the field object itself.
274      */
275     public function label($label = null, $options = [])
276     {
277         if ($label === false) {
278             $this->parts['{label}'] = '';
279             return $this;
280         }
281
282         $options = array_merge($this->labelOptions, $options);
283         if ($label !== null) {
284             $options['label'] = $label;
285         }
286
287         if ($this->_skipLabelFor) {
288             $options['for'] = null;
289         }
290
291         $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options);
292
293         return $this;
294     }
295
296     /**
297      * Generates a tag that contains the first validation error of [[attribute]].
298      * Note that even if there is no validation error, this method will still return an empty error tag.
299      * @param array|false $options the tag options in terms of name-value pairs. It will be merged with [[errorOptions]].
300      * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
301      * using [[Html::encode()]]. If this parameter is `false`, no error tag will be rendered.
302      *
303      * The following options are specially handled:
304      *
305      * - `tag`: this specifies the tag name. If not set, `div` will be used.
306      *   See also [[\yii\helpers\Html::tag()]].
307      *
308      * If you set a custom `id` for the error element, you may need to adjust the [[$selectors]] accordingly.
309      * @see $errorOptions
310      * @return $this the field object itself.
311      */
312     public function error($options = [])
313     {
314         if ($options === false) {
315             $this->parts['{error}'] = '';
316             return $this;
317         }
318         $options = array_merge($this->errorOptions, $options);
319         $this->parts['{error}'] = Html::error($this->model, $this->attribute, $options);
320
321         return $this;
322     }
323
324     /**
325      * Renders the hint tag.
326      * @param string|bool $content the hint content.
327      * If `null`, the hint will be generated via [[Model::getAttributeHint()]].
328      * If `false`, the generated field will not contain the hint part.
329      * Note that this will NOT be [[Html::encode()|encoded]].
330      * @param array $options the tag options in terms of name-value pairs. These will be rendered as
331      * the attributes of the hint tag. The values will be HTML-encoded using [[Html::encode()]].
332      *
333      * The following options are specially handled:
334      *
335      * - `tag`: this specifies the tag name. If not set, `div` will be used.
336      *   See also [[\yii\helpers\Html::tag()]].
337      *
338      * @return $this the field object itself.
339      */
340     public function hint($content, $options = [])
341     {
342         if ($content === false) {
343             $this->parts['{hint}'] = '';
344             return $this;
345         }
346
347         $options = array_merge($this->hintOptions, $options);
348         if ($content !== null) {
349             $options['hint'] = $content;
350         }
351         $this->parts['{hint}'] = Html::activeHint($this->model, $this->attribute, $options);
352
353         return $this;
354     }
355
356     /**
357      * Renders an input tag.
358      * @param string $type the input type (e.g. `text`, `password`)
359      * @param array $options the tag options in terms of name-value pairs. These will be rendered as
360      * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
361      *
362      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
363      *
364      * @return $this the field object itself.
365      */
366     public function input($type, $options = [])
367     {
368         $options = array_merge($this->inputOptions, $options);
369         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
370             $this->addErrorClassIfNeeded($options);
371         }
372
373         $this->addAriaAttributes($options);
374         $this->adjustLabelFor($options);
375         $this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options);
376
377         return $this;
378     }
379
380     /**
381      * Renders a text input.
382      * This method will generate the `name` and `value` tag attributes automatically for the model attribute
383      * unless they are explicitly specified in `$options`.
384      * @param array $options the tag options in terms of name-value pairs. These will be rendered as
385      * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
386      *
387      * The following special options are recognized:
388      *
389      * - `maxlength`: int|bool, when `maxlength` is set `true` and the model attribute is validated
390      *   by a string validator, the `maxlength` option will take the value of [[\yii\validators\StringValidator::max]].
391      *   This is available since version 2.0.3.
392      *
393      * Note that if you set a custom `id` for the input element, you may need to adjust the value of [[selectors]] accordingly.
394      *
395      * @return $this the field object itself.
396      */
397     public function textInput($options = [])
398     {
399         $options = array_merge($this->inputOptions, $options);
400
401         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
402             $this->addErrorClassIfNeeded($options);
403         }
404
405         $this->addAriaAttributes($options);
406         $this->adjustLabelFor($options);
407         $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options);
408
409         return $this;
410     }
411
412     /**
413      * Renders a hidden input.
414      *
415      * Note that this method is provided for completeness. In most cases because you do not need
416      * to validate a hidden input, you should not need to use this method. Instead, you should
417      * use [[\yii\helpers\Html::activeHiddenInput()]].
418      *
419      * This method will generate the `name` and `value` tag attributes automatically for the model attribute
420      * unless they are explicitly specified in `$options`.
421      * @param array $options the tag options in terms of name-value pairs. These will be rendered as
422      * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
423      *
424      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
425      *
426      * @return $this the field object itself.
427      */
428     public function hiddenInput($options = [])
429     {
430         $options = array_merge($this->inputOptions, $options);
431         $this->adjustLabelFor($options);
432         $this->parts['{input}'] = Html::activeHiddenInput($this->model, $this->attribute, $options);
433
434         return $this;
435     }
436
437     /**
438      * Renders a password input.
439      * This method will generate the `name` and `value` tag attributes automatically for the model attribute
440      * unless they are explicitly specified in `$options`.
441      * @param array $options the tag options in terms of name-value pairs. These will be rendered as
442      * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
443      *
444      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
445      *
446      * @return $this the field object itself.
447      */
448     public function passwordInput($options = [])
449     {
450         $options = array_merge($this->inputOptions, $options);
451
452         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
453             $this->addErrorClassIfNeeded($options);
454         }
455
456         $this->addAriaAttributes($options);
457         $this->adjustLabelFor($options);
458         $this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options);
459
460         return $this;
461     }
462
463     /**
464      * Renders a file input.
465      * This method will generate the `name` and `value` tag attributes automatically for the model attribute
466      * unless they are explicitly specified in `$options`.
467      * @param array $options the tag options in terms of name-value pairs. These will be rendered as
468      * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
469      *
470      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
471      *
472      * @return $this the field object itself.
473      */
474     public function fileInput($options = [])
475     {
476         // https://github.com/yiisoft/yii2/pull/795
477         if ($this->inputOptions !== ['class' => 'form-control']) {
478             $options = array_merge($this->inputOptions, $options);
479         }
480         // https://github.com/yiisoft/yii2/issues/8779
481         if (!isset($this->form->options['enctype'])) {
482             $this->form->options['enctype'] = 'multipart/form-data';
483         }
484
485         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
486             $this->addErrorClassIfNeeded($options);
487         }
488
489         $this->addAriaAttributes($options);
490         $this->adjustLabelFor($options);
491         $this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options);
492
493         return $this;
494     }
495
496     /**
497      * Renders a text area.
498      * The model attribute value will be used as the content in the textarea.
499      * @param array $options the tag options in terms of name-value pairs. These will be rendered as
500      * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
501      *
502      * If you set a custom `id` for the textarea element, you may need to adjust the [[$selectors]] accordingly.
503      *
504      * @return $this the field object itself.
505      */
506     public function textarea($options = [])
507     {
508         $options = array_merge($this->inputOptions, $options);
509
510         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
511             $this->addErrorClassIfNeeded($options);
512         }
513
514         $this->addAriaAttributes($options);
515         $this->adjustLabelFor($options);
516         $this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options);
517
518         return $this;
519     }
520
521     /**
522      * Renders a radio button.
523      * This method will generate the `checked` tag attribute according to the model attribute value.
524      * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
525      *
526      * - `uncheck`: string, the value associated with the uncheck state of the radio button. If not set,
527      *   it will take the default value `0`. This method will render a hidden input so that if the radio button
528      *   is not checked and is submitted, the value of this attribute will still be submitted to the server
529      *   via the hidden input. If you do not want any hidden input, you should explicitly set this option as `null`.
530      * - `label`: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass
531      *   in HTML code such as an image tag. If this is coming from end users, you should [[Html::encode()|encode]] it to prevent XSS attacks.
532      *   When this option is specified, the radio button will be enclosed by a label tag. If you do not want any label, you should
533      *   explicitly set this option as `null`.
534      * - `labelOptions`: array, the HTML attributes for the label tag. This is only used when the `label` option is specified.
535      *
536      * The rest of the options will be rendered as the attributes of the resulting tag. The values will
537      * be HTML-encoded using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
538      *
539      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
540      *
541      * @param bool $enclosedByLabel whether to enclose the radio within the label.
542      * If `true`, the method will still use [[template]] to layout the radio button and the error message
543      * except that the radio is enclosed by the label tag.
544      * @return $this the field object itself.
545      */
546     public function radio($options = [], $enclosedByLabel = true)
547     {
548         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
549             $this->addErrorClassIfNeeded($options);
550         }
551
552         $this->addAriaAttributes($options);
553         $this->adjustLabelFor($options);
554         
555         if ($enclosedByLabel) {
556             $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
557             $this->parts['{label}'] = '';
558         } else {
559             if (isset($options['label']) && !isset($this->parts['{label}'])) {
560                 $this->parts['{label}'] = $options['label'];
561                 if (!empty($options['labelOptions'])) {
562                     $this->labelOptions = $options['labelOptions'];
563                 }
564             }
565             unset($options['labelOptions']);
566             $options['label'] = null;
567             $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
568         }
569
570         return $this;
571     }
572
573     /**
574      * Renders a checkbox.
575      * This method will generate the `checked` tag attribute according to the model attribute value.
576      * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
577      *
578      * - `uncheck`: string, the value associated with the uncheck state of the radio button. If not set,
579      *   it will take the default value `0`. This method will render a hidden input so that if the radio button
580      *   is not checked and is submitted, the value of this attribute will still be submitted to the server
581      *   via the hidden input. If you do not want any hidden input, you should explicitly set this option as `null`.
582      * - `label`: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass
583      *   in HTML code such as an image tag. If this is coming from end users, you should [[Html::encode()|encode]] it to prevent XSS attacks.
584      *   When this option is specified, the checkbox will be enclosed by a label tag. If you do not want any label, you should
585      *   explicitly set this option as `null`.
586      * - `labelOptions`: array, the HTML attributes for the label tag. This is only used when the `label` option is specified.
587      *
588      * The rest of the options will be rendered as the attributes of the resulting tag. The values will
589      * be HTML-encoded using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
590      *
591      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
592      *
593      * @param bool $enclosedByLabel whether to enclose the checkbox within the label.
594      * If `true`, the method will still use [[template]] to layout the checkbox and the error message
595      * except that the checkbox is enclosed by the label tag.
596      * @return $this the field object itself.
597      */
598     public function checkbox($options = [], $enclosedByLabel = true)
599     {
600         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
601             $this->addErrorClassIfNeeded($options);
602         }
603
604         $this->addAriaAttributes($options);
605         $this->adjustLabelFor($options);
606         
607         if ($enclosedByLabel) {
608             $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
609             $this->parts['{label}'] = '';
610         } else {
611             if (isset($options['label']) && !isset($this->parts['{label}'])) {
612                 $this->parts['{label}'] = $options['label'];
613                 if (!empty($options['labelOptions'])) {
614                     $this->labelOptions = $options['labelOptions'];
615                 }
616             }
617             unset($options['labelOptions']);
618             $options['label'] = null;
619             $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
620         }
621
622         return $this;
623     }
624
625     /**
626      * Renders a drop-down list.
627      * The selection of the drop-down list is taken from the value of the model attribute.
628      * @param array $items the option data items. The array keys are option values, and the array values
629      * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
630      * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
631      * If you have a list of data models, you may convert them into the format described above using
632      * [[ArrayHelper::map()]].
633      *
634      * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
635      * the labels will also be HTML-encoded.
636      * @param array $options the tag options in terms of name-value pairs.
637      *
638      * For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeDropDownList()]].
639      *
640      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
641      *
642      * @return $this the field object itself.
643      */
644     public function dropDownList($items, $options = [])
645     {
646         $options = array_merge($this->inputOptions, $options);
647
648         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
649             $this->addErrorClassIfNeeded($options);
650         }
651
652         $this->addAriaAttributes($options);
653         $this->adjustLabelFor($options);
654         $this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options);
655
656         return $this;
657     }
658
659     /**
660      * Renders a list box.
661      * The selection of the list box is taken from the value of the model attribute.
662      * @param array $items the option data items. The array keys are option values, and the array values
663      * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
664      * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
665      * If you have a list of data models, you may convert them into the format described above using
666      * [[\yii\helpers\ArrayHelper::map()]].
667      *
668      * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
669      * the labels will also be HTML-encoded.
670      * @param array $options the tag options in terms of name-value pairs.
671      *
672      * For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeListBox()]].
673      *
674      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
675      *
676      * @return $this the field object itself.
677      */
678     public function listBox($items, $options = [])
679     {
680         $options = array_merge($this->inputOptions, $options);
681
682         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
683             $this->addErrorClassIfNeeded($options);
684         }
685
686         $this->addAriaAttributes($options);
687         $this->adjustLabelFor($options);
688         $this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options);
689
690         return $this;
691     }
692
693     /**
694      * Renders a list of checkboxes.
695      * A checkbox list allows multiple selection, like [[listBox()]].
696      * As a result, the corresponding submitted value is an array.
697      * The selection of the checkbox list is taken from the value of the model attribute.
698      * @param array $items the data item used to generate the checkboxes.
699      * The array values are the labels, while the array keys are the corresponding checkbox values.
700      * @param array $options options (name => config) for the checkbox list.
701      * For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeCheckboxList()]].
702      * @return $this the field object itself.
703      */
704     public function checkboxList($items, $options = [])
705     {
706         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
707             $this->addErrorClassIfNeeded($options);
708         }
709
710         $this->addAriaAttributes($options);
711         $this->adjustLabelFor($options);
712         $this->_skipLabelFor = true;
713         $this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options);
714
715         return $this;
716     }
717
718     /**
719      * Renders a list of radio buttons.
720      * A radio button list is like a checkbox list, except that it only allows single selection.
721      * The selection of the radio buttons is taken from the value of the model attribute.
722      * @param array $items the data item used to generate the radio buttons.
723      * The array values are the labels, while the array keys are the corresponding radio values.
724      * @param array $options options (name => config) for the radio button list.
725      * For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeRadioList()]].
726      * @return $this the field object itself.
727      */
728     public function radioList($items, $options = [])
729     {
730         if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
731             $this->addErrorClassIfNeeded($options);
732         }
733
734         $this->addRoleAttributes($options, 'radiogroup');
735         $this->addAriaAttributes($options);
736         $this->adjustLabelFor($options);
737         $this->_skipLabelFor = true;
738         $this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options);
739
740         return $this;
741     }
742
743     /**
744      * Renders a widget as the input of the field.
745      *
746      * Note that the widget must have both `model` and `attribute` properties. They will
747      * be initialized with [[model]] and [[attribute]] of this field, respectively.
748      *
749      * If you want to use a widget that does not have `model` and `attribute` properties,
750      * please use [[render()]] instead.
751      *
752      * While widgets extending from [[Widget]] work with active field, it is preferred to use
753      * [[InputWidget]] as a base class.
754      *
755      * For example to use the [[MaskedInput]] widget to get some date input, you can use
756      * the following code, assuming that `$form` is your [[ActiveForm]] instance:
757      *
758      * ```php
759      * $form->field($model, 'date')->widget(\yii\widgets\MaskedInput::className(), [
760      *     'mask' => '99/99/9999',
761      * ]);
762      * ```
763      *
764      * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
765      *
766      * @param string $class the widget class name.
767      * @param array $config name-value pairs that will be used to initialize the widget.
768      * @return $this the field object itself.
769      * @throws \Exception
770      */
771     public function widget($class, $config = [])
772     {
773         /* @var $class \yii\base\Widget */
774         $config['model'] = $this->model;
775         $config['attribute'] = $this->attribute;
776         $config['view'] = $this->form->getView();
777         if (is_subclass_of($class, 'yii\widgets\InputWidget')) {
778             foreach ($this->inputOptions as $key => $value) {
779                 if (!isset($config['options'][$key])) {
780                     $config['options'][$key] = $value;
781                 }
782             }
783             $config['field'] = $this;
784             if (!isset($config['options'])) {
785                 $config['options'] = [];
786             }
787             if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
788                 $this->addErrorClassIfNeeded($config['options']);
789             }
790
791             $this->addAriaAttributes($config['options']);
792             $this->adjustLabelFor($config['options']);
793         }
794
795         $this->parts['{input}'] = $class::widget($config);
796
797         return $this;
798     }
799
800     /**
801      * Adjusts the `for` attribute for the label based on the input options.
802      * @param array $options the input options.
803      */
804     protected function adjustLabelFor($options)
805     {
806         if (!isset($options['id'])) {
807             return;
808         }
809         $this->_inputId = $options['id'];
810         if (!isset($this->labelOptions['for'])) {
811             $this->labelOptions['for'] = $options['id'];
812         }
813     }
814
815     /**
816      * Returns the JS options for the field.
817      * @return array the JS options.
818      */
819     protected function getClientOptions()
820     {
821         $attribute = Html::getAttributeName($this->attribute);
822         if (!in_array($attribute, $this->model->activeAttributes(), true)) {
823             return [];
824         }
825
826         $clientValidation = $this->isClientValidationEnabled();
827         $ajaxValidation = $this->isAjaxValidationEnabled();
828
829         if ($clientValidation) {
830             $validators = [];
831             foreach ($this->model->getActiveValidators($attribute) as $validator) {
832                 /* @var $validator \yii\validators\Validator */
833                 $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView());
834                 if ($validator->enableClientValidation && $js != '') {
835                     if ($validator->whenClient !== null) {
836                         $js = "if (({$validator->whenClient})(attribute, value)) { $js }";
837                     }
838                     $validators[] = $js;
839                 }
840             }
841         }
842
843         if (!$ajaxValidation && (!$clientValidation || empty($validators))) {
844             return [];
845         }
846
847         $options = [];
848
849         $inputID = $this->getInputId();
850         $options['id'] = Html::getInputId($this->model, $this->attribute);
851         $options['name'] = $this->attribute;
852
853         $options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID";
854         $options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID";
855         if (isset($this->selectors['error'])) {
856             $options['error'] = $this->selectors['error'];
857         } elseif (isset($this->errorOptions['class'])) {
858             $options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY));
859         } else {
860             $options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span';
861         }
862
863         $options['encodeError'] = !isset($this->errorOptions['encode']) || $this->errorOptions['encode'];
864         if ($ajaxValidation) {
865             $options['enableAjaxValidation'] = true;
866         }
867         foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) {
868             $options[$name] = $this->$name === null ? $this->form->$name : $this->$name;
869         }
870
871         if (!empty($validators)) {
872             $options['validate'] = new JsExpression('function (attribute, value, messages, deferred, $form) {' . implode('', $validators) . '}');
873         }
874
875         if ($this->addAriaAttributes === false) {
876             $options['updateAriaInvalid'] = false;
877         }
878
879         // only get the options that are different from the default ones (set in yii.activeForm.js)
880         return array_diff_assoc($options, [
881             'validateOnChange' => true,
882             'validateOnBlur' => true,
883             'validateOnType' => false,
884             'validationDelay' => 500,
885             'encodeError' => true,
886             'error' => '.help-block',
887             'updateAriaInvalid' => true,
888         ]);
889     }
890
891     /**
892      * Checks if client validation enabled for the field.
893      * @return bool
894      * @since 2.0.11
895      */
896     protected function isClientValidationEnabled()
897     {
898         return $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
899     }
900
901     /**
902      * Checks if ajax validation enabled for the field.
903      * @return bool
904      * @since 2.0.11
905      */
906     protected function isAjaxValidationEnabled()
907     {
908         return $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
909     }
910
911     /**
912      * Returns the HTML `id` of the input element of this form field.
913      * @return string the input id.
914      * @since 2.0.7
915      */
916     protected function getInputId()
917     {
918         return $this->_inputId ?: Html::getInputId($this->model, $this->attribute);
919     }
920
921     /**
922      * Adds aria attributes to the input options.
923      * @param $options array input options
924      * @since 2.0.11
925      */
926     protected function addAriaAttributes(&$options)
927     {
928         if ($this->addAriaAttributes) {
929             if (!isset($options['aria-required']) && $this->model->isAttributeRequired($this->attribute)) {
930                 $options['aria-required'] = 'true';
931             }
932             if (!isset($options['aria-invalid']) && $this->model->hasErrors($this->attribute)) {
933                 $options['aria-invalid'] = 'true';
934             }
935         }
936     }
937
938     /**
939      * Add role attributes to the input options
940      * @param $options array input options
941      * @param string $role
942      * @since 2.0.16
943      */
944     protected function addRoleAttributes(&$options, $role)
945     {
946         if (!isset($options['role'])) {
947             $options['role'] = $role;
948         }
949     }
950
951     /**
952      * Adds validation class to the input options if needed.
953      * @param $options array input options
954      * @since 2.0.14
955      */
956     protected function addErrorClassIfNeeded(&$options)
957     {
958         // Get proper attribute name when attribute name is tabular.
959         $attributeName = Html::getAttributeName($this->attribute);
960
961         if ($this->model->hasErrors($attributeName)) {
962             Html::addCssClass($options, $this->form->errorCssClass);
963         }
964     }
965 }