Add loading indicator to button on form submit

Updated: 8th July 2022
Tags: html javascript

Here one of my favorite function I've made year ago for my website. I was not sure if this small thing is needed to share on tech blog, but why not.

Problem

Sometimes users use double click on button and submit form twice, especially when their mouse have defects. It is very useful on submit files forms or anything that creates new item in database. Yeah, I have user that few days double created posts and I have to manually delete duplicates.

To fix this issue, we will need to disable button on first click.

document.querySelectorAll('.js-loading-form').forEach(function (form) {
    form.addEventListener('submit', function (e) {
        const button = form.querySelector('[type="submit"],button:not([type="button"])');
        if (button == null) {
            return;
        }
        button.disabled = true;
    });
});

Okay, this should disable button on submitting form with class js-loading-form. If you use ajax you will need to disable button before ajax send and enable after ajax complete (I have planned article about my universal ajax function).

Now we need to go fancy. Let's add some stuff indicating it is working. I'll make a simple example with three dots, but you can change loading to any svg you find.

const loadingFormSpinnerHtml = '•••';
document.querySelectorAll('.js-loading-form').forEach(function (form) {
    form.addEventListener('submit', function (e) {
        const spinnerHtml = form.getAttribute('data-spinner') || loadingFormSpinnerHtml;
        const button = form.querySelector('[type="submit"],button:not([type="button"])');

        if (button == null) {
            return;
        }
        button.disabled = true;

        if (!spinnerHtml) {
            return;
        }
        const domRect = button.getBoundingClientRect();
        button.style.width = domRect.width + 'px';
        button.style.height = domRect.height + 'px';
        button.innerHTML = spinnerHtml;
    });
});

Note: we use getBoundingClientRect because if we used simply button.clientWidth we would receive rounded integer width which is not exactly as our button. So we get exact width and our users won't be scary if button width changes, but only see loading indicator.

As you can see with this new update you can change globally loadingFormSpinnerHtml to your favorite spinner (it can be some fancy svg or some element with css). But You can always add data-spinner attribute to form overriding spinner.

Demo: https://codepen.io/AucT/pen/ZExazWx