Loop trough existing images, checking with Promise - javascript

Desired to have a JavaScript to loop trough a specific URL to find existing images using number incrementation ("https://6od.hu/data/hej_Page_%D.png", where %D is incremented).
Trying to Check if image exists without loading it and using the while loop:
The working code
function imageExists(url) {
return new Promise((resolve, reject) => {
const img = new Image(url);
img.onerror = reject;
img.onload = resolve;
const timer = setInterval(() => {
if (img.naturalWidth && img.naturalHeight) {
img.src = ''; /* stop loading */
clearInterval(timer);
resolve();
}
}, 10);
img.src = url;
});
}
function loadImg(i) {
const src_pattern = "https://6od.hu/data/hej_Page_0%D.png"
let src = src_pattern.replace("%D", i);
imageExists(src)
.then(() => console.log("Image exists."))
.catch(() => console.log("Image not exists."));
}
console.log(loadImg(5)); // true|false needed to return here
How this Promise object can return true/false for the loop?
I am just learning these Promise objects, please try to be descriptive. Thank you.
Desired loop
let i = 1;
while (loadImg(i)) {
console.log(i + ": " + loadImg(i)); // log all of the existing img than stop
i++;
}

I added missing return and changed then and catch handlers
Also, removed setInterval
Check one:
function imageExists(url) {
return new Promise((resolve, reject) => {
const img = new Image(url);
img.onerror = reject;
img.onload = resolve;
img.src = url;
});
}
function loadImg(i) {
const src_pattern = "https://6od.hu/data/hej_Page_0%D.png"
let src = src_pattern.replace("%D", i);
return imageExists(src)
.then(() => true)
.catch(() => false);
}
(async() => {
console.log(await loadImg(5));
})()
Loop:
function imageExists(url) {
return new Promise((resolve, reject) => {
const img = new Image(url);
img.onerror = reject;
img.onload = resolve;
img.src = url;
});
}
function loadImg(i) {
const src_pattern = "https://6od.hu/data/hej_Page_0%D.png"
let src = src_pattern.replace("%D", i);
return imageExists(src)
.then(() => true)
.catch(() => false);
}
(async() => {
for (let i = 1; true; i += 1) {
const exists = await loadImg(i)
if (!exists) break
console.log(i, exists);
}
})()

Related

Pass json objects into another function to call in a for loop

I have a function that gets a list of json objects through XmlHTTPRequest:
function getDataByXHR(method, url) {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function () {
console.log(xhr.response);
gameSettings = JSON.parse(xhr.response);
console.log(gameSettings);
this.data = gameSettings;
};
xhr.onerror = function () {
console.error("An error occur getting the XMLHttpRequest");
};
xhr.send();
}
How can I pass them into a function like this
function waitConsoleLog() {
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
let count = 0;
console.log(this.data);
for (let index = 0; index < this.data.length; index++) {
async (element) => {
count++;
await sleep(500 * count);
console.log(element);
};
}
}
to use in the for loop since data/gameSettings always return as undefined
Try Using:
function getDataByXHR(method, url) {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function () {
console.log(xhr.response);
gameSettings = JSON.parse(xhr.response);
console.log(gameSettings);
this.data = gameSettings;
waitConsoleLog(this.data)
};
xhr.onerror = function () {
console.error("An error occur getting the XMLHttpRequest");
};
xhr.send();
}
function waitConsoleLog(data) {
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
let count = 0;
console.log(data);
for (let index = 0; index < data.length; index++) {
async (element) => {
count++;
await sleep(500 * count);
console.log(element);
};
}
}

Pre-load images and get progress

