Completed
Push — 2.1 ( 75349f...bf116e )
by Alexander
29:27
created

ActiveForm::endField()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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