`e.getModifierState is not a function` error on triggered keyup event - javascript

I am trying to detect whether the client has their CapsLock enabled by temporarily creating a hidden input field, triggering a keyup event on that input, and then checking the boolean value of e.getModifierState('CapsLock').
The function I've written is below, which currently throws the error Uncaught TypeError: e.getModifierState is not a function on the line which includes e.getModifierState and I'm struggling to diagnose why this is occurring. I'm open to any help here!
let checkCaps = () => {
let tempInput = document.createElement('input');
let keyupEvent = new Event('keyup');
tempInput.type = 'hidden';
tempInput.id = 'check-caps';
let capsOn = false;
tempInput.addEventListener('keyup', e => {
e.which = 65;
capsOn = e.getModifierState('CapsLock');
})
tempInput.dispatchEvent(keyupEvent);
tempInput.remove();
return capsOn;
}
checkCaps(); // testing the function

The class Event is the base class for all specific event classes.
If you want to generate a keyboard event, then you must use the KeyboardEvent class like this:
let checkCaps = () => {
let tempInput = document.createElement('input');
let keyupEvent = new KeyboardEvent('keyup');
tempInput.type = 'hidden';
tempInput.id = 'check-caps';
let capsOn = false;
tempInput.addEventListener('keyup', e => {
e.which = 65;
capsOn = e.getModifierState('CapsLock');
})
tempInput.dispatchEvent(keyupEvent);
tempInput.remove();
return capsOn;
}
checkCaps(); // testing the function
If tested this and the code runs, but I always get false for getModifierState('CapsLock')
It seems that for you must specify the current value self-generated KeyboardEvents. But this isn't what you want.
It seems that other people had the same issue and there is no solution without real user input.
See: How to detect Caps Lock state on page load (that is, not on keypress) with JavaScript?

Related

Programmatically dispatch keyboard (e.g. keypress) events using JavaScript

I am trying to dispatch events programatically for keyboard and mouse events.
So far there are few things I have tried. Right now I am able to dispatch mouse events programatically and see the changes:
const element = document.getElementById("element");
const event = new Event('click', {bubbles: true});
element.dispatchEvent(event);
Above method is working fine for Mouse Event. And I have tried following method for keyboard events:
const element = document.getElementById("input-element");
const event = new KeyboardEvent('keypress', {'key': 'e'});
element.dispatchEvent(event);
It seems here that the event is being executed, but the values are not being updated in the input field.
There are a number of events that occur when you press a key. None of them result in an input value being changed, but you can use this function to approximate what happens when a key is pressed.
const element = document.getElementById("input-element");
const key = 'e';
var success = triggerKey(key, element);
console.log(success?'success':'fail');
function triggerKey(key, element){
if(!/^[a-z]$/.test(key)) return false;
if(!['INPUT','TEXTAREA'].includes(element.tagName)) return false;
const events = ['keydown', 'keypress', 'textInput', 'keyup'];
events.forEach(event_name=>{
const opts = 'textInput' === event_name ? {
inputType: 'insertText',
data: key
} : {
key: key,
code: `Key${key.toUpperCase()}`
};
const event = 'textInput' === event_name ?
new InputEvent('input', opts) :
new KeyboardEvent(event_name, opts);
element.dispatchEvent(event);
if('textInput' === event_name) element.value += key;
});
return true;
}
<input id="input-element">

is there a single event object for both keydown & keyup

We would like to raise a flag variable within keydown event object, and capture it within keyup is this possible to do so without using an outside variable?
Example :
var keydown = e => {
e.flag = true;
}
var keyup = e => {
e.flag // undefined
}
document.addEventListener('keydown',keydown);
document.addEventListener('keyup',keyup);
The problem is that it seems not to be the same event being fired for both keyup and keydown...
we know we can...
var flag = false;
var keydown = e => {
flag = true;
}
var keyup = e => {
if( flag ) // do something
}
Both this seems less clean rather then using the event itself...
is there anyway to get a single event object for both keydown / keyup?

Remove event listener doesn't work as it should

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);

How do I stopPropagation when changing an event inside an event?

So I have got the following javascript code:
function showSearch() {
var ev = new Event("onkeydown");
ev.keyCode = 13;
document.getElementById("search").onclick = search(ev);
}
The function showSearch() is called in the onclick-event of the Element #search. So when assigning the new event, the new one triggers immediately.
I want to prevent that using stopPropagation().
How do I do that? How else could I solve this?
onclick accepts a function as a parameter - you're providing it undefined (the result of showSearch() ; your current showSearch(), being a function call, executes immediately. Assign onclick a function instead of a function call:
document.getElementById("search").onclick = showSearch;
const search = () => console.log('searching');
function showSearch() {
var ev = new Event("onkeydown");
ev.keyCode = 13;
document.getElementById("search").onclick = () => search(ev);
}
<div id="search">text</div>
But using on-handlers is pretty bad practice. It would be better to add an event listener instead:
const searchDiv = document.getElementById("search");
searchDiv.addEventListener('click', showSearch);
const search = () => console.log('searching');
function showSearch() {
var ev = new Event("onkeydown");
ev.keyCode = 13;
searchDiv.removeEventListener('click', showSearch);
searchDiv.addEventListener('click', () => search(ev));
}
<div id="search">text</div>

Is there any unified way to set both `innerHTML` and `onclick` to one element?

I'm trying to create element using its JSON description.
JavaScript now provides this:
elem.innerHTML = "Text"; // works fine
elem.onclick = "alert(true)"; // doesn't work
elem.setAttribute("innerHTML", "Text"); // doesn't work
elem.setAttribute("onclick", "alert(true)"); // works fine
Unfortunately, I'm not allowed to print
elem.onclick = function() {alert(true)};
Is there any unified way to set both innerHTML and onclick to one element?
Like this:
var props = {"innerHTML":"Text", "onclick":"alert(true)"};
var elem = document.createElement("BUTTON");
for (property in props) elem[property] = props[property];
/* or */
for (property in props) elem.setAttribute(property, props[property]);
/* or maybe something else */
You cold use the Function constructor:
elem.onclick = new Function('', 'return alert(true);');
See working Fiddle
EDIT: I couldn't found a unified way to do this to both events and attributes on an element, but since all events starts with on keyword, you could check if it's an event or attribute on your for loop:
for (property in props) {
if(property.substr(0,2) === 'on') {
//an event
elem.setAttribute(property, props[property]);
}
else {
//an attribute
elem[property] = props[property];
}
}
See working Fiddle

Categories