Completed
Push — 15476-add-option-to-change-val... ( ec09a1...21fb7c )
by Alexander
10:03
created

ActiveForm::getClientOptions()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 34
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 2.0002

Importance

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