2

I am having a really strange problem with Jquery validate that only occurs in Chrome. The validation on this page seems to be firing both the Highlight and the Unhighlight functions in the .validate() function so if I dont fill out the form it cycles through each element and applies the "invalid" class in the highlight function but then for some reason it goes through and immediately applies the code in unhighlight and I cant work out why?

JS

$(document).ready(function () {
    //Validation for form fields on Payment form

    /*This adds a method to test whether value is equal to placeholder, and if it is, don't consider it filled out.  This is necessary to circumvent IE placeholder plugin*/
    jQuery.validator.addMethod("notEqual", function (value, element, param) {
        return this.optional(element) || value != param;
    }, "Required.");

    $('#payment-form').validate({
        onfocusout: function (element) {
            $(element).valid();
        },
        rules: {
            "data[Payment][card_holder]": { required: true, minlength: 2 },
            "data[Payment][card_number]": { required: true, creditcard: true },
            "data[User][first_name]": { required: true, notEqual: "First Name" },
            "data[User][last_name]": { required: true, notEqual: "Last Name" },
            "data[UserDetail][company]": { required: true },
            "data[UserDetail][job_title]": { required: true },
            "data[UserDetail][telephone]": { required: true },
            "data[User][email]": {
                required: true,
                email: true,
                remote: {
                    url: "/usermgmt/users/email_exists",
                    type: "post"
                }
            },
            "data[User][password]": { required: true },
            "data[Address][billing_line_1]": { required: true },
            "data[Address][billing_line_2]": { required: true },
            "data[Address][billing_state]": { required: true },
            "data[Address][billing_postcode]": { required: true },
            credit_exp_month: { required: true, notEqual: "MM", number: true, max: 12, minlength: 2, maxlength: 2 },
            credit_exp_year: { required: true, notEqual: "YYYY", number: true, minlength: 2, maxlength: 4 },
            "data[Payment][cvv]": { required: true, number: true, minlength: 3, maxlength: 4 },
        },
        errorClass: 'error',
        unhighlight: function (element, errorClass, validClass) {
            $(element).removeClass(errorClass).addClass(validClass);
            validateIcon(element);
        },
        highlight: function (element, errorClass, validClass) {
            $(element).addClass(errorClass).removeClass(validClass);
            validateIcon(element);
        }
    });

    function validateIcon(element) {
        $(element).siblings('span.validate_icon').remove();
        if ($(element).hasClass('error')) {
            alert("error");
            $(element).closest('li').find('label>span:first').html('<span class="validate_icon invalid"> <span class="icon-stack"><i class="icon-sign-blank icon-stack-base"></i><i class="icon-exclamation"></i></span></span>');
        } else if ($(element).hasClass('valid')) {
            alert("valid");
            $(element).closest('li').find('label>span:first').html('<span class="validate_icon valid"> <span class="icon-stack"><i class="icon-sign-blank icon-stack-base"></i><i class="icon-ok"></i></span></span>');
        }
    }
});

PHP Code that handles the email exists:

public function email_exists() {
    $this->autoRender = false;
    if($this->request->is('post')) {
        $this->RequestHandler->respondAs('json');
        if(!$this->User->findByEmail($this->request->data['User']['email'])) {
            echo json_encode(true);
        } else {
            echo json_encode(false);
        }
    }
}

I have also tried simply echo "true"; and echo 1; I have tried everything suggested in the comments below but regardless - the problem exists.

the1dv
  • 893
  • 7
  • 14
  • From what I can gather it is to do with this part: $(element).removeClass(errorClass).addClass(validClass); Would the removal of a class in jQuery cause a focusout event? if so this would cause revalidation of the form? – the1dv Apr 07 '14 at 05:51
  • And actually this only seems to occur on Chrome on Linux - I havent been able to replicate this on BrowserStack! – the1dv Apr 07 '14 at 06:09

2 Answers2

7

I had the exact same problem, and by seeing your code I might say that you have the same cause, but let's break it down.

Checking

First, let's check that my comment is relevant, and I can actually help you. Comment the remote param on your email validation set up:

"data[User][email]": {
            required: true,
            email: true
        },

Is your problem fixed? Great, keep reading (feel free to skip to the fix section).

The problem

1. When the plugin validates, it creates a list of errors, stored into an array called "errorList".

2. Have you ever used the showErrors functionality? It's there to show all the errors, but also to target-show errors. If you want to show specific errors, or to show errors that are out of the limits of the plugin (ej.: a 60s timeout has expired), you can use that method.

3. When showing specific errors, what that method does is to add the specified error(s) to the errorList.

4. The problem is that before adding new errors that list is cleared up (I didn't write the code, but it seems that it's done in order to keep that list nice and clean, and not having two different errors of the same input).

5. Now, when the email is checked remotely we are in the same situation of a timeout. So it uses the showErrors functionality, and that means that the form is validated when click, and some seconds later (with the PHP response), the email error is shown, but clearing up the errorList. That's what is happening.

