Passing an function array to a button.onclick function - javascript

I have trouble with TypeScript/JavaScript
I have an array of function like this
private listeners: ((name: string) => void)[] = [];
I'm adding functions to it in another function that seems to work.
now I want to call those functions if a button is pressed
button.onclick = function(){
this.listeners.forEach((callback) => callback(username));
}
if I try my code this way nothing happens, I bet it is because it doesn't know listeners.
my next try was
button.onclick = function(listeners){
listeners.forEach((callback) => callback(username));
}
Now tsc tells me
error TS2339: Property 'forEach' does not exist on type 'MouseEvent'.
I bet I'm missing a type here, but idk how to tell him that this array is an array of functions of type ((name: string) => void)[] = [];
Would be cool if someone knows a quick workaround.
Before you ask: I'm sure that my function added functions to my array (at least in the add function the size of listerns got bigger) :D

Try to define your onclick handler like this to keep 'this' pointing to the current class instance:
private onClick(ev)
{
this.listeners.forEach((callback) => callback(username));
}
//Then somewhere later
button.onclick = (ev) => this.onClick(ev);

Your main problem is that this doesn't refer to your expected context. Instead, this refers to the MouseEvent context of your click handler.
As a simple solution for your problem you might use Function.prototype.bind().
Change
button.onclick = function(){
this.listeners.forEach((callback) => callback(username));
}
To this
button.onclick = function(){
this.listeners.forEach((callback) => callback(username));
}.bind(this);
Basic explanation:
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

A more sophisticated way of dealing with your issue is not to use onclick at all, but rather addEventListener, and you can use the following little-known approach of passing an object with a handleEvent method to addEventListener, which makes all your issues with this moot:
class Foo {
private listeners: ((name: string) => void)[] = [];
constructor(button) {
button.addEventListener('click', this);
}
handleEvent(event) {
// check event type
this.listeners.forEach(callback => callback(username));
}
}
const new Foo(document.getElementById('myButton'));

Thanks for the awnsers
i guess that a direct add of a function wont work because its an Eventhandler function, which works somewhere in the code but not there where i defined it.
I changed to addeventhandler and defined with let a function inside my function, and call a new function for the Eventhandler which calls the function it should call.#funcception :D
let triggerOnClick = () => {
let username = tf_name.value;
this.listeners.forEach((callback) => callback(username));
};
btn_enter.addEventListener("click", () => {
triggerOnClick();
});
that code works :)

Related

React curried arrow function in event handlers

How does react know to provide the event as a second argument in the code below?
const clickMe = (parameter) => (event) => {
event.preventDefault();
// Do something
}
<button onClick={clickMe(someParameter)} />
Does it generate this to:
<button onClick={(event) => clickMe(someParameter)(event)} />
Or how does it work?
Thanks.
Maybe this will help explain it a little better.
Closures are functions that carry the information (variables etc.) from their local environment with them when they're returned.
I'll work this example without arrow functions as they can be a little deceiving.
// `multplyBy` accepts a number as its argument
// It returns a new function that "remembers" that number
// when it's returned. But that new function *also*
// accepts a number
function multiplyBy(n) {
return function(n2) {
return n2 * n;
}
}
// So `multiplyBy(5)` returns a new function
// which we assign to the variable `five`
const five = multiplyBy(5);
// And when we call `five` with a number we get
// the result of calling 5 * 10.
console.log(five(10));
If you substitute multiplyBy(n) with clickMe(n) you'll see that you'll get a new function that gets used by the click listener, and the first argument of that function will always be the event.
Clickme variable in your code is a function which has return is function e => {...}. So when you specify like this:
<button onClick={clickMe(someParameter)} />
it is equivalent to
<button onClick={e => {...} />
which is basic form of a event handler in react

Understand arrow function based on event

I have the following codes:
class Autocomplete {
constructor(ele, options) {
this.ele = ele;
this.options = options;
this.input = this.ele.querySelector('.input');
// with keyup we want to fire it right away
// this.input.addEventListener('keyup', (e) => this.display(e);
}
}
The display function will then display the user's input onto the page. However, I wonder what the difference is between these 3 functions:
1)
this.input.addEventListener('keyup', (e) => this.display(e));
this.input.addEventListener('keyup', this.display);
this.input.addEventListener('keyup', () => this.display());
Only function 1 and 2 work, and to my understanding, function 1 will put e as argument into the display function whereas function 2 will execute immediately on keyup. However, for 3, why does it not work, because I thought the e argument is passed in implicitly by the DOM API?
Out of the first 2 functions, which one is more performant and why?
Arrow function is not bound to the class where it is declared when it is called, you should use function only if you want to use the this in the function because in the arrow function, it's the global this that is get called

