I have a div with `contentEditable="true"
<div id="msgInput" contentEditable="true"></div>
When the user types in it and presses 'enter' it appends what the user typed to another div
<div id="output"></div>
But I need it so the user can type multiple lines (with shift+enter) without firing the 'keydown' event on the 'enter' key
Here is how I achieved this: (please note that I added the 'input' event Listener because I need to save what the user types in my state variable)
let input = document.getElementById('msgInput');
let output = document.getElementById('output');
let state = '';
input.addEventListener('input', function(event) {
state = event.target.textContent;
});
input.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.target.innerHTML = '';
output.innerHTML = state;
event.preventDefault();
}
});
On Chrome, this is working fine,
for example, If I type "first"
then shift+enter,
then type "second"
and then press enter, it will be appended like this:
first
second
And that's exactly what I want.
But on firefox, it's appended in one line (merged as one word)
firstsecond
I need it in firefox to have the same behavior as in chrome
here's a fiddle for this
https://jsfiddle.net/Lrnx24tb/2/
You could try replace textContent for innerText:
input.addEventListener('input', function(event) {
state = event.target.innerText;
});
Related
I want to have a text field (<input> in HTML) that the user can only use to write text, but when deleting the text, it should prevent him from updating the state of the input element.
<input type="text" />
I couldn't come up with a solution except to detect the user key presses on the keyboard, i.e, using the onKeyUp attribute, and watch the user keystrokes until he presses the Backspace character:
const input = document.querySelector('input')
input.addEventListener('keydown', (e) => {
const keyCode = e.keyCode
if (keyCode === 8) console.log("Backspace pressed!")
})
<input type="text" />
However, there's a missing part here, even though the user won't be able to clear the input content by the Backspace key, he can instead use the Del key.
So I would have then to handle the Del key the same way I handle the Backspace key.
Now, there's another problem, which is trying to modify the input content by overwriting the content as follows:
and then, after that, I will have to prevent the user from editing the text by cutting the content (which I have no idea how to do).
So, what are the alternatives?
Please feel free to use HTML, JavaScript, CSS.
If you're using React, I was able to solve this problem simply by checking if the length of the input value is shorter than the one being memorized in the state, then prevent the user from updating the state:
The answer resides between controlling the value prop (or attribute if you wish) and the onChange handler.
const [inputVal, setInputVal] = useState('')
const handleChange = (e) => {
const value = e.target.value
if(value.length < inputVal.length) return // prevent modifications
setInputVal(value)
}
return <input value={inputVal} onChange={handleChange}/>
Now, the question is, how to do the same when using only HTML/JavaScript?
Perhaps something like this? Then it doesn't matter what they press.
const input = document.querySelector('input')
let previousState = input.value;
input.addEventListener('keydown', (e) => {
if ( previousState.length > input.value.length ) input.value = previousState;
previousState = input.value;
})
In the keydown event, put whatever keycodes you want in an array and see if the current keycode is included. Then create a select event, and blur the input when it fires.
const input = document.querySelector('input');
input.addEventListener('keydown', (e) => {
if ([8, 46].includes(e.keyCode)) {
e.preventDefault()
}
})
input.addEventListener('select', (e) => {
e.target.blur()
})
<input type="text" />
#dqhendricks answer is in the right way, but misses some possibilities:
User can hit backspace (reduce length by one) (covered)
User can select a piece of text and hit backspace or other key (covered)
User can select a piece (or all text) and replace by a different string with same length. (Not covered)
So my suggestion is:
You should listen for change event, then compare the previous string, if the new string starts with the previous string, it's ok. Otherwise we revert the change.
In code ir should look like:
const input = document.querySelector('input');
let previousState = input.value; // initial state
input.addEventListener('change', (e) => {
if (!input.value.startsWith(previousState)) {
input.value = previousState; // revert change
}
previousState = input.value;
})
you can check the length of the input's value, if it became shorter replace it with the previous value
let value;
input.addEventListener('change', ()=>{
if(value && input.value.length < value.length) input.value=value;
else value=input.value;
}
You must have some piece of code that sets the value from the barcode scanner. Once that is being set, also do input.dataset.barcodeValue="scanresult".
Then to make it sufficiently difficult for a regular user to ruin the barcode, you need to prevent several things:
pasting
cutting
typing over the barcode value
dragging text into the input
pressing delete or backspace which would mess up the barcode value.
All of this is covered in the input event.
const input = document.querySelector('#bc-value');
function setValueFromBarcodeScanner(val) {
input.value = input.dataset.barcodeValue = val;
input.addEventListener('input', (e) => {
const { barcodeValue } = input.dataset;
if (barcodeValue && !input.value.startsWith(barcodeValue)) input.value = barcodeValue;
})
}
<input type="text" id="bc-value" />
<button type="button" onclick="setValueFromBarcodeScanner('433-224-221-456')">Set value from Barcode Scanner</button>
I think this might work for you:
const inputElement = document.getElementById('write-only-input');
let lastValue = '';
inputElement.oninput = function(e) {
if (inputElement.value.startsWith(lastValue)) {
lastValue = inputElement.value;
} else {
inputElement.value = lastValue;
}
}
<input type="text" id="write-only-input" />
I want to detect when user focuses a link or input field by pressing the Tab key. Does anyone know of a way to do this? I have tried using the onFocus event, but it does not seem to work.
(I am not using jQuery, and I want to detect focus on links as well, so the similar question 16144611 is not the same as this one.)
By combining keyUp and document.activeElement we can see if an element is currently focused.
We then need to look for the Tab key on keyup and you have a reasonable start at a solution.
const els = document.querySelectorAll('a, input');
window.addEventListener('keyup', function(e){
// check the key code
const code = (e.keyCode ? e.keyCode : e.which);
if(code == 9 && els.length){
checkFocus();
}
});
function checkFocus(){
// loop all elements (within our selector at the start) and see if they match the document.activeElement
for (const el of els) {
if(document.activeElement == el){
console.log("focused tag:" + el.tagName);
}
}
}
<button>Click me for focus, I am not checked</button>
Focused Link
<label for="input1">I am also reported</label>
<input id="input1">
<button>Another button not announced</button>
Note that if you only ever want to check for links and inputs only a more efficient way to check would be:
const els = ['A', 'INPUT'];
window.addEventListener('keyup', function(e){
// check the key code
const code = (e.keyCode ? e.keyCode : e.which);
if(code == 9 && els.length){
checkFocus();
}
});
function checkFocus(){
// by checking the tagName we only have to do 2 loops no matter how many links or inputs there are on a page, which is far more efficient. The downside is it will check **every** link and input on the page. You would access the element with document.activeElelemnt instead if you need to know which item was focused.
for (const el of els) {
if(document.activeElement.tagName == el){
console.log("focused tag:" + document.activeElement.tagName);
}
}
}
<button>Click me for focus, I am not checked</button>
Focused Link
<label for="input1">I am also reported</label>
<input id="input1">
<button>Another button not announced</button>
Using AG-Grid, I need to be able to hit the tab key and have the focused element change from the grid/cell currently selected, to the next element on the page outside of the grid. The problem is that the tab key seems to be locked within the grid, and will not move outside of the data table to the next element.
I have an even listener on the cells that stores the last focused cell (used to store the last location to be able to tab back into the grid to the previously focused cell), but need to have the next focused cell be outside of the data grid:
const cells = document.getElementsByClassName('ag-cell');
[...cells].map(cell => {
cell.addEventListener("keydown", function(e) {
if(e.key === "Tab") {
let lastCell = cell.attributes[2].value;
console.log("Last Cell Focused: ", lastCell)
}
})
})
How can I remove the focus selection from the grid on keypress to the next focusable page element?
Here's a plnkr link to the current grid: Link
=====================================================
UPDATE
I've updated my code, and instead of attaching an event listener to every cell, it's now looking for the event triggered on the document. However, I'm still running into the issue that it's not getting the last_cell value and seeing the focus-visible class on hasFocusVisible.
//on arrow right if last_call === header that has 'focus-visible', set focus to first cell in body
const headerCells = document.getElementsByClassName('ag-header-cell-label');
const last_cell = headerCells[headerCells.length-1].attributes[2];
const hasFocusVisible = document.querySelector('.ag-header-cell-label').classList.contains('focus-visible');
document.addEventListener("keydown", function(e) {
if(e.key === "ArrowRight") {
// if(hasFocusVisible && last_cell) {
console.log("EVENT TRIGGERED FROM: ", event.target, "Last Cell Value: ", last_cell, hasFocusVisible);
//if last_call and 'ag-header-cell-label' has 'focus-visible', set focus to first cell in body
const bodyCell = document.getElementsByClassName('ag-cell')[0];
// }
}
});
UPDATED Plnkr: Link
====================================================
UPDATE 2
I've updated the element selector to the following:
const last_cell = document.querySelector('.ag-header-cell:last-child');
const hasFocusVisible = document.querySelector('.ag-header-cell-label').classList.contains('.focus-visible');
document.addEventListener("keydown", function(e) {
console.log('document.activeElement', document.activeElement)
const activeElement = document.activeElement;
if(e.key === "ArrowRight" && activeElement) {
if(last_cell) {
console.log("EVENT TRIGGERED FROM: ", event.target, "Last Cell Value: ", last_cell, hasFocusVisible);
//if last_call and 'ag-header-cell-label' has 'focus-visible', set focus to first cell in body
const bodyCell = document.getElementsByClassName('ag-cell')[0];
}
}
else if(e.key === "ArrowDown"){
//look for first child in first row with same id as header and set focus
document.querySelector('.ag-cell').focus();
}
});
however, the hasFocusVisible variable is always coming up false when logging out the div that has the focus-visible class. I'm not sure if I have my logic incorrect, or its not able to get the focus-visible class on the ag-header-cell-label when the event listener is fired.
If tab works within the cells, don't add a listener to every cell, just add a single one to your document, and make it move focus to whatever you know is next on the page manually. For instance:
var b = document.querySelector('button');
b.passThrough = true;
b.update = pass => {
b.passThrough = pass;
b.textContent = "click me to " + (b.passThrough ? "block" : "allow") + " tabbing";
}
b.addEventListener('click', e => b.update(!b.passThrough));
b.update(b.passThrough);
var focussable = Array.from(
document.querySelectorAll([
'button',
'[href]',
'input',
'select',
'textarea',
'[tabindex]:not([tabindex="-1"])'
].join(','))
);
// let's pretend this is your last cell.
var p = document.querySelector('p');
// make it kill off keydown events, BUT, also have it redirect focus
// to "the next focussable element", so you can see what that code looks like.
p.addEventListener('keydown', e => e.preventDefault());
document.addEventListener('keydown', e => {
if (b.passThrough && e.target === p) {
var next = focussable.indexOf(p) + 1;
focussable[next % focussable.length].focus();
}
});
<button>toggle</button>
<p tabindex=0>first</p>
second
third
Run this snippet, click the button, hit tab, notice that the tab event is now trapped (like in your cells). Now, click the button again, hit tab, hit tab again: notice it seems like the event is no longer trapped, when it fact it is: the event for the element itself is getting killed off, but the event listener for the document now explicitly moves focus for us.
When user clicks on an img i want to add it's alt value into textarea and add a space to it as if user pressed it himself but not like this el.value += ' ';
Code :
var chatInput = document.querySelector('textarea[data-a-target="chat-input"]'); //textarea selector
var chatSend = document.querySelector('button[data-a-target="chat-send-button"]');
emoteList.addEventListener('click', function(e){
if(e.target.nodeName == 'IMG'){
chatInput.focus();
chatInput.value += e.target.alt;
}
})
The text gets added into textarea, but textarea change event doesn't fire.
What would be the best way to do this? I have spent hours using both Javascript and jQuery to make it work using events but i can't get it right.
I tried focusing on textarea and using dispatch/fire event on window but it does nothing.
I tried firing keypress on textarea but it has no effect either.
if your problem is that change event is not triggering then you can manually trigger it by
var e = $.Event( "change" );
$("#altArea").trigger(e);
Don't need jQuery for this - just use chatInput.dispatchEvent(new Event('change'));.
const chatInput = document.querySelector('textarea');
chatInput.addEventListener('change', () => {
console.log('saw a change');
})
const chatSend = document.querySelector('button');
document.querySelector('#emote-list').addEventListener('click', (e) => {
if(e.target.nodeName !== 'DIV') return;
chatInput.focus();
chatInput.value += 'foo ';
chatInput.dispatchEvent(new Event('change'));
})
<textarea></textarea>
<div id="emote-list">click</div>
How to add multiple event listeners in the same initialization?
For example:
<input type="text" id="text">
<button id="button_click">Search</button>
JavaScript:
var myElement = document.getElementById('button_click');
myElement.addEventListener('click', myFunc());
This is working correctly however I would like to have another event listener for this input filed in the same call if that is possible, so when user clicks enter or presses the button it triggers the same event listener.
Just one note. User needs to be focused on the input field to trigger an "enter" event.
Just bind your function to 2 listeners, each one of the wished element:
document.getElementById('button_click').addEventListener('click', myFunc);
document.getElementById('text').addEventListener('keyup', keyupFunc);
where the new function test if the user pressed enter and then execute the other function :
function keyupFunc(evt) {
if(evt.keyCode === 13) // keycode for return
myFunc();
}
Working jsFiddle : http://jsfiddle.net/cG7HW/
Try this:
function addMultipleEvents(elements, events){
var tokens = events.split(" ");
if(tokens.length == elements.length){
for(var i = 0; i< tokens.length; i++){
elements[i].addEventListener(tokens[i], (e.which == 13 || e.which == 48)?myFunc:); //not myFunc()
}
}
}
var textObj = document.getElementById("textId");
var btnObj = document.getElementById("btnId");
addMultipleEvents([textObj,btnObj], 'click keyup');
UPDATE:
function addMultipleEvents(elements, events) {
var tokens = events.split(" ");
if (tokens.length == elements.length) {
for (var i = 0; i < tokens.length; i++) {
elements[i].addEventListener(tokens[i], myFunc); //not myFunc()
}
}
}
var textObj = document.getElementById("textId");
var btnObj = document.getElementById("btnId");
addMultipleEvents([btnObj, textObj], 'click keyup');
function myFunc(e) {
if (e.which == 13 || e.which == 1) {
alert("hello");
}
}
Working Fiddle
I think the best way to do this is by using for loops.
const events = ["click", "mouseover"]
for (i in events) {
document.getElementById("button_click").addEventListener(events[i], () => myFunc())
}
The code above loops through every events inside an array and adds it to the button.
Yeah this is a good question and can apply to other scenarios. You have a form and a user will have input text field, a radio box, a select option. So now you want the submit button to go from disabled to enabled. You decide to add an event listener to check if fieldA and fieldB and fieldC is first to enable submit button.
If you use event listener on Keyup", and all your fields are valid, the submit button will become enabled only if the last field is a text field because the event will only be triggered when you let go the key. This means it will not trigger if the radio box or select option is selected with your mouse. We must not rely in the order the fields are filled for the logic to work. Again, If you use "click", it sucks, because user will have to click somewhere on page in order for the event listener to fire and run the logic. So i think we'll need an event lister on mouseup, keyup and change for this example below. I assume you made all your validations and variables for the form fields already. We need a function with parameters of multiple events names as a string, the element we want to target (document, or button or form), and a custom function that contains our logic.
// Create function that takes parameters of multiple event listeners, an element to target, and a function to execute logic
function enableTheSubmitButton(element, eventNamesString, customFunction) {
eventNamesString.split(' ').forEach(e => element.addEventListener(e, listener, false));
}
// Call the above function and loop through the three event names inside the string, then invoke each event name to your customFunction, you can add more events or change the event names maybe mousedown, keyup etc.
enableSubmitButton(document, 'keyup mouseup change', function(){
// The logic inside your customFunction
if (isNameValid && isLocationValid && isProjectValid){
publishButton.disabled = false;
} else {
publishButton.disabled = true;
// Do more stuff like: "Hey your fields are not valid."
}
});
// The isNameValid isLocationValid, isProjectValid are coming from your previous validation Javascript for perhaps a select field, radio buttons, and text fields. I am adding it as an example, they have to be equal to true.
// The publishButton is a variable to target the form submit button of which you want enabled or disabled based one weather the form fields are valid or not.
// For example: const publishButton = document.getElementById("publish");