4

I have a form which in fact consists of two forms. Each form is a reservation form. There are two dropdowns in both forms - destination from and destination to. There is an even handler, which calls AJAX to get possible destinations to when destination from is being selected/changed.

Another event handler (round trip checkbox) fills second form dropdowns by switching destinations from first form.

So if the first form has:

destination one: France
destination two: Austria

Then, if round trip is checked, the second form is immediately filled:

destination one: Austria
destination two: France 

The problem is that this two events don't cooperate correctly.

When this code is executed:

id_form_1_destination_from.val(destination_to_0.val());
id_form_1_destination_to.val(destination_from_0.val());
id_form_1_destination_from.change();
id_form_1_destination_to.change();

The first line calls another handler which fills second form (this is the only case when it's not needed). Since it's AJAX, the second line overtakes this AJAX, so now, the second form is correctly filled (switched destinations from first form), but when AJAX is done, it changes the selection of the destination two field.

Is there a way how to avoid this? For example to turn off the event handler or better make JQuery wait until the AJAX is done and then continues. I can't just do .off() on destination to field because I use select2 plugin.

Here is my JQuery:

$(document).ready(function () {
    var destination_from_0 = $("#id_form-0-destination_from");
    var destination_to_0 = $('#id_form-0-destination_to');
    var ride_two = $('#ride_two');

    $('.class-destination-from').on('change', function () {
        destination_from_changed.call(this);
    });

    $("#id_round_trip").on('change', function () {

        if (($('#id_round_trip').is(':checked')) ) {

            var id_form_1_destination_from =$('#id_form-1-destination_from');
            var id_form_1_destination_to = $('#id_form-1-destination_to');

            ride_two.show('fast');

            //id_form_1_destination_from.off();
            id_form_1_destination_from.val(destination_to_0.val()).change();
            //id_form_1_destination_from.on();
            //id_form_1_destination_from.change();
           id_form_1_destination_to.val(destination_from_0.val()).change();


        }else{
            ride_two.hide('fast');
            ride_two.find(':input').not(':button, :submit, :reset, :checkbox, :radio').val('').change();
            ride_two.find(':checkbox, :radio').prop('checked', false).change();
        }
    });
    $('.class-destination-to').on('change', destination_to_changed);



});

function destination_to_changed() {
    var destination_id = $(this).val();
    var arrival_container = $(this).siblings('.arrival-container');
    var departure_container = $(this).siblings('.departure-container');

    if (destination_id == '') {
        return;
    }
    $.ajax({
        url: '/ajax/is-airport/' + destination_id + '/',
        success: function (data) {
            if (data.status == true) {
                arrival_container.hide("slow");
                departure_container.show("slow");

            }
            if (data.status == false) {

                departure_container.hide("slow");
                arrival_container.show("slow");
            }
            arrival_container.change();
            departure_container.change();
        }
    })
}

function destination_from_changed() {
    var destination_id = $(this).val();
    if (destination_id == '') {
        return;
    }
    var ajax_loading_image = $('#ajax-loading-image');
    var destination_to = $(this).siblings('.class-destination-to');
    destination_to.empty();
    ajax_loading_image.show();
    $.ajax({
        url: '/ajax/get-destination-to-options/' + destination_id + '/',
        async:false, // ADDED NOW - THIS HELPED BUT IT'S NOT NECESSARY EVERYTIME
        success: function (data) {
            ajax_loading_image.hide();
            destination_to.append('<option value="" selected="selected">' + "---------" + '</option>');
            $.each(data, function (key, value) {
                destination_to.append('<option value="' + key + '">' + value + '</option>');
            });
            destination_to.change();
        }
    })
}
Milano
  • 18,048
  • 37
  • 153
  • 353

3 Answers3

2

If i'm understanding correctly, you have a concurrency issue. You basically want your first ajax call to be terminated before calling the second right?

I don't see any ajax request in your code but I think the paramter async: false, might be what you need.

Check the documentation: http://api.jquery.com/jquery.ajax/

Hope it helps

vegas2033
  • 250
  • 1
  • 4
  • 16
  • Hi, thanks for the answer, I've added the rest of the script at the bottom of the question. In fact, async helped, but it's not a best idea because it's needed only when round trip checkbox is checked. – Milano Aug 03 '16 at 20:31
  • The ideal would be something like id_form_1_destination_from.val(destination_to_0.val()).change().makeAjaxAsyncForThisTime().... – Milano Aug 03 '16 at 20:33
  • I'm pretty sure you can create a variable $isRoundTrip of boolean type and have your async: $isRoundTrip, If it's round trip, $isRoundTrip = true otherwise false – vegas2033 Aug 03 '16 at 20:41
1

You definitely have a classic "race condition" going on here.

Since the AJAX calls seem fairly unrelated to one another, you might need to add some code on the JavaScript side so that potentially "racing" situations cannot occur. For example, to recognize that a combo is "being populated" if you've issued an AJAX request to populate it but haven't gotten the response back yet. You might disable certain buttons.

Incidentally, in situations like this, where two (or more ...) forms are involved, I like to try to centralize the logic. For example, there might be "a singleton object" whose job it is to know the present status of everything that's being done on or with the host. A finite state machine (FSM) (mumble, mumble ...) works very well here. This object might broadcast events to inform "listeners" when they need to change their buttons and such.

Mike Robinson
  • 8,490
  • 5
  • 28
  • 41
  • Javascript does not have race conditions because it's single-threaded. In this case, the issue is that the XHR from the first click is not stopped when the second one is started. Therefore, both are completing and the order in which they complete cannot be guaranteed. This is not a race condition though, as neither request interferes with each other. – Daniel T. Feb 14 '17 at 04:25
0

You need to cancel the first AJAX request before you start the second. From this SO question:

Abort Ajax requests using jQuery

var xhr;

function getData() {
  if (xhr) { xhr.abort(); }
  xhr = $.ajax(...);
}
Daniel T.
  • 37,212
  • 36
  • 139
  • 206