I have an event handler that is executed only after a specific condition is met, as seen in the pseudo code below:
if(condition)
{
window.ondeviceorientation = function(e){
//my code
}
}
I only need this to run once to capture the gyroscope values and set some sessionStorage values.
How do I disable the ondeviceorientation loop after the first iteration? Here's what I'm currently doing, but not sure it's the best solution:
if(condition)
{
var stop_event_loop;
window.ondeviceorientation = function(e){
if(stop_event_loop) break;
//my code
stop_event_loop = true;
}
}
Simply clear out the event handler, like this:
if(condition)
{
window.ondeviceorientation = function(e){
//my code
window.ondeviceorientation = null;
};
}
Depending on your target browsers, I would recommend using the appropriate DOM-spec implementation for hitching functions to events, and then make sure you're stopping the handling of the event elsewhere.
So for the DOM 2 spec, you would use the EventTarget.addEventListener(); function to add something like:
window.addEventListener('deviceorientation', eventHandler, onCapture);
Then from within the event handler, you should check for and use the "e.preventDefault()" function to stop the code from executing if the "stop_event_loop" flag has been set to true, as well as return false to handle browser-specific implementations of event propagation.
Although, keep in mind that the scope of that "stop_event_loop" variable means it will be instantiated every time you step into that conditional block of code, so it will always be false when that function is tied to the event. If that's a close approximation to your current implementation, that's probably why your code is triggered every time the event is fired. You need to move that variable declaration somewhere where it will be a bit more permanent. I recommend NOT using the global scope, but anywhere outside of that conditional block should prevent it from being re-instantiated every time that function would trigger.
So your code would look like:
//elsewhere in your code
var stop_event_loop;
//your event loop
if(condition)
{
window.addEventListener('deviceorientation', eventHandler, onCapture);
var eventHandler = function (e) {
if (stop_event_loop) {
if (e.preventDefault) {
e.preventDefault();
}
return false;
} else {
//your code
}
}
}
Related
I'm creating an HTML5 Canvas Snake game with Javascript, and in trying to make my code more object-oriented, I came across a situation where I wasn't sure if what I was doing was valid.
Game.prototype.gameOver = function() {
game.isOver = true;
clearInterval(intervalId);
console.log("Game Over!");
window.addEventListener("keydown", function(e) {
if (e.code === "Space") {
game.reset();
}
})
}
game.gameOver() gets called when the snake collides with the walls or itself.
Is it better to just add the reset eventListener at the global scope level and check if the game is over, or is this perfectly fine?
Edit: I realized just after posting this that a new event listener was getting added each time gameOver was called, and that was the source of my issues. I've since fixed the code so that there is never more than once instance of the event listener at once.
As long as you remove that listener again after it kicks in, this should be fine, with the only tricky bit being that you can only remove a listener if you remove it in exactly the same way you added it.
Using modern JS class syntax here instead of the legacy prototype syntax:
class Game {
constructor(...args) {
// ...
this.reset();
}
reset() {
this.isOver = false;
// ...
// If reset() triggered from a window keydown event, remove
// that keydown listener so it will only kick in once:
if (this.resetHandler) {
window.removeEventListener(`keydown`, this.resetHandler);
this.resetHandler = false;
}
}
gameOver() {
this.isOver = true;
console.log(`Game Over!`);
// Create a function that can handle a keydown event
// and calls reset() if it's the right key, and bind
// it so we can remove it later:
this.resetHandler = (evt) => {
if (evt.code === `Space`) {
this.reset();
}
};
window.addEventListener(`keydown`, this.resetHandler);
console.log(`Press "space" on the page to continue...`);
}
}
Note that arrow function, which makes sure that this gets preserved to what it was at declare time (i.e. the game instance this function's running for), rather than function(evt) { ... } in which this will be whatever the scope is at runtime (which for event listeners will be global scope, i.e. window).
Consider following:
$('#theElement').on('click',function(){
$(this).animate(...);
$(this).doThis(...);
$(this).doThat(...);
$('anotherElement').animate(...);
$('anotherElement').doThis(...);
$('anotherElement').doThat(...);
});
As you see, here is a simple delegate function for onClick event. Now how is possible to make during this function execution, no other event be triggered on "#theElement"?
I tried to use preventDefualt(), but it stops whole execution which means that animate(), doThis() etc will not run too.
Set a Boolean flag on the element when the function starts.
$('#theElement').on('click',function(){
$(this).data('flagname',true);
// ...
Test for the flag in your other events.
if (!$(this).data('flagname')) { // !(undefined) is true
// run code
}
Clear the flag when your animations are complete.
var $this = $(this); // 'this' is locally scoped
$this.animate(/* ... */, function() {
$this.data('flagname',false);
});
You have to nest them like this:
$(".yourclass").animate({}, time, function(){
$(".yoursecondclass").animate({}, time, function(){
});
});
I'm wondering which would be the proper way to deal with events which depend on the status of a variable.
Right now, I have a listener which it is only added if the option isTablet is set to true. (as if not, it breaks in old versions of IE). So it looks like this:
if(options.isTablet){
document.addEventListener('touchmove', function(e){
....
});
}
Now, I'm having troubles if I want to change the variable isTablet dynamically with a setter and It won't load the event touchmove.
$.fn.myPlugin.setIsTablet = function(value){
options.isTablet = value;
}
I guess the simple way is always adding the event and inside it deciding whether or not to execute the code:
document.addEventListener('touchmove', function(e){
if(options.isTablet){
....
}
});
But throws an error in IE 8:
Object doesn't support property or method 'addEventListener'
What would be the way of doing it?
Thanks.
Generally, I would always add the listener and check the condition inside. To avoid your error, since you're using jQuery, just use jQuery:
$(document).on('touchmove', function(e){
if(options.isTablet){
....
}
});
If you have a handler that is called very often, you could consider turning it off when not needed. Something like this:
function myHandler(e) { ... }
$.fn.myPlugin.setIsTablet = function(value){
options.isTablet = value;
if (value) {
$(document).off('touchmove').on('touchmove', myHandler);
} else {
$(document).off('touchmove');
}
}
Be careful not to bind the handler more than once (like if true is sent to setIsTablet more than once in a row). You could also use a flag instead of unbinding/binding like I've shown.
In source code here
http://www.daftlogic.com/sandbox-javascript-slider-control.htm
There is these instructions:
// safely hook document/window events
if (document.onmousemove != f_sliderMouseMove) {
window.f_savedMouseMove = document.onmousemove;
document.onmousemove = f_sliderMouseMove;
}
I don't understand what it does and why it would be safer to do that this way, does someone understand?
It might be that some other code already assigned an event handler to document.onmousemove. The problem with this method, as opposed to addEventListener, is that only one function can be assigned to element.onXXXX. Thus, if you blindly assign a new event handler, an already existing one might be overwritten and other code might break.
In such a case, I would write:
if (document.onmousemove) {
(function() {
var old_handler = document.onmousemove;
document.onmousemove = function() {
old_handler.apply(this, arguments);
f_sliderMouseMove.apply(this, arguments);
};
}());
}
else {
document.onmousemove = f_sliderMouseMove;
}
This way it is ensured that both event handlers are executed. But I guess that depends on the context of the code. Maybe f_sliderMouseMove calls window.f_savedMouseMove anyway.
It is just saving the current hook, presumably so it can call it at the end of its own hook method.
It avoids stamping on some other codes hook that was already set up.
You would expect the hook code to be something like:
f_sliderMouseMove = function(e) {
// Do my thing
// Do their thing
window.f_savedMouseMove();
}
[obligatory jquery plug] use jquery events and you can ignore problems like this...
It appears that this code is storing the function that is currently executed on a mouse move, before setting the new one. That way, it can presumably be restored later, or delegated to, if need be. This should increase compatibility with other code or frameworks.
There are 24 div-objects waiting/listening for a mouse-click. After click on one div-object, I want to remove the EventListener from all 24 div-objects.
for (var i=1;i<=24;i++){
document.getElementById('div'+i).addEventListener('click',function(event){
for (var z=1;z<=24;z++){
document.getElementById('div'+z).removeEventListener()//Problem lies here
}
//Some other code to be run after mouseclick
},false);
}
The problem is that the removeEventListener is nested in addEventListener and I need to define type, listener, caption as attributes to the removeEventListener method. And I think it is impossible to define the listener because of nesting.
I also tried to define a function name, but it didn't worked:
for (var i=1;i<=24;i++){
document.getElementById('div'+i).addEventListener('click',function helpme(event){
for (var z=1;z<=24;z++){
document.getElementById('div'+z).removeEventListener('click',helpme,false);
}
//Some other code to be run after mouseclick
},false);
}
You can tell the event listener to simply fire just once:
document.addEventListener("click", (e) => {
// function which to run on event
}, { once: true });
The documentation says:
once:
A boolean value indicating that the listener should be invoked at most once after being added. If true, the listener would be automatically removed when invoked.
It should work with a named function. If your second approach does not work properly, try storing the initial listener into a variable, like this:
var handler = function(event) {
for(...) {
removeEventListener('click', handler, false);
}
};
addEventListener('click', handler, false);
Ps. if you care about speed, you may wish to consider using just one event handler. You can put the handler into the parent element of the divs, and then delegate the event from there. With 24 handlers your current approach probably doesn't have a very big performance hit, but this is something you should keep in mind if it ever feels slow.
For those who needs to remove after a certain condition (or even inside a loop too), one alternative is using AbortController and AbortSignal:
const abortController = new AbortController();
let handler = function(event) {
if(...) {
abortController.abort();
}
};
addEventListener('click', handler, {signal: abortController.signal});
The same answer:
element.addEventListener("click", (e) => {
// function which to run on event
}, { once: true });
You can read more here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener