Null items in js array are appearing on my set interval loop - javascript

let arrayA=[1,2,"",3,""]
console.log(arrayA);
arrayA= arrayA.filter((item) => item !== "");
console.log(arrayA);
const loopArray = () => {
counter++;
if (counter == arrayA.length) {
counter = 0;
}
if (arrayA[counter] !== "") {
arrayID.innerHTML=array[counter];
}
loopQuotes();
setInterval(loopArray,5500);
When my loop is being displayed, it shows extends the prior item before the "". So instead of looping 1, 2, and 3 for 5 seconds each what happens is 1 gets 5 seconds, while 2 and 3 get 10 seconds.
How do I make it consistent that each of them gets 5 seconds each? Even if say I add and remove stuff from the array consistently?
I am using vanilla JS.

const arrayA = [1,2,"",3,""].filter((item) => item !== "");
const loopArray = arr => {
let counter = 0;
return () => arrayID.innerHTML=arr[counter++ % arr.length];
}
setInterval(loopArray(arrayA),250);
<div id=arrayID>

Related

How to render elements based on a given value in React JS?

I want to render some stars based on a specific value, and this is what I have done so far
const Rating = ({ value }) => {
const renderStars = () => {
let stars = []; // This array should contain all stars either full, half or empty star
for (let i = 0; i < value; i++) {
if (value % 1 !== 0) {
// If the value has decimal number
} else {
// If the value has NO decimal number
}
}
return stars?.map((star) => star); // Mapping the stars array
};
return <div className="rating">{renderStars()}</div>;
};
export default Rating;
Now I have 3 icons: a full star, a half star, and an empty star. Let's say the rating value is 3.5, so what I want is to push to the stars array 3 full stars 1 half star and 1 empty star so that will be 5 stars in total. And then I can map through the array and render all the stars.
You can loop through up until your value as you're currently doing, where for each iteration you push a full star, and then after the loop is complete, check if value is a decimal to determine if you should push an additional half star:
const STAR_COUNT = 5;
const Rating = ({ value }) => {
const stars = Array.from({length: STAR_COUNT}, () => <EmptyStar />);
let i;
for (i = 0; i < value; i++) { // this will loop Math.floor(value) times
stars[i] = <FullStar />;
}
if (value % 1 != 0) // if value is a decimal, add a half star
stars[i-1] = <HalfStar />;
return <div className="rating">{stars}</div>;
};
I would also suggest wrapping this component in a call to React.memo() so that the for loop logic only runs when your value prop changes, and not what the parent rerenders.
Another, perhaps more concise way, is to use some array methods to help, such as .fill() to populate an array firstly with empty stars, then replace those empty stars up to a given index based on your value, and finally add a half star if required:
const STAR_COUNT = 5;
const Rating = ({ value }) => {
const stars = Array(STAR_COUNT).fill(<EmptyStar />).fill(<FullStar />, 0, Math.floor(value));
if (value % 1 != 0) // if value is a decimal, add a half star
stars[Math.floor(value)] = <HalfStar />;
return <div className="rating">{stars}</div>;
};
Try this in your code
let rating = 3.5;
let ratingCount = 0
for(let i=1; i<=5; i++){
if(i<=rating){
console.log("full")
ratingCount++
}
}
if(rating-Math.floor(rating) !== 0){
console.log("Half")
ratingCount++
}
for(let i=ratingCount ; i<5; i++){
console.log("empty")
}

How to return the first 2 objects of an array of 4 objects

I want to return the first 2 objects of an array of 4 objects then add the other two objects with 5 seconds in between.
note: I am reversing the copied array revEvents with reverse() as items are in descending order by date/time, the most recent item goes on top.
My current issue is the first two objects are displayed ok, then after 5 seconds, it loads only the third object and it stops.
useEffect(() => {
let ms = 3000
let i = 0
let displayedEvents = [...props].splice(i, 2)
setEventsProps(displayedEvents.reverse())
const interval = setInterval(() => {
if (++i <= props.length) {
displayedEvents = props.splice(0, i + 2)
setEventsProps(displayedEvents.reverse())
}
}, ms)
return () => { clearInterval(interval) }
}, [])
//JSX as below
displayedEvents.map(event () => ...etc
I'd like to share an improved solution.
useEffect(() => {
const ms = 5000
let i = 2
const displayedEvents: IEventsProps = props.slice(0, 2)
setEventsProps(displayedEvents.reverse())
let interval: NodeJS.Timeout = setInterval((): void => {
if (i < props.length) {
displayedEvents.push(props[i])
setEventsProps(displayedEvents.reverse())
i += 1
} else {
clearInterval(interval)
interval = null
}
}, ms)
return () => { if (interval) { clearInterval(interval) } }
}, [])
This avoids making unnecessary new arrays by mutating the same array, and also clears the interval when the work is done, so it doesn't run forever.
Fixed this issue, instead of using the splice() function I needed to use the slice() function instead.
The code remains the same, added typescript for anyone who find this.
useEffect(() => {
let ms: number = 5000
let i: number = 0
let displayedEvents: IEventsProps = [...props].slice(i, 2)
setEventsProps(displayedEvents.reverse())
const interval: NodeJS.Timeout = setInterval((): void => {
if (++i <= props.length) {
displayedEvents = props.slice(0, i + 2)
setEventsProps(displayedEvents.reverse())
}
}, ms)
return () => { clearInterval(interval) }
}, [])

Change multiple image src with jquery in a circular fashion

I have 3 img tag in my html file. I want to change all of the 3 img's src with button click from an array where I stored 9 images src link.
So when the page loads for the very first time it will show first 3 images[0,1,2]. Then on each next-btn click it will change images to the next 3 [3,4,5], [6,7,8]. And when the prev-btn is clicked it will go back to 3 images.
please help me with that. and please suggest me is there any other way(better) around it?
Here is what I've tried so far:
const $img1 = $("#img1")
const $img2 = $("#img2")
const $img3 = $("#img3")
const $nextBtn = $("#next-btn")
const $prevBtn = $("#prev-btn")
$.get("https://someApiLink.com/all.json", function (characters) {
const charactersList = []
for (let i=0; i<characters.length; i++) {
// here casts is another array to match predefined characters with the
// character from the api
if (casts.includes(characters[i].name)) {
charactersList.push(characters[i])
}
}
$img1.attr("src", charactersList[0].image)
$img2.attr("src", charactersList[1].image)
$img3.attr("src", charactersList[2].image)
// it will only switch back and forth 2 images on the first img tag
$nextBtn.on("click", function () {
const src = ($img1.attr("src") === charactersList[0].image)
? charactersList[1].image
: charactersList[0].image;
$img1.attr("src", src)
})
})
You'll need two things:
A function that sets images based on an index.
An index to keep track on where to in charactersList to start counting from.
With the function you can render you images based on a starting point with +1 and +2 index. So let's say you start at 2, then 3 and 4 will also be rendered.
The nextBtn should increment the index with +3 so that the next 3 images will be rendered.
The prevBtn should decrement the index with -3 so that the previous 3 images will be rendered.
const $img1 = $("#img1")
const $img2 = $("#img2")
const $img3 = $("#img3")
const $nextBtn = $("#next-btn")
const $prevBtn = $("#prev-btn")
$.get("https://someApiLink.com/all.json", function (characters) {
const charactersList = []
for (let i=0; i<characters.length; i++) {
if (casts.includes(characters[i].name)) {
charactersList.push(characters[i])
}
}
// Index to start counting from.
let characterIndex = 0;
/**
* Sets three images based in the given index.
* Indexes will be counted upwards starting with the index.
* #param {number} index
*/
const setThreeImageFromIndex = index => {
$img1.attr("src", charactersList[index].image)
$img2.attr("src", charactersList[index + 1].image)
$img3.attr("src", charactersList[index + 2].image)
});
// Set images for the first time.
setThreeImageFromIndex(characterIndex);
// Go to next 3.
$nextBtn.on("click", function () {
// Don't go over the length of the characterList.
if (characterIndex + 3 < charactersList.length) {
characterIndex += 3;
setThreeImageFromIndex(characterIndex);
}
});
// Go to previous 3.
$prevBtn.on("click", function () {
// Don't go below 0.
if (characterIndex - 3 >= 0) {
characterIndex -= 3;
setThreeImageFromIndex(characterIndex);
}
});
})
You can store a sort of "page number" for example page to keep track of what page are they on and increase/decrease it on each "press".
let page = 0;
const imgInSet = 3;
const reloadImages = ()=>{
const imgSet = Math.abs(page); // we get abs if someone do more previous than nexts
$img1.attr("src", characterList[imgSet*imgInSet].image); // get first image
$img2.attr("src", characterList[imgSet*imgInSet+1].image); // get second image
$img3.attr("src", characterList[imgSet*imgInSet+2].image); // get third image
}
$nextBtn.on("click", ()=>reloadImages(++page)); // do reloadImages after adding 1 to roll
$prevBtn.on("click", ()=>reloadImages(--page);) // do reloadImages after removing 1 from roll
You could add a numberOfPages value to restrict the number of available pages like that:
let page = 0;
const imgInSet = 3;
const numberOfPages = Math.floor(characterList.length/3);
const reloadImages = ()=>{
const imgSet = Math.abs(page)%numberOfPages; // we get abs if someone do more previous than nexts
$img1.attr("src", characterList[imgSet*imgInSet].image); // get first image
$img2.attr("src", characterList[imgSet*imgInSet+1].image); // get second image
$img3.attr("src", characterList[imgSet*imgInSet+2].image); // get third image
}
Those two swap previous/next buttons when they get to the negative numbers by doing it like that:
let page = 0;
const imgInSet = 3;
const numberOfPages = Math.floor(characterList.length/3);
const reloadImages = ()=>{
const imgSet = page<0?numberOfPages+page%imgInSet:page)%numOfPages;
$img1.attr("src", characterList[imgSet*imgInSet].image);
$img2.attr("src", characterList[imgSet*imgInSet+1].image);
$img3.attr("src", characterList[imgSet*imgInSet+2].image);
}
$nextBtn.on("click", ()=>reloadImages(++page));
$prevBtn.on("click", ()=>reloadImages(--page);)
This answer is for more "universal solution" where you won't need to change too much with each new img that you would need to be cycled through.

