addeventlistener built multiple times - javascript

I have an addEventListener built if there is a real-time change in my database:
db.collection(xx).doc(yy).onSnapshot(user => {
accept_button.addEventListener("click", function() {
initializeChat();
})
The .onSnapshot is a real-time listener to my database, in another word, if there is a change in my database of document yy, the accept_button appears, then the initializeChat function is built into accept_button.
When the document is changed x times, the addEventListener function is built x times, and initializeChat is executed x times. How do I make such that the same addEventListener function overwrites one another, and initializeChat only executes once?
Thanks for your time and help.

Use onclick instead of addEventListener.
This will remove multiple event handling.
db.collection(xx).doc(yy).onSnapshot(user => {
accept_button.onclick = function() {
initializeChat();
}
})

It sounds like you just need to check a flag to see if a listener has been added to the button yet:
let listenerAdded = false;
db.collection(xx).doc(yy).onSnapshot(user => {
if (listenerAdded) {
return;
}
listenerAdded = true;
accept_button.addEventListener("click", initializeChat);
})
Note that there's no need for an extra anonymous function for the click callback.
It's usually not a good idea (because only one onclick listener can be assigned at a time), but if you're sure no other click listeners will be added to the button, you could also assign to the onclick and check it instead:
db.collection(xx).doc(yy).onSnapshot(user => {
if (accept_button.onclick) {
return;
}
accept_button.onclick = initializeChat;
})

Related

Javascript addEventListener within if statement (which checks a state)

I am trying to write a simple language game, where the user can begin the game by clicking whether they want to translate or fill gaps. As I want different code to run depending on whether the user has selected translate or gapfill, I thought that having a 'state' variable which is changed depending on the game mode, and then having an eventlistener within an 'if' statement, would work.
I can check on the console that the state is getting set to 0, but the event listener within the if statement doesn't run. It works fine if the 'if' statement isn't there. Is this something to do with the if statement only running at the moment the state is set to 0?
let state = 0;
document.querySelector('#translations').addEventListener('click', (e) => {
state = 1;
console.log(state);
});
document.querySelector('#gapfill').addEventListener('click', (e) => {
state = 2;
});
if (state === 1) {
answerBtn.addEventListener('click', (e) => {
console.log('hello from translations');
if (document.querySelector('input').value == sentences[quNum].sentence) {
quNum++;
displayQu();
} else {
sentences.splice(quNum + 5, 0, sentences[quNum]);
quNum++;
displayQu();
}
});
}
addEventListener does no more than what its name implies... It adds an event listener. When that event happens (such as when the user clicks on something), the code within the function will be executed. So this code isn't going to be executed until some later time:
state = 1;
But right away you check the value:
if (state === 1)
So basically you're doing this:
let state = 0;
if (state === 1)
Which will never be true.
Instead of conditionally adding the last event listener (based on a condition that won't be met until some future time), always add it but within its function conditionally perform the operation:
answerBtn.addEventListener('click', (e) => {
if (state === 1) {
// the rest of the logic
}
});
That way when that click event happens at a later time there's a chance the other event may have also happened and set the value to 1. And if it hasn't, the event listener will still execute but won't do anything because the if condition fails.
This can be simplified greatly. There's no need for a state variable and multiple event handlers. Instead, leverage "event delegation" by setting up just one handler at an ancestor of both buttons. Then when a button is clicked, the event will bubble up to the element that is handling the event. Within the handler, you can determine which button actually triggered the event with event.target and then just proceed as needed.
Here's an example:
// Set up one handler at a common ancestor of both buttons
document.querySelector(".wrapper").addEventListener("click", function(event){
// Was one of the buttons the thing that caused the event?
if(event.target.classList.contains("gap")){
// Do gap stuff here
console.log("You clicked gap");
} else if(event.target.classList.contains("translate")){
// Do translate stuff here
console.log("You clicked translate");
}
});
<div class="wrapper">
<button class="gap">Gap Fill</button>
<button class="translate">Translate</button>
</div>

how to prevent a event listener function to be triggered several times at the same time

my question is easy I'm using discord js and have this event listener in my code.
this listener function is triggered when a user sends a particular message in a channel. But if 2 users send this particular message at the same time the listener function is triggered twice at the same time. How can I prevent this ?
If you are happy to ignore the second message coming through you could have a look at wrapping your function with debouncing, which would cause it to only trigger once if called in short succession.
Lodash has a package for this that can be imported individually
import { myFunc } from 'somewhere';
import { debounce } from 'somewhereElse';
const DEBOUNCE_DELAY_MS = 500;
const myDebouncedFunc = debounce(myFunc, DEBOUNCE_DELAY_MS);
// Call myDebouncedFunc twice immediately after each other, the
// debouncing will result in the function only getting called max once
// every 500ms;
myDebouncedFunc();
myDebouncedFunc();
Otherwise, if you need to have both messages processed, just not at the same time, then you would need something like a queue for processing these events. Then you could process these messages in an interval for example.
// Some lexically available scope
const myQueue = [];
// Event handler
const myHandler = (msg) => {
myQueue.push(msg);
}
// Interval processing
setInterval(() => {
if (myQueue.length > 0) {
const msgToProcess = myQueue.shift();
processMessage(msgToProcess);
}
}, 500)
There are multiple solutions to this problem depending on your needs.
Unregister the event listener as soon as the even comes. In most libraries, you can easily achieve by registering your event using once() method, instead of "on()"
Use debounce() to aggregate all the events triggered within a certain amount of time into one.
The same or similar methods exist in most of JS libraries similar to lodash.

onChange / onInput alternatives?

I'm creating a typing test page and running into a small issue.
I'm trying to start the timer when the user types, and using onInput works, but the problem is that it registers every time I type into the textArea which causes the starttimer function to repeat itself.
onChange works but it only works after I click outside the page which makes sense, but is not what im looking for. I need the timer to start as soon as the user starts typing. Also with onChange the stop function works, but the timer starts after i click outside of the page.
Is there a JS event that fits what i'm looking for, or is there a way to change onInput or onChange to fix the problem i'm having.
JavaScript
document.getElementById("test-area").onchange = function () {
startTimer(1);
};
Thank you.
You need to listen to the input event because as you said change is only triggered after the input loses focus which is not what you want. You also need to handle the event only once.
document.getElementById("test-area").addEventListener("input", () => {
startTimer(1);
}, {once: true});
Note that this removes the event handler after it is fired. If you need to run it again you will have to register the event one more time. Maybe in your timer callback.
Try like this, In JavaScript, using the addEventListener() method:
The addEventListener() method attaches an event handler to the specified element.
document.getElementById("test-area").addEventListener("change", function () {
if(!this.value.length){
startTimer(1);
}
}, {once : true});
Have you tried onfocus ? its not exactly when they start typing but it works. Another option would be that you use onInput and on the clocks start function change a boolian -isRunning - to true. then put a if (isRunning) return. something like:
function start() {
if(isRunning) return;
isRunning = true;
}
and then change the boolian to false when you stop onChange
Some solutions:
Variant 1
Just create a flag:
var timerStarted = false; // Was the timer started?
document.getElementById("test-area").oninput = function () {
if(timerStarted) return; // If timer was started - do nothing
startTimer(1); // Else - start the timer
timerStarted = true; // Remember what we've started the timer
};
Variant 2
(A bit shorter)
document.getElementById("test-area").addEventListener("input", function () {
startTimer(1);
}, {once: true}); // Good thing about addEventListener
// With this it will run the event only single time
More here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
Can you use JQuery? If so it has a method .one() which will execute the function only once. You can freely use keydown/keyup event handler then. For eg.
<script>
$(document).ready(function(){
$("#test-area").one("keydown", function() {
alert("hey");
});
});
</script>

attaching to an event - but only conditional

I am using kendo schedule widget and want to prevent datsource from reading after crud operations under certain circumstances.
I tryed this by attaching to the requestStart event:
function subscribeToEvent(e) {
if (e.condition===condition) {
var scheduler = $("#scheduleCustomerSchedule").data("kendoScheduler");
scheduler.dataSource.bind("requestStart", dataSource_requestStart);
}
}
function dataSource_requestStart(e) {
e.preventDefault();
}
This works but the probem is, that I dont know how die unbind this event after it has been executed.
In my case this code prevents dataSource.Read() forever, of course.
thx
You need to subscribe to the requestStart event and check for the condition in the actual event handler. If that specific condition is only available in the subscribeToEvent method, you can pass it using a closure.
function subscribeToEvent(e) {
var scheduler = $("#scheduleCustomerSchedule").data("kendoScheduler");
scheduler.dataSource.bind("requestStart", dataSource_requestStart);
}
function dataSource_requestStart(e) {
if (e.condition === condition)
e.preventDefault();
}

How to let JavaScript wait until certain event happens?

I am writing a webpage with the following structure:
One section (table A) depends on another section (table B);
Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.
In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.
For now, I am using the following method:
Declare a global variable updated and make it false;
After the first table received input, I make an empty while loop until updated is true;
Add an listener, once the calculation is done and the event received, set updated to true.
This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?
Thanks for any inputs!
In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForButtonClick() {
const div = document.querySelector("div")
const button = document.querySelector("button")
div.innerText = "Waiting for you to press the button"
await getPromiseFromEvent(button, "click")
div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Add an listener, once the calculation is done and the event received, set updated to true.
Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.
myEventBus.addListener(function () {
// do whatever
updateTable();
alert('table updated!');
});
Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.
What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.
//this is just illustrative
//Your actual code will be probably very different from this.
function update_part(){
//do something
signal_finished_part()
}
var parts_done = 0;
function signal_finished_part(){
parts_done ++;
if(parts_done >= 5){
signal_all_parts_done();
}
}
function signal_all_parts_done()
{
//do something to table A
}
You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.

Categories