yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.24k stars 6.9k forks source link

yiiActiveForm.js broken client validation of radioList #17893

Open dicrtarasov opened 4 years ago

dicrtarasov commented 4 years ago

TestModel.php

class TestModel extends Model
{
    public $myfield;

    public function rules()
    {
        return [
            ['myfield', 'trim'],
            ['myfield', 'required']
        ];
    }
}

view.php

$model = new TestModel([  'myfield' => 1 ]);

$form = ActiveForm::begin(['method' => 'post']);

echo $form->field($model, 'myfield')->radioList([
    0 => 'myval 0',
    1 => 'myval 1',
]);

echo Html::submitButton();

ActiveForm::end();

After pressing submit button, client validation is run, get error: Myfield cannot be blank. and submitting canceled. img

I found the problem is connected with yii2-bootstrap4 custom-control functional, which layout is incompatiple with yii.validation.js. The generated HTML-code for field is:

<div class="form-group field-testmodel-myfield required">
  <label>Myfield</label>
  <input type="hidden" name="TestModel[myfield]" value="">
  <div id="testmodel-myfield" role="radiogroup" aria-required="true" aria-invalid="true">
    <div class="custom-control custom-radio">
      <input type="radio" id="i0" class="custom-control-input is-invalid" name="TestModel[myfield]" value="0">
      <label class="custom-control-label" for="i0">myval 0</label>
    </div>
    <div class="custom-control custom-radio">
      <input type="radio" id="i1" class="custom-control-input is-invalid" name="TestModel[myfield]" value="1" checked="">
      <label class="custom-control-label" for="i1">myval 1</label>
      <div class="invalid-feedback">Myfield cannot be blank.</div>
    </div>
</div>

Here we see, that id="testmodel-myfield" assigned not to input element, but div, which is complex input group. The inputs itself get strage id="i0" ids ...

yii.validation.js

        trim: function ($form, attribute, options, value) {
            var $input = $form.find(attribute.input);
            if ($input.is(':checkbox, :radio')) {
                return value;
            }

Validation function search input element by attribute.input and instead of input it get the div with id="testmodel-myfield", so next operations will fail, because $input.is(':checkbox, :radio') can't be applied to div.

dicrtarasov commented 4 years ago

The temporary workaround is to turn off enableClientValidation for all radioList, checkboxList, file and other fields, which using bootstrap4 custom-control template.

echo $form->field($model, 'myfield', [
    'enableClientValidation' => false
])->radioList([
    0 => 'myval 0',
    1 => 'myval 1',
]);
alex-code commented 4 years ago

Remove the trim rule from your model and it'll work.

dicrtarasov commented 4 years ago

Wow, you are right. I see only trim and file validation functions of yii.validations.js use id to find input. Thank..... But this bug must be fixed, because this is bug.

alex-code commented 4 years ago

Is it a bug though? Why do you need to trim the value of a radio/checkbox input?

dicrtarasov commented 4 years ago

Why need - this is other question from other story.

dicrtarasov commented 4 years ago

Because model propagate data not only from web form posing, but other sources.

alex-code commented 4 years ago

You can use a scenario then. When POSTing with the form don't use the trim rule. https://www.yiiframework.com/doc/guide/2.0/en/structure-models#scenarios

dicrtarasov commented 4 years ago

I think, that a lot of workarounds - is not a preferred programming way of Qiang Xue.

Lets's see yii.activeForm.js - it already has this fix in function findInput:

    var findInput = function ($form, attribute) {
        var $input = $form.find(attribute.input);
        if ($input.length && $input[0].tagName.toLowerCase() === 'div') {
            // checkbox list or radio list
            return $input.find('input');
        } else {
            return $input;
        }
    };

So, I think that trim in yii.validation.js must also be fixed, than advise everyone to look for workarounds.

dicrtarasov commented 4 years ago

Created pull request: https://github.com/yiisoft/yii2/pull/17894

alex-code commented 4 years ago

The trim function in client validation will not work with checkbox/radio inputs. Even on a single checkbox/radio it'll return the value unchanged.

dicrtarasov commented 4 years ago

It will, because of trim code:

            if ($input.is(':checkbox, :radio')) {
                return value;
            }

Currencty it produce validation error on correct field, so this is bug.

dicrtarasov commented 4 years ago

The fix is the save as in activeForm findInput function:

if ($input.length && $input[0].tagName.toLowerCase() === 'div') {
    // checkbox list or radio list
    $input = $input.find('input');
}
dicrtarasov commented 4 years ago

Also, the input selector is incorrect in generated plugin script: https://github.com/yiisoft/yii2/issues/17895