why doesn't removeEventListener work? - javascript

I'm not sure what's wrong here, but testing in the chromium and firefox, I find that I'm doing it wrong with respect to removing an EventListener from an element in javascript.
The context is a canvas game. At first, there's a splash screen shown where you click to begin the game. After you click to begin, I want to remove the listener.
The main point of interest is the removeEventListener in the startGame function. It doesn't throw an error. And the code executes (I see the game starting message in the console and I can see that "this" is the Game instance). I'm totally confused why if I keep on clicking on the canvas runs startGame each time. The expected behavior is that clicking there does nothing once the EventListener is removed.
Help!
function Game(canvas) {
this.c = canvas;
this.ctx = this.c.getContext("2d");
this.c.width = CANVAS_WIDTH;
this.c.height = CANVAS_HEIGHT;
// Background image
this.bgReady = false;
this.bgImage = new Image();
this.bgImage.onload = function () {
window.g.bgReady = true;
};
this.bgImage.src = MAIN_BACKGROUND;
}
Game.prototype.setSplash = function() {
if (this.bgReady) {
this.ctx.drawImage(window.g.bgImage, 0, 0);
this.ctx.font="48px Helvetica";
this.ctx.textAlign = "center";
this.ctx.fillStyle="rgb(0,0,255)";
this.ctx.fillText("Click To Start",310,240);
document.getElementById("cnvs").addEventListener(
'click',this.startGame.bind(this),true);
} else {
// since setSplash is an early function
// wait a bit for the background image and then try again
setTimeout(this.setSplash.bind(this),100);
console.log("bgImage not ready...");
}
}
Game.prototype.startGame = function() {
console.log("game starting ...");
console.log(this);
// step 1, remove the click listener for this function
// why isn't this working?!
document.getElementById("cnvs").removeEventListener(
'click',this.startGame,true);
}
...
// other stuff ...
function initialize() {
// Get the canvas
var c = document.getElementById("cnvs");
// Create a game object
window.g = new Game(c);
// Set the splash page
g.setSplash();
}
window.onload=initialize;
Further info:
I also had a version where the non-working removal was written as:
this.c.removeEventListener('click',this.startGame,true);
Same behavior as the code referenced above.
EDIT: in reply to the first answer by mczepiel
I'm trying to implement your answer like this:
Typer.prototype.setSplash = function() {
if (this.bgReady) {
this.ctx.drawImage(window.t.bgImage, 0, 0);
this.ctx.font="48px Helvetica";
this.ctx.textAlign = "center";
this.ctx.fillStyle="rgb(0,0,255)";
this.ctx.fillText("Click To Start",310,240);
var boundFunction = this.startGame.bind(this);
document.getElementById("cnvs").addEventListener(
'click',boundFunction,true,boundFunction);
} else {
// since setSplash is an early function
// wait a bit for the background image and then try again
setTimeout(this.setSplash.bind(this),100);
console.log("bgImage not ready...");
}
}
Typer.prototype.startGame = function(boundFunction) {
console.log("game starting ...");
console.log(this); // strangely, now this is an Object rather
// than Game, it still has the properties of
// Game tho
// step 1, remove the click listener for this function
// still isn't working...
document.getElementById("cnvs").removeEventListener(
'click',boundFunction,true);
}
I think I understood your suggestion, but perhaps not. The code above still doesn't remove the listener. Any help appreciated.

You'll need to store a reference to the result of calling this.startGame.bind(this) and pass that same value to both addEventListener and removeEventListener
The remove call is expecting to remove the exact same object that was added as a listener.
Likely duplicate of removeEventListener is not working and others if you want to see the same issue in various flavors.
EDIT untested off-the-cuff suggestion:
Typer.prototype.setSplash = function() {
if (this.bgReady) {
// draw stuff
var canvasElement = document.getElementById("cnvs");
var dismissSplash = function (evt) {
canvasElement.removeEventListener('click', dismissSplash, true);
this.startGame();
}.bind(this);
canvasElement.addEventListener('click', dismissSplash, true);
} else {
// try to show splash later
}
}
Typer.prototype.startGame = function() {
// start game
}

Related

Detect sound is ended in THREE.PositionalAudio?

