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.

422 lines
16KB

  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\InvalidCallException;
  10. use yii\base\Widget;
  11. use yii\base\Model;
  12. use yii\helpers\ArrayHelper;
  13. use yii\helpers\Url;
  14. use yii\helpers\Html;
  15. use yii\helpers\Json;
  16. /**
  17. * ActiveForm is a widget that builds an interactive HTML form for one or multiple data models.
  18. *
  19. * @author Qiang Xue <qiang.xue@gmail.com>
  20. * @since 2.0
  21. */
  22. class ActiveForm extends Widget
  23. {
  24. /**
  25. * @var array|string $action the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]].
  26. * @see method for specifying the HTTP method for this form.
  27. */
  28. public $action = '';
  29. /**
  30. * @var string the form submission method. This should be either `post` or `get`. Defaults to `post`.
  31. *
  32. * When you set this to `get` you may see the url parameters repeated on each request.
  33. * This is because the default value of [[action]] is set to be the current request url and each submit
  34. * will add new parameters instead of replacing existing ones.
  35. * You may set [[action]] explicitly to avoid this:
  36. *
  37. * ```php
  38. * $form = ActiveForm::begin([
  39. * 'method' => 'get',
  40. * 'action' => ['controller/action'],
  41. * ]);
  42. * ```
  43. */
  44. public $method = 'post';
  45. /**
  46. * @var array the HTML attributes (name-value pairs) for the form tag.
  47. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  48. */
  49. public $options = [];
  50. /**
  51. * @var string the default field class name when calling [[field()]] to create a new field.
  52. * @see fieldConfig
  53. */
  54. public $fieldClass = 'yii\widgets\ActiveField';
  55. /**
  56. * @var array|\Closure the default configuration used by [[field()]] when creating a new field object.
  57. * This can be either a configuration array or an anonymous function returning a configuration array.
  58. * If the latter, the signature should be as follows:
  59. *
  60. * ```php
  61. * function ($model, $attribute)
  62. * ```
  63. *
  64. * The value of this property will be merged recursively with the `$options` parameter passed to [[field()]].
  65. *
  66. * @see fieldClass
  67. */
  68. public $fieldConfig = [];
  69. /**
  70. * @var boolean whether to perform encoding on the error summary.
  71. */
  72. public $encodeErrorSummary = true;
  73. /**
  74. * @var string the default CSS class for the error summary container.
  75. * @see errorSummary()
  76. */
  77. public $errorSummaryCssClass = 'error-summary';
  78. /**
  79. * @var string the CSS class that is added to a field container when the associated attribute is required.
  80. */
  81. public $requiredCssClass = 'required';
  82. /**
  83. * @var string the CSS class that is added to a field container when the associated attribute has validation error.
  84. */
  85. public $errorCssClass = 'has-error';
  86. /**
  87. * @var string the CSS class that is added to a field container when the associated attribute is successfully validated.
  88. */
  89. public $successCssClass = 'has-success';
  90. /**
  91. * @var string the CSS class that is added to a field container when the associated attribute is being validated.
  92. */
  93. public $validatingCssClass = 'validating';
  94. /**
  95. * @var boolean whether to enable client-side data validation.
  96. * If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field.
  97. */
  98. public $enableClientValidation = true;
  99. /**
  100. * @var boolean whether to enable AJAX-based data validation.
  101. * If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field.
  102. */
  103. public $enableAjaxValidation = false;
  104. /**
  105. * @var boolean whether to hook up `yii.activeForm` JavaScript plugin.
  106. * This property must be set `true` if you want to support client validation and/or AJAX validation, or if you
  107. * want to take advantage of the `yii.activeForm` plugin. When this is `false`, the form will not generate
  108. * any JavaScript.
  109. */
  110. public $enableClientScript = true;
  111. /**
  112. * @var array|string the URL for performing AJAX-based validation. This property will be processed by
  113. * [[Url::to()]]. Please refer to [[Url::to()]] for more details on how to configure this property.
  114. * If this property is not set, it will take the value of the form's action attribute.
  115. */
  116. public $validationUrl;
  117. /**
  118. * @var boolean whether to perform validation when the form is submitted.
  119. */
  120. public $validateOnSubmit = true;
  121. /**
  122. * @var boolean whether to perform validation when the value of an input field is changed.
  123. * If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field.
  124. */
  125. public $validateOnChange = true;
  126. /**
  127. * @var boolean whether to perform validation when an input field loses focus.
  128. * If [[ActiveField::$validateOnBlur]] is set, its value will take precedence for that input field.
  129. */
  130. public $validateOnBlur = true;
  131. /**
  132. * @var boolean whether to perform validation while the user is typing in an input field.
  133. * If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field.
  134. * @see validationDelay
  135. */
  136. public $validateOnType = false;
  137. /**
  138. * @var integer number of milliseconds that the validation should be delayed when the user types in the field
  139. * and [[validateOnType]] is set `true`.
  140. * If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field.
  141. */
  142. public $validationDelay = 500;
  143. /**
  144. * @var string the name of the GET parameter indicating the validation request is an AJAX request.
  145. */
  146. public $ajaxParam = 'ajax';
  147. /**
  148. * @var string the type of data that you're expecting back from the server.
  149. */
  150. public $ajaxDataType = 'json';
  151. /**
  152. * @var boolean whether to scroll to the first error after validation.
  153. * @since 2.0.6
  154. */
  155. public $scrollToError = true;
  156. /**
  157. * @var array the client validation options for individual attributes. Each element of the array
  158. * represents the validation options for a particular attribute.
  159. * @internal
  160. */
  161. public $attributes = [];
  162. /**
  163. * @var ActiveField[] the ActiveField objects that are currently active
  164. */
  165. private $_fields = [];
  166. /**
  167. * Initializes the widget.
  168. * This renders the form open tag.
  169. */
  170. public function init()
  171. {
  172. if (!isset($this->options['id'])) {
  173. $this->options['id'] = $this->getId();
  174. }
  175. ob_start();
  176. ob_implicit_flush(false);
  177. }
  178. /**
  179. * Runs the widget.
  180. * This registers the necessary JavaScript code and renders the form close tag.
  181. * @throws InvalidCallException if `beginField()` and `endField()` calls are not matching.
  182. */
  183. public function run()
  184. {
  185. if (!empty($this->_fields)) {
  186. throw new InvalidCallException('Each beginField() should have a matching endField() call.');
  187. }
  188. $content = ob_get_clean();
  189. echo Html::beginForm($this->action, $this->method, $this->options);
  190. echo $content;
  191. if ($this->enableClientScript) {
  192. $id = $this->options['id'];
  193. $options = Json::htmlEncode($this->getClientOptions());
  194. $attributes = Json::htmlEncode($this->attributes);
  195. $view = $this->getView();
  196. ActiveFormAsset::register($view);
  197. $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
  198. }
  199. echo Html::endForm();
  200. }
  201. /**
  202. * Returns the options for the form JS widget.
  203. * @return array the options.
  204. */
  205. protected function getClientOptions()
  206. {
  207. $options = [
  208. 'encodeErrorSummary' => $this->encodeErrorSummary,
  209. 'errorSummary' => '.' . implode('.', preg_split('/\s+/', $this->errorSummaryCssClass, -1, PREG_SPLIT_NO_EMPTY)),
  210. 'validateOnSubmit' => $this->validateOnSubmit,
  211. 'errorCssClass' => $this->errorCssClass,
  212. 'successCssClass' => $this->successCssClass,
  213. 'validatingCssClass' => $this->validatingCssClass,
  214. 'ajaxParam' => $this->ajaxParam,
  215. 'ajaxDataType' => $this->ajaxDataType,
  216. 'scrollToError' => $this->scrollToError,
  217. ];
  218. if ($this->validationUrl !== null) {
  219. $options['validationUrl'] = Url::to($this->validationUrl);
  220. }
  221. // only get the options that are different from the default ones (set in yii.activeForm.js)
  222. return array_diff_assoc($options, [
  223. 'encodeErrorSummary' => true,
  224. 'errorSummary' => '.error-summary',
  225. 'validateOnSubmit' => true,
  226. 'errorCssClass' => 'has-error',
  227. 'successCssClass' => 'has-success',
  228. 'validatingCssClass' => 'validating',
  229. 'ajaxParam' => 'ajax',
  230. 'ajaxDataType' => 'json',
  231. 'scrollToError' => true,
  232. ]);
  233. }
  234. /**
  235. * Generates a summary of the validation errors.
  236. * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden.
  237. * @param Model|Model[] $models the model(s) associated with this form.
  238. * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
  239. *
  240. * - `header`: string, the header HTML for the error summary. If not set, a default prompt string will be used.
  241. * - `footer`: string, the footer HTML for the error summary.
  242. *
  243. * The rest of the options will be rendered as the attributes of the container tag. The values will
  244. * be HTML-encoded using [[\yii\helpers\Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
  245. * @return string the generated error summary.
  246. * @see errorSummaryCssClass
  247. */
  248. public function errorSummary($models, $options = [])
  249. {
  250. Html::addCssClass($options, $this->errorSummaryCssClass);
  251. $options['encode'] = $this->encodeErrorSummary;
  252. return Html::errorSummary($models, $options);
  253. }
  254. /**
  255. * Generates a form field.
  256. * A form field is associated with a model and an attribute. It contains a label, an input and an error message
  257. * and use them to interact with end users to collect their inputs for the attribute.
  258. * @param Model $model the data model.
  259. * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
  260. * about attribute expression.
  261. * @param array $options the additional configurations for the field object. These are properties of [[ActiveField]]
  262. * or a subclass, depending on the value of [[fieldClass]].
  263. * @return ActiveField the created ActiveField object.
  264. * @see fieldConfig
  265. */
  266. public function field($model, $attribute, $options = [])
  267. {
  268. $config = $this->fieldConfig;
  269. if ($config instanceof \Closure) {
  270. $config = call_user_func($config, $model, $attribute);
  271. }
  272. if (!isset($config['class'])) {
  273. $config['class'] = $this->fieldClass;
  274. }
  275. return Yii::createObject(ArrayHelper::merge($config, $options, [
  276. 'model' => $model,
  277. 'attribute' => $attribute,
  278. 'form' => $this,
  279. ]));
  280. }
  281. /**
  282. * Begins a form field.
  283. * This method will create a new form field and returns its opening tag.
  284. * You should call [[endField()]] afterwards.
  285. * @param Model $model the data model.
  286. * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
  287. * about attribute expression.
  288. * @param array $options the additional configurations for the field object.
  289. * @return string the opening tag.
  290. * @see endField()
  291. * @see field()
  292. */
  293. public function beginField($model, $attribute, $options = [])
  294. {
  295. $field = $this->field($model, $attribute, $options);
  296. $this->_fields[] = $field;
  297. return $field->begin();
  298. }
  299. /**
  300. * Ends a form field.
  301. * This method will return the closing tag of an active form field started by [[beginField()]].
  302. * @return string the closing tag of the form field.
  303. * @throws InvalidCallException if this method is called without a prior [[beginField()]] call.
  304. */
  305. public function endField()
  306. {
  307. $field = array_pop($this->_fields);
  308. if ($field instanceof ActiveField) {
  309. return $field->end();
  310. } else {
  311. throw new InvalidCallException('Mismatching endField() call.');
  312. }
  313. }
  314. /**
  315. * Validates one or several models and returns an error message array indexed by the attribute IDs.
  316. * This is a helper method that simplifies the way of writing AJAX validation code.
  317. *
  318. * For example, you may use the following code in a controller action to respond
  319. * to an AJAX validation request:
  320. *
  321. * ```php
  322. * $model = new Post;
  323. * $model->load(Yii::$app->request->post());
  324. * if (Yii::$app->request->isAjax) {
  325. * Yii::$app->response->format = Response::FORMAT_JSON;
  326. * return ActiveForm::validate($model);
  327. * }
  328. * // ... respond to non-AJAX request ...
  329. * ```
  330. *
  331. * To validate multiple models, simply pass each model as a parameter to this method, like
  332. * the following:
  333. *
  334. * ```php
  335. * ActiveForm::validate($model1, $model2, ...);
  336. * ```
  337. *
  338. * @param Model $model the model to be validated.
  339. * @param mixed $attributes list of attributes that should be validated.
  340. * If this parameter is empty, it means any attribute listed in the applicable
  341. * validation rules should be validated.
  342. *
  343. * When this method is used to validate multiple models, this parameter will be interpreted
  344. * as a model.
  345. *
  346. * @return array the error message array indexed by the attribute IDs.
  347. */
  348. public static function validate($model, $attributes = null)
  349. {
  350. $result = [];
  351. if ($attributes instanceof Model) {
  352. // validating multiple models
  353. $models = func_get_args();
  354. $attributes = null;
  355. } else {
  356. $models = [$model];
  357. }
  358. /* @var $model Model */
  359. foreach ($models as $model) {
  360. $model->validate($attributes);
  361. foreach ($model->getErrors() as $attribute => $errors) {
  362. $result[Html::getInputId($model, $attribute)] = $errors;
  363. }
  364. }
  365. return $result;
  366. }
  367. /**
  368. * Validates an array of model instances and returns an error message array indexed by the attribute IDs.
  369. * This is a helper method that simplifies the way of writing AJAX validation code for tabular input.
  370. *
  371. * For example, you may use the following code in a controller action to respond
  372. * to an AJAX validation request:
  373. *
  374. * ```php
  375. * // ... load $models ...
  376. * if (Yii::$app->request->isAjax) {
  377. * Yii::$app->response->format = Response::FORMAT_JSON;
  378. * return ActiveForm::validateMultiple($models);
  379. * }
  380. * // ... respond to non-AJAX request ...
  381. * ```
  382. *
  383. * @param array $models an array of models to be validated.
  384. * @param mixed $attributes list of attributes that should be validated.
  385. * If this parameter is empty, it means any attribute listed in the applicable
  386. * validation rules should be validated.
  387. * @return array the error message array indexed by the attribute IDs.
  388. */
  389. public static function validateMultiple($models, $attributes = null)
  390. {
  391. $result = [];
  392. /* @var $model Model */
  393. foreach ($models as $i => $model) {
  394. $model->validate($attributes);
  395. foreach ($model->getErrors() as $attribute => $errors) {
  396. $result[Html::getInputId($model, "[$i]" . $attribute)] = $errors;
  397. }
  398. }
  399. return $result;
  400. }
  401. }