ibrewster
ibrewster

Reputation: 3612

query ajax- change return value of calling function based on AJAX response?

This question has been asked and answered many times with the answer "call another function from the success callback" Unfortunately, I can't figure out how to make that answer work in my situation, for a couple of reasons.

For starters, the ajax being called is the submit function of an ajaxForm.

  1. In one place I call the ajax submit, the calling function ITSELF needs to return either true or false, depending on the success of the AJAX call and some data returned from it. This is based on a tab widget used in multiple places: you can bind a function to the tab, which is run when the tab clicked. The tab widget itself expects this function to return either true (show the tab) or false (don't show the tab). Since this tab widget is used throughout my site, changing this behavior would be difficult at best.

  2. The form itself can be submitted from more than one place, so I can't simply call the rest of this tab function from the success handler (even if I re-wrote the tab handler to work asynchronously), as that may not be the appropriate response to success when the form is submitted elsewhere. In this case, I want to show the tab the user clicked. In another case I want to reload the page or reset the form (new, blank form), and in a third case I want to go to a different page. But in all cases, it is in response to submitting the same form.

I suppose I could make it so the tab ALWAYS switches when clicked, and then (by setting flags or something so this only happens after a tab click) if the submit doesn't work, switch back, but that feels quite kludgy. So, is there any way to make the calling function wait until the callbacks are complete? Or is there another way to structure things that I am not thinking of?

EDIT: I now have a jsfiddle of the current structure available here. The general idea is that back before I knew jquery had a tab widget, I needed tabs, so I wrote my own. It works, so I've seen no reason to change :-).

On document ready, each button with the appropriate class gets a click handler assigned. The flow is as follows:

  1. The onClick handler triggers a beforeShow event to run the function (if any) bound to the tab object
  2. The beforeShow function does any sort of prep work (such as loading data) desired before showing the tab. If needed, the 'show' data of the tab object can be set to false here to prevent the tab from being shown - such as if an error was detected on the current tab that needs to be fixed before switching.
  3. After the beforeShow function returns, the onClick handler checks to see if the 'show' data was set to false, and if not hides all other tabs and shows the clicked tab.

So my dilemma here is that to prevent the tab from being shown, I need to set the 'show' data of the tab object to false before the onClick handler checks for it (i.e. during the beforeShow function). But with the AJAX callbacks being asynchronous, I don't get the result of the AJAX to see if I should show the tab or not until after the onClick handler has completed - thereby showing the tab.

As I mentioned, I can do something like store the current tab, and if the ajax results indicate NOT to show the new tab, switch back, but that just feels wrong to me.

The click handler:

function changeTab(){
    var jqButton=$(this);
    jqButton.data('show',true); //start by assuming we will show the tab.
    if(jqButton.hasClass("current")) //don't do anything if switching to the current tab
        return;

    //run the "before show" function, if any.
    jqButton.trigger('beforeShow');
    var showTab=jqButton.data('show'); //see if the show flag has been set to false

    if(showTab===false) //if no before show function, then showTab is null, not false
        return; //if false, then don't show the tab.

    $(".tabDiv").hide();

    //run any "after hide" scripts bound to the current tab
    $('.tabButton.current').trigger('afterHide');

    var requestedDiv=jqButton.val(); //the "value" of the tab button is the ID of the tab content div
    $("#"+requestedDiv).show(); //may show nothing, if no div exists for the current tab.
    $(".tabButton").removeClass("current"); //remove the current class from all tab buttons, so it will only be applied to the new current tab button.
    jqButton.addClass("current");
}

Upvotes: 0

Views: 665

Answers (2)

tcooc
tcooc

Reputation: 21209

This is a method to do it asynchronously.

Let the beforeShow handler accept an optional promise parameter:

var eventParams = {
    promise: null
};
jqButton.trigger('beforeShow', eventParams);

In the handler itself, create a promise if necessary:

function tab2BeforeShow(e, eventParams) {
    eventParams.promise = $.Deferred();
    $.ajax(... async code...
        var showTab = [some value received from request];
        eventParams.promise.resolve(showTab);
    );
}

Then if a promise is created, use a promise chain instead of synchronous function calls:

// doShowTab is the logic that actually shows/hides a tab
if (eventParams.promise) {
  // use promise instead
  eventParams.promise.then(doShowTab);
} else {
  // beforeShow handler didn't return a promise, assume showTab=true
  doShowTab(true);
}

Fiddle: http://jsfiddle.net/669v1Lcd/ (I used setTimeout to simulate an async request)

Upvotes: 1

Thad Blankenship
Thad Blankenship

Reputation: 2302

This is pretty much what you want

function tab2BeforeShow(){
    var $form = $('#SomeajaxFormObject'); // This is the form you want to submit
    // I'd probably show some load indicator here
    var result;
        $.ajax({
        url: $form.attr('action'),
        type: 'POST',
        data: $form.serialize(),
        async: false,
        success: function(response) {
            result = true; // set the result variable declared in outer function
        },
        error: function() {
            result = false; // bad response - dont show new tab
        }
    });

    // Hide your load indicator now

    // since we declared async: false on the ajax request, 
    // we can be positive that result is set to a boolean value at this point
    $(this).data('show', result);
}

We make a synchronous post to your forms action url, serializing the form's data so we can make an ajax POST with it. This should drop in, with maybe some minor tweaking.

The key factors here are that we set data to $form.serialize() and we set async to false, so the browser does not continue execution while the request is working.

If there is any way to avoid using synchronous ajax, you really should. If you can't avoid it, this should work for you. Do note that if the server hangs, it is going to hang your client as well.

Upvotes: 2

Related Questions