Swap DOM Elements with Timeout - javascript

I currently try to get into JavaScript a bit. (vanilla)
So at the moment what i want to do is swap different element nodes.
As an example imagine a list with different entries, which later shall be swapped.
For this for element.length times random elements should be swapped. After each of this swaps should be a timeout.
The problem which i now encounter is that the list only updates after the shuffle function finishes.
It is intended to swap a pair, wait for 1 second and then do the next swap.
function shuffle(element){
disableButtons();
let clicked_elmnt = document.getElementsByTagName(element);
console.log(clicked_elmnt);
if(clicked_elmnt.length !== 1 && clicked_elmnt !== 0){
for (let i = 0; i<clicked_elmnt.length;++i){
setTimeout(function(){doIt(element,randomNumber(clicked_elmnt.length),randomNumber(clicked_elmnt.length))}, 1000)
}
}
enableButtons();
}
This code should check if there is a list in a list and then roll new numbers if it tries to swap a parent with its child. (Seems to be working)
function doIt(element,first,second){
let clicked_elmnt = document.getElementsByTagName(element);
if(clicked_elmnt[first].children.length > 0){
if(clicked_elmnt[first].firstElementChild.isSameNode(clicked_elmnt[second].parentNode)){
doIt(element,randomNumber(clicked_elmnt.length),randomNumber(clicked_elmnt.length));
}else{
if(clicked_elmnt[second].children.length > 0){
if(clicked_elmnt[second].firstElementChild.isSameNode(clicked_elmnt[first].parentNode)){
doIt(element,randomNumber(clicked_elmnt.length),randomNumber(clicked_elmnt.length));
}else{
doSwap(clicked_elmnt[first],clicked_elmnt[second]);
}
}else{
doSwap(clicked_elmnt[first],clicked_elmnt[second]);
}
}
}else{
if(clicked_elmnt[second].children.length > 0){
if(clicked_elmnt[second].firstElementChild.isSameNode(clicked_elmnt[first].parentNode)){
doIt(element,randomNumber(clicked_elmnt.length),randomNumber(clicked_elmnt.length));
}else{
doSwap(clicked_elmnt[first],clicked_elmnt[second]);
}
}else{
doSwap(clicked_elmnt[first],clicked_elmnt[second]);
}
}
}
And the swap then happens in the next function. Here they are swapped.
function doSwap(n1, n2){
console.log("swap");
const afterN2 = n2.nextElementSibling;
const parent = n2.parentNode;
if (n1 === afterN2) {
parent.insertBefore(n1, n2);
} else {
n1.replaceWith(n2);
parent.insertBefore(n1, afterN2);
}
}
Hope someone can help me here without that much weird stuff.
Cheers!

My understanding is you wish that the doIt calls, the callbacks of setTimeout to be 1 second apart from each other. The way setTimeout works is the callbacks are queued to be executed x milliseconds after setTimeout is called. If you want one function to be called 1 second after the previous function finishes, you could call another setTimeout from inside the callback.
Here's your example adjusted as such - the doItLater function includes a setTimeout call, and will recursively call itself clicked_elmnt.length times. You may be able to set it up more cleanly for a real scenario, but this fits without deviating too much from your example.
function shuffle(element) {
disableButtons();
let clicked_elmnt = document.getElementsByTagName(element);
if (clicked_elmnt.length !== 1 && clicked_elmnt !== 0) {
const doItLater = function (i) {
setTimeout(() => {
doIt(element, randomNumber(clicked_elmnt.length), randomNumber(clicked_elmnt.length));
if (i + 1 < clicked_elmnt.length) {
doItLater(i + 1);
}
}, 1000);
}
doItLater(0);
}
enableButtons();
}
If you want to only call enableButtons after the last doIt call finishes, you could move that call to the else clause of if (i + 1 < clicked_elmnt.length).

Related

Javascript: Trying to refactor so many IF statements in order to display pictures continuously

