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

779 lines
34KB

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