I am recently learning electron I have encountered a problem that I want to listen ESC in both process,
In Main process,
menu.append(new MenuItem({
label: "Hide on Escape",
visible: false,
accelerator: "Escape",
click: (item, window, event) => {
if (window.isVisible()) {
window.hide();
}
}
}))
In Render Process,
// on Key press Handler
const onKeyDownHandler = (event: React.KeyboardEvent) => {
if (isContentEditable && event.key === "Enter") {
applyContentEditable();
} else if (event.key === "Enter") {
onSelected && onSelected(pair);
} else if (event.key === "Delete") {
onDelete && onDelete(pair);
} else if (event.key === "F2") {
setIsContentEditable(true);
} else if (event.key === "Escape") {
resetContentEditable(); // Stop Renaming and get back to default
}
}
But this didn't work the window hides before the render process. How can I Stop hide when it is listened by Render Process
There is an event.stopPropagation() method which prevents further propagation/calls on the same event Docs
And there is event.preventDefault() which can be used to stop default handling Docs
I still don't quite fully understand your setup and what you are try to achieve.
Another solution is to stop listening on the escape key in the main process at all. Only listen in the render process and then pass the event manually to the main process in case you want to take some action there as well (or the other way around, depending on what makes more sense for you).
For this passing you are probably looking at the electron ipcMain module https://www.electronjs.org/docs/latest/api/ipc-main
Related
using element ui component el-pagination i am trying to add the code to detect the enter and space for the pagination
but here
addKeyEvent() {
document.querySelectorAll('ul.el-pager li.number').forEach((element, index) => {
element.addEventListener('keyup', function (e) {
if (e.key == 'Enter' || e.key == 'Space') {
console.log(e.key);
console.log(element.innerHTML);
console.log(this.$parent);
this.$parent.handleCurrentChange(element.innerHTML);
}
})
});
}
i am unable to call the method, if i just console this, all i i get is the li element on which the pagination is linked. how can i call the method to pass pagination and make it work
also i am calling this function on the watch which this.addKeyEvent();
So I made this modal https://codesandbox.io/s/amazing-morning-ukxp2?file=/src/components/Modal.js
Whenever you press the esc key, it is suppose to close the modal. I added a message to show each time I press esc.
But if you check the console it prints my messages in multiples of 2x each time, so after a while it will say the message like 100 times
This is the function I'm referring to
function keyPress(e) {
if (e.key === 'Escape' && showModal) {
setShowModal(false);
console.log('I pressed');
}
}
document.addEventListener('keydown', keyPress);
I tried to put it into a useEffect function and add showModal as a dependency, but I couldn't get it to work properly
How would I prevent the keydown event listener from showing so many times in the console when I only want it to trigger when the modal is open and when I press the esc key once?
It seems like every time I re open the modal, it doubles the esc key message.
You need to handle the event listeners (add, removing) inside useEffect hook:
useEffect(() => {
document.addEventListener("keydown", keyPress);
return () => document.removeEventListener("keydown", keyPress);
});
This way, you will subscribe to keyPress function in every lifecycle.
To improve it further, you could wrap keyPress in useCallback hook (to memoize the function in new renders) and add it as a dependency:
const keyPress = useCallback(
(e) => {
if (e.key === "Escape" && showModal) {
setShowModal(false);
console.log("I pressed");
}
},
[setShowModal, showModal]
);
useEffect(() => {
document.addEventListener("keydown", keyPress);
return () => document.removeEventListener("keydown", keyPress);
}, [keyPress]);
Modified codesandbox
useEffect(()=>{
document.addEventListener('keydown', someFunction);
}, []);
the empty array at the end will till react to not refresh the component once loaded
In my componentDidMount I am trying to remove a localstorage item and also adding an event listener for further changes in the localstorage and it is not working
componentDidMount() {
localStorage.removeItem("receivedLocation");
window.addEventListener("storage", event => {
console.log("Event listener triggered");
if (event.key === "receivedLocation" && event.newValue === true) {
this.setState({
receivedLocation: true
});
}
});
}
Whereas if I remove the update item it works fine.
componentDidMount() {
window.addEventListener("storage", event => {
console.log("Event listener triggered");
if (event.key === "receivedLocation" && event.newValue === true) {
this.setState({
receivedLocation: true
});
}
});
}
I am unable to reset the localstorage and also listen to future changes in the localstorage
The storage events do not fire in the current document. It is used to notify the other open tabs of your app of some change.
The storage event of the Window interface fires when a storage area
(localStorage or sessionStorage) has been modified in the context of
another document
If you are trying to notify other components in the current tab, you can use the DOMEvents for that.
const event = new Event('EVENT_NAME', { detail: { yourPayload }});
window.dispatchEvent(event);
// in your component
window.addEventListener('EVENT_NAME', configChangedHandler, false);
// cleanup
window.removeEventListener('EVENT_NAME', configChangedHandler);
In an Aurelia viewmodel component, I have the following JQuery code that works to capture Ctrl+S or Ctrl+Enter while a modal is visible and call the save function:
$(window).bind('keydown', function(event) {
if (event.ctrlKey || event.metaKey) { // Ctrl + ___
if ((event.which == 83) || (event.which == 115) || (event.which == 10) || (event.which == 13)) { // Ctrl+Enter or Ctrl+S
// Save button
event.preventDefault();
if ($(self.edit_calendar).is(':visible')) {
self.saveCalendar();
}
}
}
});
However, I foresee adding a similar function to 40+ viewmodels, and that doesn't seem very DRY and adds some ugly code to each of my viewmodels. I would like to create a generic addEventListener function in a singleton class to easily call from each of my views. Here's what I have in mind:
addListenerSave(visible, callback) {
// Add an event listener to redirect keyboard shortcuts to specific actions
console.log("addListenerSave()");
$(window).bind('keydown', function(event) {
if (event.ctrlKey || event.metaKey) { // Ctrl + ___
if ((event.which == 83) || (event.which == 115) || (event.which == 10) || (event.which == 13)) { // Ctrl+Enter or Ctrl+S
// Save button
event.preventDefault();
if ($(visible).is(':visible')) {
console.log("Keyboard shortcut: Save");
callback();
}
}
}
});
}
Then, in my individual components, I should only need the following code on instantiation (in the attached() component life cycle):
this.config.addListenerSave(this.edit_calendar, this.saveCalendar);
However, this does not work. saveCalendar() is called but maybe from another scope/context, so I get an error inside saveCalendar that says:
"Cannot read property 'selectedId' of undefined".
This is referring to the saveCalendar() code if (this.selectedId).... What am I doing wrong?
Finally, should I also be removing this event listener when my Aurelia component is detached? How?
One alternate idea I had was to use Aurelia's eventAggregator to create a global event listener that always is listening for Ctrl+S or Ctrl+Enter and then publishing a message that can be subscribed in each component.
To answer your original question, you're on the right track - but due to the semantics of this in JavaScript, you'll need to bind your function. (If you're coming from a C# perspective, it may help to think that all functions in JavaScript are essentially extension methods; as such, passing functions can be VERY powerful.) It's easy to miss this because of the new ES6 class syntax.
This should work to mitigate your issue:
this.config.addListenerSave(this.edit_calendar, this.saveCalendar.bind(this));
That said, your solution using Aurelia's Event Aggregator is a much better fit for your use case and much more scalable. I thought I'd post this answer to address the original issue, which was simply a matter of function scope.
I successfully implemented the alternate solution of adding a global event listener that uses Aurelia's EventAggregator to share Ctrl+S/Ctrl+Enter. Original question still stands but perhaps it wasn't the best approach anyway. Here's my solution:
config.js (global singleton class)
#inject(EventAggregator)
export class Config {
constructor(eventAggregator) {
var self = this;
this.eventAggregator = eventAggregator;
// listen for Ctrl+S or Ctrl+Enter and publish event
window.addEventListener("keydown", function(event) {
if (event.ctrlKey || event.metaKey) { // Ctrl + ___
if ((event.keyCode == 83) || (event.keyCode == 115) || (event.keyCode == 10) || (event.keyCode == 13)) { // Ctrl+Enter or Ctrl+S
// Save button
console.log("Publishing ewKeyboardShortcutSave...");
event.preventDefault();
self.eventAggregator.publish('ewKeyboardShortcutSave', true);
}
}
});
}
}
Then, inside my component viewmodel calendar.js:
#inject(EventAggregator)
export class Calendar {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
attached() {
var self = this;
// Ctrl+Enter is save
this.eventAggregator.subscribe('ewKeyboardShortcutSave', response => {
console.log("I heard ewKeyboardShortcutSave: " + response);
if ($(self.edit_calendar).is(':visible')) {
self.saveCalendar();
}
});
}
}
Works like a charm, and now I can freely add more component event listeners and even extend the functionality to add a global listener for Ctrl+F (for find), etc.
On Mac browsers, javascript does not receive keyup events for most keys (other modifier keys seem to be an exception) when the metakey is down. Use this jsfiddle to demonstrate (focus the result area and try something like cmd + x, the x will not receive a keyup event):
http://jsfiddle.net/mUEaV/
I've reproduced this in stable releases for Chrome, FF, Safari and Opera. The same thing does not seem to happen with the control key in Windows 7.
Is the OS hijacking the keyup event? This seems especially strange since commands that use the metakey such as save, find, cut, copy, etcetera all activate on keydown not on keyup, and can be hijacked by the javascript just fine.
It's simply not possible to get the onKeyUp events when meta is used, I learned today. Very unfortunate and difficult to work around. You'll have to emulate them some other way.
Edit: To clarify, this is only on Mac and occurs due to OS level handling of the event. It cannot be overridden. Sorry to be the bearer of bad news.
Although event.metaKey returns false, event.keyCode and event.key are still populated.
document.addEventListener('keyup', function(e) {
console.log(e.metaKey || e.key);
});
Click here then press the Command, Control, or Option keys.
Is the browser window retaining the focus when you press those keys? In windows you can get similar result when pressing windows+R or CTRL+ESC and similar key combinations that make browser to loose focus and that results in missed events.
While keyup events are indeed not available when the meta key is pressed, you can still get keydown events for all keys, as well as keyup events for the meta key itself.
This allows us to just simply keep track of the state of the meta key ourselves, like so:
let metaKeyDown = false;
window.addEventListener("keydown", event => {
if (event.key == 'Meta') { metaKeyDown = true; }
});
window.addEventListener("keyup", event => {
if (event.key == 'Meta') { metaKeyDown = false; }
});
By now additionally checking for the main key, plus cancelling the default behavior with Event.preventDefault() we can easily listen for key combinations (like here e.g. CMD+K) and prevent the browser from handling them:
let metaKeyDown = false;
window.addEventListener("keydown", event => {
if (event.key == 'Meta') { metaKeyDown = true; }
if (event.key == 'k' && metaKeyDown) {
event.preventDefault();
console.log('CMD+K pressed!');
}
});
window.addEventListener("keyup", event => {
if (event.key == 'Meta') { metaKeyDown = false; }
});
(Note the observation of the k key taking place already on keydown.)
Also, please be aware that when used incorrectly, this can break standard browser functionality (e.g. like CMD+C or CMD+R), and lead to poor user experience.
You can create an artificial keyup event by waiting for a certain period after the last keydown event. The only caveat is people will have different repeat rates on their os.
https://jsfiddle.net/u7t43coz/10/
const metaKeyCodes = ["MetaLeft", "MetaRight"];
const shiftKeyCodes = ["ShiftLeft", "ShiftRight"];
const ctrlKeyCodes = ["ControlLeft", "ControlRight"];
const altKeyCodes = ["AltLeft", "AltRight"];
const modifierKeyCodes = [
...metaKeyCodes,
...shiftKeyCodes,
...ctrlKeyCodes,
...altKeyCodes
];
// record which keys are down
const downKeys = new Set()
const artificialKeyUpTimes = {}
function onKeydown(e) {
downKeys.add(e.code);
// do other keydown stuff here
console.log("meta", e.metaKey, e.code, "down")
// check if metaKey is down
if (metaKeyCodes.some(k => downKeys.has(k))) {
downKeys.forEach(dk => {
// we want to exclude modifier keys has they dont repeat
if (!modifierKeyCodes.includes(dk)) {
// fire artificial keyup on timeout
if (!artificialKeyUpTimes[dk])
setTimeout(
() => fireArtificialKeyUp(dk, e),
500
);
artificialKeyUpTimes[dk] = Date.now();
}
});
}
}
function fireArtificialKeyUp(code, e) {
// if enough time has passed fire keyup
if (Date.now() - artificialKeyUpTimes[code] > 100) {
delete artificialKeyUpTimes[code];
//if key is still down, fire keyup
if (downKeys.has(code)) {
const eCode = isNaN(code) ? { code: code } : { keyCode: code };
document.dispatchEvent(
new KeyboardEvent("keyup", { ...e, ...eCode })
);
}
} else {
setTimeout(() => fireArtificialKeyUp(code, e), 100);
}
}
function onKeyup(e) {
downKeys.delete(e.code);
// do keyup stuff here
console.log("meta", e.metaKey, e.code, "up")
}
document.addEventListener("keydown", onKeydown)
document.addEventListener("keyup", onKeyup)