Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActiveForm: Validation delay is applied in non validateOnType scenarios #20217

Open
mathias-schwaller opened this issue Jun 28, 2024 · 0 comments
Labels
JS JavaScript related

Comments

@mathias-schwaller
Copy link

What steps will reproduce the problem?

Create a ActiveForm with validationOnType = false and validateOnBlur = true.

What is the expected result?

For my understanding, validationDelay shouldn't be used because it states:

number of milliseconds that the validation should be delayed when the user types in the field and [[validateOnType]] is set true. If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field

Configuring the validationDelay property with value 0 or 1 for the widget has no effect.

What do you get instead?

The default validationDelay value defined in yii.activeForm.js is used. Therefore the applied validation delay when creating a ActiveForm with validateOnBlur seems to be 200ms.

Additional info

Cause in code:

In yii.ActiveForm.js watchAttribute() only in if (attribute.validateOnType) { case the validationDelay value from attribute gets passed to validateAttribute() function:

validateOnType:

validateAttribute($form, attribute, false, attribute.validationDelay);

validateOnBlur:

validateAttribute($form, attribute, true);

In validateAttribute function, validationDelay is checked for empty. When empty, 200ms is used instead. When no value is passed validationDelay is empty, consequently 200ms is used in validateOnBlur case:

}, validationDelay ? validationDelay : 200);

Possible duplicates

Perhaps #18422 adresses the same problem.

Why is this a Problem?

The delayed validation causes problems when using a screen reader.
When an validation error occours, the error message gets read after the description for the following input field, even when using `aria-live="assertive". After some digging we found out that the 200ms validation delay causes the screen reader to start reading the next focus first instead of directly reading out the error message.

Suggestions to resolve the issue

  1. Idea 1:

    Move validation delay from validateAttribute function to watchAttribute function. The validation delay should only be used when attribute.validateOnType is true. So why keep it in validateAttribute funciton that is used in every case?

    Code would look something like this then:

    ...
    
    var watchAttribute = function ($form, attribute) {
        var $input = findInput($form, attribute);
        if (attribute.validateOnChange) {
            $input.on('change.yiiActiveForm', function () {
                validateAttribute($form, attribute, false);
            });
        }
        if (attribute.validateOnBlur) {
            $input.on('blur.yiiActiveForm', function () {
                if (attribute.status == 0 || attribute.status == 1) {
                    validateAttribute($form, attribute, true);
                }
            });
        }
        if (attribute.validateOnType) {
            $input.on('keyup.yiiActiveForm', function (e) {
                if ($.inArray(e.which, [16, 17, 18, 37, 38, 39, 40]) !== -1) {
                    return;
                }
                if (attribute.value !== getValue($form, attribute)) {
                    data.settings.timer = window.setTimeout(function () {
                        validateAttribute($form, attribute, false);
                    }, attribute.validationDelay ? attribute.validationDelay : 200);
                }
            });
        }
    };
    
    ...
    
    var validateAttribute = function ($form, attribute, forceValidate) {
        var data = $form.data('yiiActiveForm');
    
        if (forceValidate) {
            attribute.status = 2;
        }
        $.each(data.attributes, function () {
            if (!isEqual(this.value, getValue($form, this))) {
                this.status = 2;
                forceValidate = true;
            }
        });
        if (!forceValidate) {
            return;
        }
    
        if (data.settings.timer !== undefined) {
            clearTimeout(data.settings.timer);
        }
    
        if (data.submitting || $form.is(':hidden')) {
            return;
        }
        $.each(data.attributes, function () {
            if (this.status === 2) {
                this.status = 3;
    
                var $container = $form.find(this.container),
                    $input = findInput($form, this);
    
                var $errorElement = data.settings.validationStateOn === 'input' ? $input : $container;
    
                $errorElement.addClass(data.settings.validatingCssClass);
            }
        });
        methods.validate.call($form);
    };
  2. Idea 2:

    Specify a default value for validateAttribute's validationDelay parameter.

    ...
    
    var validateAttribute = function ($form, attribute, forceValidate, validationDelay = 0) {
        var data = $form.data('yiiActiveForm');
    
        if (forceValidate) {
            attribute.status = 2;
        }
        $.each(data.attributes, function () {
            if (!isEqual(this.value, getValue($form, this))) {
                this.status = 2;
                forceValidate = true;
            }
        });
        if (!forceValidate) {
            return;
        }
    
        if (currentAjaxRequest !== null) {
            currentAjaxRequest.abort();
        }
        if (data.settings.timer !== undefined) {
            clearTimeout(data.settings.timer);
        }
        data.settings.timer = window.setTimeout(function () {
            if (data.submitting || $form.is(':hidden')) {
                return;
            }
            $.each(data.attributes, function () {
                if (this.status === 2) {
                    this.status = 3;
    
                    var $container = $form.find(this.container),
                        $input = findInput($form, this);
    
                    var $errorElement = data.settings.validationStateOn === 'input' ? $input : $container;
    
                    $errorElement.addClass(data.settings.validatingCssClass);
                }
            });
            methods.validate.call($form);
        }, validationDelay);
    };
    
    ...
Q A
Yii version >=2.0
PHP version 8.1
Operating system Linux
@samdark samdark added the JS JavaScript related label Jun 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS JavaScript related
Projects
None yet
Development

No branches or pull requests

2 participants