Alex Ironside
Alex Ironside

Reputation: 5049

Waiting for an async call to finish before submitting a form

This one is a bit tricky.

So here's my jQuery code:

/**
 * Updating websites
 */
$('.website').on('blur', function () {
    var websiteId = this.id;
    console.log(websiteId);
    var website = this.value;
    console.log(website);
    fetch(`/api/edit/${websiteId}&${website}`).then(() => {

    });
})

Here's my HTML

<form method="post">
    <% for(let i = 0; i < websites.length; i++){ let website = websites[i]; %>
    <fieldset id="site<%= i %>">
        <p style="color: <% if(errorMsgs[i] === 'Wrong website address'){ %> red;<% } else { %> green;<% } %>
                padding-bottom: 0;
                padding-top: 10px;
                margin-bottom: 0;"
           class="">
            <%= errorMsgs[i] %>
        </p>
        <div class="">
            <input class="website"
                   name="website<%= i %>"
                   id="<%= i %>"
                   value="<%= website %>"
                   type="text"/>
            <a href="/websites/edit/<%= i %>"
               class="btn btn-info hide">
                Edit
            </a>
            <a href="/websites/delete/<%= i %>"
               data-id="<%= i %>"
               class="btn btn-danger confirmation removeBtn">
                Remove
            </a>
        </div>
    </fieldset>
    <% } %>
    <button type="submit"
            class="btn btn-primary col-sm-offset-2"
            value="Generate a report"
            name="generateReport"
            data-toggle="modal"
            data-target="#exampleModal">
        Generate a report
    </button>
    <!-- Save button created using Javascript. In case JS is disabled -->
</form>

Here's my API

// GET: api/edit/_id - save updates
router.get('/edit/:_id&:url', (req, res, next) => {
    Account.findById(req.user._id, (err, acc) => {
        if (err) {
            console.log(err);
        } else {
            acc.websites.set(req.params._id, req.params.url);
            acc.save((err, webs) => {
                if (err) {
                    console.log(err);
                } else {
                    console.log('all good');
                    // res.redirect('/reports');
                    res.json(webs);
                }
            });
        }
    });
});

How it works: The user inputs the value into the input fields, on blur, I make an AJAX call to the API, the value is saved in the db. And all this works.

There is one problem though. If the user hits the submit button, the fetch has no time to run. And it makes sense.

But I need the submission to wait until the value is updated. I was thinking about doing smth like, adding a preventDefault to the submit button, then after it's saved, removing the preventDefault.

But I have no idea how to do it, if it's a good idea or even if it makes any sense.

So to recap: I need the form submission to wait until the AJAX call is finalized.

This <% smth %> is just the EJS templating system. Shouldn't affect how it all works.

Upvotes: 4

Views: 3832

Answers (3)

gilly3
gilly3

Reputation: 91497

It looks like fetch() returns a promise. Save those promises in a shared variable. In your submit button code, use Promise.all() to wait for all the promises to resolve. Something like:

const fetchPromises = [];
$('.website').on('blur', function () {
    var websiteId = this.id;
    console.log(websiteId);
    var website = this.value;
    console.log(website);
    var fetchPromise = fetch(`/api/edit/${websiteId}&${website}`).then(() => {

    });
    fetchPromises.push(fetchPromise);
});
$('form').on('submit', function (e) {
    e.preventDefault();
    Promise.all(fetchPromises).then(() => this.submit());
});

You may want to add some error handling. If any of your fetch() calls fail, that will prevent the form from being submitted. Ideally a failed AJAX call would result in some messaging to the user that, once dismissed, removes the promise from the fetchPromises array, and until it is dismissed, the submit button should be disabled (actually, and visibly).

Upvotes: 4

user5421954
user5421954

Reputation:

You could set your button to a disabled state, add a listener to it, then throwing an event during your .then() function.

$('.website').on('blur', function () {
    var elem = document.getElementById('submitButtonId');

    // create the event
    var isFinishedEvent = new Event('fetched');

    elem.addEventListener('fetched', function() {
        // logic to enable your button
    }

    var websiteId = this.id;
    console.log(websiteId);
    var website = this.value;
    console.log(website);
    fetch(`/api/edit/${websiteId}&${website}`).then(() => {
       // Fire the event
       elem.dispatchEvent(isFinishedEvent);
  });
})

Upvotes: 1

thingEvery
thingEvery

Reputation: 3434

How about making a fake submit button with a preventDefault and hiding your real submit button with display:none? Then you can call a click() function on the hidden button once you've got your AJAX success.

Upvotes: 1

Related Questions