Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

816 lines
37KB

  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. namespace yii\widgets;
  8. use Yii;
  9. use yii\base\Component;
  10. use yii\base\ErrorHandler;
  11. use yii\helpers\ArrayHelper;
  12. use yii\helpers\Html;
  13. use yii\base\Model;
  14. use yii\web\JsExpression;
  15. /**
  16. * ActiveField represents a form input field within an [[ActiveForm]].
  17. *
  18. * @author Qiang Xue <qiang.xue@gmail.com>
  19. * @since 2.0
  20. */
  21. class ActiveField extends Component
  22. {
  23. /**
  24. * @var ActiveForm the form that this field is associated with.
  25. */
  26. public $form;
  27. /**
  28. * @var Model the data model that this field is associated with
  29. */
  30. public $model;
  31. /**
  32. * @var string the model attribute that this field is associated with
  33. */
  34. public $attribute;
  35. /**
  36. * @var array the HTML attributes (name-value pairs) for the field container tag.
  37. * The values will be HTML-encoded using [[Html::encode()]].
  38. * If a value is null, the corresponding attribute will not be rendered.
  39. * The following special options are recognized:
  40. *
  41. * - tag: the tag name of the container element. Defaults to "div".
  42. *
  43. * If you set a custom `id` for the container element, you may need to adjust the [[$selectors]] accordingly.
  44. *
  45. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  46. */
  47. public $options = ['class' => 'form-group'];
  48. /**
  49. * @var string the template that is used to arrange the label, the input field, the error message and the hint text.
  50. * The following tokens will be replaced when [[render()]] is called: `{label}`, `{input}`, `{error}` and `{hint}`.
  51. */
  52. public $template = "{label}\n{input}\n{hint}\n{error}";
  53. /**
  54. * @var array the default options for the input tags. The parameter passed to individual input methods
  55. * (e.g. [[textInput()]]) will be merged with this property when rendering the input tag.
  56. *
  57. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  58. *
  59. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  60. */
  61. public $inputOptions = ['class' => 'form-control'];
  62. /**
  63. * @var array the default options for the error tags. The parameter passed to [[error()]] will be
  64. * merged with this property when rendering the error tag.
  65. * The following special options are recognized:
  66. *
  67. * - tag: the tag name of the container element. Defaults to "div".
  68. * - encode: whether to encode the error output. Defaults to true.
  69. *
  70. * If you set a custom `id` for the error element, you may need to adjust the [[$selectors]] accordingly.
  71. *
  72. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  73. */
  74. public $errorOptions = ['class' => 'help-block'];
  75. /**
  76. * @var array the default options for the label tags. The parameter passed to [[label()]] will be
  77. * merged with this property when rendering the label tag.
  78. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  79. */
  80. public $labelOptions = ['class' => 'control-label'];
  81. /**
  82. * @var array the default options for the hint tags. The parameter passed to [[hint()]] will be
  83. * merged with this property when rendering the hint tag.
  84. * The following special options are recognized:
  85. *
  86. * - tag: the tag name of the container element. Defaults to "div".
  87. *
  88. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  89. */
  90. public $hintOptions = ['class' => 'hint-block'];
  91. /**
  92. * @var boolean whether to enable client-side data validation.
  93. * If not set, it will take the value of [[ActiveForm::enableClientValidation]].
  94. */
  95. public $enableClientValidation;
  96. /**
  97. * @var boolean whether to enable AJAX-based data validation.
  98. * If not set, it will take the value of [[ActiveForm::enableAjaxValidation]].
  99. */
  100. public $enableAjaxValidation;
  101. /**
  102. * @var boolean whether to perform validation when the value of the input field is changed.
  103. * If not set, it will take the value of [[ActiveForm::validateOnChange]].
  104. */
  105. public $validateOnChange;
  106. /**
  107. * @var boolean whether to perform validation when the input field loses focus.
  108. * If not set, it will take the value of [[ActiveForm::validateOnBlur]].
  109. */
  110. public $validateOnBlur;
  111. /**
  112. * @var boolean whether to perform validation while the user is typing in the input field.
  113. * If not set, it will take the value of [[ActiveForm::validateOnType]].
  114. * @see validationDelay
  115. */
  116. public $validateOnType;
  117. /**
  118. * @var integer number of milliseconds that the validation should be delayed when the user types in the field
  119. * and [[validateOnType]] is set true.
  120. * If not set, it will take the value of [[ActiveForm::validationDelay]].
  121. */
  122. public $validationDelay;
  123. /**
  124. * @var array the jQuery selectors for selecting the container, input and error tags.
  125. * The array keys should be "container", "input", and/or "error", and the array values
  126. * are the corresponding selectors. For example, `['input' => '#my-input']`.
  127. *
  128. * The container selector is used under the context of the form, while the input and the error
  129. * selectors are used under the context of the container.
  130. *
  131. * You normally do not need to set this property as the default selectors should work well for most cases.
  132. */
  133. public $selectors = [];
  134. /**
  135. * @var array different parts of the field (e.g. input, label). This will be used together with
  136. * [[template]] to generate the final field HTML code. The keys are the token names in [[template]],
  137. * while the values are the corresponding HTML code. Valid tokens include `{input}`, `{label}` and `{error}`.
  138. * Note that you normally don't need to access this property directly as
  139. * it is maintained by various methods of this class.
  140. */
  141. public $parts = [];
  142. /**
  143. * PHP magic method that returns the string representation of this object.
  144. * @return string the string representation of this object.
  145. */
  146. public function __toString()
  147. {
  148. // __toString cannot throw exception
  149. // use trigger_error to bypass this limitation
  150. try {
  151. return $this->render();
  152. } catch (\Exception $e) {
  153. ErrorHandler::convertExceptionToError($e);
  154. return '';
  155. }
  156. }
  157. /**
  158. * Renders the whole field.
  159. * This method will generate the label, error tag, input tag and hint tag (if any), and
  160. * assemble them into HTML according to [[template]].
  161. * @param string|callable $content the content within the field container.
  162. * If null (not set), the default methods will be called to generate the label, error tag and input tag,
  163. * and use them as the content.
  164. * If a callable, it will be called to generate the content. The signature of the callable should be:
  165. *
  166. * ~~~
  167. * function ($field) {
  168. * return $html;
  169. * }
  170. * ~~~
  171. *
  172. * @return string the rendering result
  173. */
  174. public function render($content = null)
  175. {
  176. if ($content === null) {
  177. if (!isset($this->parts['{input}'])) {
  178. $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $this->inputOptions);
  179. }
  180. if (!isset($this->parts['{label}'])) {
  181. $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $this->labelOptions);
  182. }
  183. if (!isset($this->parts['{error}'])) {
  184. $this->parts['{error}'] = Html::error($this->model, $this->attribute, $this->errorOptions);
  185. }
  186. if (!isset($this->parts['{hint}'])) {
  187. $this->parts['{hint}'] = '';
  188. }
  189. $content = strtr($this->template, $this->parts);
  190. } elseif (!is_string($content)) {
  191. $content = call_user_func($content, $this);
  192. }
  193. return $this->begin() . "\n" . $content . "\n" . $this->end();
  194. }
  195. /**
  196. * Renders the opening tag of the field container.
  197. * @return string the rendering result.
  198. */
  199. public function begin()
  200. {
  201. if ($this->form->enableClientScript) {
  202. $clientOptions = $this->getClientOptions();
  203. if (!empty($clientOptions)) {
  204. $this->form->attributes[] = $clientOptions;
  205. }
  206. }
  207. $inputID = Html::getInputId($this->model, $this->attribute);
  208. $attribute = Html::getAttributeName($this->attribute);
  209. $options = $this->options;
  210. $class = isset($options['class']) ? [$options['class']] : [];
  211. $class[] = "field-$inputID";
  212. if ($this->model->isAttributeRequired($attribute)) {
  213. $class[] = $this->form->requiredCssClass;
  214. }
  215. if ($this->model->hasErrors($attribute)) {
  216. $class[] = $this->form->errorCssClass;
  217. }
  218. $options['class'] = implode(' ', $class);
  219. $tag = ArrayHelper::remove($options, 'tag', 'div');
  220. return Html::beginTag($tag, $options);
  221. }
  222. /**
  223. * Renders the closing tag of the field container.
  224. * @return string the rendering result.
  225. */
  226. public function end()
  227. {
  228. return Html::endTag(isset($this->options['tag']) ? $this->options['tag'] : 'div');
  229. }
  230. /**
  231. * Generates a label tag for [[attribute]].
  232. * @param string|boolean $label the label to use. If null, the label will be generated via [[Model::getAttributeLabel()]].
  233. * If false, the generated field will not contain the label part.
  234. * Note that this will NOT be [[Html::encode()|encoded]].
  235. * @param array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]].
  236. * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
  237. * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
  238. * @return static the field object itself
  239. */
  240. public function label($label = null, $options = [])
  241. {
  242. if ($label === false) {
  243. $this->parts['{label}'] = '';
  244. return $this;
  245. }
  246. $options = array_merge($this->labelOptions, $options);
  247. if ($label !== null) {
  248. $options['label'] = $label;
  249. }
  250. $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options);
  251. return $this;
  252. }
  253. /**
  254. * Generates a tag that contains the first validation error of [[attribute]].
  255. * Note that even if there is no validation error, this method will still return an empty error tag.
  256. * @param array|boolean $options the tag options in terms of name-value pairs. It will be merged with [[errorOptions]].
  257. * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
  258. * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
  259. *
  260. * The following options are specially handled:
  261. *
  262. * - tag: this specifies the tag name. If not set, "div" will be used.
  263. *
  264. * If this parameter is false, no error tag will be rendered.
  265. *
  266. * If you set a custom `id` for the error element, you may need to adjust the [[$selectors]] accordingly.
  267. *
  268. * @return static the field object itself
  269. */
  270. public function error($options = [])
  271. {
  272. if ($options === false) {
  273. $this->parts['{error}'] = '';
  274. return $this;
  275. }
  276. $options = array_merge($this->errorOptions, $options);
  277. $this->parts['{error}'] = Html::error($this->model, $this->attribute, $options);
  278. return $this;
  279. }
  280. /**
  281. * Renders the hint tag.
  282. * @param string $content the hint content. It will NOT be HTML-encoded.
  283. * @param array $options the tag options in terms of name-value pairs. These will be rendered as
  284. * the attributes of the hint tag. The values will be HTML-encoded using [[Html::encode()]].
  285. *
  286. * The following options are specially handled:
  287. *
  288. * - tag: this specifies the tag name. If not set, "div" will be used.
  289. *
  290. * @return static the field object itself
  291. */
  292. public function hint($content, $options = [])
  293. {
  294. $options = array_merge($this->hintOptions, $options);
  295. $tag = ArrayHelper::remove($options, 'tag', 'div');
  296. $this->parts['{hint}'] = Html::tag($tag, $content, $options);
  297. return $this;
  298. }
  299. /**
  300. * Renders an input tag.
  301. * @param string $type the input type (e.g. 'text', 'password')
  302. * @param array $options the tag options in terms of name-value pairs. These will be rendered as
  303. * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
  304. *
  305. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  306. *
  307. * @return static the field object itself
  308. */
  309. public function input($type, $options = [])
  310. {
  311. $options = array_merge($this->inputOptions, $options);
  312. $this->adjustLabelFor($options);
  313. $this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options);
  314. return $this;
  315. }
  316. /**
  317. * Renders a text input.
  318. * This method will generate the "name" and "value" tag attributes automatically for the model attribute
  319. * unless they are explicitly specified in `$options`.
  320. * @param array $options the tag options in terms of name-value pairs. These will be rendered as
  321. * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
  322. *
  323. * The following special options are recognized:
  324. *
  325. * - maxlength: integer|boolean, when `maxlength` is set true and the model attribute is validated
  326. * by a string validator, the `maxlength` option will take the value of [[\yii\validators\StringValidator::max]].
  327. * This is available since version 2.0.3.
  328. *
  329. * Note that if you set a custom `id` for the input element, you may need to adjust the value of [[selectors]] accordingly.
  330. *
  331. * @return static the field object itself
  332. */
  333. public function textInput($options = [])
  334. {
  335. $options = array_merge($this->inputOptions, $options);
  336. $this->adjustLabelFor($options);
  337. $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options);
  338. return $this;
  339. }
  340. /**
  341. * Renders a hidden input.
  342. *
  343. * Note that this method is provided for completeness. In most cases because you do not need
  344. * to validate a hidden input, you should not need to use this method. Instead, you should
  345. * use [[\yii\helpers\Html::activeHiddenInput()]].
  346. *
  347. * This method will generate the "name" and "value" tag attributes automatically for the model attribute
  348. * unless they are explicitly specified in `$options`.
  349. * @param array $options the tag options in terms of name-value pairs. These will be rendered as
  350. * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
  351. *
  352. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  353. *
  354. * @return static the field object itself
  355. */
  356. public function hiddenInput($options = [])
  357. {
  358. $options = array_merge($this->inputOptions, $options);
  359. $this->adjustLabelFor($options);
  360. $this->parts['{input}'] = Html::activeHiddenInput($this->model, $this->attribute, $options);
  361. return $this;
  362. }
  363. /**
  364. * Renders a password input.
  365. * This method will generate the "name" and "value" tag attributes automatically for the model attribute
  366. * unless they are explicitly specified in `$options`.
  367. * @param array $options the tag options in terms of name-value pairs. These will be rendered as
  368. * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
  369. *
  370. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  371. *
  372. * @return static the field object itself
  373. */
  374. public function passwordInput($options = [])
  375. {
  376. $options = array_merge($this->inputOptions, $options);
  377. $this->adjustLabelFor($options);
  378. $this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options);
  379. return $this;
  380. }
  381. /**
  382. * Renders a file input.
  383. * This method will generate the "name" and "value" tag attributes automatically for the model attribute
  384. * unless they are explicitly specified in `$options`.
  385. * @param array $options the tag options in terms of name-value pairs. These will be rendered as
  386. * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
  387. *
  388. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  389. *
  390. * @return static the field object itself
  391. */
  392. public function fileInput($options = [])
  393. {
  394. // https://github.com/yiisoft/yii2/pull/795
  395. if ($this->inputOptions !== ['class' => 'form-control']) {
  396. $options = array_merge($this->inputOptions, $options);
  397. }
  398. $this->adjustLabelFor($options);
  399. $this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options);
  400. return $this;
  401. }
  402. /**
  403. * Renders a text area.
  404. * The model attribute value will be used as the content in the textarea.
  405. * @param array $options the tag options in terms of name-value pairs. These will be rendered as
  406. * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
  407. *
  408. * If you set a custom `id` for the textarea element, you may need to adjust the [[$selectors]] accordingly.
  409. *
  410. * @return static the field object itself
  411. */
  412. public function textarea($options = [])
  413. {
  414. $options = array_merge($this->inputOptions, $options);
  415. $this->adjustLabelFor($options);
  416. $this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options);
  417. return $this;
  418. }
  419. /**
  420. * Renders a radio button.
  421. * This method will generate the "checked" tag attribute according to the model attribute value.
  422. * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
  423. *
  424. * - uncheck: string, the value associated with the uncheck state of the radio button. If not set,
  425. * it will take the default value '0'. This method will render a hidden input so that if the radio button
  426. * is not checked and is submitted, the value of this attribute will still be submitted to the server
  427. * via the hidden input. If you do not want any hidden input, you should explicitly set this option as null.
  428. * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass
  429. * 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.
  430. * When this option is specified, the radio button will be enclosed by a label tag. If you do not want any label, you should
  431. * explicitly set this option as null.
  432. * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
  433. *
  434. * The rest of the options will be rendered as the attributes of the resulting tag. The values will
  435. * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
  436. *
  437. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  438. *
  439. * @param boolean $enclosedByLabel whether to enclose the radio within the label.
  440. * If true, the method will still use [[template]] to layout the checkbox and the error message
  441. * except that the radio is enclosed by the label tag.
  442. * @return static the field object itself
  443. */
  444. public function radio($options = [], $enclosedByLabel = true)
  445. {
  446. if ($enclosedByLabel) {
  447. $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
  448. $this->parts['{label}'] = '';
  449. } else {
  450. if (isset($options['label']) && !isset($this->parts['{label}'])) {
  451. $this->parts['{label}'] = $options['label'];
  452. if (!empty($options['labelOptions'])) {
  453. $this->labelOptions = $options['labelOptions'];
  454. }
  455. }
  456. unset($options['labelOptions']);
  457. $options['label'] = null;
  458. $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
  459. }
  460. $this->adjustLabelFor($options);
  461. return $this;
  462. }
  463. /**
  464. * Renders a checkbox.
  465. * This method will generate the "checked" tag attribute according to the model attribute value.
  466. * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
  467. *
  468. * - uncheck: string, the value associated with the uncheck state of the radio button. If not set,
  469. * it will take the default value '0'. This method will render a hidden input so that if the radio button
  470. * is not checked and is submitted, the value of this attribute will still be submitted to the server
  471. * via the hidden input. If you do not want any hidden input, you should explicitly set this option as null.
  472. * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass
  473. * 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.
  474. * When this option is specified, the checkbox will be enclosed by a label tag. If you do not want any label, you should
  475. * explicitly set this option as null.
  476. * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
  477. *
  478. * The rest of the options will be rendered as the attributes of the resulting tag. The values will
  479. * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
  480. *
  481. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  482. *
  483. * @param boolean $enclosedByLabel whether to enclose the checkbox within the label.
  484. * If true, the method will still use [[template]] to layout the checkbox and the error message
  485. * except that the checkbox is enclosed by the label tag.
  486. * @return static the field object itself
  487. */
  488. public function checkbox($options = [], $enclosedByLabel = true)
  489. {
  490. if ($enclosedByLabel) {
  491. $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
  492. $this->parts['{label}'] = '';
  493. } else {
  494. if (isset($options['label']) && !isset($this->parts['{label}'])) {
  495. $this->parts['{label}'] = $options['label'];
  496. if (!empty($options['labelOptions'])) {
  497. $this->labelOptions = $options['labelOptions'];
  498. }
  499. }
  500. unset($options['labelOptions']);
  501. $options['label'] = null;
  502. $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
  503. }
  504. $this->adjustLabelFor($options);
  505. return $this;
  506. }
  507. /**
  508. * Renders a drop-down list.
  509. * The selection of the drop-down list is taken from the value of the model attribute.
  510. * @param array $items the option data items. The array keys are option values, and the array values
  511. * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
  512. * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
  513. * If you have a list of data models, you may convert them into the format described above using
  514. * [[ArrayHelper::map()]].
  515. *
  516. * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
  517. * the labels will also be HTML-encoded.
  518. * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
  519. *
  520. * - prompt: string, a prompt text to be displayed as the first option;
  521. * - options: array, the attributes for the select option tags. The array keys must be valid option values,
  522. * and the array values are the extra attributes for the corresponding option tags. For example,
  523. *
  524. * ~~~
  525. * [
  526. * 'value1' => ['disabled' => true],
  527. * 'value2' => ['label' => 'value 2'],
  528. * ];
  529. * ~~~
  530. *
  531. * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
  532. * except that the array keys represent the optgroup labels specified in $items.
  533. *
  534. * The rest of the options will be rendered as the attributes of the resulting tag. The values will
  535. * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
  536. *
  537. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  538. *
  539. * @return static the field object itself
  540. */
  541. public function dropDownList($items, $options = [])
  542. {
  543. $options = array_merge($this->inputOptions, $options);
  544. $this->adjustLabelFor($options);
  545. $this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options);
  546. return $this;
  547. }
  548. /**
  549. * Renders a list box.
  550. * The selection of the list box is taken from the value of the model attribute.
  551. * @param array $items the option data items. The array keys are option values, and the array values
  552. * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
  553. * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
  554. * If you have a list of data models, you may convert them into the format described above using
  555. * [[\yii\helpers\ArrayHelper::map()]].
  556. *
  557. * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
  558. * the labels will also be HTML-encoded.
  559. * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
  560. *
  561. * - prompt: string, a prompt text to be displayed as the first option;
  562. * - options: array, the attributes for the select option tags. The array keys must be valid option values,
  563. * and the array values are the extra attributes for the corresponding option tags. For example,
  564. *
  565. * ~~~
  566. * [
  567. * 'value1' => ['disabled' => true],
  568. * 'value2' => ['label' => 'value 2'],
  569. * ];
  570. * ~~~
  571. *
  572. * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
  573. * except that the array keys represent the optgroup labels specified in $items.
  574. * - unselect: string, the value that will be submitted when no option is selected.
  575. * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple
  576. * mode, we can still obtain the posted unselect value. If you do not want any hidden input,
  577. * you should explicitly set this option as null.
  578. *
  579. * The rest of the options will be rendered as the attributes of the resulting tag. The values will
  580. * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
  581. *
  582. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  583. *
  584. * @return static the field object itself
  585. */
  586. public function listBox($items, $options = [])
  587. {
  588. $options = array_merge($this->inputOptions, $options);
  589. $this->adjustLabelFor($options);
  590. $this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options);
  591. return $this;
  592. }
  593. /**
  594. * Renders a list of checkboxes.
  595. * A checkbox list allows multiple selection, like [[listBox()]].
  596. * As a result, the corresponding submitted value is an array.
  597. * The selection of the checkbox list is taken from the value of the model attribute.
  598. * @param array $items the data item used to generate the checkboxes.
  599. * The array values are the labels, while the array keys are the corresponding checkbox values.
  600. * Note that the labels will NOT be HTML-encoded, while the values will.
  601. * @param array $options options (name => config) for the checkbox list. The following options are specially handled:
  602. *
  603. * - unselect: string, the value that should be submitted when none of the checkboxes is selected.
  604. * By setting this option, a hidden input will be generated. If you do not want any hidden input,
  605. * you should explicitly set this option as null.
  606. * - separator: string, the HTML code that separates items.
  607. * - item: callable, a callback that can be used to customize the generation of the HTML code
  608. * corresponding to a single item in $items. The signature of this callback must be:
  609. *
  610. * ~~~
  611. * function ($index, $label, $name, $checked, $value)
  612. * ~~~
  613. *
  614. * where $index is the zero-based index of the checkbox in the whole list; $label
  615. * is the label for the checkbox; and $name, $value and $checked represent the name,
  616. * value and the checked status of the checkbox input.
  617. * @return static the field object itself
  618. */
  619. public function checkboxList($items, $options = [])
  620. {
  621. $this->adjustLabelFor($options);
  622. $this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options);
  623. return $this;
  624. }
  625. /**
  626. * Renders a list of radio buttons.
  627. * A radio button list is like a checkbox list, except that it only allows single selection.
  628. * The selection of the radio buttons is taken from the value of the model attribute.
  629. * @param array $items the data item used to generate the radio buttons.
  630. * The array values are the labels, while the array keys are the corresponding radio values.
  631. * Note that the labels will NOT be HTML-encoded, while the values will.
  632. * @param array $options options (name => config) for the radio button list. The following options are specially handled:
  633. *
  634. * - unselect: string, the value that should be submitted when none of the radio buttons is selected.
  635. * By setting this option, a hidden input will be generated. If you do not want any hidden input,
  636. * you should explicitly set this option as null.
  637. * - separator: string, the HTML code that separates items.
  638. * - item: callable, a callback that can be used to customize the generation of the HTML code
  639. * corresponding to a single item in $items. The signature of this callback must be:
  640. *
  641. * ~~~
  642. * function ($index, $label, $name, $checked, $value)
  643. * ~~~
  644. *
  645. * where $index is the zero-based index of the radio button in the whole list; $label
  646. * is the label for the radio button; and $name, $value and $checked represent the name,
  647. * value and the checked status of the radio button input.
  648. * @return static the field object itself
  649. */
  650. public function radioList($items, $options = [])
  651. {
  652. $this->adjustLabelFor($options);
  653. $this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options);
  654. return $this;
  655. }
  656. /**
  657. * Renders a widget as the input of the field.
  658. *
  659. * Note that the widget must have both `model` and `attribute` properties. They will
  660. * be initialized with [[model]] and [[attribute]] of this field, respectively.
  661. *
  662. * If you want to use a widget that does not have `model` and `attribute` properties,
  663. * please use [[render()]] instead.
  664. *
  665. * For example to use the [[MaskedInput]] widget to get some date input, you can use
  666. * the following code, assuming that `$form` is your [[ActiveForm]] instance:
  667. *
  668. * ```php
  669. * $form->field($model, 'date')->widget(\yii\widgets\MaskedInput::className(), [
  670. * 'mask' => '99/99/9999',
  671. * ]);
  672. * ```
  673. *
  674. * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
  675. *
  676. * @param string $class the widget class name
  677. * @param array $config name-value pairs that will be used to initialize the widget
  678. * @return static the field object itself
  679. */
  680. public function widget($class, $config = [])
  681. {
  682. /* @var $class \yii\base\Widget */
  683. $config['model'] = $this->model;
  684. $config['attribute'] = $this->attribute;
  685. $config['view'] = $this->form->getView();
  686. $this->parts['{input}'] = $class::widget($config);
  687. return $this;
  688. }
  689. /**
  690. * Adjusts the "for" attribute for the label based on the input options.
  691. * @param array $options the input options
  692. */
  693. protected function adjustLabelFor($options)
  694. {
  695. if (isset($options['id']) && !isset($this->labelOptions['for'])) {
  696. $this->labelOptions['for'] = $options['id'];
  697. }
  698. }
  699. /**
  700. * Returns the JS options for the field.
  701. * @return array the JS options
  702. */
  703. protected function getClientOptions()
  704. {
  705. $attribute = Html::getAttributeName($this->attribute);
  706. if (!in_array($attribute, $this->model->activeAttributes(), true)) {
  707. return [];
  708. }
  709. $enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
  710. $enableAjaxValidation = $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
  711. if ($enableClientValidation) {
  712. $validators = [];
  713. foreach ($this->model->getActiveValidators($attribute) as $validator) {
  714. /* @var $validator \yii\validators\Validator */
  715. $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView());
  716. if ($validator->enableClientValidation && $js != '') {
  717. if ($validator->whenClient !== null) {
  718. $js = "if (({$validator->whenClient})(attribute, value)) { $js }";
  719. }
  720. $validators[] = $js;
  721. }
  722. }
  723. }
  724. if (!$enableAjaxValidation && (!$enableClientValidation || empty($validators))) {
  725. return [];
  726. }
  727. $options = [];
  728. $inputID = Html::getInputId($this->model, $this->attribute);
  729. $options['id'] = $inputID;
  730. $options['name'] = $this->attribute;
  731. $options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID";
  732. $options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID";
  733. if (isset($this->selectors['error'])) {
  734. $options['error'] = $this->selectors['error'];
  735. } elseif (isset($this->errorOptions['class'])) {
  736. $options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY));
  737. } else {
  738. $options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span';
  739. }
  740. $options['encodeError'] = !isset($this->errorOptions['encode']) || $this->errorOptions['encode'];
  741. if ($enableAjaxValidation) {
  742. $options['enableAjaxValidation'] = true;
  743. }
  744. foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) {
  745. $options[$name] = $this->$name === null ? $this->form->$name : $this->$name;
  746. }
  747. if (!empty($validators)) {
  748. $options['validate'] = new JsExpression("function (attribute, value, messages, deferred, \$form) {" . implode('', $validators) . '}');
  749. }
  750. // only get the options that are different from the default ones (set in yii.activeForm.js)
  751. return array_diff_assoc($options, [
  752. 'validateOnChange' => true,
  753. 'validateOnBlur' => true,
  754. 'validateOnType' => false,
  755. 'validationDelay' => 500,
  756. 'encodeError' => true,
  757. 'error' => '.help-block',
  758. ]);
  759. }
  760. }