1

I'm validating a form containing user profile information, and I want to check with my server to see if the user name specified by the user is already in use. I've defined a validation method like so:

$.validator.addMethod(
    'check_username', 
    function(value, element, param) { 
        console.log('starting');
        var name_is_in_use = 0;

        // Start the throbber.
        $('form#user-profile-form input#edit-name').addClass('searching');

        // Hit the server and see if this name is already in use by someone else.
        $.ajax({
            url: '/checkusername/' + value + '/" . $form['#user']->uid . "',
            dataType: 'json',
            data: {},
            async: false,
            success: 
                function(data) {
                        console.log('back from the server');
                        name_is_in_use = data; // unnecessary, but whatever

                        // Kill the throbber
                        $('form#user-profile-form input#edit-name').removeClass('searching');
                    },
        });

        return name_is_in_use;
    },
    'Sorry, but this username is already in use.');

Overall, the validation process works; what I'm having trouble with is getting a "busy" throbber attached to the input field to work properly -- the throbber never changes from its regular state to its busy state. But after adding some sleep() calls back on the server to exaggerate what's really happening back there, what I'm seeing is that both of those console.log statements appear in the console at the same time -- after the call to the server has been completed. That explains why I'm not seeing the throbber change -- the .searching class is removed immediately after it was added. But what I don't understand is why I'm not seeing what I think ought to be happening:

  1. I click the field in the browser to fire the validation method.
  2. console.log('starting') shows up in the console
  3. The throbber gets turned on.
  4. There's a pause while the server gets called, sleeps a bit, and returns its value,
  5. console.log('back') shows up in the console
  6. The throbber gets turned off.

Instead, I'm getting this (ignoring the throbber class changes):

  1. I click the field in the browser to fire the validation method.
  2. There's a pause while the server gets called, sleeps a bit, and returns its value,
  3. console.log('starting') shows up in the console, and then, with no delay,
  4. console.log('back') shows up in the console

Steps 3 and 4 happen at virtually the same time.

I'm really puzzled about this -- it looks like the entire execution of the handler is getting held up by the ajax call, which makes no sense to me at all. Can anybody offer some insights? Thanks!

Jim Miller
  • 3,291
  • 4
  • 39
  • 57
  • `async: false` should be `async: true` - read more into what this setting does. Async set to false stops the request being asyncronous. http://api.jquery.com/jQuery.ajax/ – Pebbl Feb 26 '13 at 23:31
  • See: http://stackoverflow.com/a/13579061/594235 – Sparky Feb 27 '13 at 00:13

2 Answers2

3

It's simple, that's because you are setting async to false in your $.ajax call.

You are making a synchronous request, and normally when such a thing is done web browsers tend to look as hanging while waiting for the response from the server.

Alexander
  • 23,432
  • 11
  • 63
  • 73
  • Thanks -- close but not quite. I changed async to true and moved "return name_is_in_use" to inside the success handler. The good news is that the throbber is now working right, but, since the ajax call is no longer async, the validation method returns immediately with a "false" value. Via console.log, I can see the ajax call doing the right thing, but it no longer provides the value to the validation system. Is there a way to deal with this? – Jim Miller Feb 26 '13 at 23:48
  • Yes, there is. You need to do an asynchronous validation. I never said your validation as is was gonna work after doing a proper AJAX request :) – Alexander Feb 26 '13 at 23:49
  • @JimMiller, what's the format of `data`? The Validate plugin's custom method is looking for a `return true` or `return false`. `return 1` or `return 0` also works. – Sparky Feb 26 '13 at 23:50
  • @sparky `data` gets 1 or 0. @Alexander Do you have a reference to how asynchronous validations work? I've just stumbled across the `remote` method for validation, which (a) is probably the official Right Thing as long as (b) I can get the throbber class stuff to work. – Jim Miller Feb 27 '13 at 00:05
  • @JimMiller, I don't believe you can do the extra throbber code with `remote`. See: http://docs.jquery.com/Plugins/Validation/Methods/remote#options – Sparky Feb 27 '13 at 00:08
  • 1
    @JimMiller, I don't see how the throbber is a problem at all. Also, the documentation page even has [an example of how to perform a remote validation of an username](http://docs.jquery.com/Plugins/Validation#The_Remember_The_Milk_sign-up_form). As for the throbber, the easiest way is using `.ajaxStart()` and `.ajaxComplete()`, but I am pretty you can come up with something better – Alexander Feb 27 '13 at 00:18
0

OK, I've got something that not only works, but seems relatively principled. On the thought that it might be useful to others...

$('form#user-profile-form').validate({
    debug: true,
    rules: {
        name: {
            required: true,
            regexp: /\W/,
            remote: {
                url: '/checkusername',
                type: 'post',
                data: {
                    username: function () { return $('#edit-name').val() },
                    uid: " . $form['#user']->uid . "
                    },
                beforeSend: function (jqXHR, settings) {
                                $('form#user-profile-form input#edit-name').addClass('searching');
                            },
                complete: function (jqXHR, settings) {
                                $('form#user-profile-form input#edit-name').removeClass('searching');
                            },
            }
        }
    },

    messages: {
        name: {
            regexp: '" . USERNAME_VALIDATION_MESSAGE . "',
            remote: 'Sorry, but this username has been claimed by someone else.',
            },
        ...
        },
    errorPlacement: function(error, element) {
        switch (element.attr('name')) {
            case 'name':
                error.insertAfter('.control-group.form-item-name > label');
                $('form#user-profile-form input#edit-name').removeClass('searching');
                break;
             ...

Some things of note here:

  • The heavy lifting is done through the remote option, which, as the doc says, is meant for stuff like this. The code at the other end of the url returns a JSONed 1/true if the username is available, and 0/false if it's not.
  • The beforeSend handler turns on the throbber at the very beginning of the validation, before the server call happens.
  • The complete handler turns off the throbber in those cases where the user has specified a non-conflicting name.
  • The removeClass call down in errorPlacement turns off the throbber in those cases when the server call has returned and an error has been reported.
  • Since I have TWO rules on username -- the already-in-use test AND a regexp test to make sure the specified name is a "word" -- I have to specify appropriate messages for each, in the messages section.

Phew. Comments are welcome; thanks to all for their help!

Jim Miller
  • 3,291
  • 4
  • 39
  • 57