Search code examples
javascriptjsonecmascript-6es6-promise

Add a loading animation while the promise loads and displays the JSON


I finished the functionality side of this simple app but now I want to add some good UX aswell so I want to add a loading animation (a spinner) while the JSON loads and before it displays the result of the promise, but I cannot seem to find a solution by googling.

Here is the pen: https://codepen.io/kresimircoko/pen/ZLJjVM.

Here is the JavaScript code:

const API_KEY = '?api_key=625023d7336dd01a98098c0b68daab7e';
const root = 'https://www.warcraftlogs.com:443/v1/';
const zonesBtn = document.querySelector('#zones');
const responseList = document.querySelector('#response');
console.clear();

const requestJSON = objType => {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.onload = function() {
            try {
                resolve(JSON.parse(this.responseText));
            }
            catch (e) {
                reject(e);
            }
        };
        xhr.onerror = reject;
        xhr.open('GET', root + objType + API_KEY);
        xhr.send();
    });
};

function displayBosses(zoneID) {
    let bosses = document.querySelectorAll(`.bosses[data-zoneid="${zoneID}"]`);
    requestJSON('zones')
        .then(data => {
            let output = '';
            data.find(zone => 
                zone.id === parseInt(zoneID, 10)
            ).encounters.map(encounter => {
                output += `<li class="boss" data-zoneid="${zoneID}">${encounter.name}</li>`;
                bosses.forEach(boss => {
                    boss.innerHTML = output;
                });
            }).join('');
    });
}

function displayZones() {
    let output = '';
    requestJSON('zones')
        .then(zones => {
            return zones.map(zone => {
                output += `
                    <ul data-zoneid="${zone.id}" class="zones"> 
                        <span>${zone.name}</span>
                        <ul data-zoneid="${zone.id}" class="bosses"></ul>
                    </ul>`;
                response.innerHTML = output;
            }).join('');
        })
        .then(responseList.style.display = 'flex');
}

zonesBtn.addEventListener('click', displayZones);
responseList.addEventListener('click', evt => {
    const target = evt.target.parentElement;
    const zoneID = target.dataset.zoneid;
    displayBosses(zoneID);
    if (target.classList.contains('clicked'))
        target.classList.remove('clicked');
    else 
        target.classList.add('clicked')
});

Solution

  • The spinner is a FontAwesome icon wrapped in a spinner div for which we control the display property to show up when the button is clicked but hide when the promise has resolved.

    function displayZones() {
        if (!this.classList.contains('open')) {
            spinner.style.display = 'block';
            this.classList.add('open');
        }
        let output = '';
        requestJSON('zones')
            .then(zones => {
                spinner.style.display = 'none';
                return zones.map(zone => {
                    output += `
                        <ul data-zoneid="${zone.id}" class="zones"> 
                            <span>${zone.name}</span>
                            <ul data-zoneid="${zone.id}" class="bosses"></ul>
                        </ul>`;
                    response.innerHTML = output;
                }).join('');
            })
            .then(responseList.style.display = 'flex');
    }