How to create a javascript functional 'class' so that I can access a 'method' from outside and inside the function

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>

Invoke and return a function

Is there a way to do this in JS
function namedFunction(elements,args) {
const domElements = document.querySelector(elements);
const initialValue = 0;
let incrementBy = 5;
return function() {
// Do something to domElements based on initialValue and incrementBy
// function needs to run the first time namedFunction is called
// and this is the only function that needs to run on subsequent calls to namedFunction
}.call(null)
// the .call does not work as intended here, but this is basically what I want to do.
}
I think I can do namedFunction()() with the code above in order to invoke both, but I'm wondering if there is another way.
The longer version of the function would look like this:
function namedFunction(elements,args) {
const domElements = document.querySelector(elements);
const initialValue = 0;
let incrementBy = 5;
function namedFunctionEventHandler() {
// Do something to domElements based on initialValue and incrementBy
// function needs to run the first time namedFunction is called
// and this is the only function that needs to run on subsequent calls to namedFunction
}
namedFunctionEventHandler();
return namedFunctionEventHandler;
}
The goal would be to pass a single function as an event handler, that the first time it runs it does initial calculations, caches dom elements and the more heavier stuff, then executes the logic that is abstracted in the returned function and on subsequent calls it uses the data from the closure.
Edit: the namedFunction does not need to accept any arguments, its just for demonstration purposes.
document.addEventListener('scroll', namedFunction)
is what I want to be able to do.
#CertainPerformance - Sorry, I misread your answer.
If you take a look at the end result I would like to achieve, your proposition wont actually work as intended, as if I pass an invoked function as an event handler, its gonna run before an event has actually happened.
You can make namedFunction into an IIFE that saves a reference to a function (initially undefined). On call, if that variable is undefined, carry out the expensive calculations and then assign to the variable; if the variable is defined, then simply call it.
const handler = (() => {
let cheapFn;
return () => {
if (cheapFn) {
cheapFn();
return;
}
// expensive calculations here
const domElements = document.querySelector(elements);
...
cheapFn = () => {
// assign to cheapFn
};
cheapFn();
};
})();
Demo:
const handler = (() => {
let cheapFn;
return () => {
if (cheapFn) {
cheapFn();
return;
}
// expensive calculations here
console.log('expensive');
cheapFn = () => {
console.log('cheap');
};
cheapFn();
};
})();
document.addEventListener('scroll', handler);
body {
height: 400px;
}
body
You can take advantage of the fact that functions in JavaScript are first-class objects, and store the function state (initialized/uninitialized) in a property of the function.
The data computed during initialization can be stored in the function properties as well, please take a look at the demo:
const namedFunction = function(elements,args) {
if (!namedFunction.isInitialized) {
console.log('Initialization: Computing initial value...');
namedFunction.initialValue = 10 * 10;
console.log(`Initial value: ${namedFunction.initialValue}`);
namedFunction.isInitialized = true;
}
return function() {
console.log('Running regular operation:');
console.log(`Current value: ${--namedFunction.initialValue}`);
}.call(null)
}
document.getElementById('demo').addEventListener('click', namedFunction);
<button id="demo">Run</button>

