How do I cancel delayed input event? - javascript

From this How to delay the .keyup() handler until the user stops typing? question we've learned how to create delays. But any ideas on how do I cancel delayed event?
Check this out
In this example I don't want anything to be printed out after clicking the cancel button.
But I need more extensible solution. The solution might be to modify the delay() function somehow like this
delay(fn, ms, cancelCallback)
In here the cancelCallback would be a function that cancels the delay. By cancels the delay I mean to not call the fn() and just do nothing.
const inputElement = document.getElementById('input');
const buttonElement = document.getElementById('button');
const pElement = document.getElementById('p');
const delayInMs = 2000; // 2s delay
const delay = function (fn, ms) {
let timer = 0;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...args), ms || 0);
};
};
const print = text => pElement.innerHTML = text;
const handleKeyUp = e => print(e.target.value);
inputElement.addEventListener('keyup', delay(handleKeyUp, delayInMs));
// Some new logic
const cancelDelay = () => {};
inputElement.addEventListener('click', cancelDelay);
<input id="input" />
<button id="button">Cancel</button>
<br />
<h6>You typed:</h6>
<p id="p"></p>

I figured this one out on my own. I think the solution is pretty clear.
const inputElement = document.getElementById('input');
const buttonElement = document.getElementById('button');
const pElement = document.getElementById('p');
const delayInMs = 2000; // 2s delay
// Modified
function delay(fn, ms) {
let timer = 0;
return {
call(...args) {
clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...args), ms || 0);
},
cancel() {
clearTimeout(timer);
},
};
}
// Just show text
const print = text => pElement.innerHTML = text;
const myFunc = text => print(text);
const myFuncDelayed = delay(myFunc, delayInMs);
// Calling
const handleInputKeyUp = e => myFuncDelayed.call(e.target.value);
inputElement.addEventListener('keyup', handleInputKeyUp);
// Canceling
const handleBtnClick = () => { myFuncDelayed.cancel() };
buttonElement.addEventListener('click', handleBtnClick);
<input id="input" />
<button id="button">Cancel</button>
<br />
<h6>You typed:</h6>
<p id="p"></p>

First you need to add cancel handler for the buttonElement not for inputElement. In order to check if the cancel button has been clicked you can have one global variable flag and check if that is true which can be set to true when cancel button is clicked. Also, make sure to reset it to default on your input keyup handler.
const inputElement = document.getElementById('input');
const buttonElement = document.getElementById('button');
const pElement = document.getElementById('p');
const delayInMs = 2000; // 2s delay
let isCancelled = false;
const delay = function (fn, ms) {
let timer = 0;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...args), ms || 0);
};
};
const print = text => pElement.innerHTML = text;
const handleKeyUp = e => {
if (!isCancelled) {
print(e.target.value)
}
isCancelled = false;
};
inputElement.addEventListener('keyup', delay(handleKeyUp, delayInMs));
// Some new logic
const cancelDelay = () => { isCancelled = true };
buttonElement.addEventListener('click', () => cancelDelay());
<input id="input" />
<button id="button">Cancel</button>
<br />
<h6>You typed:</h6>
<p id="p"></p>

Related

Counter with clearInterval, not restarting after button click

