The following works correctly:
alert("start");
loadData(); // takes a while
alert("finished");
loadData() is a method that inserts large amounts of data into a table in the DOM and takes a few seconds.
This, however, does not work as expected:
document.getElementById("mydiv").style.display = "block";
loadData(); // takes a while
document.getElementById("mydiv").style.display = "none";
The data is loaded into the table without displaying mydiv until the loading is completed and then mydiv is quickly displayed and gone.
But this works like it's supposed to:
document.getElementById("mydiv").style.display = "block";
alert("start");
loadData(); // takes a while
alert("finish");
document.getElementById("mydiv").style.display = "none";
mydiv is displayed, the alert dialog is shown, the data is loaded into the table, and then mydiv disappears, as expected.
Anyone know why the second code section above does not work properly?
I dont know the exact reason, but i can think of is that LoadData is a heavy function so browser is busy evaluating that, so it holds the rendering. When u give alert in between it provides sufficient time to render div and then evaluate LoadData.
Work Around:
function LoadData()
{
//Show Div here
//window.setTimeout(newFunc,100);
}
function newFunc()
{
//Do data operations here
//Hide Div
}
Think of javascript as inserting some interpreted code into a larger program. The larger program looks a little like this:
void eventLoop () {
struct event event;
while(event = getEvent()){
runJavascript(event);
runBrowserCode();
if(viewHasChanged) {
redrawViewRegions();
}
}
}
This isn't really accurate at all, but it gives you a little bit of an idea. While javascript code is running, the browser is not doing anything else: including updating the view. it hasn't got up to that bit. The browser gui does not run in a seperate asynchronous thread, in parallel to the javascript as you seem to imagine. The browser has to wait for javascript to finish before it can do anything.
You can see the gui update when you use alerts, because, well- To display an alert you have to update the gui! so it's kind of a special case.
Related
I have a function that takes a little while to run, so I'm trying to set the display value of a div (that basically says "please wait") to "block" at the start of the function and then to "none" when the function is complete to alert the user that the function is running.
The display setting is being set properly and I can see the changes in the console log; however, I cannot get it to redraw until after the function is complete. After hours of scouring forum answers to similar questions, it seems like my best option is to call window.getComputedStyle or element.offsetHeight, but these don't seem to be working.
Here is my code:
function renumber(){
notif = document.getElementByID("notification"); //this is my div
notif.style.display = "block";
window.getComputedStyle(notif, null).height; //I've tried this as well as...
notif.offsetHeight; //trying this seperately
/////////big loop that takes a long time to run/////////
notif.style.display = "none";
}
I have also tried setting the CSS instead, and then using window.getComputedStyle and element.offsetHeight:
doc = document.styleSheets[6].cssRules[0].style;
disp = doc.setProperty('display','block');
window.getComputedStyle(notif, null).height; //I've tried this as well as...
notif.offsetHeight; //trying this seperately
And, I have even tried abandoning my notification div and using setTimeout() and alert(), but even that doesn't display until the function is complete (it doesn't matter how much time I put in):
function renumber(){
setTimeout(function() { alert("my message"); }, 50);
////big loop that takes a long time to run////
}
So with all of that, my question is why is window.getComputedStyle and element.offsetHeight not redrawing and making my div display the way the values are set within the function? Everything I've seen online sounds like simply calling one of those should force a redraw, so I'm confused why I'm not seeing a redraw. Is there a better way I should be doing this?
Would tell problem is JS is single threaded, you block UI by your slow operation.
Do not know if there is a process events signal to UI common in frameworks like Qt, etc., but setTimeout can help here (may not work perfect here in snippet, slow part takes around 6s in my Chrome):
function renumber(){
notif = document.getElementById("notification"); //this is my div
notif.style.display = "block";
setTimeout(long, 0, notif);
}
renumber();
function long(notif) {
console.log("Long starts", new Date());
for(var a=0;a<1000000000;a++) if(a % 10) a++; else a += 0.1;
console.log("Long end", new Date());
notif.style.display = "none";
}
<div id="notification" style="display: none;">
Wait please !
</div>
Some of the scripts that I run take a long time and users might get concerned that a script stopped working if they can't see the status/step. I have added a spinner to the Sidebar to at least indicate that the script started running, but I would like to do more than that.
Ideally, I would be able to directly update the Sidebar contents from the GAS, but I gather than is not possible because of sandboxing. I have seen other questions and answers that discuss using success handlers in a daisy chain like this:
function uploadActivities(){
google.script.run.withSuccessHandler(onSuccess).activities_upload();
}
function onSuccess(lastStatus){
$('#codestatus').text(lastStatus);
google.script.run.step_two();
}
It is a hack and it would require me to split the code into smaller steps and pass values to the UI, which don't belong in the UI, and back to the code. I really don't like that approach and maintenance could be a bear.
I have tried creating a var in GAS and updating that value as the code progresses. However, I can't find a way to get the UI to periodically check until the code execution is complete AND to successfully update the UI after each step.
Here is the code I have created:
function uploadActivities(){
google.script.run.activities_upload();
getStatus();
}
function getStatus(){
var isActive = true;
while(isActive){
var lastStatus = google.script.run.getStatus();
$('#codestatus').text(lastStatus);
if(lastStatus === 'Complete'){ isActive = false; }
}
}
In GAS I use this code:
var codeStatus = 'start';
function getStatus(){
return codeStatus;
}
function activities_upload(){
codeStatus = 'Started Execution';
...
codeStatus = 'Extracting Values';
...
codeStatus = 'Uploading Activities';
...
codeStatus = 'Complete';
}
It runs the required code, and even updates the #codestatus div with the first value, but it doesn't get any values beyond the first value. Additionally, it creates a continuous loop if there is an error in the code execution, so that isn't good either.
Is there a good, efficient, and safe way to complete this approach? Or, is there a better way to notify the user of the code execution status so they don't get worried if it takes a while, and can tell if there has been an issue?
I have struggled with this for some time. Unfortunately, I don't have a good fix for your approach, but I can show what I finally did and it seems to be working.
First, create an easy way to send a toast to your users.
function updateStatus_(alert,title){
var ui = SpreadsheetApp.getActiveSpreadsheet();
var title_ = title!=""?title:"";
ui.toast(alert,title_);
}
Second, as required, use the toast to update the user.
function activities_upload(){
updateStatus_('Started Execution');
...
updateStatus_('Extracting Values');
...
updateStatus_('Uploading Activities');
...
updateStatus_('Complete');
}
This will alert the user with a temporary message as the code progresses and not require the user to clear an alert.
Please note that if the steps progress rapidly the user will see the toast flash on the screen only to be quickly replaced by the next toast. So, make sure you don't have too many throughout your execution.
I want to display a spinner before some complicated function, i.e. dummyCounter(). The code looks like:
function add1() {
msg.html('start counting~<br \>');
document.body.appendChild(div);
spinner.spin(div);
// display spinner before doing stuff
dummyCounter();
}
jsfiddle: http://jsfiddle.net/eGB5t/
However the spinner shows after the dummyCounter() function is finished.
I try to use callback to force spinner display earlier but still no good. Can anybody help? Thanks.
jsfiddle: http://jsfiddle.net/eGB5t/2/
You have a thinking failure. Spinners are usually used for asynchronous tasks, so you can see that there is something in progress. A callback is then used to remove the spin when the async action has finished, since you cannot tell before it starts when it will finish.
I made up a quick example to show you, how such an async function would work in this case, and you can clearly see how the spinner appears slightly before "google finished" appears.
http://jsfiddle.net/eGB5t/4/
I added the following instead of your counting method:
$.ajax("http://google.de").always(function() {
msg.append("google finished");
});
You add the spin before you count, then it counts, then you could remove the spinner. This is perfecty fine. Thing is, if you would count to let's say 9999999999999 (so it would take some seconds), a normal for loop like you're doing is completely blocking the browser, so you won't have any repaints (and therefore no spinner) at all, while the loop is running.
What you would have to do (in this case) is to introduce a worker to have multithreading functionality in javascript.
var x;
function add1() {
msg.html('start counting~<br \>');
spinner.spin(div);
x= setTimeout(document.body.appendChild(div),500);
}
To see the problem in action, see this jsbin. Clicking on the button triggers the buttonHandler(), which looks like this:
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking";
longPrimeCalc();
}
You would expect that this code changes the text of the div to "thinking", and then runs longPrimeCalc(), an arithmetic function that takes a few seconds to complete. However, this is not what happens. Instead, "longPrimeCalc" completes first, and then the text is updated to "thinking" after it's done running, as if the order of the two lines of code were reversed.
It appears that the browser does not run "innerHTML" code synchronously, but instead creates a new thread for it that executes at its own leisure.
My questions:
What is happening under the hood that is leading to this behavior?
How can I get the browser to behave the way I would expect, that is, force it to update the "innerHTML" before it executes "longPrimeCalc()"?
I tested this in the latest version of chrome.
Your surmise is incorrect. The .innerHTML update does complete synchronously (and the browser most definitely does not create a new thread). The browser simply does not bother to update the window until your code is finished. If you were to interrogate the DOM in some way that required the view to be updated, then the browser would have no choice.
For example, right after you set the innerHTML, add this line:
var sz = elm.clientHeight; // whoops that's not it; hold on ...
edit — I might figure out a way to trick the browser, or it might be impossible; it's certainly true that launching your long computation in a separate event loop will make it work:
setTimeout(longPrimeCalc, 10); // not 0, at least not with Firefox!
A good lesson here is that browsers try hard not to do pointless re-flows of the page layout. If your code had gone off on a prime number vacation and then come back and updated the innerHTML again, the browser would have saved some pointless work. Even if it's not painting an updated layout, browsers still have to figure out what's happened to the DOM in order to provide consistent answers when things like element sizes and positions are interrogated.
I think the way it works is that the currently running code completes first, then all the page updates are done. In this case, calling longPrimeCalc causes more code to be executed, and only when it is done does the page update change.
To fix this you have to have the currently running code terminate, then start the calculation in another context. You can do that with setTimeout. I'm not sure if there's any other way besides that.
Here is a jsfiddle showing the behavior. You don't have to pass a callback to longPrimeCalc, you just have to create another function which does what you want with the return value. Essentially you want to defer the calculation to another "thread" of execution. Writing the code this way makes it obvious what you're doing (Updated again to make it potentially nicer):
function defer(f, callback) {
var proc = function() {
result = f();
if (callback) {
callback(result);
}
}
setTimeout(proc, 50);
}
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking...";
defer(longPrimeCalc, function (isPrime) {
if (isPrime) {
elm.innerHTML = "It was a prime!";
}
else {
elm.innerHTML = "It was not a prime =(";
}
});
}
When this function is called, the style change on the "gif" element does not show up until "lotsOfProcessing()" finishes. However, when I uncomment the alert("test"), the style change is shown before the alert pops up.
What I am trying to do is have an animated gif displayed while lotsOfProcessing is running. This seemed pretty straight forward solution but it is clearly not working. Any suggestions / solutions?
function nameOfFuntion()
{
document.getElementById("gif").style.display = "inline";
//alert("test");
lotsOfProcessing();
}
JavaScript code executes on the same thread as the browser's rendering. Everything that needs to be drawn waits for JavaScript execution to complete - including the next frame of any GIF animation.
The only solution is to break your long processing code down into smaller parts and delay each part using timers.
For example:
function nameOfFuntion() {
document.getElementById("gif").style.display = "inline";
//alert("test");
lotsOfProcessing();
}
function lotsOfProcessing() {
var i = 0;
window.setTimeout(function () {
partOfIntenseProcessing();
if (i < 1000000)
i++, window.setTimeout(arguments.callee, 10);
}, 10);
}
This will delay how long it will take for your processing to complete, but between timer execution the GIF can continue to animate.
You can also take a look at Web Workers, which allow you to run JavaScript operations in a background thread. However, they are not widely implemented yet (read: not available in Internet Explorer).
Perform your heavy processing in a delayed function with window.setTimeout():
function nameOfFunction()
{
document.getElementById("gif").style.display = "inline";
window.setTimeout(lotsOfProcessing, 10);
}
That's strange indeed. Seems like lotsOfProcessing gets javascript's single thread before the dom has time to refresh, but it's the first time I hear of something like that.
You might try this (not that is not an ideal solution):
function nameOfFuntion()
{
document.getElementById("gif").style.display = "inline";
setTimeout(lotsOfProcessing, 100);
}
This is a vaguely educated guess but it may be worth trying to put document.getElementById("gif").style.display = "inline"; into a function eg.
function nameOfFuntion()
{
showGif();
//alert("test");
lotsOfProcessing();
}
function showGif() {
document.getElementById("gif").style.display = "inline";
}
My thinking is that perhaps the lotsOfProcessing() is getting hoisted to the top of nameOfFunction() because it's a function and therefore getting processed first.