How can I test for equality to a bound function when unit testing?

I want to test that an argument passed to a function is a function reference but the function reference is being passed using bind().
Consider this code which is to be tested (shortened for brevity):
initialize: function () {
this.register(this.handler.bind(this));
}
And this unit test to check if register() was called with handler():
it('register handler', function () {
spyOn(bar, 'register');
bar.initialize();
expect(bar.register.calls.argsFor(0)[0]).toEqual(bar.handler);
});
The arg doesn't equal the function reference I guess due to the bound function using bind() - how can I test that the correct function reference is being passed while still using the bind() method on it?
Note: This isn't specific to jasmine, I just thought it was appropriate because of the methods being used.
Instead of
expect(bar.register.calls.argsFor(0)[0]).toEqual(bar.handler);
you can do
expect(Object.create(bar.handler.prototype) instanceof bar.register.calls.argsFor(0)[0])
.toBe(true);
or
expect(Object.create(bar.handler.prototype)).
toEqual(jasmine.any(bar.register.calls.argsFor(0)[0]));
This works because the internal [[HasInstance]] method of the bound function delegates to the [[HasInstance]] method of the original function.
This blog post has a more detailed analysis of bound functions.
this.handler.bind(this) creates completely a new function, therefore it is not equal to bar.handler.
See Function.prototype.bind().
You can pass bounded function as argument to your initialize function and then test it, e.g.:
var handler = bar.handler.bind(bar);
bar.initialize(handler);
expect(bar.register.calls.argsFor(0)[0]).toEqual(handler);
I've managed to keep the test and code and work around it.
I spy on the function reference with an empty anon func, then call it when spying on the register method - if the spy gets called, I know it's passed the correct reference.
it('register handler', function () {
spyOn(bar, 'handler').and.callFake(function(){}); // do nothing
spyOn(bar, 'register').and.callFake(function(fn){
fn();
expect(bar.handler).toHaveBeenCalled();
});
bar.initialize();
});
I thought I'd add another approach that, to me, is a bit less awkward.
given a class like:
class Bar {
public initialize() {
this.register(this.handler.bind(this));
}
private register(callback) {}
private handler() {}
}
the full spec might look like:
describe('Bar', () => {
let bar;
beforeEach(() => {
bar = new Bar();
});
describe('initialize', () => {
let handlerContext;
beforeEach(() => {
bar.handler = function() {
handlerContext = this;
};
bar.register = jest.fn(callback => {
callback();
});
bar.initialize();
});
it('calls register with the handler', () => {
expect(bar.register).toHaveBeenCalledWith(expect.any(Function));
});
it('handler is context bound', () => {
expect(handlerContext).toEqual(bar);
});
});
});
In my case (using jest) I just mocked the implementation of bind for the function I wanted and I tweaked it so that it returns the original function and not a bound copy of it.
Specifically here's what I tried and worked:
Code to be tested:
// module test.js
export const funcsToExecute = [];
function foo(func) {
funcsToExecute.push(func);
}
export function bar(someArg) {
// bar body
}
export function run(someArg) {
foo(bar.bind(null, someArg));
}
I wanted to assert that when run is called, funcsToExecute contains bar
So I wrote the test like this:
import * as test from 'test';
it('should check that "funcsToExecute" contain only "bar"', () => {
jest.spyOn(test.bar, 'bind').mockImplementation((thisVal, ...args) => test.bar);
test.run(5);
expect(test.funcsToExecute.length).toBe(1);
expect(test.funcsToExecute[0]).toBe(test.bar);
});
For your example, I suppose it would be something like this:
it('register handler', function () {
spyOn(bar, 'register');
spyOn(bar.handler, 'bind').mockImplementation((thisVal, ...args) => bar.handler);
bar.initialize();
expect(bar.register.calls.argsFor(0)[0]).toBe(bar.handler);
});
though I haven't tested it.

Categories

Resources