How can I wait for a click to be executed? - javascript

I am trying to figure out how I can update the playerValue for my Rock Scissor Paper game. I have a function that registers a button click and should then update the playerValue accordingly(1,2 or three depending on which button was clicked). The problem is, when I call the function, the playerValue remains 0 and I don't know what I need to change to fix that. playerValue itself is defined at the very beginning of my file. It starts out as 0.
Here is my JavaScript code(or at least the relevant part of it):
//register button click and:
function player_choose_value(){
//check which button has been clicked -> rock 1, scissor 2 or paper 3
btnRock.addEventListener("click", () =>{
playerValue = 1;
});
btnScissor.addEventListener("click", () =>{
playerValue = 2;
});
btnPaper.addEventListener("click", () =>{
playerValue = 3;
});
}
This here is where the playerValue is meant to be used. The playerValue is always 0. I think that is because the player_choose_value() function does not wait for the click event to happen. So the function is executed but the user does not have the chance to actually click a button, so it stays 0:
function play_round(){
let computerValue = computer_choose_value();
player_choose_value();//is always zero
console.log(playerValue);
won_tie_lost(computerValue, playerValue);
}
I was wondering how I could add a "wait for one of the three buttons to be clicked" functionality?

In your case, player_choose_value doesn't wait until the player has actually picked a value.
You could probably do this using async await/promises:
function player_choose_value(){
return new Promise(resolve => {
bntRock.onclick = () => resolve(1)
btnScissor.onclick = () => resolve(2)
btnPaper.onclick = () => resolve(3)
})
}
async function play_round(){
let computerValue = computer_choose_value();
const playerValue = await player_choose_value();
console.log(playerValue);
won_tie_lost(computerValue, playerValue);
}
;(async function main() {
while (true) { // Keep repeating the game
await play_round()
}
})()

