Home > OS >  Styling isn't applying in Javascript
Styling isn't applying in Javascript

Time:06-09

I'm trying to hide the text of a button and show a spinner in its place while processing something. However no styling is applied even though the classes get properly toggled to the right value as I see on the Developer Tools.

function toggleSpinner() {
    $('button span').toggleClass('display-none');
    $('button .fa-spinner').toggleClass('display-none');
}

function generateGrid(size) {
    let arr = getRandomNumbers(size);

    toggleSpinner(); // <--- HIDE TEXT SHOW SPINNER
    $('.grid').empty();

    let html = '';
    arr.forEach((element) => {
        html  = '<div >'   element.toLocaleString()   '</div>';
    });
    $('.grid').append(html);

    toggleSpinner(); // <--- HIDE SPINNER SHOW TEXT
}

And this is my HTML:

<button>
    <span>Generate Random Numbers</span>
    <i ></i>
</button>

Generating the grid sometimes takes like 3 seconds depending on the size of the grid and during that time, I would like to show the spinner on the button instead of the text.

More Info

The first time toggleSpinner() gets called, I expect the button to only display a spinning icon without the text. The second time toggleSpinner() gets called, I expect the spinning icon to go away and the button's text to show up again.

What's happening is that even though the classes get toggled, it's like the browser isn't repainting the page until everything is done so in effect, it's like toggleSpinner() gets called twice which results to the same classes from the beginning resulting in no changes to the button.

CodePudding user response:

It's rudamentary and there are probably better ways but one option is to take the first toggleSpinner out of the method and use a timeout, which then calls your generateGrid function. This gives the CSS and class toggles time to do their thing before the render blocking of the generateGrid happens.

function toggleSpinner() {
    $('button span').toggleClass('display-none');
    $('button .fa-spinner').toggleClass('display-none');
}

function getRandomNumbers(size) {
  return new Array(size).fill('foo')
}

function generateGrid(size) {
    let arr = getRandomNumbers(size);

    $('.grid').empty();

    let html = '';
    arr.forEach((element) => {
        html  = '<div >'   element.toLocaleString()   '</div>';
    });
    $('.grid').append(html);
    
    toggleSpinner()
}

$('button').on('click', () => {
  toggleSpinner()
  setTimeout(() => { generateGrid(100000); }, 1000)
  
})
.display-none { display: none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button>
    <span>Generate Random Numbers</span>
    <i >SPINNING</i>
</button>
<div ></div>

CodePudding user response:

Changes to the DOM are not being rendered because generating the grid is blocking the event loop. Hence running generateGrid asynchronously should solve the issue. However just running it as an asynchronous function and waiting for it to finish (using await), or putting the code directly in a promise handler, risks putting processing in the microtask queue - which also blocks the event loop.

This example simulate "generating the grid" by simply running a loop for 3 seconds. It is called from a timer call with an additional callback argument to signal when it has done. The calling function, makeGrid returns a promise this is fulfilled when processing has completed:

"use strict";
function toggleSpinner() {
    $('button span').toggleClass('display-none');
    $('button .fa-spinner').toggleClass('display-none');
}

function makeGrid(size) {
    toggleSpinner(); // show spinner
    return new Promise( resolve => {
        setTimeout( generateGrid, 0, size, function() {
            toggleSpinner(); // hide spinner
            resolve();
        });
    });
}
function generateGrid( size, done) {
   // dummied code just to run synchonously for 3 seconds
    const end = Date.now()   3000;
    while( Date.now() < end);
    done();  // callback when done
}


function test() {
   console.log( "calling makeGrid");
    makeGrid(1).then( ()=>console.log("makeGrid finished"));
}
.display-none {
   display: none;
}
.fa-spinner:before {
   content: "⏳";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js" crossorigin></script>

<!-- body-html -->
<button>
    <span>Generate Random Numbers</span>
    <i ></i>
</button>
<p>
<button onclick="test()">test</button> <-- click here to test

  • Related