I have a DOM element btn and have function with setTimeout which listens on this element.
I want to remove my listener while function is running to prevent multiple calls of setTimeout and time overlapping, but this in my callback function is window? I tried to use bind but it's not working
const checkTable = () => {
this.removeEventListener('click', checkTable);
console.log(this); // i get "window"
setTimeout(() => {
//some code
}, 3000);
};
const app = () => {
const checkBtn = document.querySelector('.check-btn');
checkBtn.addEventListener('click', checkTable.bind(checlBtn))
};
app();
and the same result with onclick event
You can't call .bind() on an arrow function. Arrow functions are used to automatically use the this value from where it's defined. For this to work, your checkTable needs to be a "normal" function.
function checkTable() {
this.removeEventListener('click', checkTable);
console.log(this); // i get "window"
setTimeout(() => {
//some code
}, 3000);
}
Related
So i have a very simple class which does the following:
Adds an event handler on an element on it's creation
The event handler is an instance method of the class
I need a cleanup method to remove the handler on demand
export default class TestClass {
constructor() {
this.element = document.getElementById("test");
this.element.addEventListener("click", this.clickHandler);
}
clickHandler(e) {
console.log("Click handler called");
}
cleanup() {
this.element.removeEventListener("click", this.clickHandler);
}
}
And i have a test where i stub the handler (i need to change it's behavior) and make some checks.
describe("Test", function () {
beforeEach(function () {
this.instance = new TestClass();
this.clickHandlerStub = sinon.stub(this.instance, "clickHandler");
});
afterEach(function () {
this.instance.cleanup();
delete this.instance;
});
it("test case", function () {
document.getElementById("test").click();
expect(this.clickHandlerStub.calledOnce).to.equal(true);
});
});
The expected behavior is to stub (override) the handler (so no logging should appear) and my expectation should pass since it should be called once when the element is clicked.
However it seems that it keeps logging and the expectation fails.
If i change the way i bind the event handler and use an arrow function everything works fine.
this.element.addEventListener("click", () => this.clickHandler());
But then i can't remove the event handler in my cleanup method since addEventListener and removeEventListener need to pass the same handler reference in order to work properly.
I created a reproducable example in codesandbox in case it's helpful.
So what am i missing here?
You can stub TestClass's prototype.
this.clickHandlerStub = sinon
.stub(TestClass.prototype, "clickHandler")
.callsFake(() => {
console.log(`Now it's calling the fake handler`);
});
The cleanup would be
this.clickHandlerStub.restore();
https://codesandbox.io/s/mocha-unit-test-example-forked-sstfz?file=/src/index.js
References:
Restore sinon stub
The idea of stubing prototype
By the way, I prefer not to use this in global context. Instead, I'd create them as let variables.
describe('...', () => {
let clickHandlerStub;
beforeEach(() => {
clickHandlerStub = ...
});
afterEach(() => {
clickHandlerStub.restore();
});
});
The removeEventListener will not work here because the functions are referring to an anonymous function:
const startSelectNode = (stepIndex) => {
document.addEventListener("click", (e) => onClick(e, stepIndex), true);
};
const stopSelectNode = (stepIndex) => {
document.removeEventListener("click", (e) => onClick(e, stepIndex), true);
};
But I can't name the function here because it requires stepIndex which is a local var. How can I make this work?
Yair Cohen's answer has the right idea, but it's missing something. addEventListener requires a function reference and not a function call. In his code, onStepIndex will get executed once and then never again.
To create a function reference and be able to feed it parameters and be able to remove the event listener later, you could use the concept called currying.
const onStepIndex = function(stepIndex) {
return function actualOnStepIndex(event) {
console.log(event);
console.log(stepIndex);
}
}
const handlers = [];
const startSelectNode = (stepIndex) => {
document.addEventListener("click", handlers[stepIndex] = onStepIndex(stepIndex), true);
};
const stopSelectNode = (stepIndex) => {
document.removeEventListener("click", handlers[stepIndex], true);
};
startSelectNode(1); // This adds the event listener for stepIndex = 1
stopSelectNode(1); // This removes the event listener for stepIndex = 1
Basically, by calling onStepIndex you return the actual function, which is now the event handler. We saved the reference to the function in the handlers array and we need that reference if we later want to call removeEventListener.
You can definitely name the function, you just have to do it outside of the function's scope, like this:
function onStepIndex(e, stepIndex) {
// your action here
}
const startSelectNode = (stepIndex) => {
document.addEventListener("click", onStepIndex(e, stepIndex), true);
};
const stopSelectNode = (stepIndex) => {
document.removeEventListener("click", onStepIndex(e, stepIndex), true);
};
I am creating a function that handles a bunch of stuff around pagenating and sorting a table. It contains a key function that submits the db query and updates the display table.
I want to be able to access that inner function/method from both inside the function and also from outside on the object created.
testFunction = function() {
keyMethod = function() {
console.log('ya got me');
};
document.getElementById('test').addEventListener('click', function (e) {
keyMethod();
});
keyMethod();
};
myTest = new testFunction();
myTest.keyMethod();
testFunction = function() {
this.keyMethod = function() {
console.log('ya got me');
};
document.getElementById('test').addEventListener('click', function (e) {
// would have to use bind here which then messes up trying to
// find the correct target etc.
keyMethod();
});
this.keyMethod();
};
myTest= new DrawShape();
myTest.keyMethod();
Creating it the first way means that the keyMethod function is available everywhere within the testFunction but I cant call it from outside.
Creating it the second way means I can do myTest.keyMethod but I then cant call it from within an inner function without using bind everywhere.
Is there a better way..?
You could replace the function provided as callback with an arrow function or use bind the function first like you already said.
testFunction = function() {
this.keyMethod = function() {
console.log('ya got me');
};
// Replace callback by simply providing the function to call.
// This works as long as you don't use the `this` keyword inside the
// provided function.
document.getElementById('test').addEventListener('click', this.keyMethod);
// If your callback method does use the `this` keyword you can either use an
// arrow function or bind the function up front.
document.getElementById('test').addEventListener('click', event => this.keyMethod());
document.getElementById('test').addEventListener('click', this.keyMethod.bind(this));
this.keyMethod();
};
console.log("constructor output:");
myTest = new testFunction();
console.log(".keyMethod() output:");
myTest.keyMethod();
console.log("click event output:");
<button id="test">test</button>
I write code in pure JS and I need to have reusable callback for my event listener within the class. It is required to meet following:
reusable by another functions
event listener needs to be revocable by another functions
I need to pass argument (event)
I need to be possible to call another function from the callback (this.doSomething())
I tried define callback as method and also as function expression but every time I solve one issue another occurred. I have walked through many questions here too but still can not make my code to work.
class Foo {
constructor() {
functionA()
this.howAndWhereToDefineThisCallback = function(event) {
event.preventDefault();
this.doSomething();
}
}
functionA() {
let el = document.getElementById('element1');
el.addEventListener( 'click', howAndWhereDefineThisCallback );
this.functionB();
}
functionB() {
let el = document.getElementById('element1');
el.removeEventListener( 'click', howAndWhereToDefineThisCallback );
}
doSomething() {
// something meaningful
}
}
How can I modify my code to use it the way I just described?
Here you have an implementation:
// Callback defined outside the class.
function callback(event) {
this.doSomething();
}
class Foo {
constructor(cb) {
// Important: You have to bind it.
this.cb = cb.bind(this);
this.functionA();
}
functionA() {
let el = document.getElementById('element1');
el.addEventListener('click', this.cb);
}
functionB() {
let el = document.getElementById('element1');
el.removeEventListener('click', this.cb);
}
doSomething() {
console.log('doing something...');
}
}
const foo = new Foo(callback);
// foo.functionB();
<button id="element1">
Click here
</button>
If you want to reuse your callback function, simply put it outside of your class scope. In order to call another function from your callback, just put that function as a argument of your callback, for example:
var howAndWhereToDefineThisCallback = function(event, anotherCallback) {
event.preventDefault();
if (anotherCallback) anotherCallback();
}
To use the callback in your class method:
el.removeEventListener( 'click', function(event) {
howAndWhereToDefineThisCallback(event, this.doSomething);
});
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