You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

803 lines
35KB

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