I am trying to display a slideshow of images from an array using a promise. On load, I want the page to display the first image, then the next, and so on. When it's at the last picture index, I want the first picture to display and the code to keep repeating.
The class '.show-image' is visibility: visible. I have the first picture index classed as '.show-image', but the rest are hidden.
Is there a cleaner way of writing this without having so many IF statements?
let pictures= document.querySelectorAll(".moving-pics img");
async function changePicture() {
for (let x=0; x < pictures.length; x++) {
await new Promise(resolve => {
setTimeout(() => {
if (!pictures[x - 1]) {
pictures[pictures.length - 1].classList.remove('show-image');
}
if (pictures[x - 1] && pictures[x - 1].classList.contains('show-image')) {
pictures[x - 1].classList.remove('show-image');
}
pictures[x].classList.add('show-image');
if (!pictures[x + 1]) {
resolve();
changePicture();
}
resolve()
}, 3000)
});
}
}
If you keep the index being iterated over in a persistent outer variable, you can use it to identify the last element with the show-image. Use optional chaining so the first iteration doesn't throw. To move on to the next image, use modulo to keep things concise.
I'd also just use setTimeout, if you have an individual call of changePicture change a single picture, I don't think there's any need for the Promise anymore:
let i = -1;
function changePicture() {
pictures[i]?.classList.remove('show-image');
i = (i + 1) % pictures.length;
pictures[i].classList.add('show-image');
setTimeout(changePicture, 3000);
}

Prevent a function to fire more than 3 times

I'm Using the code below to let my function A only be triggered 3 times.
as I'm new to Javascript I think maybe you guys could show me a better way.
var num = 0;
if(num<4){
function A() {
num++
}
}
I'd put the num check inside the function, in case you want to call it anywhere else it will check your num record when you call it instead of having it automatically run 3 times when you start your program.
var num = 0;
function A() {
if(num<4){
//perform whatever you want your func to do
num++;
} else {
console.log("You performed this function 3 times already");
}
}
This depends highly on what you want to achieve, but one way is using recursion:
function foo(param1, param2, count = 3) {
if (count > 0) {
// ... some code ...
return foo(param1, param2, count-1)
}
return null; // just as example and check for the null later
}

The output order is different if setTimeout is present

The code to return the tree structure to console.log with a recursive function as shown below. I added setTimeout because I need some delays during the code processing, but when I add it, the order of processing is output in a completely different format. Also, the delay time does not seem to be constant.
The intended value is
If there is a child in the node after the first category title is searched, the second category title is searched by recursive, and the children value is inquired again, and then the third .. .. If setTimeout is added, The whole will be sown first, then the second whole, then the third whole, and so on.
Why is this happening?
var time = 0;
function searchTree(v, t){
$(v).each(function(i,k){
setTimeout(function(){
if (t == 'clone'){
console.log(k.sCategoryTitle);
if (k.children){
searchTree(k.children,'clone');
}
}
}, time = time + 100);
});
}
sample code : http://jsfiddle.net/uahg5qd9/3/
you have make your function synchronous.
try below code, if it will help you. here i have removed loop and put another synchronous function to make it synchronous.
var time = 100;
function searchTree(v, t){
var i=0;
function loop(){
if(i<v.length){
let k = v[i];
if (t == 'clone'){
console.log(k.sCategoryTitle);
if (k.children){
setTimeout(function(){
time = time + 100;
searchTree(k.children,'clone');
},time);
}else{
i++;
loop();
}
}else{
i++;
loop();
}
}else{
i++;
loop();
}
}
loop();
}

Break setTimeout function inside foreach loop

