Reputation: 709
I have created a method to start an activity indicator and a different method to stop the given indicator. Then when doing tasks that are time consuming, I call the start method before and the stop method after. An example of the call is shown below:
// Show loading
viewControllerShowAjax();
// Tasks that take time including large calculations and ajax requests
app.timeConsumingFunction();
// Hide loading
viewControllerHideAjax();
This is working on firefox, but not working on either IE or Chrome. Is there a fix for this?
Edit: Below are the functions that I am using:
function viewControllerInit() {
// Other initializations
...
// Initializing the activity indicator; the activity indicator is simply a jqueryui modal view with a spinjs spinner in it.
$(this.ajaxModalSelector).dialog({
resizable : false,
dialogClass : "no-close ajax",
autoOpen : false,
height : 500,
width : 500,
modal : true,
open: function( event, ui ) {
$(this.ajaxModalSelector).dialog("open");
var opts = {
lines: 12,
length: 0,
width: 20,
radius: 40,
color: '#000',
speed: 1.3,
trail: 50,
shadow: false,
};
this.spinner = new Spinner(opts).spin(document.getElementById("ajax-modal"));
},
beforeClose: function( event, ui ) {
this.spinner.stop();
}
});
}
// Opens the jquery ui modal view
function viewControllerShowAjax() {
$(this.ajaxModalSelector).dialog("open");
}
// Closes the jquery ui modal view
function viewControllerHideAjax() {
$(this.ajaxModalSelector).dialog("close");
}
Edit: For more information regarding the problem; I have found that if the hide function is not called for the activity indicator, then it will appear after the time consuming task is completed. Again this is the behaviour in both IE and Chrome but not firefox.
Upvotes: 1
Views: 3191
Reputation:
The reason that this doesn't work is that JavaScript is single threaded.
The time-consuming function app.timeConsumingFunction()
will block the UI from updating in Chrome and IE.
The reason for it to work in Firefox is simply because FF has a different implementation that allow UI events to execute in-between so to speak (Canary and I believe Chrome as well has an experimental mode that allow UI updates to be executed on a separate thread).
A browser in general cues up different events such as paint events. Then this queue is traversed and executed event by event.
However, if a queue-entry is for example executing the context for a time consuming function, the queue won't have a "breathing space" to continue with the other events in that queue.
That would be among others executing paint events that would otherwise paint the spinner. As it is blocked the spinner won't appear until the time consuming function finishes.
The way to solve this so it works is to part up the execution using either setTimeout
or a Web Worker (see below for Web Worker example).
You could for instance try this to see if it improves current code (which I doubt but worth a try as it is simple) - as there is not enough code in the post to actually test this, consider it theory. Try by replacing this line:
app.timeConsumingFunction();
with
setTimeout(app.timeConsumingFunction, 0); //or use 11 if no difference
If that is not enough you will need to do a similar setup but with the internals of the function (which you don't show) which you divide, if possible, into parts that handles different stages of the operations. A skeleton example could be:
app.timeConsumingFunction = function() {
var hereIsAvailableToAllParts;
function part1() {
// step 1 in time-consuming operation
setTimout(part2, 0); //start next part
}
function part2() {
// step 2 in time-consuming operation
setTimout(part3, 0);
}
function part3() {
// step 3 in time-consuming operation
...done or next...
}
setTimeout(part1, 0);
}
The 0 argument to setTimeout
tells the browser to queue this event at first available chance. This might not always work but you can use an interval of about 11 milliseconds instead which is close to the minimum available queue slice.
By splitting up the operations like this you will allow the browser to execute for example paint events in between and therefor be able to see the spinner.
Without knowing your code I can not say Web Workers are usable in this scenario as there are certain restrictions for those such as no DOM access, no storage access etc. The support for Web Workers is however excellent and I would recommend this approach if possible with your code (ref. restrictions).
But if you do pure calculations consider Web Workers as an option:
https://developer.mozilla.org/en-US/docs/Web/Guide/Performance/Using_web_workers
A bare-bone example:
In your main script you can allocate a Web Worker like this:
var ww = new Worker(urlToWebWorkerScriptHere);
Web Workers communicates through messages so on "our" side we listen to the message
event:
ww.onmessage = function (e) {
var msg = e.data;
/// message returned can be anything you like (see next section)
if (msg === 'Done!') viewControllerHideAjax();
};
The Web Worker uses a separate script which is either loaded as a separate JS-file or you can use a little trick to inline it with the HTML code (shown in the fiddle below).
The script is executed in an isolated context and is where all the calculations take place. When done it signals our main script:
onmessage = function(e) {
/// you can send any commands you want and call them what you want
/// for example, I have called the command start
if (e.data === 'start') {
/// the time consuming stuff..
/// when finished, send 'Done!' to main script
postMessage('Done!');
}
};
And to start the calculation you would do this:
// Show loading
viewControllerShowAjax();
// Tasks that take time including large calculations and ajax requests
ww.postMessage('start');
(the hide is called when the 'Done!' message is returned from worker).
You can of course send forth and back many messages, for example to return partly results and to trigger next part of the calculation (or operation).
I have made a simple demo in the following fiddle - notice that you can click the right button even if the loop in the fiddle is doing busy-looping/"heavy work" (in the example you can also see how to inline the Web Worker script instead of loading it separately):
But again, without seeing your code for the work I cannot tell if Web Workers can be used or not.
In any case this should give some pointers to how to solve this.
Upvotes: 10