I have got the following script I use to pre-load images:
export default function PreloadImages(images) {
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
img.src = src;
});
}
return Promise.all(images.map(src => loadImage(src)));
}
I then use this within a Vue component like follows:
PreloadImages(this.images).then(() => (this.imagesPreloaded = true));
I want to be able to get progress from this however within my component so I can display for example 2/50 Images Loaded. How would I go about doing this?
Edit
This is what I ended up with:
PreloadImages.js
export default function PreloadImages(images) {
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
img.src = src;
});
}
return images.map(src => loadImage(src));
}
Within my component:
handlePreload() {
PreloadImages(this.images).forEach(p => p.then(() => {
this.imagesPreloaded++;
}));
},
try this:
function PreloadImages(images) {
let promises = [];
images.map(src => loadImage(src));
function loadImage(src) {
promises.push(new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
img.src = src;
}));
}
Promise.all(promises);
return promises;
}
let images = [];
let counter = 0;
let promises = PreloadImages(images);
promises.forEach(p => p.then(() => {
counter++;
console.log(counter);
}));
This is how you should approach it, don't introduce any extra looping:
var imageSrcList = [
"https://image.flaticon.com/icons/svg/565/565860.svg",
"https://image.flaticon.com/icons/svg/565/565861.svg",
"https://Error.Done.One.Purpose",
"https://image.flaticon.com/icons/svg/565/565855.svg",
];
var imageContainer = document.getElementById("loadedImages");
var loadedCount = 0;
function displayCurrentLoadStatus(){
console.log("Current load progress: ", loadedCount, " of ", imageSrcList.length);
}
function PreloadImages(images) {
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = function()
{
++loadedCount;
imageContainer.appendChild(img);
displayCurrentLoadStatus();
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
});
}
// Note that I removed Promise.all, let's just return a list of promises for the images passed
return images.map((imgSrc, i)=> loadImage(imgSrc).catch((rejectedSrcError=> rejectedSrcError)));
}
// now let's create all promises for each image in images
Promise.all(PreloadImages(imageSrcList)).then( resolvedSrcList =>
{
// Use your resolved Src's here
console.log("Load complete: ",loadedCount, "/",resolvedSrcList.length, " images were loaded successfully.");
});
img{
width: 60px;
height: 60px;
display: inline-block;
}
<div id="loadedImages"></div>
... tldr ...
There will be 2 different basic ideas and 4 solutions for demonstration purpose.
The first two approaches are valid in case one can not or is not allowed to change the return value (a single promise) of PreloadImages as from the OP's originally provided example code.
The 3rd example presents the preferred approach which is to change the return value of PreloadImages to a list of (image preloading) promises, whereas the 4th example is just No.3 that in addition features a real live demo of progress-bar and statistics rendering.
1) Proxy based
One could use a Proxy object that one, in addition to the image source list, does provides to the preloadImages function.
One would use such an object in its set and validate configuration.
Choosing a Proxy based approach comes with following advantages ...
Introducing eventProxy into preloadImages makes this new implementation agnostic to later user cases.
One just needs to implement functionality for an API that provides all necessary information about image loading states in a/the most generic way.
The user specific, but in the preloadImages' scope unknown use case, that later needs to be handled, will be provided as part of the eventProxy.
/*export default */function preloadImages(imageSourceList, eventProxy) {
// - the introduction of an `eventProxy` makes this implementation
// agnostic to later user cases.
// - one just needs to implement the necessary api that provides
// information about image loading states.
// - the user specific, but in this scope unknown use case, that later
// needs to be handled, will be provided as part of the `eventProxy`.
const maxCount = imageSourceList.length;
let successCount = 0;
let failureCount = 0;
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve(image);
++successCount;
eventProxy.event = {
image,
maxCount,
successCount,
failureCount
}
};
image.onerror = image.onabort = function() {
reject(src);
++failureCount;
eventProxy.event = {
image,
maxCount,
successCount,
failureCount
}
};
image.src = src;
});
}
return Promise.all(imageSourceList.map(src => loadImage(src)));
}
function renderImageLoadProgress(evt) {
// the render functionality goes in here ...
console.log('renderImageLoadProgress :: evt :', evt);
}
function isImageLoadType(type) {
return (
type
&& ('image' in type)
&& ('maxCount' in type)
&& ('successCount' in type)
&& ('failureCount' in type)
)
}
const loadEventValidator = {
set: function(obj, key, value) {
let isSuccess = false;
if ((key === 'event') && isImageLoadType(value)) {
obj[key] = value;
isSuccess = true;
// provide render functionality as part of
// the proxy's correct setter environment.
renderImageLoadProgress(value);
}
return isSuccess;
}
}
const loadEventProxy = new Proxy({}, loadEventValidator);
const imageSourceList = [
'https://picsum.photos/id/1011', // loading will fail.
'https://picsum.photos/id/1011/5472/3648',
'https://picsum.photos/id/1012/3973/2639',
'https://picsum.photos/id/1013/4256/2832',
'https://picsum.photos/id/1013', // loading will fail.
'https://picsum.photos/id/1014/6016/4000',
'https://picsum.photos/id/1015/6000/4000',
'https://picsum.photos/id/1016/3844/2563'
];
preloadImages(imageSourceList, loadEventProxy);
.as-console-wrapper { min-height: 100%!important; top: 0; }
2) Event based
In case of not being able making use of proxies, one easily can switch the above approach to EventTarget and CustomEvent, especially since there are polyfills available that also do work in older versions of Internet Explorer. The idea behind this solution and its advantages are the same as with the former Proxy based one ...
/*export default */function preloadImages(imageSourceList, eventTarget) {
// - additionally injecting/providing an `eventTarget` makes this
// implementation agnostic to later user cases.
// - one just needs to dispatch all relevant information via the
// `details` property of a custom event.
// - the user specific, but in this scope unknown use case, will be
// handled from ouside via listening to the custom event's type.
// - there are polyfills for `EventTarget` as well as for `CustomEvent`
// that also do work in older versions of Internet Explorer.
const maxCount = imageSourceList.length;
let successCount = 0;
let failureCount = 0;
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve(image);
++successCount;
eventTarget.dispatchEvent(new CustomEvent("loadstatechange", {
detail: {
image,
maxCount,
successCount,
failureCount
}
}));
};
image.onerror = image.onabort = function() {
reject(src);
++failureCount;
eventTarget.dispatchEvent(new CustomEvent("loadstatechange", {
detail: {
image,
maxCount,
successCount,
failureCount
}
}));
};
image.src = src;
});
}
return Promise.all(imageSourceList.map(src => loadImage(src)));
}
function renderImageLoadProgress(evt) {
// the render functionality goes in here ...
console.log('renderImageLoadProgress :: evt.detail :', evt.detail);
}
const loadEventTarget = new EventTarget();
loadEventTarget.addEventListener('loadstatechange', renderImageLoadProgress);
const imageSourceList = [
'https://picsum.photos/id/1011', // loading will fail.
'https://picsum.photos/id/1011/5472/3648',
'https://picsum.photos/id/1012/3973/2639',
'https://picsum.photos/id/1013/4256/2832',
'https://picsum.photos/id/1013', // loading will fail.
'https://picsum.photos/id/1014/6016/4000',
'https://picsum.photos/id/1015/6000/4000',
'https://picsum.photos/id/1016/3844/2563'
];
preloadImages(imageSourceList, loadEventTarget);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Note
Since the 2 above given approaches did not change the return value/signature of the original implementation of PreloadImages they had to be implemented exactly each in their own way. But both together come with a lot of additional code, the proxy based approach being a little fatter than the event based one.
3) Return Array of Promises
Anyhow, if one is/was just willing to change the return value of the former PreloadImages to an array of image loading promises one could achieve a very clean and lean implementation that might look similar to the next one ...
/*export default */function getAsyncLoadingImageList(imageSourceList) {
const totalCount = imageSourceList.length;
let successCount = 0;
let failureCount = 0;
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve({
image,
counts: {
total: totalCount,
success: ++successCount,
failure: failureCount
},
success: true,
});
};
image.onerror = image.onabort = function() {
reject({
image,
counts: {
total: totalCount,
success: successCount,
failure: ++failureCount
},
success: false,
});
};
image.src = src;
});
}
// return list of *image loading* promises.
return imageSourceList.map(src => loadImage(src));
}
function renderImageLoadProgress(imageLoadData) {
// the render method.
console.log('imageLoadData : ', imageLoadData);
}
const imageSourceList = [
'https://picsum.photos/id/1011', // loading will fail.
'https://picsum.photos/id/1011/5472/3648',
'https://picsum.photos/id/1012/3973/2639',
'https://picsum.photos/id/1013/4256/2832',
'https://picsum.photos/id/1013', // loading will fail.
'https://picsum.photos/id/1014/6016/4000',
'https://picsum.photos/id/1015/6000/4000',
'https://picsum.photos/id/1016/3844/2563'
];
getAsyncLoadingImageList(imageSourceList).forEach(promise =>
promise
.then(renderImageLoadProgress)
.catch(renderImageLoadProgress)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
4) Bonus
In order to present a real progress bar demo, the 3rd example, that already had it all, was taken as is, but now features a full blown render method instead of just logging the image-loading progress-data ...
/*export default */function getAsyncLoadingImageList(imageSourceList) {
const totalCount = imageSourceList.length;
let successCount = 0;
let failureCount = 0;
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve({
image,
counts: {
total: totalCount,
success: ++successCount,
failure: failureCount
},
success: true,
});
};
image.onerror = image.onabort = function() {
reject({
image,
counts: {
total: totalCount,
success: successCount,
failure: ++failureCount
},
success: false,
});
};
image.src = src;
});
}
// return list of *image loading* promises.
return imageSourceList.map(src => loadImage(src));
}
function getImageLoadProgressRenderConfig(progressContainer) {
const elmProgress = progressContainer.querySelector('progress');
const countsContainer = progressContainer.querySelector('.image-counts');
const failuresContainer = progressContainer.querySelector('.load-failures');
const elmImageCount = countsContainer.querySelector('.count');
const elmImageTotal = countsContainer.querySelector('.total');
const elmFailureCount = failuresContainer.querySelector('.failure-count');
const elmCurrentCount = failuresContainer.querySelector('.current-count');
return {
nodes: {
progress: {
display: elmProgress,
count: elmImageCount,
total: elmImageTotal
},
failure: {
display: failuresContainer,
failureCount: elmFailureCount,
currentCount: elmCurrentCount
}
},
classNames: {
failure: 'failures-exist'
}
};
}
function renderImageLoadProgressViaBoundConfig(imageLoadData) {
const imageCounts = imageLoadData.counts;
const imageCountTotal = imageCounts.total;
const imageCountSuccess = imageCounts.success;
const imageCountFailure = imageCounts.failure;
const imageCountCurrent = (imageCountSuccess + imageCountFailure);
const renderConfig = this;
const renderNodes = renderConfig.nodes;
const progressNodes = renderNodes.progress;
const failureNodes = renderNodes.failure;
const classNameFailure = renderConfig.classNames.failure;
const isFailureOnDisplay =
failureNodes.display.classList.contains(classNameFailure);
progressNodes.display.textContent = `${ imageCountCurrent } \/ ${ imageCountTotal }`;
progressNodes.display.value = imageCountCurrent;
progressNodes.count.textContent = imageCountCurrent;
if (!imageLoadData.success) {
if (!isFailureOnDisplay) {
failureNodes.display.classList.add(classNameFailure);
}
failureNodes.failureCount.textContent = imageCountFailure;
} else if (isFailureOnDisplay) {
failureNodes.currentCount.textContent = imageCountCurrent
}
}
function preloadImagesAndRenderLoadProgress(imageSourceList, progressContainer) {
const totalImageCount = imageSourceList.length;
const renderConfig = getImageLoadProgressRenderConfig(progressContainer);
const renderNodes = renderConfig.nodes;
const progressNodes = renderNodes.progress;
const failureNodes = renderNodes.failure;
const renderImageLoadProgress =
renderImageLoadProgressViaBoundConfig.bind(renderConfig);
failureNodes.display.classList.remove(renderConfig.classNames.failure);
failureNodes.failureCount.textContent = 0;
failureNodes.currentCount.textContent = 0;
progressNodes.display.max = totalImageCount;
progressNodes.total.textContent = totalImageCount;
renderImageLoadProgress({
counts: {
success: 0,
failure: 0
},
success: true,
});
getAsyncLoadingImageList(imageSourceList).forEach(promise => promise
.then(renderImageLoadProgress)
.catch(renderImageLoadProgress)
);
}
const imageSourceListWithFailure = [
'https://picsum.photos/id/1011', // loading will fail.
'https://picsum.photos/id/1011/5472/3648',
'https://picsum.photos/id/1012/3973/2639',
'https://picsum.photos/id/1013/4256/2832',
'https://picsum.photos/id/1013', // loading will fail.
'https://picsum.photos/id/1014/6016/4000',
'https://picsum.photos/id/1015/6000/4000',
'https://picsum.photos/id/1016/3844/2563',
'https://picsum.photos/id/1018', // loading will fail.
'https://picsum.photos/id/1018/3914/2935',
'https://picsum.photos/id/1019/5472/3648',
'https://picsum.photos/id/1020/4288/2848',
'https://picsum.photos/id/1021', // loading will fail.
'https://picsum.photos/id/1021/2048/1206',
'https://picsum.photos/id/1022/6000/3376',
'https://picsum.photos/id/1023/3955/2094'
];
const imageSourceListWithoutFailure = [
'https://picsum.photos/id/1039/6945/4635',
'https://picsum.photos/id/1038/3914/5863',
'https://picsum.photos/id/1037/5760/3840',
'https://picsum.photos/id/1036/4608/3072',
'https://picsum.photos/id/1035/5854/3903',
'https://picsum.photos/id/1033/2048/1365',
'https://picsum.photos/id/1032/2880/1800',
'https://picsum.photos/id/1031/5446/3063',
'https://picsum.photos/id/1029/4887/2759',
'https://picsum.photos/id/1028/5184/3456',
'https://picsum.photos/id/1027/2848/4272',
'https://picsum.photos/id/1026/4621/3070'
];
preloadImagesAndRenderLoadProgress(
imageSourceListWithFailure,
document.querySelector('#loading-with-failure')
);
preloadImagesAndRenderLoadProgress(
imageSourceListWithoutFailure,
document.querySelector('#loading-without-failure')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
.image-loading-progress {
margin: 20px 0;
}
.image-loading-progress,
.image-loading-progress > span,
.image-loading-progress progress {
display: block;
width: 100%;
}
.image-loading-progress > .load-failures {
display: none
}
.image-loading-progress > .load-failures.failures-exist {
display: block
}
<label class="image-loading-progress" id="loading-with-failure">
<progress value="0" max="0">0 / 0</progress>
<span class="image-counts">
...
<span class="count">0</span>
out of
<span class="total">0</span>
images finished loading ...
</span>
<span class="load-failures">
...
<span class="failure-count">0</span>
out of
<span class="current-count">0</span>
images are not available ...
</span>
</label>
<label class="image-loading-progress" id="loading-without-failure">
<progress value="0" max="0">0 / 0</progress>
<span class="image-counts">
...
<span class="count">0</span>
out of
<span class="total">0</span>
images finished loading ...
</span>
<span class="load-failures">
...
<span class="failure-count">0</span>
out of
<span class="current-count">0</span>
images are not available ...
</span>
</label>

var result = await someFunc() returns an object but I expected list of object

I wrote following function for loading indexeddb. (from IndexedDB 備忘メモ)
I think this function should return Array of object. But, sometimes it returns an object. What are the possibilities of bug ?
Chrome developer tool said type of object was Array during in "load" function. But, after received "records" is type of object.
async function load(dbobj, db, index, range) {
return new Promise(async (resolve, reject) => {
const saves = [];
const req = db.transaction(dbobj.storeName).objectStore(dbobj.storeName).index(index).openCursor(range);
const onfinished = () => {
console.log(`${saves.length} saves found.`);
if (saves.length > 0) {
resolve(saves[saves.length - 1]);
}
};
req.onerror = reject;
req.onsuccess = (ev) => {
const cur = ev.target.result;
if (cur) {
saves.push(cur.value);
cur.continue();
} else {
onfinished();
}
};
});
}
// example of receiving data
var records = await load(dbobj, db, index, range);
you are resolving only the value at the last index! resolve(saves) if you need the entire array;
async function load(dbobj, db, index, range) {
return new Promise(async (resolve, reject) => {
const saves = [];
const req = db.transaction(dbobj.storeName).objectStore(dbobj.storeName).index(index).openCursor(range);
const onfinished = () => {
console.log(`${saves.length} saves found.`);
if (saves.length > 0) {
resolve(saves); // you are resolving only the value at the last index! resolve(saves) if you need the entire array;
}
};
req.onerror = reject;
req.onsuccess = (ev) => {
const cur = ev.target.result;
if (cur) {
saves.push(cur.value);
cur.continue();
} else {
onfinished();
}
};
});
}

Why array.length is not working while array is filled by a async function in javascript

I'm learing WebAudio API. I'm facing a problem with it. Basically things are asynchronous here...so im getting a bit confused. Please help.Here is my code:-
//"use strict";
var sources = new Array();
var actx;
var songs = ['src1.mp3', 'src2.mp3'];
async function start() {
console.log("WELCOME!!");
try {
actx = new AudioContext();
} catch (e) {
console.log('WebAudio api is not supported!!');
}
await getBuffers(actx, songs);
console.log(sources);
console.log(sources.length);
}
function load_song(url) {
let promise = new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = () => {
let audioData = request.response;
resolve(audioData);
}
request.onerror = () => {
reject(new error("Could not load the song:- " + url));
}
request.send();
});
return promise;
}
//creats buffers
async function getBuffers(actx, songs) {
// let buffer_list = new Array();
for (let x = 0; x < songs.length; x++) {
let temp = actx.createBufferSource();
await load_song(songs[x]).then((audioData) => {
actx.decodeAudioData(audioData).then((decodedAudioData) => {
temp.buffer = decodedAudioData;
sources.push(temp);
}).catch((error) => {
console.error(error);
});
});
}
//console.log(buffers.length);
}
async function play() {
//start();
sources[0].start(0);
//sources[1].start(0);
}
function stop() {
sources[0].stop(0);
//sources[1].stop(0);
}
Here in the two lines console.log(sources) and console.log(sources.length). Here the results are. Why console.log(sources.length) is 0?
Please help me........Thank you.
You need to return
actx.decodeAudioData(audioData).then((decodedAudioData) => {
As you are not returning it, you dont await it. Therefore the log appears before the array gets filled, however console.log(sources) is live, so you see the latest change.
Such mistakes are more unlikely to happen when you use await everywhere, and thats also easier to read IMO:
async function getBuffers(actx, songs) {
const sources = []; //mutating a global variable isnt good, so lets make it local
for (const song of songs) { //for...of is so beautiful, why dont you use it?
try { //this is similar to .catch
let temp = actx.createBufferSource();
const audioData = await load_song(song);
const decodedAudioData = await actx.decodeAudioData(audioData);
temp.buffer = decodedAudioData;
sources.push(temp);
} catch(e){ console.error(e); } //okay that does not count as an error handler...
}
return sources; //getBuffers implies that it returns sth
}
You should change your getBuffers code for something like this
async function getBuffers(actx, songs) {
try {
for (let song of songs) {
let temp = actx.createBufferSource();
var audioData = await load_song(songs[x])
var decodedAudioData = await actx.decodeAudioData(audioData)
temp.buffer = decodedAudioData;
sources.push(temp);
}
} catch (e) { console.log(e) }
}

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')
});
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');
}

Categories