views:

494

answers:

2

I'm working on some form validation using jQuery, validating each field onblur. Everything is working great, except when I have grouped (dependent) fields. What I'm looking for is a way to validate these fields only once all of them have been blurred. They can either be grouped as a collection of jQuery objects, or as children of a containing element.

One example is a birthday input consisting of three < select > elements:

<fieldset>
    <label for="bday_month">Birthday:</label>
    <select name="userBirthday[month]" id="bday_month">
        <option value="0">Month</option>
        <option value="1">January</option>
        <option value="2">February</option>
        <option value="3">March</option>
        ...
    </select>
    <select name="userBirthday[day]" id="bday_day">
        <option value="0">Day</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
        ...
    </select>
    <select name="userBirthday[year]" id="bday_year">
        <option value="0">Year</option>
        <option value="1991">1991</option>
        <option value="1990">1990</option>
        <option value="1989">1989</option>
        ...
    </select>
</fieldset>

As it currently stands, the validation function is tied to the blur() event of each field. Grouped fields will find their dependents and validate correctly, but when tabbing through the fields, the error message is displayed because the user has not finished the input.

I've tried adding the blur() event to the fieldset and binding custom events, neither with much luck.

For reference, here's what I currently have for the blur() function:

fieldBlur = function(e){
    // Array of objects w/specific validation functions, arguments, etc.
    validators = $(this).data('validators');

    // Process each validator separately
    for(key in validators){
        validator = validators[key];

        $field = validator.$field;

        // Extracts the value from grouped fields as an array
        val = valHelper($field);

        // Call one of the pre-defined validation functions
        functionResponse = eval(validator.options.functionName + "(val, validator.options.functionArgs);");
            if(!functionResponse){
                validator.$error.find('.text').text(validator.options.errorMsg);
                validator.$info.hide();
                validator.$error.show();

                e.preventDefault();
                break; // Only display the first error
            } else {
                validator.$error.hide();
            }       
        }
    return true;
};

Thanks in advance, and let me know if more code / explanation would be helpful.

A: 

Tie the blur event to only the last field. You could use a selector like 'fieldset :input:last' to access it without much hassle. That way you can safely tab through grouped elements and the form won't check for an error until the last one has been blurred.

Josh Leitzel
Not a bad idea. Won't work if the user is tabbing backwards through the form, of it they use the mouse, but I really like the simplicity!
nleach
+1  A: 

Finally got something working. I'll do my best to outline it here.

  1. blur() event is attached to each field
  2. Function off of blur() calls setTimeout(function(){ fieldBlurHelper(e); }, 100);
  3. in fieldBlurHelper() I check to see if any of the grouped fields are currently focused w/a class that is applied to every element that has focus:

    $field.filter('.hasFocus');

  4. If none of the fields have focus, I run the validators

Full (simplified) fieldBlur function:

fieldBlur = function(e){
    fieldBlurHelper = function(e){

        // Array of validation data (function name, args, etc.)
        validators = $(e.target).data('validators');    
            for(key in validators){
                validator = validators[key];

                // $field contains all the dependent fields (determined on ready())
                $field = validator.$field;

                // If any of the dependent fields have focus, don't bother with validation
                if($field.filter('.hasFocus').length > 0){ break; }

                // Extracts value as an array for all the dependent fields (.val() only returns the first)
                val = valHelper($field);

                functionResponse = eval(validDater.options.functionName + "(val, validDater.options.functionArgs);");
                if(!functionResponse){
                    console.log('error!');
                    break; // we only want to show the user one error at a time
                } else {
                    console.log('valid!');
                }       
            }
        };
    // running the function after the timeout allows the fields to lose focus
    setTimeout(function(){ fieldBlurHelper(e); }, 100);
};
nleach