Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DOM refresh on long running function

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/

like image 743
MirrorMirror Avatar asked Jun 01 '13 19:06

MirrorMirror


People also ask

How do you refresh a DOM element?

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.

Are DOM updates synchronous?

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.

What is updating the DOM?

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.


2 Answers

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...  ... 
like image 77
Mike 'Pomax' Kamermans Avatar answered Oct 02 '22 20:10

Mike 'Pomax' Kamermans


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!

like image 38
Kylie Avatar answered Oct 02 '22 19:10

Kylie