I've obtained an anonymous function that takes an event object as input as well as another input like so:
// define a state toggle function
const toggleDrawer = (open) => (event) => {
// quit for certain key events
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
} // if
// set the given toggle open state
setState(open);
};
I would like to incorporate this as a method in a class, but unsure how to rewrite inputs.
I could just store the variable as a property on the object, but I would like to write it like so:
/*
View-model of the Drawer view.
*/
class DrawerModel {
// class constructor
constructor() {
// initialise a drawer state
const [open, setOpen] = React.useState(false);
// store the state and set function on the object
this.open = open;
this.setOpen = setOpen;
} // constructor
// method to toggle the drawer
toggleDrawer(open) {
// quit for certain key events
// TODO: how to pass event in like with the anon fnc?
// const toggleDrawer = (open) => (event) => {...
//if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
// return;
//} // if
// set the given toggle open state
setState(open);
} // toggleDrawer
} // class
The toggleDrawer function can be assigned as the callback for some UI component events.
I don't understand the anonymous function definition with multiple inputs "= (open) => (event) =>".
Could someone explain/link how that works?
How do I make that event data available with how I'm writing the class method?
It is currying function. It is function which returns function. With classic syntax (not arrow syntax as your example is) you can write it like this:
class DrawerModel {
toggleDrawer(open) {
return function(event) {
// do your magic here
}
}
}
Related
I am using a debounce function in my react native project. It gets triggered after user enters 3 characters but it is triggering after 2 sec instead of the delay i assigned to it.
constructor() {
this.init()
this.hitSearchApi = debounce(() => {
log('inside if is called debounce')
this.getStudentTableData(() => this.updateFetchingStatus(true))
this.updateSearchLoaderStatus(false)
this.updateFetchingStatus(false)
}, 100)
}
I had created my debounce function inside the store constructor like this.
#action
onChangeSearchText = (searchText) => {
if (get(searchText, 'length', 0) > 2) {
log('inside search text if condition')
this.updateSearchLoaderStatus(true)
this.hitSearchApi.cancel()
// this.updateSearchLoaderStatus(true)
log('before hit search api')
this.hitSearchApi()
// fetch student list on search basis
} else if (get(searchText, 'length') === 0) {
this.setStudentListData({})
this.updateFetchingStatus(true)
this.resetSearchData()
}
this.searchText = searchText
}
This is my onChange text triggered, which is getting called on textinput onChange event.
Please help me out in this. What am I doing wrong here?
What I am trying to achieve with this is quite simple. I have an array of options/tags displayed in a div. I want the classes to change on click to simulate an "active" state. While I've seen a few snippets on codesandbox that I could use to achieve what I want, my curiosity still is why this wouldn't even trigger the class change.
The referenced event elements and id match...
const variantHandler = (e: any) => {
e.preventDefault()
const d = e.currentTarget
const c = () => {
variants.map((variant: any) => {
if (d.id !== variant.node.id) {
d.classList.remove('bg-gray-900', 'text-myGray', 'dark:bg-myGray', 'dark:text-gray-900')
//alert('reached remove')
}
else {
d.classList.add('bg-gray-900', 'text-myGray', 'dark:bg-myGray', 'dark:text-gray-900')
//alert('reached add')
}
})
}
c();
}
I simply tried to addEventListener and removeEventListener to element, but it doesn't remove.
I suppose that the problem could be with parameters, but I used them to follow the DRY. So I could simply reuse it like nextSection.addEventListener('mouseover', showContent(event, nextSection)) and so on and so on so I do not need any if statements or stuff like that.
* EDIT *
I made some more examples of elements that I will be using. There’s a chance, that there will be event more. If I do not use parameter, there would be a lot more of functions. Also, there will be click instead of mouse events on mobile, so I need to remove them.
As I understand now, the problem is with return statement. If I use event instead of parameter and so event.target I get some weird bug.
const loginSection = document.querySelector('#js-login-section');
const searchSection = document.querySelector('#js-search-section');
const shoppingBagSection = document.querySelector('#js-shopping-bag-section');
const wishlistSection = document.querySelector('#js-wishlist-section');
function showContent(element) {
return () => {
const toggle = element.lastElementChild;
toggle.style.maxHeight = toggle.scrollHeight + 'px';
}
}
function hideContent(element) {
return () => {
const toggle = element.lastElementChild;
toggle.style.maxHeight = null;
}
}
/* Media queries - min width 992px */
loginSection.addEventListener('mouseover', showContent(loginSection));
loginSection.addEventListener('mouseout', hideContent(loginSection));
searchSection.addEventListener('mouseover', showContent(searchSection));
searchSection.addEventListener('mouseout', hideContent(searchSection));
shoppingBagSection.addEventListener('mouseover', showContent(shoppingBagSection));
shoppingBagSection.addEventListener('mouseout', hideContent(shoppingBagSection));
wishlistSection.addEventListener('mouseover', showContent(wishlistSection));
wishlistSection.addEventListener('mouseout', hideContent(wishlistSection));
/* Media queries - max width 992px */
loginSection.removeEventListener('mouseover', showContent(loginSection));
loginSection.removeEventListener('mouseout', hideContent(loginSection));
searchSection.removeEventListener('mouseover', showContent(searchSection));
searchSection.removeEventListener('mouseout', hideContent(searchSection));
shoppingBagSection.removeEventListener('mouseover', showContent(shoppingBagSection));
shoppingBagSection.removeEventListener('mouseout', hideContent(shoppingBagSection));
wishlistSection.removeEventListener('mouseover', showContent(wishlistSection));
wishlistSection.removeEventListener('mouseout', hideContent(wishlistSection));
Thank you in advance!
What is happening is that return () => {}; is returning a new function every time it's run. So every time you call one of your functions a new event handler is being created.
This means that the handler that is added is different to the one you're trying to remove.
To remedy this, I'd keep it simple:
const loginSection = document.querySelector('#js-login-section');
function showContent(e)
{
const toggle = e.currentTarget.lastElementChild;
toggle.style.maxHeight = toggle.scrollHeight + 'px';
}
function hideContent(e)
{
const toggle = e.currentTarget.lastElementChild;
toggle.style.maxHeight = null;
}
loginSection.addEventListener('mouseover', showContent);
loginSection.addEventListener('mouseout', hideContent);
loginSection.removeEventListener('mouseover', showContent);
loginSection.removeEventListener('mouseout', hideContent);
I'm not sure what you want to avoid repeating, so I can't advise on that, but I'm sure you'll figure it out.
const loginSection = document.querySelector('#js-login-section');
function showContent(event) {
var element = event.target;
return () => {
const toggle = element.lastElementChild;
toggle.style.maxHeight = toggle.scrollHeight + 'px';
}
}
function hideContent(event) {
var element = event.target;
return () => {
const toggle = element.lastElementChild;
toggle.style.maxHeight = null;
}
}
loginSection.addEventListener('mouseover', showContent);
loginSection.addEventListener('mouseout', hideContent);
loginSection.removeEventListener('mouseover', showContent);
loginSection.removeEventListener('mouseout', hideContent);
You must set in events method function without call. Element you can get from event event.target
In your code, I found the following errors,
param 'event' will be always undefined - the event should go as a parameter to inner function.
you don't need closure here - You can directly assign the function without creating an inner function and access the element with event.target or this
with your implementation, you should pass the same handler reference used in addEventListener to removeEventListener. So, you should store the handler in a variable and pass it to both addEventListener and removeEventListener
Solution:
if you don't know the handler name, you can use window.getEventListeners to do the magic,
window.getEventListeners returns a dictionary of events associated with the element.
function removeEventListener(el, eventName) {
if (!el) {
throw new Error('Invalid DOM reference passed');
}
const listeners = getEventListeners(el)[eventName] || [];
listeners.forEach(({
listener
}) => {
removeEventListener(eventName, listener);
});
}
function removeAllEventListener(el) {
if (!el) {
throw new Error('Invalid DOM reference passed');
}
const events = Object.entries(getEventListeners(el) || {});
events.forEach(([eventName, listeners]) => {
listeners.forEach(({
listener
}) => {
removeEventListener(eventName, listener);
});
});
}
// example
// remove mouseout event
removeEventListener(loginSection, 'mouseout');
// remove all event listeners
removeAllEventListener(loginSection);
I'm trying to find out how to handle custom DOM events emitted by something outside of Angular, for example the following:
document.querySelector('my-custom-element').dispatchEvent(new Event('my.customEvent'));
So far I have tried to register a new EventManagerPlugin that supports everything starting with 'my.' but if I print out all events that come by all 'normal' event like 'click' and 'submit' are printed out; but none of my custom events.
html:
<my-custom-element (my.customEvent)="handleCustomEvent($event)"></my-custom-element>
ts:
supports(eventName: string):boolean {
var ret = false;
if (eventName.indexOf('my.') === 0) {
ret = true;
}
console.log('supports event?', eventName, ret);
return ret;
}
The console.log line only prints native events and ng*events but not my custom event :(
EDIT Fixed solution
I've moved the (my.customEvent) inside the component annd the log showed the custom event.
Binding an external event to the angular2 internal event while seperating the 2 is fixed by using a custom eventHandler in the EventManagerPlugin
Relevate code
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
let zone = this.manager.getZone();
// Entering back into angular to trigger changeDetection
var outsideHandler = (event: any) => {
zone.run(() => handler(event));
};
// Executed outside of angular so that change detection is not constantly triggered.
var addAndRemoveHostListenersForOutsideEvents = () => {
this.manager.addEventListener(element, 'external.' + eventName, outsideHandler);
}
return this.manager.getZone().runOutsideAngular(addAndRemoveHostListenersForOutsideEvents);
}
Trigger the event via DOM:
document.querySelector('my-custom-element').dispatchEvent(new Event('external.my.customEvent'));
Now you can trigger an event from the DOM which is pushed into angular2 world and can the code is handled from within the component.
Try to extend the DomEventsPlugin, for example:
import {DomEventsPlugin} from 'angular2/platform/common_dom';
// Have to pull DOM from src because platform/common_dom returns DOM as null.
// I believe its a TS bug.
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {Injectable} from 'angular2/core';
import {noop} from 'angular2/src/facade/lang';
#Injectable()
export class DOMOutsideEventPlugin extends DomEventsPlugin {
eventMap: Object = {
"clickOutside": "click",
"mousedownOutside": "mousedown",
"mouseupOutside": "mouseup",
"mousemoveOutside": "mousemove"
}
supports(eventName: string): boolean {
return this.eventMap.hasOwnProperty(eventName);
}
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
var zone = this.manager.getZone();
var documentEvent = this.eventMap[eventName];
// Entering back into angular to trigger changeDetection
var outsideHandler = (event) => {
zone.run(() => handler(event))
};
// Executed outside of angular so that change detection is not constantly triggered.
var addAndRemoveHostListenersForOutsideEvents = () => {
DOM.onAndCancel(DOM.getGlobalEventTarget('document'), documentEvent,
(event) => {
let current = event.target;
// if the element/event is propagating from the element its bound to, don't handle it.
if (current.parentNode && current !== element) {
outsideHandler(event);
}
});
}
return this.manager.getZone().runOutsideAngular(addAndRemoveHostListenersForOutsideEvents);
}
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
var element = DOM.getGlobalEventTarget(target);
var zone = this.manager.getZone();
var outsideHandler = (event) => zone.run(() => handler(event));
if ((target === "document") || (target === "window" )) {
return noop;
}
return this.manager.getZone().runOutsideAngular(
() => DOM.onAndCancel(element, eventName, outsideHandler)
);
}
}
source: https://medium.com/#TheLarkInn/creating-custom-dom-events-in-angular2-f326d348dc8b#.so0jvssnz
I want to have a onkeydown event fire a function only once. for that function to fire again, the user has to release the key and press/hold again.
I know its fairly simple but I'm new at JS. Also I prefer to avoid using jQuery or other libs.
One more thing, this should work for both ie and firefox.
I'm surprised it's not mentioned, there's also event.repeat:
document.addEventListener('keydown', (e) => {
if (e.repeat) return;
console.log(e.key);
});
This will only fire once per each keypress, since event.repeat turns true after holding the key down.
https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key#keyboardevent_sequence
You could set a flag:
var fired = false;
element.onkeydown = function() {
if(!fired) {
fired = true;
// do something
}
};
element.onkeyup = function() {
fired = false;
};
Or unbind and rebind the event handler (might be better):
function keyHandler() {
this.onkeydown = null;
// do something
}
element.onkeydown = keyHandler;
element.onkeyup = function() {
this.onkeydown = keyHandler;
};
More information about "traditional" event handling.
You might also want to use addEventListener and attachEvent to bind the event handlers. For more information about that, have a look at quirksmode.org - Advanced event registration models.
There's a "once" parameter you can use
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
Eg:
element.addEventListener('keydown', function(event) {
doSomething()
}, {once: true});
It'll remove it as soon as it's been called.
Alternatively you can use removeEventListener if it's a named function
Here is a method that uses addEventListener and removeEventListener
var textBox = document.getElementById("textBox");
function oneKeyDown(){
$("body").append("<h1>KeyDown<h1>"); //just to show the keypress
textBox.removeEventListener('keydown', oneKeyDown, false);
}
function bindKeyDown(){
textBox.addEventListener('keydown', oneKeyDown, false);
}
textBox.addEventListener('keyup', bindKeyDown, false)
bindKeyDown();
Code example on jsfiddle.
One note, for IE you will need to use attachEvent, detachEvent.
Here you go:
test.onkeydown = function() {
if ( this.className === 'hold' ) { return false; }
this.className = 'hold';
// call your function here
};
test.onkeyup = function() {
this.className = '';
};
Live demo: http://jsfiddle.net/simevidas/xAReL/2/
JQuery's one will help you.
What it does is, bind the eventHandler to event, and when event occurs, it runs the eventHandler and unbinds it, so that its not fired at next event.
as stated in the other answers, there is no 'onkeyfirstdown' or similar event to listen for.
the best solution is to keep track of which keys are already down in a js-object:
var keysdown = {};
element.addEventListener('keydown', function(evt) {
if(!(evt.key in keysdown)) {
keysdown[evt.key] = true;
// key first pressed
}
});
element.addEventListener('keyup', function(evt) {
delete keysdown[evt.key];
});
this way, you will not be skipping 'keyfirstpressed' events if more than one key is held down.
(many of the other solutions posted here will only fire when no other keys are down).
Here is my solution that will only run the function you pass it when a key is FIRST pressed on the target (eg window or some input field). If the user wants to trigger a key again, they'll have to release it and press it again.
Vanilla JS
const onKeyPress = (func, target = window) => {
// persistent "store" to track what keys are being pressed
let pressed = {};
// whenever a keydown event is fired ontarget element
const onKeyDown = (event) => {
// if key isn't already pressed, run func
if (!pressed[event.which])
func(event);
// add key to store
pressed = { ...pressed, [event.which]: true };
};
// whenever a keyup event is fired on the window element
const onKeyUp = (event) => {
const { [event.which]: id, ...rest } = pressed;
// remove key from store
pressed = rest;
};
// add listeners
target.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
// return a function that can be called to remove listeners
return () => {
target.removeEventListener('keydown', onKeyDown);
window.removeEventListener('keyup', onKeyUp);
};
};
And then to use it:
const removeListener = onKeyPress((event) => console.log(event.which + ' key pressed'))
removeListener(); // when you want to remove listeners later
React and React Hooks
import { useState } from 'react';
import { useEffect } from 'react';
import { useCallback } from 'react';
export const useKeyPress = (func, target = window) => {
// persistent "store" to track what keys are being pressed
const [pressed, setPressed] = useState({});
// whenever a keydown event is fired ontarget element
const onKeyDown = useCallback(
(event) => {
// if key isn't already pressed, run func
if (!pressed[event.which])
func(event);
// add key to store
setPressed({ ...pressed, [event.which]: true });
},
[func, pressed]
);
// whenever a keyup event is fired on the window element
const onKeyUp = useCallback((event) => {
// remove key from store
const { [event.which]: id, ...rest } = pressed;
setPressed(rest);
}, [pressed]);
useEffect(() => {
// add listeners when component mounts/changes
target.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
// cleanup/remove listeners when component unmounts/changes
return () => {
target.removeEventListener('keydown', onKeyDown);
window.removeEventListener('keyup', onKeyUp);
};
}, [target, onKeyDown, onKeyUp]);
};
And then to use it:
import { useKeyPress } from 'wherever';
useKeyPress((event) => console.log(event.which + ' key pressed'))