I have lodash debounce connected to an input which works well, however i cant seem to call other methods. I tried binding the search in my constructor. Here is my code:
class SearchBar {
constructor() {
this.searchState = {
term: '',
loading: false,
};
this.$searchBar = $('.ngaire-search-form > input');
this.onLoad();
this.onSearch = _.debounce(this.onSearch.bind(this), 400);
}
onLoad() {
this.$searchBar.on('keyup change', this.onSearch);
}
onSearch(e) {
const searchTerm = e.currentTarget.value;
if (searchTerm.length > 0) {
console.log(searchTerm); // this works
this.verifySearchTerm(searchTerm); // i get this is not a function
}
}
You need to swap
this.onLoad();
this.onSearch = _.debounce(this.onSearch.bind(this), 400);
these 2 lines.
Otherwise inside onLoad you attach this.onSearch handler that is not a bound debounced function yet.
Related
I'm detecting keypresses in a component that is aware of what dialog component is currently open elsewhere in the app via a prop, currentDialog. Normally I'd be able to access this prop in a nested function but it seems this isn't possible when using useCallback:
export const AllAreaNav = (props) => {
console.log('AllAreaNav / props.currentDialog: ', props.currentDialog); // displays correct dialog
const handleKeyPress = useCallback((event) => {
console.log('AllAreaNav / handleKeyPress / props.currentDialog: ', props.currentDialog); // displays undefined
if(event.keyCode === 70) {
//Do whatever when F is pressed
console.log("F key pressed");
if (props.currentDialog == "DialogSearchBar") {
// Take action based on DialogSearchBar being active
} else {
// Take action based on DialogSearchBar NOT being active
}
}
}, []);
useEffect(() => {
// Listener for keypresses
document.addEventListener("keydown", handleKeyPress, false);
return () => {
document.removeEventListener("keydown", handleKeyPress, false);
};
}, []);
return (
{jsxElements}
)
};
So now I'm a little unsure of a straightforward way of passing this as a parameter - assuming this would be the next step. From researching I believe it's fine to add another parameter alongside event? That this should work as I intend:
const handleKeyPress = useCallback((event, currentDialog) => {
However, I'm not entirely sure of how to initially pass this to the function. If I modify the listener to be:
document.addEventListener("keydown", handleKeyPress(event, props.currentDialog, false);
I'm unsure if this is correct, or where exactly to define event in this context, in the manner handleKeyPress defaults its as a parameter.
It seems that you were trying to resolve the problem by parametrizing the callback but you did not have an event in your context. In order to parametrize AND have the event in context, you must create a closure on the currentDialog parameter.
You can try this solution:
/**
* move callback definition outside the component
* and create a closure on currentDialog (function returning a function)
*/
const handleKeyPress = (currentDialog) => (event) => {
if (event.keyCode === 70) {
//Do whatever when F is pressed
console.log("F key pressed");
if (currentDialog == "DialogSearchBar") {
// Take action based on DialogSearchBar being active
} else {
// Take action based on DialogSearchBar NOT being active
}
}
};
export const AllAreaNav = (props) => {
console.log("AllAreaNav / props.currentDialog: ", props.currentDialog); // displays correct dialog
useEffect(() => {
// Listener for keypresses
const listener = handleKeyPress(props.currentDialog); // use closure to create a callback closured on currentDialog value
document.addEventListener(
"keydown",
listener,
false
);
return () => {
document.removeEventListener(
"keydown",
listener,
false
);
};
}, [handleKeyPress, props.currentDialog]); // pass callback and currentDialog value to dependency array
return { jsxElements };
};
I cannot make the timer to call my class method. This works fine:
MyInput = class {
constructor(params) {
let input = params.input;
input.addEventListener('focus', this.setTimer);
}
setTimer() {
this.timer = window.setInterval(() => {
console.log('yay');
}, 300);
}
};
but this:
MyInput = class {
constructor(params) {
let input = params.input;
input.addEventListener('focus', this.setTimer);
}
setTimer() {
this.timer = window.setInterval(this.lookupOptions, 300);
}
lookupOptions() {
console.log('yay');
}
};
doesn't call the lookupOptions method but instead, my developer tools in the browser stops debugger every 300ms (checked with different values - always in synch with the timer). After a while, it opens some strange file VM[number] with different numbers. I have no idea why it doesn't work. When I used timer outside the class in the same way it worked fine it seems like some problem with calling the class method but I cannot figure out what the problem might be. Could you please help?
When setInterval calls the function it changes the context of this to the scope of the setInterval, which would be the window object.
Scope the callback in setInterval by wrapping it with an arrow function expression. This prevents the scope from being changed.
MyInput = class {
constructor(params) {
let input = params.input;
input.addEventListener("focus", () => {
this.setTimer();
});
}
setTimer() {
this.timer = window.setInterval(() => {
this.lookupOptions();
}, 300);
}
lookupOptions() {
console.log("yay");
}
}
Or use the .bind() method, that is present on each function object to manually set the scope for this.
MyInput = class {
constructor(params) {
let input = params.input;
const boundSetTimer = this.setTimer.bind(this)
input.addEventListener("focus", boundSetTimer);
}
setTimer() {
const boundLookupOptions = this.lookupOptions.bind(this)
this.timer = window.setInterval(boundLookupOptions, 300);
}
lookupOptions() {
console.log("yay");
}
}
Or use the experimental public fields, which do somewhat the same as arrow function expressions, but as a method of an object.
MyInput = class {
constructor(params) {
let input = params.input;
input.addEventListener("focus", this.setTimer);
}
setTimer = () => {
this.timer = window.setInterval(this.lookupOptions, 300);
}
lookupOptions = () => {
console.log("yay");
}
}
I have this code:
interface KnockoutObservable<T> {
reset() : void;
}
ko.observable.fn.reset = function () {
return ko.pureComputed(function () {
var target = this();
var initialValue = target();
target(null);
target(initialValue);
}, this);
}
In my calling code I have:
public selectedBoxOperator: KnockoutObservable<string>;
And I can now do:
this.selectedBoxOperator.reset();
The function pops-up with intellisence, but it is not executed. It skips the function...(I used the chrome debugger). Any idea how to get this to work? If this will works I can build more "extension" functions like this..
I have a JS class:
class MyClass {
constructor() {
}
handleClick() {
alert('clicked');
}
render() {
return `<input type="radio" onClick="${this.handleClick} /><label>My Label</label>"`;
}
}
window.addEventListener('load', init);
function init() {
const myClassInstance = new MyClass();
const el = document.getElementsByClassName('myelement')[0];
el.insertAdjacentHTML('beforeend', myClassInstance.render());
}
This renders a radio input in the element with the class myelement but when I click the radio button I see this:
Uncaught SyntaxError: Unexpected token {
The error is referring to the handleClick method.
Is my syntax off? If I use onClick="${this.handleClick()}", I don't see errors but the function is called immediately and not on click. What am I doing wrong?
Your best bet would be not to use markup, since the function you call from an onxyz-attribute-style event handler has to be a global.
Instead, create the element and then assign the function to it:
class MyClass {
constructor() {
}
handleClick(e) {
alert('clicked');
}
render() {
var input = document.createElement("input");
input.type = "radio";
input.addEventListener("click", e => this.handleClick(e));
return input;
}
}
window.addEventListener('load', init);
function init() {
const myClassInstance = new MyClass();
const el = document.getElementsByClassName('myelement')[0];
el.appendChild(myClassInstance.render());
}
<div class="myelement"></div>
Note the use of an arrow function so that this is correct when we call this.handleClick. Obviously, you don't need it for just an alert, but I assume you'll want to use properties of the instance of MyClass. (To access the element on which the click occurred, use e.currentTarget since it won't be this.)
If you have a strong preference to do this with markup, an option would be:
Have an initEvents method or similar that consumers of the class are supposed to call (like they're supposed to call render)
Use a unique ID in the markup
Use that ID to find the element and hook up events
Or better yet: Have render accept the element to add to, and do the event hookup after adding it.
But working with your existing render approach:
const MyClass = (() => {
let nextId = 1;
class MyClass {
constructor() {
this.id = `__MyClass__${nextId++}`;
}
handleClick(e) {
alert('clicked');
}
render() {
return `<input type="radio" id="${this.id}" />`;
}
initEvents() {
const e = document.getElementById(this.id);
if (!e) {
throw new Error("initEvents must be called after appending the markup from render to the DOM");
}
e.addEventListener("click", e => this.handleClick(e));
}
}
return MyClass;
})();
window.addEventListener('load', init);
function init() {
const myClassInstance = new MyClass();
const el = document.getElementsByClassName('myelement')[0];
el.insertAdjacentHTML('beforeend', myClassInstance.render());
myClassInstance.initEvents();
}
<div class="myelement"></div>
If you want to this onclick working then you need to assign global function to onclick otherwise you can not do in this way I did quick fix It is just a temporary solution you can either choose this or #T.J answer also looking good.
class MyClass {
constructor() {}
handleClick() {
alert('clicked');
}
render() {
const globalId = "h" + Math.floor((Math.random() * 100) + 12345);
window[globalId] = this.handleClick;
return `<input type="radio" onClick=window['${globalId}']() />`;
}
}
window.addEventListener('load', init);
function init() {
const myClassInstance = new MyClass();
const el = document.getElementsByClassName('myelement')[0];
el.insertAdjacentHTML('beforeend', myClassInstance.render());
}
<div class="myelement"></div>
I'm trying to call another typescript function (of any kind) inside of a handler function added to spans on a page. When I do this, the handler function works fine and will do basic things such as set variables, console.log, etc. However, when trying to call a function of any kind it will throw an error of 'Cannot read property functionName of undefined'. So for example, here is code that works:
addListenter() {
if (!this.addListenerFired) {
let iterateEl = this.el.nativeElement.querySelectorAll('span');
for (let i = 0; i < iterateEl.length; i++) {
iterateEl[i].addEventListener('click', this.showExcerptInfo);
}
this.addListenerFired = true;
}
showExcerptInfo (): void {
this.selectedExcerptId = event.srcElement.id;
console.log(this.selectedExcerptId);
}
However, if I change the handler function to do the following (or call any function located anywhere, even in the same component) it will not work and throws the error:
showExcerptInfo () {
let excerpt = this.excerptsService.getExcerpt(this.selectedExcerptId);
}
Any clues as to why this is happening and/or how it can be resolved?
You need to take care that this keeps pointing at the current class instance:
iterateEl[i].addEventListener('click', this.showExcerptInfo.bind(this));
alternatively you can use
iterateEl[i].addEventListener('click', (evt) => this.showExcerptInfo(evt));
this not pointing to your class.
there is more 2 ways to do it with keeping 'this' without 'bind':
iterateEl[i].addEventListener('click', (event) => this.showExcerptInfo(event));
or:
iterateEl[i].addEventListener('click', this.showExcerptInfo);
showExcerptInfo: (any) => void = (event:any):void => {
this.selectedExcerptId = event.srcElement.id;
console.log(this.selectedExcerptId);
}
Adding further info to the accepted answer.
If you're trying to pass data from your click element to the receiving function, you can set it up as follows:
// Function 1 - add listener
element.addListener("click", this.otherFunction.bind(this, dataFromElement));
// Function 2 - receive click data
otherFunction(dataFromElement) {
this.doStuff(dataFromElement)
}
import { Component, HostListener, Input, OnInit } from '#angular/core';
export class AppbarComponent implements OnInit {
touchstartX:number = 0;
touchstartY:number = 0;
touchendX:number = 0;
touchendY:number = 0;
#HostListener('touchstart', ['$event']) gesuredZonestart(event:any) {
this.touchstartX = event.changedTouches[0].screenX;
this.touchstartY = event.changedTouches[0].screenY;
}
#HostListener('touchend', ['$event']) gesuredZoneend(event:any) {
this.touchendX = event.changedTouches[0].screenX;
this.touchendY = event.changedTouches[0].screenY;
this.handleGesure();
}
handleGesure() {
var swiped = 'swiped: ';
if (this.touchendX < this.touchstartX) {
console.log(swiped + 'left!');
}
if (this.touchendX > this.touchstartX) {
console.log(swiped + 'right!');
}
if (this.touchendY < this.touchstartY) {
console.log(swiped + 'down!');
}
if (this.touchendY > this.touchstartY) {
console.log(swiped + 'top!');
}
if (this.touchendY == this.touchstartY) {
console.log('tap!');
}
}
}
This typescript snippet will help you to implement touch gesture in angular