There is a word-translation card page which includes:
A 3-second countdown that runs immediately after the page loads and when you move to a new word after press the 'Correct' button.
Word and his translate which appears after 'Excellent' button pressed. This button stops and hide the counter.
Button 'Show answer' which appears if 'Excellent' didn't pressed in 3 sec.
Buttons 'Wrong' and 'Correct' which appears if 'Excellent' or 'Show answer' buttons are pressed.
The problem is how the countdown works (unstable). Sometimes it doesn't restart after clicking "Correct". I tried manage countdown in separate function, but this approach provides much more issues. So now there is a timer that is called globally and a timer that is called when you click on the "Correct" button.I think the problem is near timerId. I will be glad to any comments and ideas on the code.
<div class="deck-container">
<div class="words-container">
<h2 id="primary-word"></h2>
<h2 id="secondary-word"></h2>
</div>
<div class="btn-container">
<p id="timer-count">3</p>
<button id="btn-excellent" onClick="excellent()">Excellent!</button>
<button id="btn-show-answer" onClick="showAnswerF()">Show Answer</button>
<button id="btn-wrong">Wrong</button>
<button id="btn-correct" onClick="correctF()">Correct</button>
</div>
</div>
let currentWord = 0
let timerCount = 3
let fetched_data = {}
async function getDeck () {
let response = await fetch('/api/deck_words/2/', {
method: 'get',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json'
}
}
)
let data = await response.json()
let primaryElement = document.getElementById('primary-word')
primaryElement.innerText = await data[currentWord]['primary_word']
let secondaryElement = document.getElementById('secondary-word')
secondaryElement.innerText = await data[currentWord]['secondary_word']
return data
}
fetched_data = getDeck()
const getData = async () => {
return await getDeck()
}
data = getData();
function countDecrease() {
timerCount --
if (timerCount > 0) {
document.getElementById("timer-count").innerHTML = timerCount
} else {
hideExcellent()
}
}
function hideExcellent() {
showElement(true,'btn-excellent')
showElement(true,'timer-count')
showElement(false,'btn-show-answer')
}
let timerId = setInterval(() => countDecrease(), 1000)
setTimeout(() => {
clearInterval(timerId)
}, 3000)
function showElement(showProperty, elementClass) {
showProperty = !showProperty
let element = document.getElementById(elementClass)
element.style.display = (showProperty === true) ? "inline-block" : "none";
}
function showAnswerF() {
showElement(true,'btn-show-answer')
showElement(false,'secondary-word')
showElement(false,'btn-wrong')
showElement(false,'btn-correct')
}
function excellent() {
showElement(true,'timer-count')
showElement(true,'btn-excellent')
showElement(false,'btn-wrong')
showElement(false,'btn-correct')
showElement(false,'secondary-word')
clearInterval(timerId)
timerCount = 3
}
function correctF() {
currentWord++
const changeWords = () => {
fetched_data.then((data) => {
document.getElementById('primary-word').innerText = data[currentWord]['primary_word']
document.getElementById('secondary-word').innerText = data[currentWord]['secondary_word']
document.getElementById("timer-count").innerText = '3'
timerCount = 3
timerId = setInterval(() => countDecrease(), 1000)
setTimeout(() => {
clearInterval(timerId)
}, 3000)
})
}
changeWords()
let countElement = document.getElementById('timer-count')
countElement.style.display = "block"
showElement(false,'btn-excellent')
showElement(true,'btn-wrong')
showElement(true,'btn-correct')
showElement(true,'secondary-word')
}
I think this would be better handled with an async function, maybe like so.
const timerCount = document.querySelector("#timer-count")
const reset = document.querySelector("button")
function delay(ms) {
return new Promise(res => setTimeout(res, ms))
}
async function countDown(signal) {
const aborted = new Promise(resolve => signal.addEventListener("abort", resolve))
for (let i = 10; i >= 0 && signal?.aborted != true; --i) {
timerCount.textContent = i
await Promise.race([delay(1000), aborted])
}
timerCount.textContent = ""
}
async function startCountdown() {
const ac = new AbortController()
const abort = () => ac.abort()
reset.addEventListener("click", abort, { once: true })
reset.textContent = "Cancel"
await countDown(ac.signal)
reset.removeEventListener("click", abort)
reset.addEventListener("click", startCountdown, { once: true })
reset.textContent = "Start"
}
startCountdown()
<p id="timer-count"></p>
<button>Start</button>
Alternatively, you might want to model the countdown as an object that implements EventTarget.
const timerCount = document.querySelector("#timer-count")
const btn = document.querySelector("button")
class Timer extends EventTarget {
#value; #tick_rate; #enabled; #interval_handle
constructor(tick_rate = 1000) {
super()
this.#value = 0
this.#tick_rate = tick_rate
this.#enabled = false
this.#interval_handle = null
}
get value() {
return this.#value
}
set value(value) {
this.#value = value
this.dispatchEvent(new Event("update"))
}
get tick_rate() {
return this.#tick_rate
}
get enabled() {
return this.#enabled
}
start() {
if (this.#enabled) return
this.#enabled = true
this.#interval_handle = setInterval(Timer.#tick, this.#tick_rate, this)
this.dispatchEvent(new Event("start"))
}
stop() {
if (!this.#enabled) return
this.#enabled = false
clearInterval(this.#interval_handle)
this.dispatchEvent(new Event("stop"))
}
static #tick(timer) {
timer.value = Math.max(0, timer.value - 1)
if (timer.value == 0) timer.stop()
}
}
const timer = new Timer()
timer.addEventListener("start", function() {
btn.textContent = "Stop"
})
timer.addEventListener("stop", function() {
timerCount.textContent = ""
btn.textContent = "Start"
})
timer.addEventListener("update", function() {
timerCount.textContent = timer.value
})
btn.addEventListener("click", function() {
if (timer.enabled == false) {
timer.value = 5
timer.start()
} else {
timer.stop()
}
})
<p id="timer-count"></p>
<button>Start</button>
Here's a countdown. The first count is after 1 second.
let count = 3;
let timer = [];
const start = () => {
new Array(count).fill(true).forEach((_,i) => {
timer.push(setTimeout(() => console.log('count',count - i),(i+1) * 1000))
})
}
const stop = () => timer.forEach(clearTimeout);
<button onclick="stop()">STOP</button>
<button onclick="start()">START</button>

Metronome JavaScript - function .pause() don't stop the Audio beep

I trying to do a metronome on Codepen.
But I have one problem because the function .pause() doesn't stop the beep(my sound).
This is the code that I wrote. I read about it but I don't find the problem.
The audio I found on Codepen. I don't think it's the problem. I think the problem is between my computer and my chair.
const beep = new Audio(
"data:audio/flac;base64,ZkxhQwAAACIQABAAAARBAARBAfQBcAAAAZLfwZb9QVlTtnnZLOsaWczxhAAAKCAAAAByZWZlcmVuY2UgbGliRkxBQyAxLjMuMCAyMDEzMDUyNgAAAAD/+HQMAAGRJhgKxHRX63x+6QxXqTpCYAANDJgITNAmIhAB/5BgFt4iAfgBZHY4A9UZAAZ65QManTnQUAKnx8A+ZcwFun6gX1gMVIgAfaXgEml9KIzgfUBYAnhLwJl/Oek+A5rZADhx2ApN3s198CTrfAPyHMGRV3DcKAYAAwBJ7uA8wysUF8HYV6AUyIIKv/ObD0B7BRAOf6QFVnlVIrg0zuwGYTqCTP9rJXAWm4AC1HQDxVMnllwVg94CGPDDq6XrJRgQ2zwHp6wGR5tQFGh9dygZtwIXeHNK+AHAw8BZ34BSgBUY44Ze3IEt2uEhBfQg3BcaTA+MSB/rIUTNIUTWYN9hMdT2M/TYEg+cDC6KGzFJQAzj8TBBUujjHFLnsqB07TAj7JBa4qTwQY15pQ9rKxSdhzrJwxaKA3o/BKIsTkDYtGCwxtDRCIqzegQo2pAr9KD0mKmwtRJeuhMscnKzjNLLCC4CCDoYNaPiZmNPNIsdzTJkF0zHIRyDGDZzCLozKYLfNVZUYt6pWUiy7/Bjf5CyRUJ95iW1TLkIET7MJJU5yxARVwzCNBsIZHCVflJ5ZE+0K30dXKabEjNYdP+TulglDBSFPJNlCtxqLycmj5OeMkURsMFSXgrtnXLllFm5cySbjiZQKoshh0ASOArXdBJuJ9c2syJBjM8II1VJYFtSEVbCw3f80qnABkAClxxw8mPk8NdHt4q9gNy0UpWEuO2UUx3jZSuRqVkcoWnWIWa1ahV3Y3K5KbKMH879vqjUtUZMatwgfZeOMvsQlyisbQ71rIxGX9FhUSYvRHvF3xeunSYfQ4iRItSvuvzR5iO3xDFaEdARSGOC1fGIpY/hll/Z8dBI+J6u22KvVuDK2IiXW9V8Htkhxa94kLDPz25hsMC8qImvymxd70W8D5iDfsU9TYMYt5EIfJnAWr0bS7NbeHbQu6ZsuZyvTuhwMbc9XFv3q4b4amazBWwDvKfuyGSBrwrbr8+kkvhfZKs7C2FwoVooWXOnr/sWmZ5hWFSSpEtq0O6bjQhPR6Eguo8PmOz4SoKeIgpRmJZ1CEWpm1U6F/2UKJhBBJi0KeJ6kgEoPCeWSBmv85AOeDhOk/mJgciON6g0DZHbeVa7jIhYMBSP4ukvNIr36CvTjhkZCkWJk5godIxpiOjqiEnoJQ2K3HjKtYcY2CFWiXpIrqaGEIgef4gs+JXnhRm4GziHB2h/OIRB6BgfhgH4ap2DiTgVwIUN6FjaguFoE0uENThJRYJMOBCwg3zIOyKB00gOl4LXWC79gW14DOaCQAglUoEKCApZgc1YHC2AxzgJBoFcSBWWgICoBp+BEXgO64BeGAXYgMGoCoCAOYgEk4CGWAb5gB54A2SAWGgEfoAHyAINgDuYAkeAAbgBfoAgyAEEgAC4ARGADPgAf4AFyABYgAeIACGAAMgAaYAECABJgAagfd0="
);
const bpm = document.getElementById('bpm-display');
const time = document.getElementById('time');
const timeSlice = document.getElementById('time-slice');
const rangeBpm = document.getElementById('bpm-range');
const btnStartStop = document.getElementById('start-stop');
const btnReset = document.getElementById('reset');
let intervalSound
// function to start and stop the metronome
const startMetronome = (() => {
btnStartStop.innerText==='Start'? btnStartStop.innerText= 'Stop': btnStartStop.innerText='Start';
if(btnStartStop.innerText === 'Stop'){
intervalSound = (60/bpm.value) * 1000;
console.log(intervalSound);
interval(intervalSound);
}
else{
stopBeat();
}
});
const stopBeat = (()=>{
beep.pause();
beep.currentTime = 0;
})
const playBeat = (() =>{
beep.play();
})
const interval = ((interval) => {
setInterval(playBeat, interval)
})
// function to reset all metronome
const resetMetronome = (()=>{
bpm.value = '120';
rangeBpm.value='120';
time.value='4';
timeSlice.value='4';
intervalSound = 0;
stopBeat();
});
btnReset.addEventListener('click', function(){resetMetronome()});
btnStartStop.addEventListener('click', function(){startMetronome()});
bpm.addEventListener('change', function(){
rangeBpm.value=bpm.value;
})
rangeBpm.addEventListener('click', function(){
bpm.value = rangeBpm.value;
})
console.log(bpm.value, time.value, timeSlice.value, rangeBpm.value, btnStartStop.innerText, btnReset.innerText);
Can you help me?
You need to clear your interval.
let intervalId = -1;
const stopBeat = (()=>{
beep.pause();
beep.currentTime = 0;
clearInterval(intervalId);
})
const interval = ((interval) => {
intervalId = setInterval(playBeat, interval)
})

Getting the current value of a counter used in setInterval javaScript

Suppose I define the following function
export const startMoving = () => {
let counter = 0;
var intervalId = setInterval(() => {
// Do something…
counter++;
}, 1000);
return intervalId;
};
Although 'counter' is defined with let in the function, it works, but my question is: How do I get the value of 'counter' after a while?
Rafael
Return not only the interval ID, but also a function that returns the current value of counter.
const startMoving = () => {
let counter = 0;
var intervalId = setInterval(() => {
// Do something…
counter++;
}, 1000);
return [intervalId, () => counter];
};
const [intervalId, getCounter] = startMoving();
document.body.addEventListener('click', () => document.body.textContent = getCounter());
click here

Javascript pause/resume toggle button

I am working on a little challenge with school. We have learned CSS, HTML, and Javascript. I am trying to create a timer that is always running in the background. Then I need a button that pauses said timer and changes into a resume button that will resume the timer. This is what I have come up with.
document.addEventListener("DOMContentLoaded", () => {
const counterElement = document.getElementById('counter')
let counterValue = 0
const pauseButton = document.getElementById('pause')
const resumeButton = document.getElementById('resume')
const submitButton = document.getElementById(`submit`)
const minusButton = document.getElementById(`minus`)
const plusButton = document.getElementById('plus')
const heartButton = document.getElementById('heart')
intervalId = setInterval(myCallback, 1000);
function myCallback() {
counterValue += 1;
counterElement.innerHTML = counterValue;
}
function resume() {
setInterval(myCallback, 1000);
pauseButton.style.display = '';
resumeButton.style.display = 'none';
}
function pause() {
clearInterval(intervalId);
pauseButton.style.display = 'none';
resumeButton.style.display = '';
}
pauseButton.addEventListener("click", (e) => {
pause()
})
resumeButton.addEventListener("click", (e) => {
resume()
})
It does not function properly. Only the first few clicks work.
function resume() {
intervalId = setInterval(myCallback, 1000);
pauseButton.style.display = '';
resumeButton.style.display = 'none';
}
Try this. The root issue is that you're not assigning your setInterval reference back to your intervalId variable. So when you call clearInterval(intervalId) later, your code is saying, "That's already been cleared..." and not doing anything.
In short, your current resume() function creates a NEW setInterval - it doesn't update the old one. And since there was no reference to the new setInterval, there was no way for your pause function to be able to find it and clear it.
When you call setTimeout in the resume function, you have to reassign the intervalId variable to store the new interval ID. If you don't do that, your pause function will keep cancelling the first interval, which is a no-op.
So do this instead:
document.addEventListener("DOMContentLoaded", () => {
const counterElement = document.getElementById('counter')
let counterValue = 0
const pauseButton = document.getElementById('pause')
const resumeButton = document.getElementById('resume')
//const submitButton = document.getElementById(`submit`)
//const minusButton = document.getElementById(`minus`)
//const plusButton = document.getElementById('plus')
//const heartButton = document.getElementById('heart')
//It's a good idea to declare your variables. Since you want to reassign it, you probably want `let`.
let intervalId = setInterval(myCallback, 1000);
function myCallback() {
counterValue += 1;
counterElement.innerHTML = counterValue;
}
function resume() {
//Assign the new interval ID to `intervalId`
intervalId = setInterval(myCallback, 1000);
pauseButton.style.display = '';
resumeButton.style.display = 'none';
}
function pause() {
clearInterval(intervalId);
pauseButton.style.display = 'none';
resumeButton.style.display = '';
}
pauseButton.addEventListener("click", (e) => {
pause()
})
resumeButton.addEventListener("click", (e) => {
resume()
})
})
<button id="pause" >Pause</button>
<button id="resume" style="display:none;" >Resume</button>
<div id="counter" >0</div>
When you create a new Interval in your resume() function, you need to store its return value again in your intervalId variable for the next time you call pause().

HTML onmousedown event not working on mobile

I have made a prank calculator which takes an input from the user of the answer to be displayed when the '=' button is pressed. The input is taken in the form of a prompt in JavaScript which is triggered by long-pressing the '0' button. The long press feature is implemented by setting a timeout of 2 seconds when the buttons is pressed (using onmousedown) and clearing the timeout when the mouse key is lifted (onmouseup). This works fine on computers, but I don't know how to implement this on mobile. I have tried the onpointerdown and onpointerup events too. These also work fine on computers, but when I long-press on mobile, the prompt displays a second time shortly afterwards. The HTML and JavaScript code relating to this is as follows:
HTML:
<td>
<button
class="calculator-button"
onclick="insert(0)"
onmousedown="activateMagic()"
onmouseup="disableMagic()"
>
0
</button>
</td>
JavaScript:
let answer = null;
const input = document.getElementById("calcInput");
let pressTimer;
const insert = (objToInsert) => {
input.value += objToInsert;
};
const clearInput = () => {
input.value = null;
answer = null;
};
const activateMagic = () => {
pressTimer = window.setTimeout(() => {
answer = prompt("Enter the prank answer: ");
}, 2000);
return false;
};
const disableMagic = () => {
clearTimeout(pressTimer);
};
const findAnswer = () => {
if (answer == null) {
let problemAnswer = eval(input.value);
setTimeout(() => {
input.value = problemAnswer;
}, 10);
}
input.value = answer;
};
Thanks in advance.
I found an answer!! #DanyloHalaiko was right. The touchstart and touchend event listeners actually work! To those of you interested or those of you seeing this in the future, this is my final code:
HTML:
<td>
<button
class="calculator-button"
onclick="insert(0)"
onmousedown="activateMagic()"
onmouseup="disableMagic()"
id="activateBtn"
>
0
</button>
</td>
JavaScript:
let answer = null;
const input = document.getElementById("calcInput");
const activateBtn = document.getElementById("activateBtn");
let pressTimer;
const insert = (objToInsert) => {
input.value += objToInsert;
};
const clearInput = () => {
input.value = null;
answer = null;
};
const activateMagic = () => {
pressTimer = window.setTimeout(() => {
answer = prompt("Enter the prank answer: ");
}, 2000);
return false;
};
const disableMagic = () => {
clearTimeout(pressTimer);
};
activateBtn.addEventListener("touchstart", activateMagic);
activateBtn.addEventListener("touchend", disableMagic);
const findAnswer = () => {
if (answer == null) {
let problemAnswer = eval(input.value);
setTimeout(() => {
input.value = problemAnswer;
}, 10);
}
input.value = answer;
};

Categories

Resources