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);
});
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();
});
});
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>
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);
I'm not being able to make a function of an Event Listener self invoke and the listener to work.
The following code executes the function, but the Event Listener don't work:
window.addEventListener("resize", (function () {
document.getElementById("divMenu").innerHTML = document.getElementById("divTop").offsetWidth
})())
The function will set a needed (dynamic since the beginning) CSS style essential to the website formatting. The "resize" function MUST be executed at load.
Is it possible to make this, or should i create a separate Self Invoking Function and call it on the Event Listener?
When you immediately invoke the function, it's return value is put in it's place (window.addEventListener('resize', undefined)). Instead, define your function outside of the event listener then add it and call it.
function onResize() {
document.getElementById('divMenu').innerHTML = document.getElementById("divTop").offsetWidth;
}
window.addEventListener('resize', onResize);
onResize();
Technically, you could make this work using a self-invoking function but it's a bit screwy and I wouldn't suggest it.
window.addEventListener('resize', (function onResize() {
document.getElementById('divMenu').innerHTML = document.getElementById("divTop").offsetWidth;
// Works because it returns a function
return onResize;
})());
Your IIF returns undefined, but eventlistener must be a function, or link to function. Add return to your IIF or pass function:
Anonymous function:
window.addEventListener("resize", function () {
document.getElementById("divMenu").innerHTML = document.getElementById("divTop").offsetWidth
}))
IIF, that returns a function
window.addEventListener("resize", (function () {
return function(){
document.getElementById("divMenu").innerHTML = document.getElementById("divTop").offsetWidth
}
})())
Edited (invoke assignment on startup):
window.addEventListener("resize", (function () {
function set_innerHtml(){
document.getElementById("divMenu").innerHTML = document.getElementById("divTop").offsetWidth
}
set_innerHtml();
return set_innerHtml;
})())
I have this problem:
I have defined an event handler which requires parameters on it.
var handler = function(p1, p2){
//some code
}
Then I add the event handler to an object inside a function.
function foo(p1, p2){
//Some code
obj.addEventListener('click', handler(p1, p2), false)
}
As you already know the code above is not correct. It wont listen to the event. Instead it will execute the function instantly. Now to fix this I can just erase handler(p1, p2) and insert function(){ handler(p1, p2) } instead. But the problem is that I have an another function that I want to remove the event listener, which is not possible with the latter solution.
function koo(){
//Some code
obj.removeEventListener('click', handler, false)
}
How do I fix this?
I think you'll need to create a closure:
var createHandler = function(p1, p2){
return function (event) {
//some code that uses p1 and p2
};
};
var handler;
...and now you can assign the handler like so, while still having access to p1 and p2:
function foo(p1, p2){
handler = createHandler(p1, p2);
//Some code
obj.addEventListener('click', handler, false);
}
function koo(){
//Some code
obj.removeEventListener('click', handler, false);
handler = null;
}
Note that handler is now a global variable.
Update: I just realized that in your case, this could be simplified by merging createHandler and foo as follows:
var handler; // we need this to be accessible both to foo and koo
function foo(p1, p2){
handler = function(event) {
// some code that uses p1 and p2
};
//Some code
obj.addEventListener('click', handler, false);
}
function koo(){
//Some code
obj.removeEventListener('click', handler, false);
handler = null;
}
I don't think you want to pass arguments there - because it is a callback, and you are never sure what those variables will be.
Can you do...
(function() {
var p1 = 'a',
p2 = 'b',
obj = document.getElementById('my-object');
var handleClick = function(event) {
// Access to p1 and p2
// Access to `event` object containing info about the event
}
obj.addEventListener('click', handleClick, false);
// If you want to remove it
obj.removeEventListener('click', handleClick, false);
})();
May I ask why you want to have arguments on it? Do you intend to call it from a non click triggered way as well?
Isn't it just this:
function foo(){
//Some code
obj.addEventListener('click', handler, false)
}
Pass the function instead of calling it.