The fix

  1. If you are not going to do explicit use of showErrors, truth is that you can comment the line where the errorList is cleared up:

    showErrors: function( errors ) {
        if ( errors ) {
            // add items to error list and map
            $.extend( this.errorMap, errors );
            //this.errorList = [];
            for ( var name in errors ) {
            ...
    
  2. If you are going to do an explicit use of that method, you can try this version instead. Doesn't clear the error list, but checks that you're not adding the same error twice:

    showErrors: function( errors ) {
        if ( errors ) {
            // add items to error list and map
            $.extend( this.errorMap, errors );
            for ( var name in errors ) {
                var tempElem = this.findByName(name)[0];
                this.errorList = jQuery.grep(this.errorList, function( error, i ) {
                    return error.element != tempElem;
                });
                this.errorList.push({
                    message: errors[name],
                    element: tempElem
                });
            }
    

Let me know if worked or you have any problem.

Ale
  • 1,036
  • 10
  • 12
3

This code of yours can be a problem...

onfocusout: function (element) {
    $(element).valid();
},

You cannot put the .valid() method inside of the .validate() method without causing some serious issues.

This is the default onfocusout function from the plugin...

onfocusout: function( element, event ) {
    if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
        this.element(element);
    }
}

What's the purpose of your custom onfocusout function? Generally, it's not needed since the onfocusout trigger is already built into the functionality. One constructs their own onfocusout function only to over-ride the built-in default. So if you want the default onfocusout behavior, just remove the onfocusout option entirely.

If you really want to emulate something like in your code, it would need to look like this...

onfocusout: function(element, event) {
    this.element(element);
}

Quote OP Comment:

"as I said im not really sure what good it would do you: (I cant get it to format here..)"

$this->RequestHandler->respondAs('json');
if(!$this->User->findByEmail($this->request->data['User']['email'])) { 
    return json_encode(true);
} else { 
    return json_encode(false);
}

It does a lot of good to show any code that could be affecting the problem, especially any code that's wrong. This could have been solved two days ago.

return is for returning to the PHP function that called this one. In other words, return will do nothing here since there is no PHP function to return to. On the other hand, echo will output from PHP... and that's what you need for jQuery Validate remote...

if (....) { 
    echo true;
} else { 
    echo false;
}

PHP return versus PHP echo

Also see: https://stackoverflow.com/a/21313309/594235

Community
  • 1
  • 1
Sparky
  • 98,165
  • 25
  • 199
  • 285
  • The custom onfocusout is to handle validation checking on the specific element so the user is notified as they progress through the form that something is wrong with that field, removing my code doesnt solve the error but removes this "field by field" checking. – the1dv Apr 08 '14 at 01:55
  • If you think that could be the problem that is causing this - is there a better way to accomplish the field by field checking? I inherited this code so has been a bit of a learning curve so far :) – the1dv Apr 08 '14 at 01:57
  • @Voycey, the replacement function I show you is functionally identical to yours but without putting `.valid()` inside of `.validate()`. – Sparky Apr 08 '14 at 01:58
  • Thanks @Sparky - I have swapped it out and the problem described above still exists - for some reason highlight and unhighlight are both being called when a user submits the form with nothing filled in - the form doesnt submit (as it isnt valid) but all of the fields are marked as valid because unhighlight is then called :( – the1dv Apr 08 '14 at 04:10
  • http://www.zimagez.com/zimage/screenshot-080414-141217.php here is a screenshot of what I mean – the1dv Apr 08 '14 at 04:13
  • Ok I have tracked this down to a problem with my "Remote" function - if I remove this everything behaves as expected - any ideas? – the1dv Apr 14 '14 at 06:25
  • @Voycey, I don't know. You haven't shown the server-side code for the `remote` function. :/ But you don't need to specify the `data` for `remote` when the data is just the value of the field itself... that's the default. IMO, you really need to study [the documentation](http://jqueryvalidation.org/validate) instead of blindly copying code from elsewhere. – Sparky Apr 14 '14 at 15:51
  • server side function - just returns json true or false as documented, I have studied the documentation - in detail! Just not sure why this particular event is happening. – the1dv Apr 15 '14 at 07:16
  • Appreciate all the help - I have had to create a workaround for this remote checking for now – the1dv Apr 15 '14 at 08:20
  • @Voycey, `echo true` and `echo false` is all you need. Again, show the relevant PHP in your OP if that's the source of the problem. – Sparky Apr 15 '14 at 14:26
  • @Voycey, my comment about documentation referred to this: `return $("#UserEmail").val();`... the docs clearly show that the value of the field itself is sent by default, so you don't need to specify it again with `data`. – Sparky Apr 15 '14 at 14:32
  • Yep I have changed that to not bother with the return in there - unfortunately this is still occuring - im not sure what in the remote could be causing it, whether it is a quirk of my page or just something else affecting it. I have removed the remote check in validate and used my own externally. – the1dv Apr 16 '14 at 14:34
  • @Voycey, again, for the third time, you have not yet shown any code for your server-side function so I don't know what to tell you. – Sparky Apr 16 '14 at 14:37
  • @Voycey, You should be putting that code in your OP, not here in comments, [**as explained in my comment about SIX comments ago**](http://stackoverflow.com/questions/22903707/jquery-validate-is-firing-both-highlight-and-unhighlight-in-chrome/22918993?noredirect=1#comment35285001_22918993)... you need to **ECHO, not `return`**... they are two different things. `echo true` or `echo false` is all that's needed. – Sparky Apr 17 '14 at 15:12
  • I'm using CakePHP and returning data from the controller works the same as echoing out a value (I have also tried both as stated above in response to your comment six comments ago). I have worked around this another way but I would like to know why this is happening for posterity! I appreciate all of your help - when I hit enough rep to upvote you I will do :) – the1dv Apr 22 '14 at 09:45