I want to detect when sounds is ending, but all examples that i found not working.
// Create sound
var sound1 = new THREE.PositionalAudio( listener );
sound1.load( 'sounds/Example.ogg' );
sound1.setRefDistance( 30 );
sound1.autoplay = false;
sound1.setLoop(false);
mesh1.add( sound1 );
// Start sound
setTimeout(function() {
sound1.play();
}, 2000);
// Try to detect end #1
sound1.onended = function() {
console.log('sound1 ended #1');
};
// Try to detect end #1
sound1.addEventListener('ended', function() {
console.log('sound1 ended #2');
});
Live example: http://codepen.io/anon/pen/wMRoWQ
Three.Audio is wrapper around a buffer source audio object. https://github.com/mrdoob/three.js/blob/ddab1fda4fd1e21babf65aa454fc0fe15bfabc33/src/audio/Audio.js#L12
It looks like it overrides the onended event and binds it to its own function, so we just do the same:
sound1.source.onended = function() {
console.log('sound1 ended');
this.isPlaying = false; /* sets Three wrapper property correctly */
};
We set isPlaying = false because this is what Three's Audio onended function does, which we just overrode.
working pen: http://codepen.io/anon/pen/GoambE
I just had to solve this problem in 2020. I wasn't looking at the docs right at first, but it is correctly listed, it seems.
Similar to the previously correct answer, but a bit different now (that solution did not work for me):
var sound1 = new THREE.PositionalAudio( listener );
sound1.onEnded = () => console.log("track ended")
Here's the appropriate docs: https://threejs.org/docs/#api/en/audio/Audio.onEnded
There's not much there, basically:
.onEnded () : null
Called automatically when playback finished.
Things that did not work for me:
sound1.onEnded(() => {})
el.object3D.source.onended(() => {})
sound1.source.onended(() => {}); // previous answer
If you do this, you can intercept a call, but I don't think it's necessary anymore:
THREE.Audio.prototype.onEnded = function() {
console.warn("arguments to onEnded!", arguments)
};

Can't remove the event Listener, not understanding my mistake

I have a event listener which is assigned to a div using the click listener.
The problem is it does not remove the listener it just keeps adding to it.
This is the script where it is happening:
var resourceCheckout = function (quantity,btn){
//quantity = integer
// btn = document.getElementByID('the_div');
var calculate = function (e) {
var allowed = false;
for(var i in test){
var total = quantity * test[i].q;
if(total > 200){ //if total > 200 remove eventListener
allowed = false; break;
} else {
allowed = true;
}
};
if(allowed){
btn.addEventListener('click',assign,false);
} else {
btn.removeEventListener('click', assign ,false);
}
};
var assign = function (e) {
do_it(quantity); //this gets called more than once when it should be
// a maximum of one
};
calculate();
};
I decided to make a working jsfiddle to show you it in action, simply move the slider then hit the button, it will then call the function the listener is assigned to and count how many times it was called.
JSFIDDLE LINK
I hope someone can explain my mistake as its getting confusing to understand as my script gets more complicated!
The problem is that the function you try to remove has never been added, and so nothing is removed.
Every call to your resourceCheckout function creates a new assign function, which is then used by your calculate function. Since it's a new assign function, it cannot have ever been added to the button, so calling removeEventListener and passing it in has no effect.
If a previous call to resourceCheckout put an assign on a button, you have to use that same function reference to remove it.
This may be clearer with a simpler example:
function foo() {
function bar() {
}
return bar;
}
foo creates a new bar function every time it's called. So:
var b1 = foo();
var b2 = foo();
console.log(b1 === b2); // false
b1 and b2 are different functions.
To make your resourceCheckout work, you'd need to remember the previous assign and use that to remove the handler.
You asked in a comment why this doesn't apply to the slider code in the fiddle, which looks like this (since it's not in the question):
// The OP's slider code
var initSlider = function (el,func,data) {
var clickX = null;
var startSlider = function (e) {
clickX = e.pageX;
document.body.addEventListener('mousemove', calc, false);
document.body.addEventListener('mouseup', endSlider, false);
};
var endSlider = function (e) {
document.body.removeEventListener('mousemove', calc, false);
document.body.removeEventListener('mouseup', endSlider, false);
};
var calc = function (e) {
var dif = e.pageX - clickX;
clickX = e.pageX;
var parentWidth = parseInt(window.getComputedStyle(el.parentNode).width);
var childWidth = parseInt(window.getComputedStyle(el).width);
var childLeft = parseInt(window.getComputedStyle(el).left);
var left = childLeft + dif;
if (left < 0) { left = 0; }
else if (left > (parentWidth-childWidth)) { left = (parentWidth-childWidth); }
el.style.left = left + 'px';
func(data,left,parentWidth-childWidth);
};
el.addEventListener("mousedown", startSlider, false);
};
There, the startSlider code successfully removes a handler from the body element. It works because the startSlider code has a reference to the endSlider function that was created at the same time startSlider was created (the same call to initSlider). So since startSlider has a reference to the same function that was used with addEventListener, removeEventListener works.
This code potentially adds a new listener for the event each time it is called.
Also note that every time you call ResourceCheckout a new different function object is created for variable assign and therefore the removeEventListener will never succeed because that specific function object passed has just been created and was never registered for the event (what was registered was a different function object, created in a previous call).

