Home > Software engineering >  400 error when I trigger a form submit within my $ajax() done handler
400 error when I trigger a form submit within my $ajax() done handler

Time:11-05

I have a form on an ASP.NET Core Razor Pages page. If I add a submit button (type="submit") to the form, the form posts normally as expected.

If I change the submit button to a regular button (type="button") and add a handler for that button that calls .trigger('submit'), it also posts normally as expected.

$('#myButton').on('click', function () {
    $('#myForm').trigger('submit');
});

However, if I change the handler makes an $ajax() call as shown below, and trigger the form submit from within the done handler, I get a 400 error.

$('#myButton').on('click', function () {
    var $form = $(this).closest('form');

    $.ajax({
        type: 'GET',
        url: '?handler=ValidateProductTransfer',
        contentType: 'application/json',
        dataType: 'json',
        data: { ... },
    })
    .done(function (response) {
        if (response.isSuccess)
            $form.trigger('submit'); // <----- FAILS w/400 error!
        else
            alert(response.error);
    })
    .fail(function (response) {
        alert(response.responseText);
    })
});

I can workaround the issue above by delaying the form submit.

.done(function (response) {
    if (response.isSuccess)
        setTimeout(function () {
            $form.trigger('submit'); // <----- OK
        }, 300);
    else
        alert(response.error);
})

Obviously, there is an issue triggering a form submit in the $ajax().done handler. But I don't understand why, or why it results in a 400 error.

Can anyone explain what is happening here? And how to work around it without imposing a delay that could fail if the delay is not enough?

Update

If it helps, here is my actual JavaScript handler.

    $('#product-transfer-submit').on('click', function () {
        disablePopup(true);

        var $form = $(this).closest('form');
        var $modal = $('#transfer-product-modal');
        var $activeTab = $modal.find('a.nav-link.active');
        var targetType = $activeTab.data('type');
        var targetId = $modal.find($activeTab.attr('href')   ' :input').val();
        $modal.find('#TargetType').val(targetType);

        $.ajax({
            type: 'GET',
            url: '?handler=ValidateProductTransfer',
            contentType: 'application/json',
            dataType: 'json',
            data: {
                'sourceId': $modal.find('#SourceId').val(),
                'targetType': targetType,
                'targetId': targetId,
                'productId': $modal.find('#ProductId').val()
            },
        })
        .done(function (response) {
            if (response.isSuccess)
                //setTimeout(function () {
                    $form.trigger('submit');
                //}, 300);
            else
                alert(response.error);
        })
        .fail(function (response) {
            alert(response.responseText);
        })
        .always(function (response) {
            disablePopup(false);
        });
    });

    function disablePopup(disable) {
        $("#transfer-product-modal :input").prop("disabled", disable);
        $("#transfer-product-modal a").prop("disabled", disable);
    }

And here's my complete form as rendered in the browser.

<div id="transfer-product-modal"  tabindex="-1" role="dialog" aria-modal="true" style="display: block;">
    <form method="post" action="/Transloading/Ships?handler=ProductTransfer" novalidate="novalidate">
        <input type="hidden" data-val="true" data-val-required="The SourceId field is required." id="SourceId" name="SourceId" value="2">
        <input type="hidden" data-val="true" data-val-required="The TargetType field is required." id="TargetType" name="TargetType" value="Ship">
        <div  role="document">
            <div >
                <div >
                    <h5 >Ship 2 : Transfer Product To</h5>
                    <button type="button"  data-bs-dismiss="modal" aria-label="Close"></button>
                </div>

                <div >
                    <ul >
                        <li >
                            <a href="#railcar-content" data-bs-toggle="tab" aria-expanded="false"  data-type="railcar">
                                <span ><img src="/images/mdi/calendar.svg" title="Railcar"></span>
                                <span >Railcar</span>
                            </a>
                        </li>
                            <li >
                                <a href="#storage-content" data-bs-toggle="tab" aria-expanded="false"  data-type="storage">
                                    <span ><img src="/images/mdi/calendar.svg" title="Storage"></span>
                                    <span >Storage</span>
                                </a>
                            </li>
                            <li >
                                <a href="#ship-content" data-bs-toggle="tab" aria-expanded="false"  data-type="ship">
                                    <span ><img src="/images/mdi/calendar.svg" title="Ships"></span>
                                    <span >Ship</span>
                                </a>
                            </li>
                    </ul>

                    <div >

                        <div id="railcar-content" >
                            <div >
                                <div >
                                    <div >
                                        <label  for="RailcarId">Railcar</label>
                                        <select  data-val="true" data-val-required="The Railcar field is required." id="RailcarId" name="RailcarId"><option value="36167">AOKX482582</option>
