I have a button which runs a long running function when it's clicked. Now, while the function is running, I want to change the button text, but I'm having problems in some browsers like Firefox, IE.
html:
<button id="mybutt" class="buttonEnabled" onclick="longrunningfunction();"><span id="myspan">do some work</span></button>
javascript:
function longrunningfunction() { document.getElementById("myspan").innerHTML = "doing some work"; document.getElementById("mybutt").disabled = true; document.getElementById("mybutt").className = "buttonDisabled"; //long running task here document.getElementById("myspan").innerHTML = "done"; }
Now this has problems in firefox and IE, ( in chrome it works ok )
So I thought to put it into a settimeout:
function longrunningfunction() { document.getElementById("myspan").innerHTML = "doing some work"; document.getElementById("mybutt").disabled = true; document.getElementById("mybutt").className = "buttonDisabled"; setTimeout(function() { //long running task here document.getElementById("myspan").innerHTML = "done"; }, 0); }
but this doesn't work either for firefox! the button gets disabled, changes colour ( due to the application of the new css ) but the text does not change.
I have to set the time to 50ms instead of just 0ms, in order to make it work ( change the button text ). Now I find this stupid at least. I can understand if it would work with just a 0ms delay, but what would happen in a slower computer? maybe firefox would need 100ms there in the settimeout? it sounds rather stupid. I tried many times, 1ms, 10ms, 20ms...no it won't refresh it. only with 50ms.
So I followed the advice in this topic:
Forcing a DOM refresh in Internet explorer after javascript dom manipulation
so I tried:
function longrunningfunction() { document.getElementById("myspan").innerHTML = "doing some work"; var a = document.getElementById("mybutt").offsetTop; //force refresh //long running task here document.getElementById("myspan").innerHTML = "done"; }
but it doesn't work ( FIREFOX 21). Then i tried:
function longrunningfunction() { document.getElementById("myspan").innerHTML = "doing some work"; document.getElementById("mybutt").disabled = true; document.getElementById("mybutt").className = "buttonDisabled"; var a = document.getElementById("mybutt").offsetTop; //force refresh var b = document.getElementById("myspan").offsetTop; //force refresh var c = document.getElementById("mybutt").clientHeight; //force refresh var d = document.getElementById("myspan").clientHeight; //force refresh setTimeout(function() { //long running task here document.getElementById("myspan").innerHTML = "done"; }, 0); }
I even tried clientHeight instead of offsetTop but nothing. the DOM does not get refreshed.
Can someone offer a reliable solution preferrably non-hacky ?
thanks in advance!
as suggested here i also tried
$('#parentOfElementToBeRedrawn').hide().show();
to no avail
Force DOM redraw/refresh on Chrome/Mac
TL;DR:
looking for a RELIABLE cross-browser method to have a forced DOM refresh WITHOUT the use of setTimeout (preferred solution due to different time intervals needed depending on the type of long running code, browser, computer speed and setTimeout requires anywhere from 50 to 100ms depending on situation)
jsfiddle: http://jsfiddle.net/WsmUh/5/
The location reload() method in HTML DOM is used to reload the current document. This method refreshes the current documents. It is similar to the refresh button in the browser.
log() yields this result isn't surprising because a DOM update is a synchronous event. When the properties of the DOM object are modified, that change is thrown onto the call stack, and no proceeding event can execute until the stack is empty again.
After parsing HTML, all created DOM elements will form a tree structure. Browsers will render all elements according to this tree. Once the DOM tree is changed, browser rendering changes simultaneously. This is the base of dynamically modifying a document.
Webpages are updated based on a single thread controller, and half the browsers don't update the DOM or styling until your JS execution halts, giving computational control back to the browser. That means if you set some element.style.[...] = ...
it won't kick in until your code finishes running (either completely, or because the browser sees you're doing something that lets it intercept processing for a few ms).
You have two problems: 1) your button has a <span>
in it. Remove that, just set .innerHTML on the button itself. But this isn't the real problem of course. 2) you're running very long operations, and you should think very hard about why, and after answering the why, how:
If you're running a long computational job, cut it up into timeout callbacks (or, in 2019, await/async - see note at the end of this anser). Your examples don't show what your "long job" actually is (a spin loop doesn't count) but you have several options depending on the browsers you take, with one GIANT booknote: don't run long jobs in JavaScript, period. JavaScript is a single threaded environment by specification, so any operation you want to do should be able to complete in milliseconds. If it can't, you're literally doing something wrong.
If you need to calculate difficult things, offload it to the server with an AJAX operation (universal across browsers, often giving you a) faster processing for that operation and b) a good 30 seconds of time that you can asynchronously not-wait for the result to be returned) or use a webworker background thread (very much NOT universal).
If your calculation takes long but not absurdly so, refactor your code so that you perform parts, with timeout breathing space:
function doLongCalculation(callbackFunction) { var partialResult = {}; // part of the work, filling partialResult setTimeout(function(){ doSecondBit(partialResult, callbackFunction); }, 10); } function doSecondBit(partialResult, callbackFunction) { // more 'part of the work', filling partialResult setTimeout(function(){ finishUp(partialResult, callbackFunction); }, 10); } function finishUp(partialResult, callbackFunction) { var result; // do last bits, forming final result callbackFunction(result); }
A long calculation can almost always be refactored into several steps, either because you're performing several steps, or because you're running the same computation a million times, and can cut it up into batches. If you have (exaggerated) this:
var resuls = []; for(var i=0; i<1000000; i++) { // computation is performed here if(...) results.push(...); }
then you can trivially cut this up into a timeout-relaxed function with a callback
function runBatch(start, end, terminal, results, callback) { var i; for(var i=start; i<end; i++) { // computation is performed here if(...) results.push(...); } if(i>=terminal) { callback(results); } else { var inc = end-start; setTimeout(function() { runBatch(start+inc, end+inc, terminal, results, callback); },10); } } function dealWithResults(results) { ... } function doLongComputation() { runBatch(0,1000,1000000,[],dealWithResults); }
TL;DR: don't run long computations, but if you have to, make the server do the work for you and just use an asynchronous AJAX call. The server can do the work faster, and your page won't block.
The JS examples of how to deal with long computations in JS at the client are only here to explain how you might deal with this problem if you don't have the option to do AJAX calls, which 99.99% of the time will not be the case.
edit
also note that your bounty description is a classic case of The XY problem
2019 edit
In modern JS the await/async concept vastly improves upon timeout callbacks, so use those instead. Any await
lets the browser know that it can safely run scheduled updates, so you write your code in a "structured as if it's synchronous" way, but you mark your functions as async
, and then you await
their output them whenever you call them:
async doLongCalculation() { let firstbit = await doFirstBit(); let secondbit = await doSecondBit(firstbit); let result = await finishUp(secondbit); return result; } async doFirstBit() { //... } async doSecondBit... ...
SOLVED IT!! No setTimeout()!!!
Tested in Chrome 27.0.1453, Firefox 21.0, Internet 9.0.8112
$("#btn").on("mousedown",function(){ $('#btn').html('working');}).on('mouseup', longFunc); function longFunc(){ //Do your long running work here... for (i = 1; i<1003332300; i++) {} //And on finish.... $('#btn').html('done'); }
DEMO HERE!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With