Canvas game Error

I'm trying to create a simple game loop and trying to use OOP paradigm in JS. Here is my code:
HTML
<body onload="Game.OnLoad('gameField')" onkeydown="Game.KeyDown(event)">
<p id="info">1</p>
<p id="info2">2</p>
<canvas id="gameField"
width="896px"
height="717px"
class="game-field"
style="border: 4px solid aqua"
onclick="Game.MouseClick(event)"></canvas>
</body>
JavaScript
// class Timer
// version: 1
// only tick() functionality available
// right now
function Timer() {
var date = new Date();
var prevTick = 0;
var currTick = 0;
// update timer with tick
this.tick = function() {
prevTick = currTick;
currTick = date.getTime();
}
// get time between two ticks
this.getLastTickInterval = function() {
return currTick - prevTick;
}
}
// global object Game
// which handles game loop
// and provide interfaces for
var Game = new function() {
// variables:
this.canvas = 0;
var gameLoopId = 0;
this.timer = new Timer();
// events:
this.KeyDown = function(e) {}
// game loop:
this.Run = function() {
this.timer.tick();
this.Update(this.timer.getLastTickInterval());
this.Draw();
}
this.Update = function(dt) {
document.getElementById("info").innerHTML = dt;
}
this.Draw = function() {}
this.StopGameLoop = function() {
clearInterval(gameLoopId);
}
this.OnLoad = function(canvasName) {
this.canvas = document.getElementById(canvasName);
this.timer.tick();
// start game loop
setInterval(this.Run, 1000);
}
}​
(Fiddle)
I'm trying to make Game class global. Other classes must be instantinated using new.
Classes Game and Timer are placed in different files called Game.js and Timer.js. When I run this code in Chrome I got an error in DevTools: "Uncaught TypeError: Cannot call method 'tick' of undefined" in Game.Run function at the line this.timer.tick();
So I wonder, what is the problem with my code? Thanks for reply.
Your problem is with the context. When you're calling tick this is window, not Game.
You can handle this, for example, by setting:
var self = this;
this.Run = function() {
self.timer.tick();
self.Update(self.timer.getLastTickInterval());
self.Draw();
}
Few things to consider in your code :
Concerning your issue, methode like setInterval or setTimeout loose the context. Easiest solution is to bind a context to the callback :
setInterval(this.Run.bind(this), 1000);
Secondly, avoid adding private methode inside an object function. In that case, every instance of Game will have its own set of functions (memory leak).
Prefer using prototypes :
function Game () {
this.canvas = null;
}
Game.prototype = {
init: function() {}
render: function () {}
};
Lastly, I see you redraw every seconds, which is ok. But if you want 60fps, you can use requestAnimationFrame for drawing oriented loops.
ps: just to be very nitpicking, functions' name should be camelCase so starting with lower case.

javascript time event issue (setTimeout/clearTimeout)

