kartik-v / yii2-widget-select2

Enhanced Yii2 wrapper for the Select2 jQuery plugin (sub repo split from yii2-widgets).
http://demos.krajee.com/widget-details/select2
Other
323 stars 145 forks source link

Yii2 Active Form Validation not working with select2 #289

Closed SillyPhp closed 5 years ago

SillyPhp commented 5 years ago

Prerequisites

Steps to reproduce the issue

1. 2. 3.

Expected behavior and actual behavior

When I follow those steps, I see...

I was expecting...

Environment

Browsers

Operating System

Libraries

Isolating the problem

SillyPhp commented 5 years ago

using with active form and model and the field is set to required with model rules, but on page load even though the field is required its getting submitted on form submit in blank

kartik-v commented 5 years ago

Cannot reproduce your issue. Can you please check your app code to ensure you are not overwriting the model attributes. You can cross check the demos where this works fine.

lgveronese commented 5 years ago

It happens to me too..

The attribute has required rule on model but the clientside validation didn't trigger for select2 field. Capturar

The client side validation only happens when I change the value.. but if I skip that field and try to submit form I got no validation errors and the form is submitted

Axhind commented 5 years ago

I encountered the very same problem and, after some digging, figured out why this happens. Yii ActiveForm validator (yii.activeForm.js) skips validation when encounters select inputs with no options available or with just one option whose value is empty.

Immagine

A workaround is to add the 'data' option to the widget, so there will be at least one option available at start.

buttflattery commented 5 years ago

Hello @kartik-v , The conditional validation is not working with select2 on the first-page load. once the form submits the server-side validation adds error to the attribute then it starts working correctly

i am using yii\base\Model for my form where i have 2 dropdowns

in my model rules, I am using conditional validation like

return [
            [
                ['export_type'], 'required',
            ],
            [
                'campaign_single', 'required',
                'when' => function ($model) {
                    return $model->export_type === self::EXPORT_TYPE_SINGLE;
                },
                'whenClient' => "function(attribute, value){
                    return ($('#export_type').val() == 'single');
                }",
            ],
];

The difference i have noticed is the on the first page load the default dropdown that is hidden for Select2 looks like below


<select id="campaign_single" class="form-control select2-hidden-accessible" name="ExportSubscribers[campaign_single]" data-s2-options="s2options_6cc131ae" data-krajee-select2="select2_8df6e9a7" style="display:none" data-select2-id="campaign_single" tabindex="-1" aria-hidden="true">
<option value="" data-select2-id="4">Select Campaign</option>
</select>

and once i hit the submit and the form loads with erorrs after page refresh the default dropdown looks like below, which has an extra option added <option value="" selected="" data-select2-id="6"></option>


<select id="campaign_single" class="form-control select2-hidden-accessible" name="ExportSubscribers[campaign_single]" aria-invalid="true" data-s2-options="s2options_6cc131ae" data-krajee-select2="select2_8df6e9a7" style="display:none" data-select2-id="campaign_single" tabindex="-1" aria-hidden="true">
<option value="" data-select2-id="5">Select Campaign</option>
<option value="" selected="" data-select2-id="6"></option>
</select>
buttflattery commented 5 years ago

@Axhind even if there is one option defined in the select2, the second condition wont allow to validate as it is checking if there is only one option and its value is empty, which means that if you are initializing the select2 with a placeholder it ant going to work neither adding the data option to the select2 is a workaround when you are using select2 with ajax.

The workaround i have found is to add value option under the options option like

'value' => '',

and then it starts working but for select2 single select only and not for the multi-select

Axhind commented 5 years ago

@buttflattery yes, as i wrote

or with just one option whose value is empty

so, that goes without saying, the added 'data' option needs to be not a placeholder. Of course this is a viable workaround (and then far from being a solution) only if, as was my case, some values can be made available at start.

kartik-v commented 5 years ago

@buttflattery i am not fully clear on how the validation is being triggered for your use case and what you are configuring as values for your dropdown and if you have checked cases like @Axhind has mentioned. Check if it works properly for a native HTML select and work up from there

buttflattery commented 5 years ago

@kartik-v , although i remember that i wrote in about the native drop-down but somehow it is missing in my post, Yes the native dropdown works correctly with the above rules configured.

About the values, the campaign_single isnt initialized with any data as it is using the ajax option to fetch the data, and the export_type has only 2 options ['single'=>'Single','multiple'=>'Multiple'].

The case that Axhind has mentioned is not a workaround that would work for both single and multi-select, although for the single-select adding the value attribute under the options option makes it work but not if you are using the multi-select with multiple=>true.

