8
\$\begingroup\$

On any ajax calls made that take more than 2 seconds, I want a loading icon to appear. I have css that takes care of showing the loading icon when the 'loading' class is added to the body.

And here is the relevant code:

<script>
    var loadingIconOn = false;

    $(document).on({
        ajaxStart: function () {
            loadingIconOn = true;
            setTimeout(function () {
                if(loadingIconOn == true)
                    $('body').addClass("loading");
            }, 2000);

        },
        ajaxStop: function () {
            $('body').removeClass("loading");
            loadingIconOn = false;
        }
    });
</script>

This seems to be working fine, but I'm wondering if this might get me into trouble if multiple actions are made at the same time to do different ajax operations, and is there a better way of doing this?

EDIT (Here's why I want to wait 2 seconds):

There are actions that show immediate feedback, however the ajax call takes a split second to process. I don't want the user to see the loading symbol flicker on and off in cases like this. I only want to show it when something is actually taking a long time. No reason for them to feel like the site is slow when they wouldn't otherwise notice any slow down.

\$\endgroup\$
4
\$\begingroup\$

The ajaxStart() handler is only called if there are no other ajax requests currently in progress, so if there is already something in progress, the handler will never be fired.

Th ajaxStop() handler will only be called when a request completed and there are no other outstanding requests. So, theoretically, if you have ajax requests being fired every second, the loading icon could remain on your screen in perpetuity. If you are looking at building a heavily ajax-driven UI, it might not be too wise to rely on the global ajax functionality for this reason, as you have to then concern yourself with interaction of different ajax requests and how they may impact the user experience.

From a UX standpoint, I would find it odd that you only want to display the loading icon until after 2 seconds have passed. This seems like a really long time to wait to give the user some feedback that something is happening in response to some action they have taken.

There is no reason to have your global loadingIconOn variable at all. It would be preferable instead to work with jQuery queue functionality to give explicit control over how the animations are sequenced.

For example:

$(document).on({
    ajaxStart: function () {
        $('body').delay(2000, 'loadingIcon').queue('loadingIcon', function() {    
            $(this).addClass('loading').dequeue();
        });
    },
    ajaxStop: function () {
        $('body').clearQueue('loadingIcon').removeClass('loading');
    }
});

This also gives the advantage of having the function to delay then add the class specifically "scoped" to only be relevant to the body element as well as the opportunity to set up a specific queue (loadingIcon in this example), which deals only with this specific functionality.

Note that you don't need to use a custom queue like loadingicon. You could simplify this down by using the default fx queue, which would look like:

$(document).on({
    ajaxStart: function () {
        $('body').delay(2000).queue(function() {    
            $(this).addClass('loading').dequeue();
        });
    },
    ajaxStop: function () {
        $('body').clearQueue().removeClass('loading');
    }
});

But, I find it best practice to keep animations related to a certain set of functionality within their own queues so as to minimize potential conflicts with effects you might add in the future. Like say you added some functionality in the future to change some of other property/class on <body>, if the effects were in their own queue, they could be applied to the element simultaneously with the icon loading effect without causing undesirable interactions (obviously other than if they impact the same CSS properties in the loading class).

\$\endgroup\$
  • 1
    \$\begingroup\$ @BVernon I understand the intent around the delay, but typically, but 2000ms seems like a very long time for the user to sit there not knowing if anything is happening. You may want to look at typical timings of your calls and find a timing number that coverage the worst X% of those response times if yo have not done this already. \$\endgroup\$ – Mike Brant Mar 20 '17 at 19:54
  • \$\begingroup\$ The variable prevents the scenario where ajaxStop is run before 2 seconds is up and then the loadingIcon is shown and never removed. I'm not super familiar with how the delay and queue functions work, but I assume you're saying that scenario is not an issue with this approach? \$\endgroup\$ – BVernon Mar 20 '17 at 19:58
  • \$\begingroup\$ Oh yeah, 2 seconds is not set in stone. I will update that to whatever makes sense after some testing and feedback from others. \$\endgroup\$ – BVernon Mar 20 '17 at 20:06
  • \$\begingroup\$ Also not sure if you glossed over the part where I mentioned the user gets immediate feedback. So they aren't waiting 2 seconds wondering if something happened, but rather they will assume the action was successful before it even finishes... unless it takes longer than 2 seconds. \$\endgroup\$ – BVernon Mar 20 '17 at 20:11
  • 1
    \$\begingroup\$ @BVernon Yes, I am suggesting queuing your addClass() call and working with queue() and clearQueue() instead of introducing a global variable aimed at trying to emulate queueing behavior. \$\endgroup\$ – Mike Brant Mar 20 '17 at 20:16
0
\$\begingroup\$

You shouldn't have issues with multiple ajaxCalls. The ajaxstartwill happen before the first one and the ajaxend event only when the last one has completed.

Rather than using a flag I would track the timer id and use clearTimeout like this:

<script>
    var loadingTimeoutID;

    $(document).on({
        ajaxStart: function () {
            loadingTimeoutID = setTimeout(function () {
                $('body').addClass("loading");
            }, 2000);

        },
        ajaxStop: function () {
            $('body').removeClass("loading");
            clearTimeout(loadingTimeoutID);
        }
    });
</script>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.