<option value="36400">AOKX491641</option>
<option value="36399">AOKX497959</option>
<option value="36240">CBFX305181</option>
<option value="36245">TOPX311252</option>
<option value="36193">TOPX311288</option>
<option value="36244">TOPX311390</option>
</select>
                                        <span  data-valmsg-for="RailcarId" data-valmsg-replace="true"></span>
                                    </div>
                                </div>
                            </div>
                        </div>

                            <div id="storage-content" >
                                <div >
                                    <div >
                                        <div >
                                            <label  for="StorageId">Storage</label>
                                            <select  data-val="true" data-val-required="The Storage field is required." id="StorageId" name="StorageId"><option value="15">Test Storage (100 Mesh)</option>
</select>
                                            <span  data-valmsg-for="StorageId" data-valmsg-replace="true"></span>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <div id="ship-content" >
                                <div >
                                    <div >
                                        <div >
                                            <label  for="ShipId">Ship</label>
                                            <select  data-val="true" data-val-required="The Ship field is required." id="ShipId" name="ShipId"><option value="1">Ship 1</option>
<option value="2">Ship 2</option>
</select>
                                        <span  data-valmsg-for="ShipId" data-valmsg-replace="true"></span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div >
                        <div >
                            <div >
                                <label  for="ProductId">Product</label>
                                <select  data-val="true" data-val-required="The Product field is required." id="ProductId" name="ProductId"><option value="1">100 Mesh</option><option value="18">Butane</option><option value="19">Diesel</option><option value="40">Propane</option></select>
                                <span  data-valmsg-for="ProductId" data-valmsg-replace="true"></span>
                            </div>
                        </div>
                    </div>
                    <div >
                        <div >
                            <div >
                                <label  for="Quantity">Quantity</label>
                                <input  type="text" data-val="true" data-val-number="The field Quantity must be a number." data-val-range="Quantity must be a positive number" data-val-range-max="1000000000" data-val-range-min="0.1" data-val-required="The Quantity field is required." id="Quantity" name="Quantity" value="0">
                                <span  data-valmsg-for="Quantity" data-valmsg-replace="true"></span>
                            </div>
                        </div>
                        <div >
                            <div >
                                <label  for="Timestamp">Timestamp</label>
                                <input  type="datetime-local" data-val="true" data-val-required="The Timestamp field is required." id="Timestamp" name="Timestamp" value="0001-01-01T00:00"><input name="__Invariant" type="hidden" value="Timestamp">
                                <span  data-valmsg-for="Timestamp" data-valmsg-replace="true"></span>
                            </div>
                        </div>
                    </div>
                    <div >
                        <div >
                            <div >
                                <input  type="checkbox" data-val="true" data-val-required="The Mark source as empty field is required." id="MarkAsEmpty" name="MarkAsEmpty" value="true">
                                <label  for="MarkAsEmpty">Mark source as empty</label>
                            </div>
                        </div>
                    </div>
                </div>
                <div >
                    <button type="button" id="product-transfer-submit" >Transfer</button>
                    <button type="button"  data-bs-dismiss="modal">Cancel</button>
                </div>
            </div>
        </div>
        <input name="__RequestVerificationToken" type="hidden" value="CfDJ8BlcV1FRqxZOunKbIAf54ZLanzQR8IEs7WCpxJ-3rTLTtnUwgcrJW0eL-Tl3Z_3exdKwVNmoPzo3m644U1W1i1nwO3iJDvZVW6EXTKkrdGHZJPh_QkKWU_6k5wGPNDpDzPwVOAK8p7CmxDE2QzacCwz3rUcnjpXhyrhWhup0xq7e6n50j0vA3TBlBKR2bxfOzA"><input name="MarkAsEmpty" type="hidden" value="false">
    </form>
</div>

CodePudding user response:

The issue is your disablePopup() function and when it is called.

Disabled inputs are not included in submitted data. The reason the timeout works is because it gives your .always() callback a chance to re-enable the inputs.

You can alter the order of your deferred callbacks to ensure you re-enable the inputs before processing success or failure...

$.ajax({
  url: "",
  method: "GET",
  data: {
    targetType,
    targetId,
    handler: "ValidateProductTransfer",
    sourceId: $modal.find("#SourceId").val(),
    productId: $modal.find("#ProductId").val(),
  },
  dataType: "json",
})
  .always(() => { //            
  • Related