I have provided the minimum fields above from the form but i am using a multi-select too which wont fix even if i provide the value option.

I have left office at the moment so cant add the code right now but will add tomorrow all the relevant code from the form,controller and model that you can run at your end to verify.

buttflattery commented 5 years ago

@kartik-v Here is the code that can help you reproduce the issue. Before you use the code here are the few things important to compare at your end

Yii Version : 2.0.27 kartik-v/yii2-widgets : v3.4.1 kartik-v/yii2-widget-select2 : v2.1.4

Below is the code for the model and the form the controller code isnt relevant so not adding it. just copy paste the elow code and run it

Model ExportSubscribers

<?php
namespace frontend\models;

use yii\db\Query;
use yii\base\Model;

class ExportSubscribers extends Model
{
    /**
     * @var mixed
     */
    public $export_type;
    /**
     * @var mixed
     */
    public $campaign_single;
    /**
     * @var mixed
     */
    public $campaign_multiple;

    const EXPORT_TYPE_SINGLE = 'single';
    const EXPORT_TYPE_MULTIPLE = 'multiple';

    /**
     * @return mixed
     */
    public function rules()
    {
        return [
            [
                ['export_type'], 'required',
            ],
            [
                'campaign_single', 'required',
                'when' => function ($model) {
                    return $model->export_type === self::EXPORT_TYPE_SINGLE;
                },
                'whenClient' => "function(attribute, value){
                    return ($('#export_type').val() == '" . self::EXPORT_TYPE_SINGLE . "');
                }",
            ],
            [
                'campaign_multiple', 'required',
                'when' => function ($model) {
                    return ($model->export_type === self::EXPORT_TYPE_MULTIPLE);
                },
                'whenClient' => "function(attribute,value){
                    return ($('#export_type').val() == '" . self::EXPORT_TYPE_MULTIPLE . "');
                }",
            ],
        ];
    }

    public function getExportTypes()
    {
        return [
            self::EXPORT_TYPE_SINGLE => 'Single',
            self::EXPORT_TYPE_MULTIPLE => 'Multiple',
        ];
    }

    public function attributeLabels()
    {
        return [
            'export_type' => 'Export Type',
            'campaign_single' => 'Campaign',
            'campaign_from' => 'Difference From',
        ];
    }
}

Active Form


<?php
$js=<<<JS
let EXPORT_CSV = {
    updateFormOptions(choice) {
        let showFormOptions = {
            single() {
                $("#row-single").show();
                $("#row-multiple").hide();
            },
            multiple() {
                $("#row-single").hide();
                $("#row-multiple").show();
            }
        };

        if (showFormOptions.hasOwnProperty(choice)) {
            showFormOptions[choice].call();
        }
    },
};
JS;
$this->registerJs($js,\yii\web\View::POS_READY);
    $form = ActiveForm::begin(
        [
            'id' => 'export-form',
            'fieldConfig' => [
                'options' => [
                    'tag' => 'div',
                    'class' => 'form-group form-float',
                ],
            ],
        ]
    );
?>
<div class="row clearfix">
    <div class="col-sm-12">
        <?php
            echo
            $form->field(
                $model, 'export_type',
                [
                    'template' => '{input}{error}',
                ]
            )->widget(
                Select2::class,
                [
                    'data' => $model->getExportTypes(),
                    'options' => [
                        'class' => 'form-control',
                        'id' => 'export_type',
                        'placeholder' => 'Select Export Type',
                    ],
                    'theme' => Select2::THEME_BOOTSTRAP,
                    'pluginOptions' => [
                        'allowClear' => true,
                    ],
                    'pluginEvents' => [
                        "select2:select" => "function(e) { EXPORT_CSV.updateFormOptions(e.params.data.id); }",
                    ],
                ]
            )->label(false);
        ?>
    </div>
</div>

<div class="row clearfix" id="row-single" style="<?php echo ($model->export_type === $model::EXPORT_TYPE_SINGLE ? '' : 'display:none;') ?>">
    <div class="col-sm-6">
    <?php
        echo
        $form->field(
            $model,
            'campaign_single',
            [
                'template' => '{input}{error}',
            ]
        )->widget(
            Select2::class,
            [
                'options' => [
                    'placeholder' => 'Select Campaign',
                    'class' => 'form-control',
                    'id' => 'campaign_single',
                    'value' => '',
                ],
                'theme' => Select2::THEME_BOOTSTRAP,
                'pluginOptions' => [
                    'minimumInputLength' => 3,
                    'allowClear' => true,
                    'ajax' => [
                        'url' => Url::to(['/campaign/campaign-listing']),
                        'method' => 'POST',
                        'dataType' => 'json',
                        'data' => new JsExpression(
                            'function(params){
                                let request_params= {
                                    q:params.term,
                                    page:params.page || 1
                                };
                                request_params[yii.getCsrfParam()]=yii.getCsrfToken();
                                return request_params;
                            }'
                        ),
                    ],
                ],
            ]
        )->label(false);
    ?>
    </div>
