this is a jquery/ javascript problem.
So I have an array that contains button numbers and outputs those buttons onto a button which will result in the button being clicked. The problem is that all the buttons get clicked at once if I run the loop. Instead I want it to output the numbers with a delay so that the buttons will be pressed after a 1 second delay.
Here is the link to the Simon game project:
https://codepen.io/roger1891/full/vmYqwx/
The problem is visible after following the 1st button. After that, the computer will press the next 2 buttons at the same time instead of pressing them separately after a 1 sec delay.
The problem is located in this loop which is located in the myloop() function:
sequenceArray.forEach(function(item, index, array){
//click button by getting the last index of the array
//$("#btn-"+array[array.length-1]).click();
$("#btn-"+array[index]).click();
console.log(array);
});
This is the full code:
//associating buttons to sound
var soundButton = function(buttonId, soundId) {
$("#" + buttonId).click(function(){
$("#" + soundId).get(0).play();
$("#" + buttonId).css("opacity", "0.5");
setTimeout( function(){
$("#" + buttonId).css("opacity", "1");
},500);
if(currentPlayerTurn == "human") {
var $input = $(this);
var attrString = $input.attr("id");
//only get number from id attribute
var strNum = attrString.replace( /^\D+/g, '');
//convert theNumber from string to number
var theNum = parseInt(strNum);
playerArray.push(theNum);
console.log(theNum);
console.log(playerArray);
}
});
};
function myLoop() {
setInterval( function(){
if(gameWon == false && onGoingGame == true && currentPlayerTurn == "computer" && score < 5) {
//increment score
score++;
//append to score id
$("#score").empty().append(score);
//create random number
randomNumber = Math.floor((Math.random()*4) + 1);
//push random number into array
sequenceArray.push(randomNumber);
//loop through array
sequenceArray.forEach(function(item, index, array){
//click button by getting the last index of the array
//$("#btn-"+array[array.length-1]).click();
$("#btn-"+array[index]).click();
console.log(array);
});
currentRound = sequenceArray.length;
onGoingGame = false;
currentPlayerTurn = "human";
}
if(currentPlayerTurn == "human") {
var is_same = playerArray.length == sequenceArray.length && playerArray.every(function(element, index) {
return element === sequenceArray[index];
});
is_same;
console.log(is_same);
if(is_same == true) {
playerArray = [];
onGoingGame = true;
currentPlayerTurn = "computer";
}
}
},1000);
}
myLoop();
Thank you in advance for your help.
Since you want to trigger the buttons one by one, your setTimeout() should be inside the loop. Be mindful of the index, since this is async.
sequenceArray.forEach(function(item, index, array) {
// set a timeout so each second one button gets clicked
setTimeout( (function( index ) {
// use a closure here so that our index values stay correct.
return function() {
$("#btn-"+array[index]).click();
};
}( index )), (1000 * index) );
});
edit: replaced the fixed 1000ms delay to delay * index
You need to console.log(item) instead of full array in forEach loop
Related
So, I've been trying to figure out JS, and what better way to do so than to make a small project. It's a small trivia game, and it has a question timer I've made using setInterval. Unfortunately, after answering multiple questions, the interval's behaviour gets very weird - it runs the command twice every time. I guess it's my faulty implementation of buttonclicks?
By the way, if my code is awful I am sorry, I've been desperate to fix the issue and messed with it a lot.
function startGame(){
if (clicked === true){
return;
}
else{
$("#textPresented").html("Which anthem is this?");
$("#button").css("display", "none");
currentCountry = getRndInteger(0,8);
console.log(currentCountry);
var generatedURL = anthemnflags[currentCountry];
console.log(generatedURL);
audios.setAttribute("src", generatedURL);
audios.play();
$("#button").html("I know!");
$("#button").css("display", "block");
$("#button").click(function () {
continueManager();
});
y=10;
console.log("cleared y" + y);
x = setInterval(function(){
y = y - 1;
console.log("Counting down..." + y)
}, 1000);
console.log("INTERVAL SET");
}
}
Here is the console output:
cleared y10 flaggame.js:59:17
INTERVAL SET flaggame.js:64:17
AbortError: The fetching process for the media resource was aborted by the user agent at the user's request. flaggame.js:49
Counting down...9 flaggame.js:62:20 ---- THESE TWO ARE BEING PRINTED AT THE SAME TIME
Counting down...8 flaggame.js:62:20 ---- THESE TWO ARE BEING PRINTED AT THE SAME TIME
Counting down...7 flaggame.js:62:20
Counting down...6 flaggame.js:62:20
Counting down...5 flaggame.js:62:20
Counting down...4 flaggame.js:62:20
Counting down...3 flaggame.js:62:20
Counting down...2 flaggame.js:62:20
Counting down...1 flaggame.js:62:20
Counting down...0
THE REST OF MY CODE:
function middleGame(){
$("#button").css("display", "none");
var n = document.querySelectorAll(".flagc").length;
correctIMG = getRndInteger(0,n-1);
showFlags();
var taken = new Array();
for (var i = 0; i < n; ++i){
if (i === correctIMG){
images[i].attr("src", "res/" + flagsfiles[currentCountry]);
taken[currentCountry] = true;
}
else{
var randomFlag = getRndInteger(0, flagsfiles.length);
if (randomFlag !== currentCountry && taken[randomFlag] !== true){
images[i].attr("src", "res/" + flagsfiles[randomFlag]);
taken[randomFlag] = true;
}
}
}
$(".flagc").click(function(){
clickregister(this);
});
}
function continueManager(){
if (!clicked){
audios.pause()
clearInterval(x);
x = 0;
clicked = true;
middleGame();
return;
}
}
function clickregister(buttonClicked){
if ($(buttonClicked).attr("id") != correctIMG){
points = points - 1;
flagARR[$(buttonClicked).attr("id")].css("display", "none");
console.log("INCORRECT");
}
else{
if (y >= 0) {
var addedPoints = 1 + y;
points = points + addedPoints;
$("#points").html(points);
}
else{
points = points + 1;
}
hideFlags();
clicked = false;
startGame();
}
}
$(function(){
hideFlags();
$("#textPresented").html("When you're ready, click the button below!");
$("#button").html("I am ready!");
$("#button").click(function () {
if (!gameStarted){
gameStarted = true;
alert("STARTING GAME");
startGame();
}
});
});
Basically this is how it works:
When the "I am ready" button is clicked, startGame() is called. It plays a random tune and counts down, until the player hits the "I know" button. That button SHOULD stop the interval and start the middleGame() function, which shows 4 images, generates a random correct image and awaits input, checks if it's true, then launches startGame() again.
The first and second cycles are perfect - after the third one things get messy.
I also noticed that the "INCORRECT" log gets printed twice, why?
EDIT: here is the minimized code that has the same issue:
var x;
var gameStarted = false;
var y;
var clicked;
$(function(){
$("#button").click(function () {
if (!gameStarted){
gameStarted = true;
startGame();
}
});
});
function startGame(){
console.log("startgame()");
if (clicked === true){
return;
}
else{
console.log("!true");
$("#button").css("display", "block");
$("#button").click(function () {
continueManager();
});
y=10;
x = setInterval(function(){
y = y - 1;
console.log(y);
}, 1000);
}
}
function continueManager(){
if (!clicked){
clearInterval(x);
x = 0;
clicked = true;
middleGame();
return;
}
}
function middleGame(){
$("#button").css("display", "none");
var taken = new Array();
$(".flagc").click(function(){
clickregister(this);
});
}
function clickregister(buttonClicked){
console.log("clickgregister");
//Irrelevant code that checks the answers
clicked = false;
startGame();
}
EDIT2: It appears that my clickregister() function gets called twice, and that function then calls startGame() twice.
EDIT3: I have found the culprit! It's these lines of code:
$(".flagc").click(function(){
console.log("button" + $(this).attr("id") + "is clicked");
clickregister(this);
});
They get called twice, for the same button
I fixed it!
It turns out all I had to do was to add
$(".flagc").unbind('click');
Before the .click() function!
You need to clear the interval first then call it again. You can do that by creating a variable outside of the event listener scope and in the event listener check if the variable contains anything if yes then clear the interval of x. After clearing the interval you can reset it.
Something like this:
<button class="first" type="submit">Button</button>
const btn = document.querySelector('.first');
let x;
btn.addEventListener("click", () => {
x && clearInterval(x)
x = setInterval(() => console.log("yoo"), 500)
})
This is because if you don't clear the interval of x it will create a new one on every button press.
I am trying to save the amount of clicks on a button in a textbox with localStorage, but whenever I reload the page, all the data is cleared. Here is my code:
var i = 0; // i is the number of clicks. the number of clicks is set to 0
when page is
// loaded.
function countClick() { // The next 3 lines of code are executed when the
Kill a cat // button is clicked
i++;
alert("You have clicked " + i + " cats so far"); // Make message box
localStorage.setItem("count", "i")
document.getElementById("count").value = i ; // Edits the 'clicks clicked'
text box
// Saves number of clicks, so when you close // or open the page, your
progress still exists.
}
function clear() {
document.getElementById("count").value = 0 ;
}
window.onload = function(){
var val = localStorage.getItem('value'); // or localStorage.value
if(val === null)
val = "i";
document.getElementById("myData").value = val;
}
window.onbeforeunload = function(){
localStorage.setItem('value', document.getElementById("myData").value);
localStorage.value = document.getElementById("myData").value
}
Please help.
Thanks to all the other examples from the other people, used in here.
In this function, you reset your localStorage item before page load...
window.onbeforeunload = function(){
localStorage.setItem('value', document.getElementById("myData").value);
localStorage.value = document.getElementById("myData").value
}
It's normal there is no data when you refresh.
Just try somethink more simple like this without other function :
var i = 0;
function countClick() {
i++;
localStorage.setItem("count", i)
document.getElementById("count").value = i ;
}
window.onload = function(){
var val = localStorage.getItem('count');
document.getElementById("count").value = val;
}
I have svg text fill which is dynamic. once the user click undo button it must undo the svg and textarea and once the user click redo button it should redo the svg text fill and textarea. i have completed with the textarea undo and redo functionality but not with svg element how to achieve this through jquery
$("#enter-text").on("keypress",function(){
$("#svg_id").html($(this).val());
})
//this value is kept small for testing purposes, you'd probably want to use sth. between 50 and 200
const stackSize = 10;
//left and right define the first and last "index" you can actually navigate to, a frame with maximum stackSize-1 items between them.
//These values are continually growing as you push new states to the stack, so that the index has to be clamped to the actual index in stack by %stackSize.
var stack = Array(stackSize), left=0, right=0, index = 0, timeout;
//push the first state to the stack, usually an empty string, but not necessarily
stack[0] = $("#enter-text").val();
updateButtons();
$("#enter-text").on("keydown keyup change", detachedUpdateText);
$("#undo").on("click", undo);
$("#redo").on("click", redo);
//detach update
function detachedUpdateText(){
clearTimeout(timeout);
timeout = setTimeout(updateText, 500);
}
function updateButtons(){
//disable buttons if the index reaches the respective border of the frame
//write the amount of steps availabe in each direction into the data- count attribute, to be processed by css
$("#undo")
.prop("disabled", index === left)
.attr("data-count", index-left);
$("#redo")
.prop("disabled", index === right)
.attr("data-count", right-index);
//show status
$("#stat").html(JSON.stringify({
left,
right,
index,
"index in stack": index % stackSize,
stack
}, null, 4))
}
function updateText(){
var val = $("#enter-text").val().trimRight();
//skip if nothing really changed
if(val === stack[index % stackSize]) return;
//add value
stack[++index % stackSize] = val;
//clean the undo-part of the stack
while(right > index)
stack[right-- % stackSize] = null;
//update boundaries
right = index;
left = Math.max(left, right+1-stackSize);
updateButtons();
}
function undo(){
if(index > left){
$("#enter-text").val(stack[--index % stackSize]);
updateButtons();
}
}
function redo(){
if(index < right){
$("#enter-text").val(stack[++index % stackSize]);
updateButtons();
}
}
https://jsfiddle.net/yvp3jedr/6/
$("#enter-text").on("keypress", function () {
$("#svg_id").text($(this).val());
})
//this value is kept small for testing purposes, you'd probably want to use sth. between 50 and 200
const stackSize = 10;
//left and right define the first and last "index" you can actually navigate to, a frame with maximum stackSize-1 items between them.
//These values are continually growing as you push new states to the stack, so that the index has to be clamped to the actual index in stack by %stackSize.
var stack = Array(stackSize), left = 0, right = 0, index = 0, timeout;
//push the first state to the stack, usually an empty string, but not necessarily
stack[0] = $("#enter-text").val();
updateButtons();
$("#enter-text").on("keydown keyup change", detachedUpdateText);
$("#undo").on("click", undo);
$("#redo").on("click", redo);
//detach update
function detachedUpdateText() {
clearTimeout(timeout);
timeout = setTimeout(updateText, 500);
}
function updateButtons() {
//disable buttons if the index reaches the respective border of the frame
//write the amount of steps availabe in each direction into the data-count attribute, to be processed by css
$("#undo")
.prop("disabled", index === left)
.attr("data-count", index - left);
$("#redo")
.prop("disabled", index === right)
.attr("data-count", right - index);
//show status
$("#stat").html(JSON.stringify({
left,
right,
index,
"index in stack": index % stackSize,
stack
}, null, 4))
}
function updateText() {
var val = $("#enter-text").val().trimRight();
//skip if nothing really changed
if (val === stack[index % stackSize]) return;
//add value
stack[++index % stackSize] = val;
//clean the undo-part of the stack
while (right > index)
stack[right-- % stackSize] = null;
//update boundaries
right = index;
left = Math.max(left, right + 1 - stackSize);
updateButtons();
}
function undo() {
if (index > left) {
var text = stack[--index % stackSize];
$("#enter-text").val(text);
$("#svg_id").text(text);
updateButtons();
}
}
function redo() {
if (index < right) {
var text = stack[++index % stackSize];
$("#enter-text").val(text);
$("#svg_id").text(text);
updateButtons();
}
}
I have a full width slideshow. So I have a few problems with it.
One is that clearTimeout won't work. If I call the function by a click, it should clear the Timeout.
Does someone know why this won't work? Please explain and show where exactly the problem is.
Thank you and sorry for my bad English.
var index = 0;
var slideSpeed = 1000;
function mainSlider(menuLink){
clearTimeout(slide);
if(menuLink !== false){
alert('You call this function by a click event.');
clearTimeout(slide);
}
var sliderIndex = $('.main_slider_content').length - 1;
$('.main_slider_content').hide();
index++;
if(index > sliderIndex){
index = 0;
}
$('.main_slider_content:eq(' + index + ')').show();
var slide = setTimeout(function(){mainSlider(false)}, slideSpeed);
setTimeout(countContentImg(index), slideSpeed);
}
$(document).on('click', '.main_slider_menu_link',function(){
var linkIndex = $(this).index();
mainSlider(linkIndex);
});
function countContentImg(index){
$('#main_slider_selected_img').html('');
var sliderIndex = $('.main_slider_content').length;
for(var i = 0; i < sliderIndex; i++) {
if(i === index)
$('#main_slider_selected_img').append('<li class="main_slider_menu_link main_slider_menu_link_slected"></li>');
else
$('#main_slider_selected_img').append('<li class="main_slider_menu_link"></li>');
}
}
$(document).ready(function(){
countContentImg(index);
mainSlider(false);
});
This looks like a scope issue. You're trying to clear a timeout from a variable that doesn't contain a timeout reference yet. Each call to mainSlider creates a new, locally scoped timeout reference, long after you've tried to clear it.
function mainSlider(menuLink){
clearTimeout(slide); // Clearing a timeout that doesn't exist yet
if(menuLink !== false){
alert('You call this function by a click event.');
clearTimeout(slide); // Clearing a timeout that doesn't exist yet
}
var sliderIndex = $('.main_slider_content').length - 1;
$('.main_slider_content').hide();
index++;
if(index > sliderIndex){
index = 0;
}
$('.main_slider_content:eq(' + index + ')').show();
// Now the timeout exists, but only in the scope of this current call
var slide = setTimeout(function(){mainSlider(false)}, slideSpeed);
setTimeout(countContentImg(index), slideSpeed);
}
Change it to:
var slide;
function mainSlider(menuLink){
clearTimeout(slide);
if(menuLink !== false){
alert('You call this function by a click event.');
clearTimeout(slide);
}
var sliderIndex = $('.main_slider_content').length - 1;
$('.main_slider_content').hide();
index++;
if(index > sliderIndex){
index = 0;
}
$('.main_slider_content:eq(' + index + ')').show();
// Remove var
slide = setTimeout(function(){mainSlider(false)}, slideSpeed);
setTimeout(countContentImg(index), slideSpeed);
}
I am building simple "Spot the difference" game in jQuery/HTML. There are 5 rounds/stages, each with different pictures and user needs to go through all of them starting from round 1.
I am having this issue with increment firing twice when I am in 2nd round, and then triple times when I am in 3rd round and so on. This cause points to jump up double/triple/... instead of just jump up by 1.
The code is on baby level. I did not make any stuff to refactor it and improve.
I think I don't need to provide HTML for this, as simply looking at logic in JS file should be enough.
For those who prefer pastebin version is here (http://pastebin.com/ACqafZ5G). Full code:
(function(){
var round = 1;
var points = 0;
var pointsTotal = 0;
var pointsDisplay = $(".js-calc");
var pointsTotalDisplay = $(".js-calc-total");
var counterDisplay = $(".js-counter");
var entryPage = $(".entry-page");
var mainMenu = $(".main-menu");
var submitNow = $(".js-now");
var submitResultsFinalBtn = $(".js-submit-results-final");
// rounds-categories
var allRounds = $(".round");
var divRound1 = $(".round1"),
divRound2 = $(".round2"),
divRound3 = $(".round3"),
divRound4 = $(".round4"),
divRound5 = $(".round5");
var allPic = $(".js-pic");
var pic1 = $(".js-pic1"),
pic2 = $(".js-pic2"),
pic3 = $(".js-pic3"),
pic4 = $(".js-pic4"),
picFinish = $(".js-finish");
// on the beginning hide all and leave only entry page
mainMenu.hide();
allRounds.hide();
submitResultsFinalBtn.hide();
// countdown (SEE THE FUNCTION ON THE END)
var myCounter = new Countdown({
seconds: 60, // number of seconds to count down
onUpdateStatus: function(sec){
counterDisplay.html(sec); // display seconds in html
}, // callback for each second
onCounterEnd: function(){
console.log('TIME IS OVER!');
// THIS SHOULD NOT BE HERE, I WOULD PREFER TO MOVE IT SOMEWHERE TO GAME ITSELF
pointsTotalDisplay.html(pointsTotal);
round++; // update to next round
allRounds.hide(); // hide window
mainMenu.show(); // show back again main menu
} // final action
});
var initiateRound = $(".js-initiate");
initiateRound.on("click", function(){ // START GAME
console.log("ROUND " + round + " INITIATED");
points = 0; // reset the points for this round to 0 - not sure this is the way to do it...
console.log(points + " points for this round, " + pointsTotal + " in TOTAL"); // say how many points so far
entryPage.hide();
mainMenu.hide();
allPic.hide();
if( round === 1){
divRound1.show();
pic1.show();
}else if( round === 2){
divRound2.show();
pic2.show();
}else if( round === 3){
divRound3.show();
pic3.show();
}else if( round === 4){
divRound4.show();
pic4.show();
}else if( round === 5){
divRound5.show();
picFinish.show();
initiateRound.hide();
submitNow.hide();
submitResultsFinalBtn.show();
}
counterDisplay.html("60"); //display 60sec on the beginning
myCounter.start(); // and start play time (countdown)
// pointsDisplay.html(points); // display in HTML amount of points for particular round
// if user start collecting points, add them
var mapImage = $('.transparent AREA');
mapImage.each(function(index) {
// When clicked, reveal red circle with missing element
$(this).one("click", function(e) { // YOU CAN CLICK ONLY ONCE!! Using .one() to prevent multiple clicks and eventually scoring more points
e.stopPropagation();
console.log("FIRED");
var theDifference = '#'+$(this).attr('id')+'-diff';
$(theDifference).css('display', 'inline');
if ($(theDifference).data('clicked', true)){ // found circle
// points++;
points += 1;
pointsTotal++;
console.log(points + " points for this round, " + pointsTotal + " in TOTAL");
pointsDisplay.html(points); // display in html amount of points
}
if (points === 6){ // if all points collected (max 6) for this round
myCounter.stop(); // stop countdown
console.log("time stopped, you found all");
setTimeout(function(){ // give him 2sec delay to see last circle marked
allRounds.hide(); // hide window
mainMenu.show(); // show back again main menu
console.log("round " + round + " is finished");
round++; // update to next round
console.log("round " + round + " is activated");
pointsTotalDisplay.html(pointsTotal); // display in HTML total amount of pints
}, 2000);
};
});
});
});
})();
function Countdown(options) {
var timer,
instance = this,
seconds = options.seconds || 10,
updateStatus = options.onUpdateStatus || function () {},
counterEnd = options.onCounterEnd || function () {};
function decrementCounter() {
updateStatus(seconds);
if (seconds === 0) {
counterEnd();
instance.stop();
}
seconds--;
}
this.start = function () {
clearInterval(timer);
timer = 0;
seconds = options.seconds;
timer = setInterval(decrementCounter, 1000);
};
this.stop = function () {
clearInterval(timer);
};
}
Are you sure you're not stacking the $('.transparent AREA') from a round to another ?
This would explain why you score multiple times:
var mapImage = $('.transparent AREA');
mapImage.each(function(index) {
// ...
points++;
// ...
});
Solved!
mapImage.each should be outside of initiateRound