vivin / regula

Regula: An annotation-based form-validation framework in Javascript
126 stars 24 forks source link

Forms with/without async constraints + displaying error message to the user #68

Closed Sironfoot closed 10 years ago

Sironfoot commented 10 years ago

Regula doesn't have out-of-the box validation error messages which display to the user, which I like since it lets me define my own and have 100% control over them.

So I'm trying to define a generic piece of "validation messages" code that runs for all forms throughout my site, something like:

$('form').on('submit', function() {
    var form$ = $(this);

    if (form$.data('validated')) {
        return true;
    }

    regula.validate(function(validationResults) {
        validationResults.forEach(function(result) {
            result.failingElements.forEach(function(elementSelector) {
                var input$ = $(elementSelector);
                input$.addClass('error');

                input$.after(
                    '<span class="errorMsg">' + result.message + '</span>');
            });
        });

        if (validationResults.length === 0) {
            form$.data('validated', true);
            form$.submit();
        }
    });

    return false;
});

This works fine for forms that have custom asynchronous constraints (which fire the callback in regula.validate). But if the form doesn't have any async constraints, the regula.validate() function returns the results immediately with no callback fired. E.g:

var validationResults = regula.validate();
validationResults.forEach(function(result) {
    // same code as before (snip)...

The weird thing is that regula.validate() will return immediately even with async constraints, but only with the results of the non-async constraints, then the callback will be fired later with both the async and non-async results. The result of this: I don't seem to have a way in my code to differentiate between having no async constraints, and having some async constraints (AFAIK).

My site has a mixture of forms with and without async constraints, and I'm struggling to come up with a generic solution to handle both situations. What's the best way to handle this? Is there a way to detect if the form I'm validating has any async constraints, then I can run through different code paths.

Thank you, and great validation library!

vivin commented 10 years ago

Hello,

Glad you are using Regula, and glad that you like it! Interesting that you brought this up, because I was pondering this problem quite recently. I was trying to figure out what to do, for exactly the same situation that you described! What if you have a mix of asynchronous and synchronous constraints, but you don't know how many of each you have? Some times you can have all synchronous constraints, whereas at other times you may have at least one asynchronous constraint.

Unfortunately, currently there is no way to figure out if the call to validate would include asynchronous constraints.

I've been thinking of the best way to solve this problem. An easy way might be to simply include a method that checks to see if there are any asynchronous constraints in the requested set, but I am not sure if that is the best solution.

Another option would be to return validation results in the callback regardless of whether the set of constraints to be validated are asynchronous or not. So that would be two ways of accessing validation results, but the callback way can be used if there is always a possibility that the set of constraints might include an asynchronous constraint. This way would probably be the easiest.

What do you think?

Sironfoot commented 10 years ago

"Another option would be to return validation results in the callback regardless of whether the set of constraints to be validated are asynchronous or not."

That would be my choice, just have the callback always fire. But I'll leave it to you.

In the meantime, I think I've found a workaround. First define a custom Constraint:

regula.custom({
    name: 'ForceAsyncCheck',
    formSpecific: true,
    async: true,
    defaultMessage: 'Invalid air speed velocity of laden swallow',
    validator: function(params, validator, callback) {
        callback([]);
    }
});

Then add it to every form element:

$(function() {
    $('form').attr('data-constraints', '@ForceAsyncCheck');
    regula.bind();

It should force every form validation to use the callback, even if there are no async constraints. Note above code is a naive implementation and will overwrite over form constraints, so should append @ForceAsyncCheck. But I'll leave that as an exercise for the reader.

vivin commented 10 years ago

Yup, that is a pretty good workaround for now. I was thinking of something similar as well. I will go the route of having the callback always fire. People can depend on the return value if they are sure that they have no asynchronous constraints, but otherwise they can always use the callback. Thanks for your feedback! :)

vivin commented 10 years ago

Implemented in v1.3.3.