</div>

<div id="row-multiple" style="<?php echo ($model->export_type !== $model::EXPORT_TYPE_MULTIPLE ? 'display:none;' : '') ?>">
<div class="row clearfix">

    <div class="col-sm-6">
    <?php
        echo
        $form->field(
            $model,
            'campaign_multiple',
            [
                'template' => '{input}{error}',
            ]
        )->widget(
            Select2::class,
            [
                'options' => [
                    'multiple => true
                    'placeholder' => 'Campaign Multiple',
                    'class' => 'form-control',
                    'id' => 'campaign_multiple',
                    'value' => '',
                ],
                'theme' => Select2::THEME_BOOTSTRAP,
                'pluginOptions' => [
                    'minimumInputLength' => 3,
                    'allowClear' => true,
                    'ajax' => [
                        'url' => Url::to(['/campaign/campaign-listing']),
                        'method' => 'POST',
                        'dataType' => 'json',
                        'data' => new JsExpression(
                            'function(params){
                                let request_params= {
                                    q:params.term,
                                    page:params.page || 1
                                };
                                request_params[yii.getCsrfParam()]=yii.getCsrfToken();
                                return request_params;
                            }'
                        ),
                    ],
                ],
            ]
        )->label(false);
    ?>
    </div>
</div>

</div>

<div class="form-group">
    <?php echo Html::submitButton('Export', ['class' => 'btn btn-success waves-effect']) ?>
</div>

<?php ActiveForm::end();?>
HenryVolkmer commented 5 years ago

Hi,

the issue was tracked here too: https://github.com/yiisoft/yii2/issues/17147

Yii2 ignore required <select> without options because it is no valid HTML. Have a look at https://w3c.github.io/html-reference/select.html

A select element with a required attribute and without a multiple attribute, and whose size is “1”, must have a child option element. The first child option element of a select element with a required attribute and without a multiple attribute, and whose size is “1”, must have either an empty value attribute, or must have no text content.

The select2 widget should add an empty option, if no Select2::data was configured.

kartik-v commented 5 years ago

Fixed via #298 and 9a34ab0

buttflattery commented 5 years ago

@kartik-v so , do we need to still add the value attribute under options or remove it ?

kartik-v commented 5 years ago

It should do it automatically. Refer commit 9a34ab0.

buttflattery commented 5 years ago

Yeah i looked into the commit thanks.

kadyrleev commented 4 years ago

Still not working for me: https://github.com/yiisoft/yii2/issues/17147#issuecomment-559954251

Am I missing something in the field config?

kartik-v commented 4 years ago

It seems the issue is as mentioned by @Axhind in this comment.

This is due to an update to the yii2 form validation script which behaves in a totally different way now and skips select dropdowns with no options OR just one empty option.

The solution workaround is that you need to have at least ONE EXTRA option in your SELECT dropdown beyond the empty value (which is set via placeholder).

HenryVolkmer commented 4 years ago

Hey, no kartik, its a issue in select2 widget. Please refer (again...) to my PR, which fixing this bug (since 13.Oct.2019).

kadyrleev commented 4 years ago

@HenryVolkmer Thanks for your contribution. Although with your fix and yii >=2.0.16 the problem with form validation is still there, while having yii rolled back to 2.0.15 "fixes" it.

HenryVolkmer commented 4 years ago

ty for your feedback! Please checkout my latest Version, it should work now. (https://youtu.be/JwCdHIzoTPI)

jabernalv commented 4 years ago

I found this workaround: $(".select2").on("focusout", function(e){ $('#form-id').yiiActiveForm("validateAttribute", "model-field");});

kadyrleev commented 4 years ago

It looks to me the problem is with yii.activeForm.js, not the widget itself: https://github.com/yiisoft/yii2/issues/17147#issuecomment-633016544

kartik-v commented 4 years ago

ty for your feedback! Please checkout my latest Version, it should work now. (https://youtu.be/JwCdHIzoTPI)

@HenryVolkmer - this is not a correct fix... it adds another value - # - to the list of options which one can anyway do on his own by setting data.