Wait for form.submit() / POST to complete

2019-06-05 21:47发布

I'm stuck in a really bizarre situation here. It's complicated to explain but I'll try my best.

Detailed explanation of the issue:

On every top Nav click (Green donuts/circles), or next button, I must submit the form, if it exists and is valid. If not valid, form.valid() triggers validation errors and return false would stop any further propagation. This setup was working flawlessly until I noticed a strange behavior which isn't very persistence. Form on my 3rd tab, specifically, is quite data heavy. When I hit next button it should practically go thru the same process: check for an existing form, if valid, then submit. Submit calls the POST action method and when post completes it GETs the view for next tab. It works like this 5/10 times but at other times GET executes before the POST, which causes next page to load with incomplete data. When I put breakpoints to debug, I see GET for the next tab executing before POST of the current tab.

UI Explained:

I have a UI with 4 navigation <a> buttons on top - in the center there's a always a form - and at the bottom I have Previous & Next buttons.

enter image description here

Forms are constructed in MVC using Ajax.BeginForm

For each Nav link <a> element on top, I have a JavaScript function

var LoadTabs = function (e, arg) {  
    // This is to validate a form if one of the top links is clicked and form has incomplete fields...
    if (arg !== "prev" && arg !== "next") {
        if (!window.ValidateForm(false)) return false;
    }

    var url = $(this).attr('data'); // this contains link to a GET action method
    if (typeof url != "undefined") {
        $.ajax(url, { context: { param: arg } }).done(function (data) {                
            $('#partialViewContainer').html(data);
        });
    }
}

This function above binds to each top link on page load.

$('.navLinks').on('click', LoadTabs);

My Next & Previous buttons basically trigger the click event i.e. LoadTabs function.

$('button').on('click', function () { 
        if (this.id === "btnMoveToNextTab") {
            if (!window.ValidateForm(true)) return false;                

            $.ajax({
                url: url,
                context: { param: 'next' },
                method: "GET",
                data: data,
                success: function(response) {
                    if (typeof response == 'object') {
                        if (response.moveAhead) {
                            MoveNext();
                        }
                    } else {
                        $('#mainView').html(response);
                    }
                    ScrollUp(0);
                }
            });
        } 

        if (this.id === "btnMoveToPreviousTab") {
            MoveBack();
        }
        return false;
    });


MoveNext() Implementation is as below:

function MoveNext() {        
    var listItem = $('#progressbarInd > .active').next('li');        

    listItem.find('.navLink').trigger('click', ['next']);
    ScrollUp(0);
}

The problem is, for some reasons, when Nav Link 3 is active and I hit NEXT button - Instead of posting the form first via form.submit() - the nav 4 gets triggered - hence GET for nav 4 runs before form POST of nav 3.

My ValidateForm method is basically just checking if the form exists and is valid then Submit, else returns false. Its as below:

function ValidateForm(submit) {

    var form = $('form');

    // if form doesn't exist on the page - return true and continue
    if (typeof form[0] === "undefined") return true;

    // now check for any validation errors
    if (submit) {
        if (!$(form).valid()) {                
            return false;
        } else {
            $(form).submit();
        }
    } 
    else {
        return true;
    }

    return true;
}

My speculation is that form.submit does get triggered as it should be but since submit takes a little longer to finish it continues with the next code block in the button onclick event.

I first thought that this is a server side issue as in the POST I'm saving a big chunk of data with a few loops, and any code block that's process heavy I have that part in

var saveTask = Task.Factory.StartNew(() => ControllerHelper.SomeMethod(db, model)); Task.WaitAll(saveTask);

WaitAll will wait and pause the execution until SomeMethod finishes executing. I'm not sure how can I lock a process in JavaScript and wait for it to finish execution. Because I think If i can somehow lock the form.submit() in ValidateForm until its finished processing .. via a callback method perhaps...

Please if anyone can put me in right direction, I'd greatly appreciate the help. If you need more information please let me know I'd be happy to provide!

1条回答
Viruses.
2楼-- · 2019-06-05 22:36

Ajax is async, and your forms submit which is using Ajax.BeginForm() is using ajax. What is happening is that when you click your 'Next' button, which triggers the $('button').on('click', function () { code:

  1. You call the ValidateForm() function (and assuming its valid), your $(form).submit(); line of code starts making a ajax POST
  2. The code progresses to the final return true; line while the ajax call is executing.
  3. Because the ValidateForm() function returned true, the $.ajax GET call now starts, but at that point the ajax POST in the ValidateForm() function may not have finished executing causing your GET method to return invalid data

You need to change your code so that the GET call is made once the POST method call has completed. And since your using the $.ajax() methods throughout your code, and $.ajax() gives you more flexibility, it seems unnecessary to use Ajax.BeginForm() (and the extra overhead of including the jquery.unbtrusive-ajax.js script). You should also be handling the forms .submit() function (if you do not want the 'Next' button to be a submit button in the form, you could just trigger the .submit() event in the buttons .click() handler)

$(document).on('submit', 'form', function(e) {
    e.preventDefault(); // cancel default submit
    var form = $(this);
    if (!form.valid()) {
        return; // will display the validation errors
    }
    .... // get the relevant urls to the GET and POST methods etc
    $.post(postUrl, form.serialize(), function(data) {
        .... // not clear if your [HttpPost] method returns anything
    }).done(function() {
        $.get(getUrl, someData, function(response) {
            .... // Update the DOM with the next form?
            .... // Re-parse the validator for client side validation
        }
    }).fail(function() {
        .... // code that you might run if the code in the [HttpPost] method fails
    });
});

You should also consider returning the appropriate 'next' view in the [HttpPost] method so that you don't then needs to make a second call back to the server to get it.

It is also worth reading the Deferred Object documentation and the use of $.when(), $.then() etc.

查看更多
登录 后发表回答