jQuery click event fires on parent element multiple times - javascript

https://jsfiddle.net/50Lw423g/2/
gameLogic: function() {
console.log("GAME LOGIC");
alert("Your Turn!");
var that = this;
$(".game-screen").on("click", ".tic", function() {
console.log("EVENT ATTACHED");
var movesDisplay = $(this).text(),
playerOne = that.playerOneMoves,
moveID = +(this.id);
if (movesDisplay !== "O" && movesDisplay !== "X") {
that.playerOneMoves.push(moveID);
if (playerOne.length > 2) {
if (that.checkIfWinCondition(playerOne, that.winConditions)) {
alert("GAME OVER!");
that.inGame = false;
ticTacToe.restartGame();
}
}
$(this).text(that.playerFaction === "X" ? "X" : "O");
}
});
},
I'm writing a Tic-Tac-Toe game and ran into this problem - when multiple sessions are "played" click events keep compounding. Which I solved by clearing the previous click events every time before a new one is attached.
$(".game-screen").off().on("click", ".tic", function () { //do stuff }
By the way event.stopPropagation() and event.stopImmediatePropagation() did NOT work.
Anyway, while I managed to fix the problem and understand why the click events where compounding, what I can't seem to wrap my head around is why those compounded click events kept calling the parent function gameLogic: function(). Try "playing" several sessions in a row -
console.log("GAME LOGIC");
alert("Your Turn!");
get called exponentially more with every session. Does jQuery use the parent function to track the click events or something? Can anyone explain what's happening under the hood? Why do the event handlers keep referring to the parent function?

Move your function implementation out from the gameLogic function like bellow;
function gameScreenClick(){
// your click event handler code.....
}
then
$(".game-screen").on("click", ".tic", gameScreenClick);

I traced the call stack, and looked at the jQuery dispatch: function(). Actually, as it turns out, the problem wasn't with the way jQuery internally refers to it's click functions and handlers.
The culprit was on line 46. The getUserInput() attaches an event handler which in turn attaches the chooseFaction() event handler. On the next session, getUserInput() gets attached one more time and chooseFaction() is attached 2 times (+1 time from before) - so chooseFaction() fires 3 times calling generateBoard(), and in turn, calling gameLogic()etc. Thus calls to gameLogic() exponentially increasing with every iteration.
Moved $(.game-screen).off() to the restartGame() function, to remove all click events before starting a new session, as a cleaner solution.
Solution: https://jsfiddle.net/50Lw423g/4/

Related

JavaScript: Add Event Listener to an Event Listener

I am working with ChartJS. I have a onClick function defined for the chart which changes the data for the chart and calls an update() function. It looks like the chart does not actually fully update until the onClick function has finished, so there is not way for me to access the new data in the chart in the onClick handler.
I am hoping there is a way to add an event handler on that onClick event handler, but I can't figure out how to do it. I tried adding a separate onClick event handler, but it looks like they run asynchronously, and I can't figure out a way to guarantee that the onClick event handler that is changing and updating the chart finishes first. Ideally, I would be able to create an event handler on the chart's event handler, but I don't know how to do that.
I found a similar question asked some years back, but it looks like they might have been able to add an event handler to their event handler because it was a custom event, and wasn't just 'click'. Does anyone know of a good solution to this?
function chartOnClickHandler(e) {
if (e.native.detail !== 2) return; // if not double click return
if (e.native.target.style.cursor === 'default') {
handleDoubleClickAddPoint(e);
}
else if (e.native.target.style.cursor === 'grab'){
handleDoubleClickRemovePoint(e);
}
} // I want to run code here, after chartOnClickHandler has finished executing.
// Below is my second event listener. Ideally, I could call the passed in function
// whenever chartOnClickHandler is called.
chartElement.addEventListener('chartOnClickHandler', (e) => {
// Handle event
});
99% chance this doesn't work so please someone kindly correct it so it works. Also I feel like this is not even on the right track.
function chartOnClickHandler(e) {
return new Promise((res, rej)=>{
if (e.native.detail !== 2) rej(0); // arbitrary reject value
if (e.native.target.style.cursor === 'default') {
res(handleDoubleClickAddPoint(e));
}
else if (e.native.target.style.cursor === 'grab'){
res(handleDoubleClickRemovePoint(e));
}
})
}
function handleSynchronously(e) {
chartOnClickHandler(e).then(
// do synchronous stuff here
);
}
I think I found a simple way around my issue. Instead of adding an event listener that waits for the function to finish, I found a way to mess around with the event queue. Using either queueMicrotask() or setTimeout(), I am able to add a function to the event queue that comes after the current running event.
Update: I ended up going with setTimeout(function, 1000) because I have to wait for the Chart's animation to end to properly get the data I need.
function chartOnClickHandler(e) {
if (e.native.detail !== 2) return; // if not double click return
if (e.native.target.style.cursor === 'default') {
handleDoubleClickAddPoint(e);
}
else if (e.native.target.style.cursor === 'grab'){
handleDoubleClickRemovePoint(e);
}
queueMicrotask( () => {
// Function to be run after the current task/event (chartOnClickHandler)
});
setTimeout( () => {
// Function to be run after the current task/event (chartOnClickHandler)
}, 0); // timeout can be 0, since we don't care about delay time, just to push the event down in the event queue.
} // Queued function will run here, after chartOnClickHandler has finished executing.
// Below event handler is not needed and can be removed.
chartElement.addEventListener('chartOnClickHandler', (e) => {
// This event handler is not needed anymore
});

EventListener(ended) firing more than once

I am trying to make a music-app, so what I want to do is when the current music ends, it will automatically plays the next song in line, but what happens is, the EventListener get triggered by times 2, firstly it is triggered one time, and then 2 time, and then 4, and the number keeps increasing every time the EventListener gets triggered, I've tried searching for solutions in other websites but I just can't seem to find the answer, hope anyone can help
By the way I am using vue js
these are my data
data () {
return {
current: {},
index: 0,
isPlaying: false,
api_key: '4f40bf40b2msh6d957020229b6c6p1fe925jsn4ef16ee46284',
url_base: 'https://deezerdevs-deezer.p.rapidapi.com',
songs: [],
query:'',
player: new Audio()
}
},
and this is my method for playing the music
play (song) {
if (typeof song.title != "undefined") {
this.current = song;
this.player.src = this.current.preview;
}
this.player.play();
//console.log(this.player);
this.player.addEventListener('ended', function () {
console.log(this.index);
//this.index++;
if (this.index > this.songs.length - 1) {
this.index = 0;
}
this.current = this.songs[this.index];
this.play(this.current);
}.bind(this));
this.isPlaying = true;
},
if anyone has better suggestion for me to make this will also be a huge favour
The problem is that each time your play method gets called, a new event listener gets added. Then, whenever a song ends, all of the previously added listeners get called.
(You seem to be under the impression that the listener gets retired after being called. This is not the case; they stick around until you remove them.)
You should move the addEventListener call from out of your play method, and put it somewhere else. E.g. you could do it in the constructor of your class, or in the outer-most scope.
The symptoms are caused by adding the event listener multiple times without removing it. Solutions include
adding the event listener once, outside the operational loop of play(), raising an ended event, handling the ended event, and calling play again (from within the listener).
removing the previous event handler, if one was added, from within the ended handler before adding a new handler. Not recommended because of the additional complexity of maintaining a reference to the function returned by bind in order to remove it later.
have the browser remove the handler for you after calling it, by including {once: true} as the third argument of addEventListener when adding the event handler:
this.player.addEventListener('ended', function () {
// handler to play once, loop, loop to end, shuffle or whatever
}.bind(this), {once: true});
Check the browser compatibility section of the MDN documentation however - using the option effectively means dropping for support for IE.

Alternative action if event does not occur javascript/jquery

I have a probably really simple question but did not find anything about this or maybe did not find the right words for my problem.
If have a function to be executed on keypress which also changes my variable A - fine, and it works.
But now I want to give an alternative value to my variable A if the keypress event is not happening.
So I'm looking for the correct command for the naive logic of
if ("keypress event happens") {
A = 1
} else {
A = 2
}
Is there any way to do that in js or jquery with simple true/false checks for the key event?
I've been trying and trying and it did not work once.
Usually, the way one solves this problem is with a setTimeout(). You set the timer for N seconds. If the keypress happens, you cancel the timer. If the keypress doesn't happen, the timer will fire giving you your alternate event.
You probably wrap this in some sort of function that you can trigger whenever you want, but you didn't share the overall context so this is just the general idea:
$("#myObj").keypress(function(e) {
if (timer) {
clearTimeout(timer);
}
// process key
});
var timer = setTimeout(function() {
timer = null;
// key didn't happen within the alltoted time so fire the alternate behavior
}, 5000);

An efficient way to cancel a mouseover event... read on to see why

I'm in the process of authoring a completely client side web language reference site. A problem that I encountered today; I have a side panel that is a unordered list of terms and they have onmouseover event listeners. I decided it would be a good idea to add a delay prior to execution and cancel the event at run-time if the mouse was no longer over that element. This is what I've come up with but I feel there must be a better way.
var currentXCoordinate=0
var currentYCoordinate=0
var elementFromCurrentMousePosition=0
function trackCurrentMousePosition(event) {
if (document.elementFromPoint(event.clientX, event.clientY).nodeName=="SPAN") {
elementFromCurrentMousePosition=document.elementFromPoint(event.clientX, event.clientY).parentNode
}
else {
elementFromCurrentMousePosition=document.elementFromPoint(event.clientX, event.clientY)
}
return (currentXCoordinate=event.clientX, currentYCoordinate=event.clientY, elementFromCurrentMousePosition)
}
function initPreview(event, obj) {
arg1=event
arg2=obj
setTimeout("setPreviewDataFields(arg1, arg2)", 100)
}
function setPreviewDataFields(event, obj) {
if ('bubbles' in event) {
event.stopPropagation()
}
else {
event.cancelBubble=true
}
if (elementFromCurrentMousePosition!=obj) {
return 0;
}
The code goes on to do all the wonderful stuff I want it to do if execution wasn't cancelled by the previous if statement. The problem is this method is seeming to be really processor intensive.
To sum it up: on page load all my event listeners are registered, cursor position is being tracked by a onmousemove event. Applicable list items have a onmouseover event that calls the initPreview function which just waits a given period of time before calling the actual setPreviewDataFields function. If at run-time the cursor is no longer over the list element the function stops by return 0.
Sadly that's the best I could come up with. If anyone can offer up a better solution I would be very grateful.
Why not just use mouseout to tell when the mouse leaves an element? Running all of that code every time the mouse moves isn't ideal.
Also, you really shouldn't pass a string to setTimeout like that. Instead, pass a function. As a bonus, you can get rid of those evil global variables arg1 and arg2. With those being globals, I think you will run into issues if init gets called again before the timeout expires.

How can I chain click events with Mootools so that they execute in order?

I have a series of buttons that fire the list function when they are clicked. The list function itself contains an AJAX request and a bunch of other stuff before and after which loads in a separate section of the page.
var list = function() { }
$$('.buttons').addEvent('click', list);
Everything works fine if I wait for list to complete before clicking on another button. However, if I click on two buttons quickly, the page will start to return incorrect responses. In fact, it appears as though the responses get out of sync by 1. So if I quickly click on button A then button B, it will load response A. Then if I click (much later) on button C, it will load response B.
There are two ways I can see to solve this:
1) Remove the click event from other buttons when any button is clicked and then restore it when list is complete. Unfortunately, I have tried placing $$('.buttons').removeEvents() at the top of the list function and then $$('.buttons').addEvent('click', list); at the bottom but this has no effect on the problem.
2) Chain the click events so that list is only ever executed when the preceding list has finished.
So can anybody tell me how to get the second solution working? Additionally, if anybody knows why the first solution doesn't work and why I get the weird delayed AJAX response behaviour, that would be great!
The first solution doesn't work because events on an element are fired in order, but are executed asynchronously. You'll need to setup a queue of callbacks that you can process when the event is triggered.
Here's the basic idea:
addQueuedEvent = function(node, event, callback) {
if ( typeof callback != "function" ) {
throw "Callback must be a function";
}
event = event.toLowerCase();
var eventQueue = "_" + event;
if ( !node.hasOwnProperty(eventQueue) ) {
node[eventQueue] = [];
}
node[eventQueue].push(callback)
};
processEventQueue = function(node, event) {
var eventQueue = "_" + event;
if ( node.hasOwnProperty(eventQueue) ) {
for ( var i=0, l=node[eventQueue].length; i<l; ++i ) {
node[eventQueue][i]();
}
}
};
And the usage:
var someElement = $("#some-element");
addQueuedEvent(someElement, "click", callback1);
addQueuedEvent(someElement, "click", callback2);
addQueuedEvent(someElement, "click", callback3);
someElement.addEvent("click", function() {
processEventQueue(this, "click");
});
The syntax checks out, but this is not tested. Hope that helps.
i would personally just set a global / scoped variable in your class or whatever - something like 'isClicked = false'.
then simply check at the the the click event function, something like:
var isClicked = false, click = function() {
if (isClicked)
return false;
isClicked = true;
// ... do stuff, chained or otherwise...
// when done, make click function work again:
isClicked = false; // you can do this onComplete on the fx class also if you use it
};
i would go against chaining events with effects - if you have an animation going on, simply wait for it to end--otherwise it can get messy for any trigger happy user that thinks double clicking is the way to go. an alternative is to stop / cancel any effects that are taking place on a new click. for instance, you can stop any tweens etc through FX by something like:
if (isClicked === true) fxinstance.cancel();
http://mootools.net/docs/core/Fx/Fx
the other thing you can do is look at the mootools .chain class
http://mootools.net/docs/core/Class/Class.Extras#Chain
and also, on any fx instances, you can pass on link: "chain" and simply queue them up.
good luck

Categories

Resources