Use promise.
function player_choose_value() {
return new Promise((resolve, reject) => {
//check which button has been clicked -> rock 1, scissor 2 or paper 3
document.querySelector('#btn1').addEventListener("click", () => {
resolve(1);
});
document.querySelector('#btn2').addEventListener("click", () => {
resolve(2);
});
document.querySelector('#btn3').addEventListener("click", () => {
resolve(3);
});
});
}
player_choose_value().then(
play_value => {
alert(play_value);
// won_tie_lost...
// other code 2...
}
);
// other code 1...
<button id='btn1'>btn1</button>
<button id='btn2'>btn2</button>
<button id='btn3'>btn3</button>
player_choose_value will called first. It returns a promise that is pending.
The engine will continue to execute other code 1 and the code in then(play_value => { block will not be executed until one of the resolves is called (the promise is fulfilled).
Here is another pattern. I think it suits your needs better.
document.querySelectorAll('button').forEach((button, index) => {
button.addEventListener('click', () => {
let playerValue = index;
let computerValue = computer_choose_value();
play_round(playerValue, computerValue);
})
});
function play_round(playerValue, computerValue) {
// Disable the buttons if needed
alert(`${playerValue} ${computerValue}`);
// won_tie_lost(computerValue, playerValue);
}
function computer_choose_value() {
return ~~(Math.random() * 3);
}
// start a round by enable the buttons
<button id='btn1'>btn1</button>
<button id='btn2'>btn2</button>
<button id='btn3'>btn3</button>

You're going at it in a totally wrong way.
You should have play_round() be called by the button clicks, otherwise you will add event listeners on the button every single round.
btnRock.addEventListener("click", play_round);
btnScissor.addEventListener("click", play_round);
btnPaper.addEventListener("click", play_round);
function play_round(event){
let playerValue = event.target.dataset.value;
let computerValue = computer_choose_value();
won_tie_lost(computerValue, playerValue);
}
function computer_choose_value() {
return Math.floor(Math.random() * 3) + 1;
}
function won_tie_lost(playerValue, computerValue) {
console.log(`player: ${playerValue}, computer: ${computerValue}`);
}
<button id="btnRock" data-value="1">Rock</button>
<button id="btnScissor" data-value="2">Scissor</button>
<button id="btnPaper" data-value="3">Paper</button>

Related

The parameter I'm passing through a async function is undefined in the function

I have an async function that on page load it runs a function that gets a JSON file and stores it in songResults. it then gets a random object from the JSON and takes some parameters from it and uses them to set the sources for some elements in my HTML file. I am having an issue with the callback for the event listener, the parameter I am passing through guessingGame() is undefined inside the guessingGame() function and I'm not sure why. any help would be muchly appreciated.
JS code
//A async function to run when the page loads to get the first song and album cover to display.
const onPageLoad = async () => {
let songResults = await getSongData();
let randomSong = songResults[Math.floor(Math.random() * songResults.length)];
audioSound.src = randomSong.song_path;
audioSound.load();
albumCover.src = randomSong.photo_path;
//An event listener for the submit button to run the guessing game function.
submitButton.addEventListener("click", guessingGame(randomSong.song_path))
};
//async function for when the button is clicked to check the answer in the input box to the json data.
const guessingGame = async (songPath) => {
//get the value of the input box
let input = document.getElementById("guessInputBox").value;
//check if the value of the input box matches the song path in the json data
if (input) {
if (input === songPath) {
alert('correct')
score++;
alert("that took " + score + " attempts");
score = 0;
changeSong();
} else {
alert('incorrect')
alert(songPath);
score++;
};
};
};
What a json file response looks like
{
"song_name": "Listen to Us",
"release_date": "1/05/2012",
"album": "Home Brew",
"photo_path": "/pictures/home-brew.jpg",
"song_path": "/songs/homebrewski-listenToUs.mp3"
}
so the new code updates it kind of it works the first time you guess but the second time you guess it says your incorrect then correct and keeps looping heaps really weird and lags out the browser.
const onPageLoad = async () => {
let songResults = await getSongData();
let randomSong = songResults[Math.floor(Math.random() * songResults.length)];
audioSound.src = randomSong.song_path;
audioSound.load();
albumCover.src = randomSong.photo_path;
//An event listener for the submit button to run the guessing game function.
submitButton.addEventListener("click", () => {
guessingGame(randomSong.song_name);
});
};
//async function for when the button is clicked to check the answer in the input box to the json data.
const guessingGame = async (songPath) => {
//get the value of the input box
let input = document.getElementById("guessInputBox").value;
//check if the value of the input box matches the song path in the json data
if (input) {
if (input.toLowerCase() === songPath.toLowerCase()) {
alert('correct')
score++;
alert("that took " + score + " attempts");
score = 0;
changeSong();
} else {
alert('incorrect')
alert(songPath);
score++;
};
};
};
//need to change this to an async function once figured out how to store data.
const changeSong = async () => {
let songResults = await getSongData();
let randomSong = songResults[Math.floor(Math.random() * songResults.length)];
audioSound.src = randomSong.song_path;
audioSound.load();
albumCover.src = randomSong.photo_path;
submitButton.addEventListener("click", () => {
guessingGame(randomSong.song_name);
});
};
Updated Answer
Thank you for posting more code. It looks like you may have mistakenly passed along the song_name instead of the song_path:
// From the onPageLoad and changeSong functions
guessingGame(randomSong.song_name);
// From the guessingGame function
input.toLowerCase() === songPath.toLowerCase()
Another thing to consider is your click-handler; you're adding one to the submitButton every time a song is loaded. As such, the first time you click the button, a single event handler is called. The second time you click, two are called. The third time, three, etc.
Your submit button behavior really only needs to be set once, and forgotten. It's job is to do one thing: see if the user's selection matches the currently-playing song. And, if the user got the correct answer, load another song:
// Minimal game state
let score = 0;
let songs = [];
let songIndex = -1;
// References to game elements
const submit = document.querySelector("#submit")
const selection = document.querySelector("options");
// Begin game functionality
async function onPageLoad () {
// Load meta data for all songs into `songs`
songs = await getSongData();
// TODO: Populate #options based on `songs` data
setRandomSong();
}
function setRandomSong () {
songIndex = Math.floor( Math.random() * songs.length );
audioSound.src = songs[ songIndex ].path;
albumCover.src = songs[ songIndex ].photo;
}
// This will be called whenever a song is changed
audioSound.addEventListener( "load", function audioLoaded () {
console.log( "Audio has loaded, game can be played." );
});
// We need only a single handler for our guess-button
submit.addEventListener( "click", function guess () {
// Get our values to compare
const guess = selection.value;
const answer = songs[ songIndex ].name;
// Increment the number of attempts
score += 1;
// Check lowercase values for equality
// If the answer is false, share number of attempts
if ( guess.toLowerCase() !== answer.toLowerCase() ) {
alert( `You have guessed ${ score } times.` );
return;
}
// If the answer was correct, reset game state
alert( `Correct! And after ${ score } guesses.` );
score = 0;
setRandomSong();
});
Note, this code is untested, but should clearly define a suitable approach to your problem. Please feel free to engage further within the comments as needed.
Original Answer
The addEventListener method expects a function as the second argument. Look closely at your code:
submitButton.addEventListener("click", guessingGame(randomSong.song_path))
Note that you're executing guessingGame, rather than referencing it. Instead, you'd want to provide another function:
submitButton.addEventListener("click", function () {
guessingGame( randomSong.song_path );
});
Now, when the submitButton is clicked, our anonymous function will be called, which in turn will pass randomSong.song_path to guessingGame.

How to cancel sending requests if new requests are incoming?

I have a section with files. Each file can be moved one position higher or lower with an arrow. Currently a request is sent to the database every time that the user moves a file, and new order updated. So if the user wants to move the bottom file to the top, clicks the 'up' arrow 10 times, 10 requests will be sent.
How can I send fewer requests than that?
My first idea was to create request with order numbers, wait 1 second, create second request and if they are the same (the user hasn't continued to change the order), then send it, if not, wait another second and compare again. But this doesn't seem to be a good thing to do. Are there any other ways?
$ctrl.saveFileOrder = function() {
var firstRequest = [];
for(var i = 0; 1 < $ctrl.fileArray.length; i++) {
firstRequest.push({$ctrl.fileArray[i].id, $ctrl.fileArray[i].orderNumber});
}
var secondRequest = [];
while(firstRequest != secondRequest) {
//put thread to sleep for 1 second here
for(var i = 0; 1 < $ctrl.fileArray.length; i++) {
secondRequest.push({$ctrl.fileArray.id[i], $ctrl.fileArray.orderNumber[i]});
}
}
//send the final request
}
You could disable the button until a response has been received from the server so that the user cannot click again before the DB has been updated.
const buttons = document.querySelectorAll('.js-move');
/**
* Simulation of a request with a 2 second duration
*/
const sendRequest = value => new Promise(resolve => {
console.log(`Updating to DB: ${value}`);
buttons.forEach(button => button.disabled = true);
setTimeout(() => {
console.log('Update done');
buttons.forEach(button => button.disabled = false);
resolve();
}, 2000);
});
buttons.forEach(button => {
button.addEventListener('click', event => {
const { value } = event.target;
sendRequest(value);
});
});
<button class="js-move" value="-1">Move up</button>
<button class="js-move" value="+1">Move down</button>
Alternatively you are looking for a throttle function which, like the name suggest, bottlenecks the amount of calls by a specified amount set by you. So only once every second could you make a new request.
const throttle = (callback, wait, immediate = false) => {
let timeout = null;
let initialCall = true;
return (...args) => {
const callNow = immediate && initialCall
const next = () => {
callback(...args);
timeout = null;
}
if (callNow) {
initialCall = false;
next();
}
if (!timeout) {
timeout = setTimeout(next, wait);
}
}
}
const buttons = document.querySelectorAll('.js-move');
/**
* Mock function where you send a request.
*/
const sendRequest = value => {
console.log(`Updating to DB: ${value}`);
}
/**
* Create throttled version of your request.
* The number indicates the interval in milliseconds between
* calling the sendRequest function.
*/
const throttledRequest = throttle(sendRequest, 1000, true);
buttons.forEach(button => {
button.addEventListener('click', event => {
const { value } = event.target;
throttledRequest(value);
});
});
<button class="js-move" value="-1">Move up</button>
<button class="js-move" value="+1">Move down</button>

Push function is not working properly localStorage Javascript vanilla

I am doing a time-to-click game as you can imagine the fastest time would be the first place. I just want to have 3 scores. and have it on localstorage but every time I start a new game the actual score resets its value and it doesnt generate other scores. the 3 scores should have as value 0. I tried to push them as arrays but push function is not working well. at this point I am stuck. I dont know what to do. If u may help me, I would be really grateful, thanks!
let times = Array.from({
length: 3
})
let interval2;
// Timer CountUp
const timerCountUp = () => {
let times = 0;
let current = times;
interval2 = setInterval(() => {
times = current++
saveTimes(times)
return times
}, 1000);
}
// Saves the times to localStorage
const saveTimes = (times) => {
localStorage.setItem('times', JSON.stringify(times))
}
// Read existing notes from localStorage
const getSavedNotes = () => {
const timesJSON = localStorage.getItem('times')
try {
return timesJSON ? JSON.parse(timesJSON) : []
} catch (e) {
return []
}
}
//Button which starts the countUp
start.addEventListener('click', () => {
timerCountUp();
})
// Button which stops the countUp
document.querySelector('#start_button').addEventListener('click', (e) => {
console.log('click');
times.push(score = interval2)
getSavedTimes()
if (interval) {
clearInterval(interval);
clearInterval(interval2);
}
})
This:
// Button which stops the countUp
document.querySelector('#start_button').addEventListener('click', (e) => {
Probably should be this:
// Button which stops the countUp
document.querySelector('#stop_button').addEventListener('click', (e) => {
...considering you're already attaching an event-handler to start's 'click' event in the previous statement, and no-one would use #start_button as the id="" of a stop button.

Passed when the function is not finished

So I have been working with protractor about passing/failing tests and I have created a amout of clicks to be done in the script. Basically that to run x amout of clicks and once it is finished then it should pass.
NEW EDIT
it('Click remove button', function (done) {
let allProds = element.all(by.css('div.stock-controller'));
allProds.count()
.then(function (cnt) { // amount of products
let allPromises = []
for(let index=0;index<cnt;index++) {
let section = allProds.get(index),
// message string which include qty in stock
stock_qty_str = section.element(by.css('div.message')).getText(),
// user inputed qty
user_qty_str = section.element(by.css('div.quantity-input input'))
.getAttribute('value'),
// button Descrease
btn_dec = section.element(by.css('button[aria-label="Decrease"]'));
allPromises.push(Promise.all([stock_qty_str, user_qty_str])
.then(function(data){
// use RegExp to extract qty in stock
let group = data[0].trim().match(/^Sorry.*?(\d+)/)
if(group) {
let stock_qty = group[1] * 1,
user_qty = data[1].trim() * 1,
gap = user_qty - stock_qty; // click times of Decrease button
for(let i=0;i<gap;i++) {
btn_dec.click();
browser.sleep(1000).then(function(){
console.log('Click Decrease button: ' + i + '/' + gap)
})
}
}
})
)
}
return Promise.all(allPromises)
})
.then(()=>{
done();
})
});
However my problem is that what it does now is that:
as you can see what happens is that it counts how many times it is supposed to click and then marks it as finished but yet it still clicks after the passed which is odd I would say...
I wonder how can I make it wait until the function is fully done and then mark it as passed/failed?
You need to return a promise so the test waits for that promise.
At the moment, the promise returns right away without waiting for the clicks.
You need to gather all Promise.all from the for and return that as a promise (using Promise.all again probably)
Something like this
it('Click remove button', function (done) {
let allProds = element.all(by.css('div.stock-controller'));
allProds.count()
.then(function (cnt) { // amount of products
let allPromises = []
for(let index=0;index<cnt;index++) {
let section = allProds.get(index),
// message string which include qty in stock
stock_qty_str = section.element(by.css('div.message')).getText(),
// user inputed qty
user_qty_str = section.element(by.css('div.quantity-input input'))
.getAttribute('value'),
// button Descrease
btn_dec = section.element(by.css('button[aria-label="Decrease"]'));
allPromises.push(Promise.all([stock_qty_str, user_qty_str])...)
}
return Promise.all(allPromises)
})
.then(()=>{
done();
})
});

How to call async function to use return on global scope

I'm currently struggling with a function call, when I call the function from an if statement it does work but when I call it from outside it doesn't, my if statement only checks which button was pressed but I'm trying to remove the function from the button and just call it as soon as my app starts.
We will look at fetchJokes() inside jokeContainer.addEventListener('click', event => {
This is my current code:
const jokeContainer = document.querySelector('.joke-container');
const jokesArray = JSON.parse(localStorage.getItem("jokesData"));
// Fetch joke count from API endpoint
async function sizeJokesArray() {
let url = 'https://api.icndb.com/jokes/count';
let data = await (await fetch(url)).json();
data = data.value;
return data;
}
// use API endpoint to fetch the jokes and store it in an array
async function fetchJokes() {
let url = `https://api.icndb.com/jokes/random/${length}`;
let jokesData = [];
let data = await (await fetch(url)).json();
data = data.value;
for (jokePosition in data) {
jokesData.push(data[jokePosition].joke);
}
return localStorage.setItem("jokesData", JSON.stringify(jokesData));;
}
const jokeDispenser = (function() {
let counter = 0; //start counter at position 0 of jokes array
function _change(position) {
counter += position;
}
return {
nextJoke: function() {
_change(1);
counter %= jokesArray.length; // start from 0 if we get to the end of the array
return jokesArray[counter];
},
prevJoke: function() {
if (counter === 0) {
counter = jokesArray.length; // place our counter at the end of the array
}
_change(-1);
return jokesArray[counter];
}
};
})();
// pass selected joke to print on html element
function printJoke(joke) {
document.querySelector('.joke-text p').textContent = joke;
}
sizeJokesArray().then(size => (length = size)); // Size of array in response
jokeContainer.addEventListener('click', event => {
if (event.target.value === 'Fetch') {
fetchJokes(length);
} else if (event.target.value === 'Next') {
printJoke(jokeDispenser.prevJoke(jokesArray));
} else if (event.target.value === 'Prev') {
printJoke(jokeDispenser.nextJoke(jokesArray));
}
});
And I'm trying to do something like this:
// pass selected joke to print on HTML element
function printJoke(joke) {
document.querySelector('.joke-text p').textContent = joke;
}
sizeJokesArray().then(size => (length = size)); // Size of array in response
fetchJokes(length);
jokeContainer.addEventListener('click', event => {
if (event.target.value === 'Next') {
printJoke(jokeDispenser.prevJoke(jokesArray));
} else if (event.target.value === 'Prev') {
printJoke(jokeDispenser.nextJoke(jokesArray));
}
});
By the way, I'm aware that currently, you can't actually iterate through the array elements using prev and next button without refreshing the page but I guess that will be another question.
Couldn't think of a better title.(edits welcomed)
Async functions are, as the name implies, asynchronous. In
sizeJokesArray().then(size => (length = size)); // Size of array in response
fetchJokes(length);
you are calling fetchJokes before length = size is executed because, as you may have guessed, sizeJokesArray is asynchronous.
But since you are already using promises the fix is straightforward:
sizeJokesArray().then(fetchJokes);
If you have not fully understood yet how promises work, maybe https://developers.google.com/web/fundamentals/getting-started/primers/promises helps.

Categories