vanilla javascript : intercept key on input and change key value - javascript

I want to intercept the keys typed in one input and change them to others.
For example, I want to simulate typing a 1 each time a key is pressed.
I was thinking to something like this :
//this example does not work, it will trigger an endless loop
Array.from(document.querySelectorAll('.onlyOne')).forEach(input =>
input.addEventListener('keydown', (event) => {
event.preventDefault();
event.srcElement.dispatchEvent(new KeyboardEvent('keydown', { 'key': 49 }));
});
}
);
I canot just add 1 whith event.target.value += 1;
cause when there is already text in the input and the cursor is not at the end of the text or the user has selected all text with the mouse , it would not act naturally if text is added at the end of input
Could you help me please?

By dispatching an event from within the event that causes the same event, you're creating an infinite loop that will cause a Range Error: Maximum call stack size exceeded.
Instead of the event, simply add a 1 to where the cursor is on each keydown.
Array.from(document.querySelectorAll('.onlyOne')).forEach(input =>
input.addEventListener('keydown', (event) => {
event.preventDefault();
event.target.insertAtCaret('1');
}));
HTMLInputElement.prototype.insertAtCaret = function (text) {
text = text || '';
if (document.selection) {
// IE
this.focus();
var sel = document.selection.createRange();
sel.text = text;
} else if (this.selectionStart || this.selectionStart === 0) {
// Others
var startPos = this.selectionStart;
var endPos = this.selectionEnd;
this.value = this.value.substring(0, startPos) +
text +
this.value.substring(endPos, this.value.length);
this.selectionStart = startPos + text.length;
this.selectionEnd = startPos + text.length;
} else {
this.value += text;
}
};
<input class='onlyOne' value="foo">
The HTMLInputElement.prototype.insertAtCaret is taken from this answer: https://stackoverflow.com/a/19961519/3993662
You can change that to a normal function if you don't want to extend the built in's prototype.

Related

How to simulate the backspace key being pressed in JavaScript or jQuery

I have spent an hour reading a million different posts and can't get a winner.
Simply put. I have created an on-screen keyboard.
When a user presses a letter button, the letter is inserted at the carat in the input that has focus.
This all works fine and I know how to insert all the letters and numbers and spaces but I can't figure out how to backspace at the carat. I know how to take the last character off but that is not effective as I wish it to backspace at the carat.
I will insert the code to show how it is set up... The only part that does not work is the lines in the if ($(this).html() == 'BKSP') block.
PLEASE and THANKS!
function insertAtCursor(myField, myValue) {
//IE support
if (document.selection) {
myField.focus();
sel = document.selection.createRange();
sel.text = myValue;
}
//MOZILLA and others
else if (myField.selectionStart || myField.selectionStart == '0') {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
myField.value = myField.value.substring(0, startPos)
+ myValue
+ myField.value.substring(endPos, myField.value.length);
myField.selectionStart = startPos + myValue.length;
myField.selectionEnd = startPos + myValue.length;
} else {
myField.value += myValue;
}
}
$("#keyboard").on("pointerdown", function(e){
e.preventDefault();
});
$(".sm-kb-btn").on("pointerdown", function (e) {
if ($(this).html() == 'BKSP') {
var e = new Event("keydown");
e.key = "Backspace";
e.code = "Backspace";
document.getElementById("search-box-input").dispatchEvent(e);
}
else {
insertAtCursor(document.getElementById("search-box-input"), $(this).html());
}
})
The browser and javascript have limits when it comes to accessing to device hardware, for sercurity reasons. You can throw a keydown event, but it won't perform the same action as physically pressing a key.
If you're goal is just maintaining the caret position, you can set that using selection.setSelectionRange(caret_position, caret_position)
https://developer.mozilla.org/en-US/docs/Web/API/Selection
Set keyboard caret position in html textbox
Here's a demo:
let output = document.querySelector('input');
document.querySelector('.buttons').addEventListener('click', function(e){
if (e.target.nodeName === 'BUTTON') {
let caret_position = output.selectionStart || 0, //current caret position
character = e.target.textContent, //button / key pressed
new_caret_position = Math.max(0, caret_position + (character === 'BKSP' ? -1 : 1));
//if BKSP, move caret -1, else move caret +1. also make sure it's >= 0
if (character === 'BKSP'){ //remove character preceding current caret position
output.value = output.value.substr(0, new_caret_position) + output.value.substr(caret_position);
} else { //insert character at current character position
output.value = output.value.substr(0, caret_position) + character + output.value.substr(caret_position);
}
//reset the caret position after modifying output.value
output.setSelectionRange(new_caret_position, new_caret_position);
}
});
button{
height: 24px;
margin: 16px 4px;
}
<input>
<div class="buttons">
<button>Q</button>
<button>W</button>
<button>E</button>
<button>R</button>
<button>T</button>
<button>Y</button>
<button>BKSP</button>
</div>