I have always had trouble working with time events. Could someone please explain why A doesn't work and B does? The only difference is in A I put the event binding in a function. Don't worry about the function close, it has nothing to do with the question. When I test A, there is no js errors but timer is not cleared.
A ->
Test.Navigation = (function() {
var openTimer = null;
var closeTimer = null;
var addListeners = function() {
$('.hover_container').on('mousemove', function(e) {
clearTimeout(closeTimer);
});
$('.hover_container').on('mouseleave', function(e) {
// set the close timer
var container = this;
closeTimer = setTimeout(function() {
//has the mouse paused
close(container);
}, 750);
});
};
return {
init : function() {
addListeners();
}
};
})();
B ->
Test.Navigation = (function() {
var openTimer = null;
var closeTimer = null;
$('.hover_container').on('mousemove', function(e) {
clearTimeout(closeTimer);
});
$('.hover_container').on('mouseleave', function(e) {
// set the close timer
var container = this;
closeTimer = setTimeout(function() {
//has the mouse paused
close(container);
}, 750);
});
var addListeners = function() {
// nothing here
};
return {
init : function() {
addListeners();
}
};
})();
Edit: Please ignore the container part, it has nothing to dow ith the question it is simply part of the full code that I did not take out
A is binded before the object exists where the init is called. Because your return a new object. If you are using, 2 objects are created. 1 with the vars en binds. and 1 with the returns.
B is working because you create a function where the elements are initialized and use the right scope. A is not working because the bindings are on the wrong scope because your create 2 objects:
new Test.Navigation(); // Create 1 object
// Create second object.
return {
init : function() {
addListeners();
}
};
Youd better get a structure like this, then it should work aswell:
Test.Navigation = (function() {
// Private vars. Use underscore to make it easy for yourself so they are private.
var _openTimer = null,
_closeTimer = null;
$('.hover_container').on('mousemove', function(e) {
clearTimeout(_closeTimer );
});
$('.hover_container').on('mouseleave', function(e) {
// set the close timer,
// use $.proxy so you don't need to create a exta var for the container.
_closeTimer = setTimeout(
$.proxy(function() {
//has the mouse paused
close(this);
}, this)
, 750);
});
this.addListeners = function() {
// nothing here
};
this.init = function() {
this.addListeners();
}
// Always call the init?
this.init();
return this; // Return the new object Test.Navigation
})();
And use it like
var nav = new Test.Navigation();
nav.init();
Also as you can see I upgraded your code a bit. Using $.proxy, _ for private vars.
Your use of this is in the wrong scope for the first approach.
Try
var openTimer = null;
var closeTimer = null;
var self = this;
and then later
var container = self;
In your code for example A,
$('.hover_container').on('mouseleave', function(e) {
// set the close timer
var container = this;
this is actually referring to the current $('.hover_container') element.
Also, since setTimeout will wait before the previous setTimeout finishes to start again, you can get discrepancies. You may want to switch to setInterval because it will issue its callback at every interval set regardless of if the previous callback has completed.
My guess is that in the calling code, you have a statement new Test.Navigation() which, for B, addListeners is called at the time of new Test.Navigation(). In A, you return an object ref that calls an init function. Can you verify that init() is called?
I.e. in A, init() has to be called before the handlers are added. In B, the handlers are added everytime you instantiate Test.Navigation --- which, depending on the calling code, could be bad if you intend to instantiate more than one Test.Navigation() at a time.

Making sure a javascript function is not called, if called already

I am using google maps for a project. I need to be able to hide and display multiple maps. I cannot use a basic toggleDiv type function alone. This is because Google maps will ignore the intended size of the div when the div is set to display: none from CSS. (for whatever reason it is fine with being toggled by javascript.) I could use GSize(width, height) but it cannot handle percentages. Since I need the map to be 100%, 100%, this is not an option. I figured out a way around it which is to call the second map's function using onClick, rather then loading all the functions using body onLoad. But, then the zoom of the map is not saved and the map is just reloaded.
So, I need to check if a function has been called, and if so, do not recall it. I cannot figure out how to do this. Any help is appreciated.
Thank you.
Another "lazy" variant:
var doOnce = function () {
// do something
doOnce = function () {}
}
As for the original problem, if the function you want to run once is the click event handler, you'd better remove the click event listener.
Define a variable as such. var beenFired = false;
in the function do this:
function myFunction()
{
if(!beenFired)
{
//TODO FUNCTION
beenFired = true;
}
}
That will check to see if hasn't been fired yet and if not fire it. You could even do it around the function call itself.
if(!beenFired)
myFunction()
function myFunction()
{
//TODO FUNCTION
beenFired = true;
}
var func = (function(){
var first = true;
return function( a, b, c){
if ( !first ){
return;
}
first = false;
console.log( arguments );
}
})();
func( 1, 2, 3); // in console : [ 1, 2, 3]
func( 1, 1, 1); // nothing happens;
If I understand:
var isCalled = false;
function f() {
if (!isCalled) {
isCalled = true;
// ...
}
}

Categories

Resources