Get duration of a collection of audios

I am creating an audio player from an audio collection on an HTML page:
...
<div class="tr track" id="01">
<div class="td">
<button class="play" onclick="Play(this)">▶</button>
<button class="play" onclick="Pause(this)">❚❚</button>
<span class="title">A Corda da Liberdade</span>
</div>
<div class="td">
<audio preload="metadata" src="discografia/le-gauche-gnosis/01-a-corda-da-liberdade.ogg"></audio>
</div>
<div class="td">
<span class="duracao"></span>
</div>
</div>
...
I want the <span class="duracao"></span> element to show the duration of the audio it is related to:
// This function format the audio duration number in the way I want to present
function secToStr(sec_num) {
sec_num = Math.floor( sec_num );
var horas = Math.floor(sec_num / 3600);
var minutos = Math.floor((sec_num - (horas * 3600)) / 60);
var segundos = sec_num - (horas * 3600) - (minutos * 60);
if (horas < 10) {horas = "0"+horas;}
if (minutos < 10) {minutos = "0"+minutos;}
if (segundos < 10) {segundos = "0"+segundos;}
var tempo = minutos+':'+segundos;
return tempo;
}
var i;
var audios = document.getElementsByTagName('audio'); // get all audios elements of the player
for (i = 0; i < audios.length; i++) { // looping through audios
var audio = audios[i]; // get actual audio
var duracao = audio.parentNode.nextElementSibling.getElementsByClassName('duracao')[0] // get actual audio 'duration <span>'
audio.onloadedmetadata = function() {
duracao.innerHTML = secToStr(audio.duration);
}
}
The for loop is supposed to do the job but is just adding the duration of the last audio element to the last <span class="duracao"></span> element:
Any help?
The general approach with asynchronous loops would be to promisify the async action and then wait for a Promise.all(all_promises).
However, in this particular case, it might not be that easy:
Some browsers (Chrome to not tell their name) have a limit on the maximum number of parallel network requests a page can make for Media.
From there, you won't be able to get the duration of more than six different media at the same time...
So we need to load them one after the other.
The async/await syntax introduced in ES6 can help here:
const urls = [
'1cdwpm3gca9mlo0/kick.mp3',
'h8pvqqol3ovyle8/tom.mp3',
'agepbh2agnduknz/camera.mp3',
'should-break.mp3',
'/hjx4xlxyx39uzv7/18660_1464810669.mp3',
'kbgd2jm7ezk3u3x/hihat.mp3',
'h2j6vm17r07jf03/snare.mp3'
]
.map(url => 'https://dl.dropboxusercontent.com/s/' + url);
getAllDurations(urls)
.then(console.log)
.catch(console.error);
async function getAllDurations(urls) {
const loader = generateMediaLoader();
let total = 0;
for(let i = 0; i < urls.length; i++) {
total += await loader.getDuration(urls[i]);
}
return total;
}
// use a single MediaElement to load our media
// this is a bit verbose but can be reused for other purposes where you need a preloaded MediaElement
function generateMediaLoader() {
const elem = new Audio();
let active = false; // so we wait for previous requests
return {
getDuration,
load
};
// returns the duration of the media at url or 0
function getDuration(url) {
return load(url)
.then((res) => res && res.duration || 0)
.catch((_) => 0);
}
// return the MediaElement when the metadata has loaded
function load(url) {
if(active) {
return active.then((_) => load(url));
}
return (active = new Promise((res, rej) => {
elem.onloadedmetadata = e => {
active = false;
res(elem);
};
elem.onerror = e => {
active = false;
rej();
};
elem.src = url;
}));
}
}
But it's also very possible to make it ES5 style.
var urls = [
'1cdwpm3gca9mlo0/kick.mp3',
'h8pvqqol3ovyle8/tom.mp3',
'agepbh2agnduknz/camera.mp3',
'should-break.mp3',
'/hjx4xlxyx39uzv7/18660_1464810669.mp3',
'kbgd2jm7ezk3u3x/hihat.mp3',
'h2j6vm17r07jf03/snare.mp3'
]
.map(function(url) {
return 'https://dl.dropboxusercontent.com/s/' + url;
});
getAllDurations(urls, console.log);
function getAllDurations(urls, callback) {
var loader = new Audio();
var loaded = 0;
var total = 0;
loader.onloadedmetadata = function(e) {
total += loader.duration;
loadNext();
};
loader.onerror = loadNext;
loadNext();
function loadNext() {
if(loaded >= urls.length) {
return callback(total);
}
loader.src = urls[loaded++];
}
}
This is an excellent place to learn about, and then use Array.reduce() instead of a for-loop.
The concept of reduce is that you start with some starting value (which can be anything, not just a number), and you then walk through the array in a way that, at ever step, lets you run some code to update that value. So:
const total = [1,2,3,4,5].reduce( (sofar, value) => sofar + value, 0)
will run through the array, with start value 0, and at every step it runs (sofar, value) => sofar + value, where the first argument is always "whatever the original start value is at this point". This function assumes that value is a number (or a string) and adds (or concatenates) it to the start value. So at each step we get:
start = 0
first element: add 1 to this value: 0 + 1 = 1
second element: add 2 to this value: 1 + 2 = 3
third element: add 3 to this value: 3 + 3 = 6
fourth element: add 4 to this value: 6 + 4 = 10
fifth element: add 5 to this value: 10 + 5 = 15
We can apply the same to your audio elements: once they're all done loading in, you can tally their total duration with a single reduce call:
const total = audios.reduce((sofar, audioElement) => {
sofar += audioElement.duration;
}, 0); // this "0" is the starting value for the reduce-function's first argument
console.log(`Total number of seconds of play: ${total}`);
And then you can convert total into whatever format you need.
Alternatively, you can keep a global tally, but making each audo element update the total length themselves, simply by finishing loading:
let total = 0;
function updateTotal(increment) {
total += increment;
// and then update whatever HTML element shows that value on the page,
// in whatever format you need.
}
document.querySelectorAll('audio').forEach(element => {
element.onload = (evt) => {
updateTotal(element.duration);
})
});

