Browser sometimes ignoring CSS changes (cursor style)

104 Views Asked by At

(This is an edited question, based on Adam H's help.)

I have a complex web-app in Javascript. Some actions are lengthy, and I want to show the busy cursor during these actions. These actions take on many forms, in many places in the code. They inspect, modify and rearrange the DOM, and often add event handlers.

I want to use a wrapper-function. The wrapper should show the busy cursor, execute the wrapped function, then revert the cursor to normal. Simplified, it looks like this:

function lengthy1(func) {
  document.body.classList.add('waiting');
  document.getElementById('notice').innerText = 'waiting';
  window.setTimeout(() => {
    func();
    document.body.classList.remove('waiting')
    document.getElementById('notice').innerText = 'done';
  }, 0); // Zero delay is unreliable, but works almost always with 100 msec
}

function lengthy2(func) {
  document.body.classList.add('waiting');
  document.getElementById('notice').innerText = 'waiting';
  new Promise((resolve, reject) => {
    func();
    resolve();
  }).then(() => {
    document.body.classList.remove('waiting')
    document.getElementById('notice').innerText = 'done';
  });
}

function LongAction() {
  // First add many paragraphs...
  for (let i = 0; i < 20000; i++) {
    const para = document.createElement('p');
    para.innerText = `This is paragraph ${i}`;
    para.classList.add('asdf');
    document.body.appendChild(para);
  }
  // ... then remove them again.
  let matches = document.getElementsByClassName('asdf');
  while (matches.length > 0) matches[0].remove();
}

function butt1() {
  lengthy1(LongAction);
}

function butt2() {
  lengthy2(LongAction);
}
body.waiting * {
  cursor: wait !important;
}
<body>
  <span id='notice'>xxx</span><br>
  <button onClick='butt1();'>Button 1</button>
  <button onClick='butt2();'>Button 2</button>
</body>

Function lengthy1() is my original attempt. This works often but not nearly always, and works more often when the delay is increased (!).

Function lengthy2() is rephrased from Adam H. This works fine in Adam H's version, but not in my rewritten version.

What would be the best way to reliably change the cursor during a lengthy operation?

1

There are 1 best solutions below

2
On

Use promises.

function DoSomeLengthyCalculation(){
  return new Promise((resolve, reject) => {
    // just use setTimeout to simulate a long process
    setTimeout(resolve, 3000);
  })
}

document.body.classList.add('waiting')
DoSomeLengthyCalculation().then(() => { document.body.classList.remove('waiting')});
body.waiting * { cursor: wait !important; }
<div>
  this is my content
</div>

Second Solution

Use both a promise and setTimeout.

function runLongAction(func) {
    // update the DOM now
  document.body.classList.add('waiting');
  document.getElementById('notice').innerText = 'waiting';
  
  // return a promise so the call can be chained
  // also wrapping this in a promise addresses the 
  // reliability of just using setTimeout
  return new Promise((resolve, reject) => {
    // use the setTimout work around to unblock the UI
    setTimeout(() => {
        // execute the long running function
      LongAction();
      // resolve the promise
      resolve();
    }, 0);
  })
    // wait for the promise to resolve then 
    // update the DOM to remove waiting indicators
    .then(() => {
        document.body.classList.remove('waiting')
      document.getElementById('notice').innerText = 'done';
    })
}

function LongAction() {
  // First add many paragraphs...
  for (let i = 0; i < 20000; i++) {
    const para = document.createElement('p');
    para.innerText = `This is paragraph ${i}`;
    para.classList.add('asdf');
    document.body.appendChild(para);
  }
  // ... then remove them again.
  let matches = document.getElementsByClassName('asdf');
  while (matches.length > 0) matches[0].remove();
}

function run() {
  runLongAction();
}
body.waiting * {
  cursor: wait !important;
}
<span id='notice'>xxx</span><br>
<button onClick='run();'>Run Long Action</button>