-1

My apologies for the one millionth iteration of this type of question. I've already seen a number of other posts talking about this, and I still can't wrap my head around being able to invoke a function after the callback is successful and returns. I must have read this post over half a dozen times:

How do I return the response from an asynchronous call?

The Code

Anyway .., my question I hope anyone could shed some light on. I have a custom jQuery validator method that checks if a username is available.

jQuery.validator.addMethod("usernameAvailability", 
        function(value, element) {
            console.log('starting usernameAvailability');
            if(this.optional(element) === true){return true;}
            else{
                 console.log('ending usernameAvailability, about to return get_availability');
                 return availabilityCheck.get_availability('username');
            }
        }, "Username already in use." );

The custom jQuery validator method calls get_availability(element) in namespace availabilityCheck.

var availabilityCheck = (function() {
        var usernameIsAvailable, emailIsAvailable, Callback, AJAX_Availability_Check, change_Availability;
        usernameIsAvailable = null;
        emailIsAvailable = null;
        AJAX_Availability_Check = function(callback, element){
                console.log('starting AJAX_Availability_Check');
                if(element==="username"){
                    selection = {username:$('#id_username').val()};
                }
                else{
                    selection = {email:$('#id_email').val()};
                }
                $.ajax({
                    url: '/register/',
                    type: 'get',
                    data: selection,
                    dataType:'text',
                    async: true
                    }).done(Callback(element));

            };
        change_Availability = function(element, bool) {

            if(element === 'username'){
                usernameIsAvailable = bool;
            }
            else{
                emailIsAvailable = bool;
            }

        };
        Callback = function(element) {
            return function(result, textStatus){
                bool = result === "True" ? true: false;
                change_Availability(element, bool);
                return usernameIsAvailable;
            };
        };
        return{
            get_availability: function(element){
                AJAX_Availability_Check(Callback, element);
                return element === 'username' ? usernameIsAvailable : emailIsAvailable; 

            }
        }
    })();

The Problem and My Question

My problem The input correctly validates whether the username is already in use, but the user needs to trigger validation twice since get_availability returns before the Callback can change usernameIsAvailable to the proper boolean.

My Question How do I restructure my code so my custom jQuery validate method is invoked by the callback? Or how do I ensure that it won't validate until the Callback returns?

Community
  • 1
  • 1
  • Well, it's simple: It's *impossible* to synchronously return `true`/`false` to the validator from AJAX. If your `jQuery.validator` does not support asynchronous validation, you're out of luck (get a better plugin), if it does please contact its documentation. – Bergi May 19 '14 at 22:04

1 Answers1

1

The problem is your structure... I don't know where you got that from but throw it away and burn it, then forget you ever saw it.

You can simplify your code to look like this:

var availabilityCheck = function() {
    var usernameIsAvailable = null, emailIsAvailable = null, 
        AJAXAvailabilityCheck, changeAvailability;

    AJAXAvailabilityCheck  = function(element){
        console.log('starting AJAX_Availability_Check');
        if(element==="username"){
            selection = {username:$('#id_username').val()};
        }
        else{
            selection = {email:$('#id_email').val()};
        }
        $.ajax({
            url: '/register/',
            type: 'get',
            data: selection,
            dataType:'text',
            async: true
        }).done(changeAvailability(element));
    };
    changeAvailability = function(element, theBool) {
        if(element === 'username'){
            usernameIsAvailable = theBool;
        }
        else{
            emailIsAvailable = theBool;
        }
    };
    this.getAvailability = function(element) {
        AJAXAvailabilityCheck(element);
        return element === 'username' ? usernameIsAvailable : emailIsAvailable;
    }
};

So your callback is now actually something useful instead of just another useless layer.
However as I point out below, your result wasn't ever actually defined as far as I could tell so your going to have to figure out what theBool should be.

Some things of note:

    Callback = function(element) {
        return function(result, textStatus){
            bool = result === "True" ? true: false;
            change_Availability(element, bool);
            return usernameIsAvailable;
        };
    };

You return an anonymous function for no particular reason, and your result variable isn't defined... at least with the code as you have it, so with what you have it's always resolving to false. Also if your just checking for truthyness then you don't need a tuple you can just do result === "True" which will evaluate to true or false, no need for the extra ? true : false.

Also, don't use words like bool for variable names. Bool is a type of variable and is a reserved word. Javascript lets you use it cause... Javascript will let you do just about anything, but its bad practice.

Finally you mixed like 10 different types of casing. Now casing is a personal preference (I personally prefer underscores to camelCase, which is the javascript convention), but no matter what case you use. USE ONLY ONE CASE!


Now I believe your real issue here is that you don't understand what a self-invoking function is for.

You use this syntax: var availabilityCheck = (function(){})(); which creates a self-invoking function; which means that it's going to fire without being called! Which means all this does is call your ajax and cause an extraneous server hit when your user hasn't even entered any data yet.

I believe that you did it so you could use this syntax availabilityCheck.getAvailability() in your invoking function, but the better way to do that is to do what I did above and make getAvailability a property of availabilityCheck by using the this keyword. Then you can use the same syntax without running your whole function twice.

You should almost NEVER (there are always exceptions of course) put an ajax call in a self invoking function. If the call doesn't depend on user input, then you should of just loaded the data when your page was requested the first time.

Ryan
  • 5,644
  • 3
  • 38
  • 66