How do you pass event to a setTimeout - javascript

I currently have:
var content = document.querySelectorAll(".content");
var myButtons = document.querySelectorAll(".more-button");
function enableContent(evt){
// remove class d-none so it can appear
content[evt.currentTarget.param].className = content[evt.currentTarget.param].className.replace(/\bd-none\b/g, "");
}
for(var i=0; i<myButtons.length; i++){
myButtons[i].addEventListener("click", enableContent);
myButtons[i].param = i;
}
Each myButton has it's own content. I'm trying to make content appear slowly when myButton is clicked like so:
for(var i=0; i<myButtons.length; i++){
myButtons[i].addEventListener("click", function(){setTimeout(enableContent, 2000, evt)}));
myButtons[i].param = i;
}
But it doesn't work, there is a problem with the parameter evt. How do I pass in evt to setTimeout?

I would suggest making solution easier by setting the setTimeout inside the enableConent directly
function enableContent(evt){
// remove class d-none so it can appear
setTimeout( () => {
content[evt.currentTarget.param].className =
content[evt.currentTarget.param].className.replace(/\bd-none\b/g, "");
}, 2000);
}
But if you need to learn why the event is not being passed to the enableContent event handler we need to know how setTimeout and function binding works.
setTimeout take a callback (fancy word for passing a function to another function) and a delay time(that guarantees at least x time before calling the callback function.
so using setTimeout should be used like
setTimeout( enableContent, 2000 );
yet this code won't pass the event so we need to bind the function enableContent with the event object so when the event handler is called the function know and access the event object properly
myButtons[i].addEventListener("click", function(event){
setTimeout( enableContent.bind(event), 2000);
});
binding in js has many types, implicit, explicit, hard, & new binding. using bind function bind the enalbeConent with the event and return a new function that is hard bound with the event object.

You need to pass the event (evt) to the function itself, and I think maybe you are moving the wrong way using the setTimeout, maybe you don't need to use a setTimeout? You can use css animation or transition with *-delay properties?
But if you are using setTimeout, you need to clear timeout after every click using the clearTimeout method:
myButtons[i].addEventListener("click", function(evt) {
if(evt.currentTarget.timer) {
clearTimeout(e.currentTarget.timer);
}
evt.currentTarget.timer = setTimeout(enableContent, 2000);
}));
Example: https://jsfiddle.net/6kcq2ma8/

Easy peasy!
window.addEventListener("load", (event)=> {
let setTimeoutHandler = (event) =>{
console.log(event);
}
setTimeout(()=>{
setTimeoutHandler(event);
}, 1000);
});

Related

How to create setTimeOut in one function and destroy it in another?