I am trying to break the setTimeout function which is started on page load. So what I am doing here is, If I click on the button then I making flag value to true and setTimeout should break which isn't happening here.
This setTimeout function is inside the for each loop. Below is my code.
rData[0].dt.forEach(function(d, i) {
setTimeout(function() {
if(flag === "false"){
console.log(flag);
reserRadius(i); //here I am changing radius of circle
}else{
console.log(flag);
clearTimeout();
return;
}
}, i * 2000);
});
Instead of creating all timeouts in one go, only create them when needed. This way you don't have to clear any of them when you have determined to stop:
(function repeat(list, i) {
if (i >= list.length) return; // nothing (more) to do
var d = list[i]; // do you need d at all??
setTimeout(function() {
if(flag === "false"){
console.log(flag);
reserRadius(i); //here I am changing radius of circle
repeat(list, i+1); // schedule next timeout only now.
}else{
console.log(flag);
// Don't schedule next timeout. This breaks the "loop".
}
}, 2000); // trigger 2 seconds from now. Note: no multiplying anymore.
})(rData[0].dt, 0); // pass initial values: the array and index.
In your version of the code, you would have to keep the id values returned by all setTimeout calls, and then pass them all (or at the least the remaining ones) to clearTimeout, one by one. This would make your code quite cumbersome. I think the above is a more efficient approach.
setTimeout cannot be stopped from its callback itself. setTimeout
returns a timeoutId which can be passed to clearTimeout which in turn will
stop that particualr timer.
One way to stop all such timers is to create an array of timeoutIds and make changes as following.
var timerIds = [];
rData[0].dt.forEach(function(d, i) {
timerIds.push(setTimeout(function(){
if(flag === "false"){
console.log(flag);
reserRadius(i); //here I am changing radius of circle
}
else{
console.log(flag);
}
}, i * 2000));
});
function stopTimeouts(){
timerIds.forEach(function(id){
clearTimeout(id);
}
}
function codeThatMightChangeFlag(callback) {
// do a bunch of stuff
if (condition happens to change flag value) {
// call the callback to notify other code
stopTimeouts();
}
}
Refer: Clear array of setTimeout's and Javascript - wait until flag=true

Waiting until for loop iteration to be completed visually before going onto next one

function start_map_one() {
for (var i = 0; i < 15; ++i) {
$.when(update_map()).then(evolve('W','W1',18,2,sea_limitations,20,350));
}
}
here, update_map() updates a div. However instead of the div updating visually 15 times in sequence, it appears to wait until all 15 iterations are complete and then display the finished div.
So im looking for this order ideally:
update_map() map information is used to update the div - user
sees the visual result... then...
evolve() map information
updated behind the scenes
update_map() map information is used to update the div - user
sees the visual result... then...
evolve() map information
updated behind the scenes
etc etc 15 times
Consider using recursion for this. Without knowing too much about what the code does, it could look something like this...
function start_map_one() {
start_map_one_helper(15);
}
function start_map_one_helper(count) {
if (count <= 0) {
return;
}
$.when(update_map()).then(function () {
evolve('W','W1',18,2,sea_limitations,20,350);
start_map_one_helper(count - 1);
});
}
Note that the then() callback needs to be wrapped in a function, otherwise it executes right away.
You may need to wrap the recursive call in a setTimeout to see the changes on the screen...
function start_map_one() {
start_map_one_helper(15);
}
function start_map_one_helper(count) {
if (count <= 0) {
return;
}
$.when(update_map()).then(function () {
evolve('W','W1',18,2,sea_limitations,20,350);
setTimeout(function() {
start_map_one_helper(count - 1);
});
});
}
It's because you don't give the browser enough time to change the display. Just add a delay in between the execution of each iteration. Try this solution
function start_map_one() {
for (var i = 0; i < 15; ++i) {
setTimeout(function() {
$.when(update_map()).then(evolve('W','W1',18,2,sea_limitations,20,350));
}, i * 100);
}
}
Change the 100 to a time suitable for you.
Also, you're not encapsulating the evolve function (Which means you're calling evolve during the loop instead of calling it through then. Simply wrap it in a function.

Categories

Resources