Page freezes when 3 second pause is inside a loop

When i loop through an array in javascript i have it pause for 3 seconds after each item. It does this successfully, but it freezes the webpage until the array completes.
function launchTutorial() {
HideFloatingMenu(); //freezes on page and it doesn't when i comment out the subsequent array loop
//highlightElement("diLeftColumn");
//the classes of each element to highlight in the tutorial
var tutorialClasses = [
"diLeftColumn",
"diMiddleColumn",
"diRightColumn"
];
var threeSec = new Date().getTime() + 3000;
for (var i = 0; i < tutorialClasses.length; i++) {
//$.each(tutorialClasses, function (key, value) {
if (i != 0) {
var now = new Date().getTime();
if (now >= threeSec) {
highlightElement(tutorialClasses[i]);
threeSec = new Date().getTime() + 3000;
}
else {
i = i - 1; //go back to this item if it hasn't been 3 seconds
}
}
else {
highlightElement(tutorialClasses[i]);
threeSec = new Date().getTime() + 3000;
}
}
}
I have tried setTimeout(), setInterval(0, delay(), 2 different custom sleep functions, and a while loop. none of them worked.
Use this. In javascript, when you do a while loop that takes time x, the whole page freezes for that time x. So using a while loop is no option. But you can use the function setTimeout like this. This will run the printNextElement function every 10 seconds (in my example).
At the console.log place, do your logic. And change the 10000 to your time.
const ar = ['Hello', 'World'];
let index = 0;
const printNextElement = () => {
console.log(ar[index]);
index += 1;
if(ar.length === index) {
return;
}
window.setTimeout(printNextElement, 10000);
};
printNextElement();

Categories

Resources