Cell selection stops when mouse move fast on table

Demo
var flag=false;
$(document).live('mouseup', function () { flag = false; });
var colIndex; var lastRow;
$(document).on('mousedown', '.csstablelisttd', function (e) {
//This line gets the index of the first clicked row.
lastRow = $(this).closest("tr")[0].rowIndex;
var rowIndex = $(this).closest("tr").index();
colIndex = $(e.target).closest('td').index();
$(".csstdhighlight").removeClass("csstdhighlight");
if (colIndex == 0 || colIndex == 1) //)0 FOR FULL TIME CELL AND 1 FOR TIME SLOT CELL.
return;
if ($('#contentPlaceHolderMain_tableAppointment tr').eq(rowIndex).find('td').eq(colIndex).hasClass('csstdred') == false)
{
$('#contentPlaceHolderMain_tableAppointment tr').eq(rowIndex).find('td').eq(colIndex).addClass('csstdhighlight');
flag = true;
return false;
}
});
document.onmousemove = function () { return false; };
$(".csstablelisttd").live('mouseenter', function (e) {
// Compares with the last and next row index.
var currentRow = $(this).closest("tr")[0].rowIndex;
var currentColoumn = $(e.target).closest('td').index();
// cross row selection
if (lastRow == currentRow || lastRow == currentRow - 1 || lastRow == currentRow + 1)
{
lastRow = $(this).closest("tr")[0].rowIndex;
}
else
{
flag = false;
return;
}
// cross cell selection.
if (colIndex != currentColoumn)
{
flag = false;
return;
}
if (flag)
{
$('#contentPlaceHolderMain_tableAppointment tr').eq(currentRow).find('td').eq(currentColoumn).addClass('csstdhighlight');
e.preventDefault();
return false;
}
});
Cell selection stops when I move the cursor fast over the table.
What should I do to prevent the selection from stopping while moving the cursor fast on table cells.
I dont want to change the html of the page.
The problem mostly occurs in IE 7.
I have handled the event mousedown, mouseenter on tr class.
I think the selection logic is incorrectly getting stuck in a flag = false state.
When the mouse moves quickly the lastRow == currentRow || lastRow == currentRow - 1 || lastRow == currentRow + 1 will evaluate to false since the currentRow is not next to the lastRow, therefore flag is set to false (in the else). Then for all subsequent mouseenter events flag will always be false and the highlight class will never get added.
The problem occurs on Chrome also and I assume is far more pronounced on IE7 because the JavaScript engine is so much slower in IE7. I think that the JavaScript is quite complex and also the .live() jQuery function should be avoided since it was removed in jQuery 1.9. .on() (as you already use in another event binding) is the preferred method now.
I have included an alternate approach to highlighting the last table cell of each row if the left mouse button is held down, which is a lot simpler. If I have understood the code correctly, the only functionality missing is checking if a current row is either side of a previous row, as I couldn't see a good reason for this extra checking.
There is still the possibility that if a user is moving the mouse quickly over the rows, I would expect the that some rows miss the mouseenter event as the mouse is too quick. You may be able to use a mousemove event handler on the <table> itself to help address this.
The demo uses jQuery 1.9.1, and I also removed the table height to better demonstrate the code.
JavaScript
// disable text selection
document.onselectstart = function() {
return false;
}
var $table = $('#contentPlaceHolderMain_tableAppointment');
$table.on('mouseenter', 'td:last-child', function(e) {
if (e.which === 1) {
$(this).addClass('csstdhighlight');
}
}).on('click', function() {
$table.find('.csstdhighlight').removeClass('csstdhighlight');
});
I'd be happy to explain my example code in more detail if necessary :-)
Note: An answer (https://stackoverflow.com/a/6195715/637889) on jQuery: Detecting pressed mouse button during mousemove event was very helpful when I was looking at this.
Edit: Updated demo based on revised requirements:
JavaScript
// disable text selection
document.onselectstart = function() {
return false;
}
var $table = $('#contentPlaceHolderMain_tableAppointment');
var startIndex = -1;
var direction = 0;
$table.on('mouseenter', 'td:last-child', function(e) {
var $this = $(this);
var rowIndex = $this.parent().index();
if (direction === 0 && startIndex != -1) {
direction = rowIndex > startIndex ? 1 : -1;
}
if (e.which === 1 && (
(direction === -1 && rowIndex < startIndex) ||
(direction === 1 && rowIndex > startIndex))
) {
$(this).addClass('csstdhighlight');
}
}).on('mousedown', 'td:last-child', function() {
var $this = $(this);
startIndex = $this.parent().index();
$this.addClass('csstdhighlight');
}).on('mouseup', 'td:last-child', function() {
direction = 0;
startIndex = -1;
}).on('click', 'td:last-child', function() {
$table.find('.csstdhighlight').removeClass('csstdhighlight');
});

JavaScript: Scroll to selection after using textarea.setSelectionRange in Chrome

A JavaScript function selects a certain word in a textarea using .setSelectionRange().
In Firefox, the textarea automatically scrolls down to show the selected text. In Chrome (v14), it does not. Is there a way to get Chrome to scroll the textarea down to the newly selected text?
jQuery solutions are welcome.
Here is a simple and efficient solution in pure JavaScript:
// Get the textarea
var textArea = document.getElementById('myTextArea');
// Define your selection
var selectionStart = 50;
var selectionEnd = 60;
textArea.setSelectionRange(selectionStart, selectionEnd);
// Mow let’s do some math.
// We need the number of characters in a row
var charsPerRow = textArea.cols;
// We need to know at which row our selection starts
var selectionRow = (selectionStart - (selectionStart % charsPerRow)) / charsPerRow;
// We need to scroll to this row but scrolls are in pixels,
// so we need to know a row's height, in pixels
var lineHeight = textArea.clientHeight / textArea.rows;
// Scroll!!
textArea.scrollTop = lineHeight * selectionRow;
Put this in a function, extend the prototype of JavaScript's Element object with it, and you're good.
A lot of answers, but the accepted one doesn't consider line breaks, Matthew Flaschen didn't add the solution code, and naXa answer has a mistake. The simplest solution code is:
textArea.focus();
const fullText = textArea.value;
textArea.value = fullText.substring(0, selectionEnd);
textArea.scrollTop = textArea.scrollHeight;
textArea.value = fullText;
textArea.setSelectionRange(selectionStart, selectionEnd);
You can see how we solved the problem in ProveIt (see highlightLengthAtIndex). Basically, the trick is to truncate the textarea, scroll to the end, then restore the second part of the text. We also used the textSelection plugin for consistent cross-browser behavior.
Valeriy Katkov's elegant solution works great but has two problems:
It does not work for long strings
Selected contents are scrolled to the bottom of the textarea, making it hard to see the context which surrounds the selection
Here's my improved version that works for long strings (tested with at least 50,000 words) and scroll selection to the center of the textarea:
function setSelectionRange(textarea, selectionStart, selectionEnd) {
// First scroll selection region to view
const fullText = textarea.value;
textarea.value = fullText.substring(0, selectionEnd);
// For some unknown reason, you must store the scollHeight to a variable
// before setting the textarea value. Otherwise it won't work for long strings
const scrollHeight = textarea.scrollHeight
textarea.value = fullText;
let scrollTop = scrollHeight;
const textareaHeight = textarea.clientHeight;
if (scrollTop > textareaHeight){
// scroll selection to center of textarea
scrollTop -= textareaHeight / 2;
} else{
scrollTop = 0;
}
textarea.scrollTop = scrollTop;
// Continue to set selection range
textarea.setSelectionRange(selectionStart, selectionEnd);
}
It works in Chrome 72, Firefox 65, Opera 58, and Edge 42.
For an example of using this function, see my GitHub project SmartTextarea.
This is a code inspired by the Matthew Flaschen's answer.
/**
* Scroll textarea to position.
*
* #param {HTMLInputElement} textarea
* #param {Number} position
*/
function scrollTo(textarea, position) {
if (!textarea) { return; }
if (position < 0) { return; }
var body = textarea.value;
if (body) {
textarea.value = body.substring(0, position);
textarea.scrollTop = position;
textarea.value = body;
}
}
Basically, the trick is to truncate the textarea, scroll to the end, then restore the second part of the text.
Use it as follows
var textarea, start, end;
/* ... */
scrollTo(textarea, end);
textarea.focus();
textarea.setSelectionRange(start, end);
Based on the idea from naXa and Valeriy Katkov, I refined the function with fewer bugs. It should work out of the box (It's written with TypeScript. For JavaScript, just remove the type declaration):
function scrollTo(textarea: HTMLTextAreaElement, offset: number) {
const txt = textarea.value;
if (offset >= txt.length || offset < 0)
return;
// Important, so that scrollHeight will be adjusted
textarea.scrollTop = 0;
textarea.value = txt.substring(0, offset);
const height = textarea.scrollHeight;
textarea.value = txt;
// Margin between selection and top of viewport
textarea.scrollTop = height - 40;
}
Usage:
let textarea, start, end;
/* ... */
scrollTo(textarea, start);
textarea.focus();
textarea.setSelectionRange(start, end);
Complete code for Chrome:
<script type="text/javascript">
var SAR = {};
SAR.find = function () {
debugger;
var parola_cercata = $("#text_box_1").val(); // The searched word
// Make text lowercase if search is
// supposed to be case insensitive
var txt = $('#remarks').val().toLowerCase();
parola_cercata = parola_cercata.toLowerCase();
// Take the position of the word in the text
var posi = jQuery('#remarks').getCursorPosEnd();
var termPos = txt.indexOf(parola_cercata, posi);
if (termPos !== -1) {
debugger;
var target = document.getElementById("remarks");
var parola_cercata2 = $("#text_box_1").val();
// Select the textarea and the word
if (target.setSelectionRange) {
if ('selectionStart' in target) {
target.selectionStart = termPos;
target.selectionEnd = termPos;
this.selectionStart = this.selectionEnd = target.value.indexOf(parola_cercata2);
target.blur();
target.focus();
target.setSelectionRange(termPos, termPos + parola_cercata.length);
}
} else {
var r = target.createTextRange();
r.collapse(true);
r.moveEnd('character', termPos + parola_cercata);
r.moveStart('character', termPos);
r.select();
}
} else {
// Not found from cursor pos, so start from beginning
termPos = txt.indexOf(parola_cercata);
if (termPos !== -1) {
var target = document.getElementById("remarks");
var parola_cercata2 = $("#text_box_1").val();
// Select the textarea and the word
if (target.setSelectionRange) {
if ('selectionStart' in target) {
target.selectionStart = termPos;
target.selectionEnd = termPos;
this.selectionStart = this.selectionEnd = target.value.indexOf(parola_cercata2);
target.blur();
target.focus();
target.setSelectionRange(termPos, termPos + parola_cercata.length);
}
} else {
var r = target.createTextRange();
r.collapse(true);
r.moveEnd('character', termPos + parola_cercata);
r.moveStart('character', termPos);
r.select();
}
} else {
alert("not found");
}
}
};
$.fn.getCursorPosEnd = function () {
var pos = 0;
var input = this.get(0);
// IE support
if (document.selection) {
input.focus();
var sel = document.selection.createRange();
pos = sel.text.length;
}
// Firefox support
else if (input.selectionStart || input.selectionStart === '0')
pos = input.selectionEnd;
return pos;
};
</script>
I published an answer here:
http://blog.blupixelit.eu/scroll-textarea-to-selected-word-using-javascript-jquery/
It works perfectly with just one needed rule: Set a line-height in the CSS content of the textarea!
It calculate the position of the word to scroll to just by doing some simple mathematical calculation and it worked perfectly in all my experiments!

How to insert text at the current caret position in a textarea

On a function call from an image, I am trying to insert the alt tag value from the image into the textarea at the position where the caret currently is.
This is the code that I currently have which inserts the alt tag value to the end of the text area.
$("#emoticons").children().children().click(function () {
var ch = $(this).attr("alt");
$("#txtPost").append(ch);
});
The 2 things I have been having a problem with is determining the position of the caret, and creating a new string with the value of the textarea before the carets positon + the code I'm inserting + the value of the textarea after the carets position.
i've currently got this extension in place:
$.fn.insertAtCaret = function(text) {
return this.each(function() {
if (document.selection && this.tagName == 'TEXTAREA') {
//IE textarea support
this.focus();
sel = document.selection.createRange();
sel.text = text;
this.focus();
} else if (this.selectionStart || this.selectionStart == '0') {
//MOZILLA/NETSCAPE support
startPos = this.selectionStart;
endPos = this.selectionEnd;
scrollTop = this.scrollTop;
this.value = this.value.substring(0, startPos) + text + this.value.substring(endPos, this.value.length);
this.focus();
this.selectionStart = startPos + text.length;
this.selectionEnd = startPos + text.length;
this.scrollTop = scrollTop;
} else {
// IE input[type=text] and other browsers
this.value += text;
this.focus();
this.value = this.value; // forces cursor to end
}
});
};
and you can use it like so:
$("#txtPost").insertAtCaret(ch);

Mapping the event position to the text position in non text-fields

Is there a way to map an event such as a click-event on this element
<div>Just normal text</div>
to the position in the contained text ( "You just clicked the 6th character", when hitting the 'n' )?
I don't know any pretty way of achieving that but I got a solution that would work, although it's ugly.
You could wrap each letter of your div text in a lets say span element and add unique identifiers for each letter. Then you'd hook up event handlers for those span elements, not the whole div and based on the span id you could tell which character was that.
This whole thing can be done in JS but as I said that's not the ideal solution for sure.
Here's the example (I've added a test id to the div so I could find it easier).
var letters = $('#test').text();
var spans = '';
for (var i = 0; i < letters.length; i++) {
spans += '<span id="id' + i + '">' + letters[i] + '<span>';
}
$('#test').html(spans);
$('span[id^=id]').click(function() {
alert('Clicked char: ' + (Number($(this).attr('id').substring(2)) + 1));
return false;
});
You can also give it a try on my demo.
Not as such; I guess you would have to split each character into a <span> element, either on server side or using JQuery.
Here's a hacky way that could possibly be made to work: it involves temporarily making the document editable and examining the selection. It should work in Firefox 3+, IE 6+, recent Safari and Chrome.
As it stands, there are some problems I can see:
The results in IE are different to other browsers: IE counts all characters in the whole containing element up until the caret while other browsers give an offset within the containing text node, but you could work round this;
A border appears round the current element in some browsers
Doesn't work in Opera or Firefox 2, and leaves document editable
Possibility of other UI glitches: it's a nasty hack.
Code:
window.onload = function() {
var mouseDownEl;
document.onmousedown = function(evt) {
evt = evt || window.event;
var el = evt.target || evt.srcElement;
if (evt.srcElement || !("contentEditable" in el)) {
document.designMode = "on";
} else {
el.contentEditable = "true";
}
mouseDownEl = el;
};
document.onclick = function(evt) {
evt = evt || window.event;
var el = evt.target || evt.srcElement;
if (el == mouseDownEl) {
window.setTimeout(function() {
var caretPos, range;
if (typeof window.getSelection != "undefined") {
caretPos = window.getSelection().focusOffset;
} else if (document.selection && document.selection.createRange) {
range = document.body.createTextRange();
range.moveToElementText(el);
range.collapse();
range.setEndPoint("EndToEnd", document.selection.createRange());
caretPos = range.text.length;
}
if (el.contentEditable == "true") {
el.contentEditable = "false";
} else {
document.designMode = "off";
}
alert(caretPos);
}, 1);
} else {
if (mouseDownEl.contentEditable == "true") {
mouseDownEl.contentEditable = "false";
} else {
document.designMode = "off";
}
}
mouseDownEl = null;
};
};

Categories

Resources