im trying to do something similar to google docs. It's an autosave system/loop. I'll save a document after a few seconds of inactivity (by calling a save method from the class).
If there is some activity during that few seconds, then I will reset/prolong the timer by another few seconds. This cycle will continue until the save method is being called, of which i will break the loop and wait for another activity to start the loop again.
Here's some pseudo code i came up with:
doc.on('mouse:up', (event) => {
... //<--- Initialize the timer on mouse:up event
... //<--- When time is up, execute the save method
})
doc.on('mouse:down', (event) => {
... //<--- prolong timer initialized in the mouse:up event if it is available.
}
However, i don't think this way of doing is possible as I cant access the same initialized setTimeOut object. Is there a good way to implement this? would love to hear from yall.
There are a few ways to handle this.
Approach: Basic
Cons: Low level, behavior is not as immediately obvious.
const saveDelayMs = 1000;
let timeoutId;
doc.on('mouse:up', (event) => {
timeoutId = setTimeout(save, saveDelayMs);
});
doc.on('mouse:down', (event) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(save, saveDelayMs);
}
Approach: Debounce Abstraction
Pros: Higher level, code more readable
// Trigger 1000ms after last call
const debouncedSave = debounce(save, 1000);
doc.on('mouse:up', (event) => {
debouncedSave();
});
doc.on('mouse:down', (event) => {
debouncedSave();
});
function debounce(fn, debounceDelayMs) {
let timeoutId;
return () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(fn, debounceDelayMs);
};
});
Lodash provides a debounce function, which I recommend over creating your own.
Simply initialize the variable outside of both functions. This way, it will be accessible inside either of them :
let theTimeout = null;
doc.on('mouse:up', (event) => {
theTimeout = setTimeout(...); //<--- Initialize the timer on mouse:up event
})
doc.on('mouse:down', (event) => {
clearTimeout(theTimeout);
theTimeout = setTimeout(...); //<--- prolong timer initialized in the mouse:up event if it is available.
}
You could use a debouncing method (see here: https://davidwalsh.name/javascript-debounce-function), but probably the clearest if you are going to look at multiple types of inputs (say, dragging an object stops saving, but also touch, and also pointers etc..), it's best to use a global flag and use it to toggle the save flag from anywhere in your code, so anybody can trigger the suggestion for saving:
let saveFlag = false;
setInterval(function(){
if( saveFlag === true ){
console.log( 'save' );
saveFlag = false;
} else {
console.log( 'skip save' );
}
}, 3000);
// Now you can trigger a save on any kind of event,
// and expect it to happen within 3 seconds.
document.addEventListener( 'click', e => saveFlag = true);
document.addEventListener( 'touchend', e => saveFlag = true);
document.addEventListener( 'pointerup', e => saveFlag = true);
This will also prevents duplicate timers from potentially being created, and has a maximum delay or 3000ms in my example, with very little overhead.

addEventListeners and React not working as planned

I try to make a timeline that is dynamicaly loaded when scrolling.
Due to this I need the scroll event, combined with React.
window.addEventListener("scroll", console.log('scroll'), true);
This should console log a scroll every time I scroll, but it just log it once, then nothing
EDIT:
If I use it in my real application now, with this code :
callbackFunc = () => {
for (var i = 0; i < items.length; i++) {
if (this.isElementInViewport(items[i])) {
items[i].classList.add("in-view");
}
}
}
componentDidMount() {
window.addEventListener("load", function (event) { this.callbackFunc(); }, true);
window.addEventListener("resize", function (event) { this.callbackFunc(); }, true);
window.addEventListener("scroll", function (event) { this.callbackFunc(); }, true)
}
It says callbackFunc is not a function
This isn't working because the event listener expects a function as it's second argument (or an object implementing the EventListner interface) which it will call when the "scroll" occurs. console.log is a function, but console.log("scroll") isn't a function, its a called function. And so the value you are technically putting as the second argument is undefined (as console.log("scroll") returns undefined).
const a = console.log("scroll");
console.log(a); // undefined (the value of your second arugment)
So, you need to wrap the console.log() in a function, so the function is called, which will then call your console.log() method. You can achieve this by using an ES6 arrow function:
window.addEventListener("scroll", _ => console.log('scroll'), true);
window.addEventListener("scroll", _ => console.log('scroll'), true);
body {
height: 200vh;
}
As per your edit, the arrow function should solve your issue. Currently, the window is calling your event listener function, so this is referring to the window, not the context of your app. Using an arrow function should fix this (as an arrow function doesn't have it's own this).
Try this:
window.addEventListener("scroll", function(event) { console.log('scroll'); }, true);
Try adding it in reactjs
componentDidMount() lifecycle function

Event Handler as a name vs anonymous functions

While implementing a closure function, I have noticed that if I provide a "named function" as an event handler then it gets executed straightaway when the event gets attached to the buttons. However, if I keep the function inline as an anonymous function then it doesn't execute straightaway and fires only on the
event happens. Please can anyone explain this behaviour?
var buttons = document.getElementsByTagName('button');
//function buttonHandler(buttonName){
//// return function(){
//// console.log(buttonName);
//// }
// alert("hello");
//}
var buttonHandler = function(name){
alert(name);
}
for(var i = 0; i < buttons.length; i += 1) {
var button = buttons[i];
var buttonName = button.innerHTML;
button.addEventListener('click', buttonHandler(buttonName));
button.addEventListener('click', function(buttonName){
alert("hi");
});
}
Many Thanks!
This has nothing to do with the function being named. This is about your code explicitly calling the function.
If you put (some,arguments) after a function then you will call it.
!foo() // calls the function
!function () { }(); // Also calls the function
So:
button.addEventListener('click', buttonHandler(buttonName));
Calls buttonHandler, passing buttonName as an argument to it
Calls addEventListener, passing "click" and the return value of 1 to it
buttonHandler has no return statement, so that return value of undefined which isn't a useful thing to pass to addEventListener.
The commented out version of buttonHandler returns a function. That would be a useful thing to pass to addEventListener.
As pointed out in the answers above the code
button.addEventListener('click', buttonHandler(buttonName));
is making direct call the function so if you only need to pass parameters to the handler function you may use an anonymous function instead of directly calling the function as recommended here (https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
I updated my code as below
button.addEventListener('click', function (){ buttonHandler(buttonName)}, false);

Javascript : setTimeout use on an anonymous function expression

I recently started learning javascript to help maintain some stuff and ran into this issue today:
this.moveChar = function(){
// body here
setTimeout(moveChar,1000);
}
this.initialise= function(){
this.moveChar();
}
When initialise is called, I expected moveChar to be called, then repeated call itself once every 1000ms
However, what actually happens is moveChar gets called once then that's it. Based on other stackoverflow posts I read, I suspected it might be something to do with the function being expressed rather than declared. I have tried to use
this.moveChar = function recMove(){
// body here
setTimeout(recMove,1000);
}
without luck either.
Any suggestions on how I can fix this?
EDIT: Main thing I need to do is have the moveChar function called once every second. If there is a better approach than setTimeout recursion, I'm open to it
this.moveChar is not the same as moveChar, unless this is the global scope object like window.
this.moveChar is a property on an object, while moveChar would reference any variable in a visible scope chain.
You can change it to a couple of things in order to keep scope of whatever object is being used:
Using an arrow function
this.moveChar = function(){
// body here
setTimeout(()=>this.moveChar(),1000);
}
Using .bind()
this.moveChar = function(){
// body here
setTimeout(this.moveChar.bind(this),1000);
}
You might want to consider using setInterval() which is the more appropriate API for this task.
What setInterval() does is - it will repeatedly call the given function upon a certain interval is reached.
See:
https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval
Quote:
Repeatedly calls a function or executes a code snippet, with a fixed
time delay between each call. Returns an intervalID.
Example:
Assuming moveChar() contains your operation logic. Then to repeat it you'll do this 1 line.
let moveChar = function(){
// Do stuff
console.log("Hi thanks for calling me!");
}
setInterval(moveChar, 1000);
Are you using this in side body here?
If so, you should bind correct context while call.
this.moveChar = function(){
// body here
setTimeout(this.moveChar.bind(this), 1000);
}
Or use anonymous function:
this.moveChar = function(){
// body here
var that = this;
setTimeout(function(){
that.moveChar();
}, 1000);
}
Or arrow function:
this.moveChar = function(){
// body here
setTimeout(() => this.moveChar(), 1000);
}
Same notes apply to setInterval variant:
this.initialise= function(){
setInterval(this.moveChar.bind(this), 1000);
// var that = this;
// setInterval(function(){that.moveChar();}, 1000);
// setInterval(() => this.moveChar(), 1000);
}
this.moveChar = function(){
// body here
alert('called moveChar');
}
this.initialise= function(){
setInterval(function(){moveChar();},1000);
}
this.initialise();//call here

_.debounce and Iron.controller() in eventmap

I want to implement a debounced search with iron-router where the search query is written into the controllers state. unfortunately the only way I could get _.debounce to work is to pass it directly to the event map like so:
Template.search.events({
'keydown #search': _.debounce(function(event) {
var controller = Iron.controller();
}, 750)
});
unfortunately Iron.controller() doesn't know the context here so an error drops.
But if I nest the debounce inside a function to get the Iron.controller(), _.debounce never fires.
Template.search.events({
'keydown #search': function(event) {
var state = Iron.controller().state;
var q = $(event.currentTarget).val();
_.debounce(function() {
state.set("q", q);
}, 750);
}
});
Has anybody done something similar and a solution to this problem?
I'm not familiar with debounce, but if I'm not mistaking, the following should work:
var doIt = _.debounce(function(func){
func()
}, 750);
Template.search.events({
'keydown #search': function(event) {
var state = Iron.controller().state;
var q = $(event.currentTarget).val();
doIt(function(){
state.set("q", q);
})
}
});
I have found that iron-router uses a fair amount of bare this throughout the code.
Perhaps the scope is not correctly set when you debounce the event handler. If this is the case, you could try binding it to this:
Template.search.events({
'keydown #search': _.debounce(function(event) {
var controller = Iron.controller();
}.bind(this), 750)
});
Maybe it should be bond to events. I'm not sure, as I'm not familiar with this library.
As for the second example, as Peppe L-G correctly suggests it, debounce works by returning a debounced function. In your example, you never call it!
a = function(){};
b = _.debounce(a, 200);
a() // immediate
b() // waits 200 millis, calls a()
Also, it's important to notice that you should not declare the debounced function inside the event handler. Otherwise, you'll always create a new debounced function and all of them will fire at the first opportunity, defeating the purpose.
I recommend you name your debounced handler more appropriately. For example:
var handleKeydownEventDebounced = _.debounce(function(controller, query){
controller.state.set("q", query);
}, 750);
Template.search.events({
'keydown #search': function(event) {
handleKeydownEventDebounced(Iron.controller(), $(event.currentTarget).val());
}
});

Categories

Resources