Javscript listen keyboard, on text input - javascript

I want to show popup if user input text no matter where on website, I see that javascript have key listener but what is the best way if I want to show popup when user write "ShowMePopup"?
I have write this script for test
window.addEventListener("keydown", function (event) {
if (event.defaultPrevented) {
return;
}
var name = event.key;
var code = event.code;
console.log(`Key pressed ${name} \r\n Key code value: ${code}`);
switch (event.key) {
case "ArrowDown":
alert('arrow down')
break;
case "Shift + S + h + o + w + Shift + M + e + Shift + P + o + p + u + p":
alert('Secret Popup')
break;
default:
return;
}
event.preventDefault();
}, true);
I have try to add case with
case "Shift + S + h + o + w + Shift + M + e + Shift + P + o + p + u + p":
but it wont work, any ideas?

When a key is pressed, push it into an array. Then, check the last 11 items (the length of the desired key combo) in the array for your key combination:
const keysPressed = [];
const popupKeyCombo = ['Shift', 'S', 'h', 'o', 'w', 'Shift', 'M', 'e', 'Shift', 'P', 'o', 'p', 'u', 'p'];
window.addEventListener("keydown", e => {
keysPressed.push(e.key);
if(keysPressed.slice(-popupKeyCombo.length).join() === popupKeyCombo.join()){
alert('Secret Popup');
}
});
And consider adding additional logic to keep the array at a reasonable size.

You can solve the issue by creating a storage variable outside of your listener function and store each key event inside of it. However, be careful with the timing of keypresses since holding shift longer, multiple key events will be fired. But with this you should make it work.
let storage = "";
window.addEventListener("keydown", function (event) {
if (event.defaultPrevented) {
return;
}
storage += event.key;
console.log(storage);
if(storage.includes("ShiftShowShiftMeShiftPopup")){
alert("Surprise");
storage = "";
}
event.preventDefault();
}, true);

You may keep track of what was typed in each occurence of the keydown event and then do a substraction between the expected text and what was captured so far.
As soon as the capture diverges from what's expected, the captured string will be reset.
At each character typed, the demo will print on console the full string expected, what was captured so far since the latest reset and what's still missing.
When the passphares will be catched correctly, the function secretUnlocked gets triggered.
This solution won't have a infinitely growing buffer but just the captured string as long as it was matching the expected input. As soon as the typed string diverges, the buffer gets reset and the minimum amount of memory gets used.
let captured = "";
let expected = "ShowMePopup";
//substracts captured to expected and returns the text fragment still missing
function diff(expected, capture){
const split = expected.split(capture);
if(split[0].length > 0 || split.length > 2)
return expected;
else
return split[1];
}
document.addEventListener('keydown', function(event) {
//character allowed (this is a lazy trick to exclude special keys like shift, ctrl...)
const allowedChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
if(!allowedChars.split('').includes(event.key)){
return;
}
//appends the latest typed char to the captured string
captured += event.key;
const stillExpecting = diff(expected, captured);
//if stillExpecting is still the same as expected, resets the captured part
if(stillExpecting === expected)
captured = '';
console.log(`expecting: ${expected} .. so far: ${captured} .. still expecting: ${stillExpecting}`);
//if stillExpecting has zero length, the condition is met
if (stillExpecting.length === 0){
secretUnlocked();
}
});
function secretUnlocked(){
console.log('Secret Unlocked');
}
Type the secret passphrase while the document has focus...

Wow, what a lot of code.
This one works too
let text = "";
const phrase = "ShowMePopup";
window.addEventListener("keydown", e => {
const key = event.key;
if (key.length !==1) return; // ignore shift etc. The shifted char is in the next key
text += key;
if (text.indexOf(phrase) != -1) {
console.log("Show popup");
text = ""; // to handle ShowMePopupShow....
}
else if (phrase.indexOf(key) === -1) text = ""; //reset
});

You mean a key.detector with a popup like this ?. Try this code.
function showKC(event) {
var x = event.keyCode;
var y = event.key;
document.getElementById("charcode").innerHTML = "Key: "+y+" Code: "+x;
}
function CheckPop() {
var smp = "ShowMePopup";
var text = document.getElementById("t1");
if (text.value == smp) {
alert(smp);
}
}
Just type on keyboard <input type="text" id="t1" value="" onkeyup="showKC(event), CheckPop()"/>
<div id="charcode"></div>

Related

How can I create a reset button for this game?

I have my current code posted, I'm trying to create a reset button. A button has already been created in my HTML, and I'm grabbing it at the bottom, and adding an event listener, which isn't working for some reason, also trying to figure out the correct code to add it for my game resets when the button is clicked. However having a difficult time with the syntax.
// Array of words
const words = ['planet', 'stars', 'astroid', 'moon', 'satilite', 'orbit', 'universe', 'umbra', 'lunar', 'space', 'astronomy', 'eclipse', 'nebula', 'mars', 'meteorite']
// guesses Array
let myGuesses = []
//variables
let wordSpace = ' - '
let guess = ' '
let space; //number of spaces in word
//score
let tries = 10
let counter ;
//Get random word
let index = Math.floor(Math.random()* words.length)
//play function
function play() {
let userInput = prompt(`would you like to play spaceman? (Y/N)`, "Y")
console.log(words[index])
for(let i = 0; i < words[index].length; i++){
console.log(words[0][i])
let div = document.createElement('div')
div.classList.add('letters')
div.innerHTML=' - '//words[0][i]
document.querySelector('.word-space').append(div)
}
}
//handle click function, inactivates buttons, and changes color to grey; once clicked
let handleclick = e => {
e.target.removeEventListener('click', handleclick)
e.target.style.backgroundColor= 'grey'
console.log(e.target.innerHTML)
myGuesses.push(e.target.innerHTML)
console.log(myGuesses)
console.log(words[index].includes(e.target.innerHTML))
if(words[index].includes(e.target.innerHTML)){
document.querySelector('.word-space').innerHTML= ' '
// let correct = document.createElement('ul')
for(let i = 0; i < words[index].length; i++){
// correct.setAttribute('id','correctLetters' )
// let guess= document.createElement('li')
// guess.setAttribute('class','guess')
console.log(words[0][i])
let div = document.createElement('div')
div.classList.add('letter')
if (myGuesses.includes(words[index][i])){
div.innerHTML = words[index][i]
} else {
div.innerHTML = ' - '
}
document.querySelector('.word-space').append(div)
}
getNumOfTries()
} else {
tries --
getNumOfTries()
}
}
function ans () {
const buttons = document.querySelectorAll('.letter')
buttons.forEach(letter => letter.addEventListener('click',handleclick))
}
ans()
function getNumOfTries (){
console.log(tries)
const showTries = document.querySelector('#myTries')
showTries.innerHTML = ' You have ' + tries + ' tries'
if(tries < 1){
setTimeout(() =>{prompt(`Would you like to try again? (Y,N)`, 'Y')
showTries.innerHTML = 'You loose!'
},2000)
}
// if(tries > 0 && words[index].length === myGuesses.length) {
if(tries > 0 && Array.from(document.querySelectorAll('.letters')).every(letter => letter.innerHTML !== ' - ')) {
// showTries.innerHTML = 'You Win!'
setTimeout(() =>{alert(`You Win!`)
showTries.innerHTML = 'You Win!'
},1000)
}
}
//reset game
let tryAgain = document.querySelector('.Try-Again')
tryAgain.addEventListener('clcik', play)
prevent
div.innerHTML=' - '//words[0][i]
document.querySelector('.word-space').append(div)
play()
You've got a typo there :)
tryAgain.addEventListener('clcik', play)
some notes to the written code:
you don't need to reference the element, if you're not going to use it elsewhere, just do:
document.querySelector('.Try-Again')?.addEventListener('click', play);
i'm not sure about what the "prevent" should do here
you haven't defined a global reference to a "div" parameter that you want to use at:
div.innerHTML=' - '//words[0][i]
use semicolon ';' by the end of each executed code for better code clarity + checkout some coding standards
use code editors like Visual Studio code or other tools - they usually have some basic code formatter (learn they shortcuts, it will optimize your coding process) + other usefull features like clicking through defined param references etc.

how to get id of input box?

when my webpage load then on console array of id showing
But when the whole page load array of id disappear
enter code here1: https://i.stack.imgur.com/AsdWq.png
And my main problem in this image
[enter image description here][2][2]: https://i.stack.imgur.com/0Ci3I.png
that is my code
so how to save id in var $write = $(":input").on('focus').map((i, el) => el.id).get();
and when its save in the $write function so where the cursor the value go in it.
But in it typing in one input box gets text on all input box. I want typing in one and value get in one whyen cursor update
like when my mouse cursor in itemNo_1 so the value go in that input box and when itemNo_2 so the value go in that input box.
please help me.
$(function(){
var $write = $(":input").on('focus').map((i, el) => el.id).get();
shift = false,
capslock = false;
backspace = false;
console.log(Object.values($write));
$('#keyboard li').click(function(){
var $this = $(this),
character = $this.html();
// Shift keys
if ($this.hasClass('left-shift') || $this.hasClass('right-shift')) {
$('.letter').toggleClass('uppercase');
$('.symbol span').toggle();
shift = (shift === true) ? false : true;
capslock = false;
backspace = false;
return false;
}
// Caps lock
if ($this.hasClass('capslock')) {
$('.letter').toggleClass('uppercase');
capslock = true;
return false;
}
// backspace lock
if ($this.hasClass('backspace')) {
$('.letter').toggleClass('uppercase');
capslock = true;
return false;
}
// Delete
if ($this.hasClass('delete')) {
var html = $write.html(),
txt = html.substr(0, html.length - 1);
$write.html(txt);
$write.autocomplete("search", txt);
return false;
}
// Delete
// Special characters
if ($this.hasClass('symbol')) character = $('span:visible', $this).html();
if ($this.hasClass('space')) character = ' ';
if ($this.hasClass('tab')) character = "\t";
if ($this.hasClass('return')) character = "\n";
// Uppercase letter
if ($this.hasClass('uppercase')) character = character.toUpperCase();
// Remove shift once a key is clicked.
if (shift === true) {
$('.symbol span').toggle();
if (capslock === false) $('.letter').toggleClass('uppercase');
shift = false;
}
//console.log("DO IT ", character);
// Add the character
$write.val($write.val() + character);
$write.html(txt);
$write.autocomplete("search", txt);
});
});
rest code is
here
$(function () {
var write = write();
write.init("keyboard");
//first input focus
var $firstInput = $(":input").first().focus();
write.currentElement = $firstInput;
write.currentElementCursorPosition = 0;
});

Record keypress and response time without advancing

I'm very new to JavaScript. I have a Qualtrics survey and I'm trying to create a question to which respondents must first press the spacebar, and then respond to a math problem on the screen. What I want is for Qualtrics to record (1) whether they press the spacebar (score as "C") or not (score as "X"), (2) if they do, how long it took them to press the spacebar from when the page appeared, and (3) to NOT advance to the next screen on the keypress, since they still have to answer the math problem on the screen. Here's what I've been trying:
Body of the Qualtrics question Q1:
Please press the spacebar, then solve this problem: 1+12+15-13-3+19-9=?
JavaScript associated with Qualtrics question Q1:
Qualtrics.SurveyEngine.addOnload(function()
{
var day = new Date();
trialstart = day.getTime();
$(document).on('keydown', function(e) {
if(e.keyCode === 32) {
var day = new Date();
trialend = day.getTime();
rt = trialend - trialstart;
document.getElementById("Q1").value = document.getElementById("Q1").value + "C" + rt + ",";
}
else{
document.getElementById("Q1").value = document.getElementById("Q1").value + "X" + ",";
}
});
});
Right now, this script isn't doing anything; Qualtrics outputs whatever is typed into the response textbox associated with Q1 (for responding to the math problem), but doesn't record whether they pressed the spacebar or their response time for pressing that key. Any help would be greatly appreciated. Thanks.
It is likely that you are seeing no output from this because you're using "Q1" as the ID. Qualtrics element ID's are actually a bit different from that, so the best way to find them is to preview your survey and inspect them using your browser to find the proper ID.
Now for a more full answer, your Javascript has no way to unbind the keypress event. So it will always end up as X, not C, unless they end their text entry with a space. Instead try something like this:
Qualtrics.SurveyEngine.addOnload(function(){
var pageStart = new Date();
var trialstart = pageStart.getTime();
var that = this;
that.hideNextButton();
var para = document.createElement("footnote");
var test = document.getElementById("Buttons");
var node = document.createElement('input');
var next = document.getElementById("NextButton");
var rt;
node.id = "newButton";
node.type = "button";
node.name = "newButton";
node.value = " Continue ";
var fired = 0;
var spaceFirst = '';
$(document).on('keydown', function(e) {
if(fired != 1){
if(e.keyCode === 32) {
var day = new Date();
var trialend = day.getTime();
rt = trialend - trialstart;
spaceFirst = 'C';
fired = 1;
}
else{
spaceFirst = 'X';
fired = 1;
}
}else{}
});
node.onclick = function stuff(){
if(spaceFirst == 'C'){
document.getElementById("QR~QID1").value = document.getElementById("QR~QID1").value + ', ' + spaceFirst + ", " + rt;
}else if(spaceFirst == 'X'){
document.getElementById("QR~QID1").value = document.getElementById("QR~QID1").value + ', ' + spaceFirst;
}else{}
window.alert(document.getElementById("QR~QID1").value);
that.clickNextButton();
};
para.appendChild(node);
test.insertBefore(para, next);
});
What this does is, hides the next button from view and creates a new button in it's place. (You will need to style this button, id = "newButton", to match your current button). Then it listens for the first keydown event, if it is a space, then it sets a variable equal to C, otherwise it sets it equal to X. Then it disables the keydown listener so that it doesn't fire after every keypress.
When the new button is pressed, it adds the appropriate X or C and the page time. You can remove or comment out the alert, as it is just for testing/debugging purposes.

How to insert space every 4 characters for IBAN registering?

I'm really new in JavaScript and I would like to add to my input text, space insertion for IBAN account registering.
<input type="text" name="iban" onkeyup="if(this.value.length > 34){this.value=this.value.substr(0, 34);}" />
There is my input field; could someone tell me how I can do this?
The existing answers are relatively long, and they look like over-kill. Plus they don't work completely (for instance, one issue is that you can't edit previous characters).
For those interested, according to Wikipedia:
Permitted IBAN characters are the digits 0 to 9 and the 26 upper-case Latin alphabetic characters A to Z.
Here is a relatively short version that is similar to the existing answers:
document.getElementById('iban').addEventListener('input', function (e) {
e.target.value = e.target.value.replace(/[^\dA-Z]/g, '').replace(/(.{4})/g, '$1 ').trim();
});
<label for="iban">iban</label>
<input id="iban" type="text" name="iban" />
As stated above, the caveat is that you can't go back and edit previous characters. If you want to fix this, you would need to retrieve the caret's current position by initially accessing the selectionEnd property and then setting the caret's position after the regex formatting has been applied.
document.getElementById('iban').addEventListener('input', function (e) {
var target = e.target, position = target.selectionEnd, length = target.value.length;
target.value = target.value.replace(/[^\dA-Z]/g, '').replace(/(.{4})/g, '$1 ').trim();
target.selectionEnd = position += ((target.value.charAt(position - 1) === ' ' && target.value.charAt(length - 1) === ' ' && length !== target.value.length) ? 1 : 0);
});
<label for="iban">iban</label>
<input id="iban" type="text" name="iban" />
You will notice that there is a slight issue when the character after the caret is a space (because the space wasn't accounted for when initially retrieving the caret's position to begin with). To fix this, the position is manually incremented if the succeeding character is a space (assuming a space was actually added - which is determined by comparing the length before and after replacing the characters).
Using plain-JavaScript, I'd suggest:
function space(el, after) {
// defaults to a space after 4 characters:
after = after || 4;
/* removes all characters in the value that aren't a number,
or in the range from A to Z (uppercase): */
var v = el.value.replace(/[^\dA-Z]/g, ''),
/* creating the regular expression, to allow for the 'after' variable
to be used/changed: */
reg = new RegExp(".{" + after + "}","g")
el.value = v.replace(reg, function (a, b, c) {
return a + ' ';
});
}
var el = document.getElementById('iban');
el.addEventListener('keyup', function () {
space(this, 4);
});
JS Fiddle demo.
Somewhat belatedly, my rewrite of the above to handle strings, rather than DOM nodes:
function space(str, after) {
if (!str) {
return false;
}
after = after || 4;
var v = str.replace(/[^\dA-Z]/g, ''),
reg = new RegExp(".{" + after + "}", "g");
return v.replace(reg, function (a) {
return a + ' ';
});
}
var el = document.getElementById('iban');
el.addEventListener('keyup', function () {
this.value = space(this.value, 4);
});
JS Fiddle demo.
References:
addEventListener().
JavaScript regular expressions.
I wrote a simple function extending David's function to handle the last space. Also you can specify the separator.
function spacify(str, after, c) {
if (!str) {
return false;
}
after = after || 4;
c = c || " ";
var v = str.replace(/[^\dA-Z]/g, ''),
reg = new RegExp(".{" + after + "}", "g");
return v.replace(reg, function (a) {
return a + c;
}).replace(/[^0-9]+$/, "");
}
console.log(spacify("123123123131",4," "))
console.log(spacify("12312312313",4,"-"))
The code from Josh Crozie is really nice, but not complete.
Two issues with it;
If the caret is not at the end but e.g. at the before last position and the user starts typing, sometimes the caret doesn't stay at the before last position
Another issue is with Android 7+ devices. Those devices update the caret position slightly later, that means it needs a setTimeout() before reading the caret location
The code below is based on the code of Josh Crozie, now with the two issues mentioned above fixed and a little more verbose for readability purpose:
var isAndroid = navigator.userAgent.indexOf("ndroid") > -1;
var element = document.getElementById('iban');
element.addEventListener('input', function () {
if (isAndroid) {
// For android 7+ the update of the cursor location is a little bit behind, hence the little delay.
setTimeout(reformatInputField);
return;
}
reformatInputField();
});
function reformatInputField() {
function format(value) {
return value.replace(/[^\dA-Z]/gi, '')
.toUpperCase()
.replace(/(.{4})/g, '$1 ')
.trim();
}
function countSpaces(text) {
var spaces = text.match(/(\s+)/g);
return spaces ? spaces.length : 0;
}
var position = element.selectionEnd;
var previousValue = element.value;
element.value = format(element.value);
if (position !== element.value.length) {
var beforeCaret = previousValue.substr(0, position);
var countPrevious = countSpaces(beforeCaret);
var countCurrent = countSpaces(format(beforeCaret));
element.selectionEnd = position + (countCurrent - countPrevious);
}
}
<label for="iban">iban</label>
<input id="iban" type="text" name="iban" size="35" />
You have to capture each group of 4 digits and then put a space between each group.
$('input').blur(function () {
//Replace each group 4 digits with a group plus a space
var reformat = this.value.replace(/(\d{4})/g, function(match){
return match + " ";
});
this.value = reformat;
})
And this one updates the element while typing
//Keys pressed 0 times
var downed = 0;
$('#test').keydown(function (g) {
if(g.code.match("^Digit")){
downed++;
console.log(g)
}
if(downed == 1){
var reformat = this.value.replace(/(\d{4}\s*)/g, function(match){
//Strip spaces
if(match.match(/\s/)){return match;}
return match + " ";
});
console.log(reformat);
this.value = reformat;
//Start recount
downed = 0;
}
});
Check out the fiddle
for thousands on angular 4 in a pipe
integer = integer.replace(/[^\dA-Z]/g, '').replace(/(.{3})/g, '$1.').trim();
I need the same but for BVR/BVR+ swiss payment form.
So what I need is add a space every 5 chars but from the end of the string.
Example : "52 86571 22001 00000 10520 15992" or sometimes shorter like "843 14293 10520 15992".
So, here is the solution by reversing the string before and after adding spaces if rev=1.
function space(str, stp, rev) {
if (!str) {
return false;
}
if (rev == 1) {
str = str.split('').reverse().join('');
}
if(stp > 0) {
var v = str.replace(/[^\dA-Z]/g, ''),
reg = new RegExp(".{" + stp + "}", "g");
str = v.replace(reg, function (a) {
return a + ' ';
});
}
if (rev == 1) {
str = str.split('').reverse().join('');
}
return str;
}
Use :
var refTxt = space(refNum, 5, 1);
EDIT : PHP version added
function space($str=false, $stp=0, $rev= false) {
if(!$str)
return false;
if($rev)
return trim(strrev(chunk_split(strrev($str), $stp, ' ')));
else
return trim(chunk_split($str, $stp, ' '));
}
document.getElementById('iban').addEventListener('input', function (e) {
e.target.value = e.target.value.replace(/[^\dA-Z]/g, '').replace(/(.{4})/g, '$1 ').trim();
});
<label for="iban">iban</label>
<input id="iban" type="text" name="iban" />
This is the shortest version using JQuery on input with type number or tel:
$('input[type=number], input[type=tel]').on('input', function (e) {
e.target.value = e.target.value.replace(/[^\dA-Z]/g, '').replace(/(.{4})/g, '$1 ').trim();
});
You can also change the 4 to any other character limit you want.
onChangeText={number => {
const data =
number.length % 5 !== 4
? number
.replace(/[^\dA-Z]/g, '')
.replace(/(.{4})/g, '$1-')
.trim()
: number;
this.setState({
...this.state,
card: {...this.state.card, number: data},
});
}}
If you are trying to use for text input to adjust with credit card then this method will help you solve the backspace problem too
To Add space after 4 Digits
Useful to validate IBAN Number
document.getElementById('IBAN').addEventListener('input', function (e) {
e.target.value = e.target.value.replace(/[^\dA-Z]/g, '').replace(/(.{4})/g, '$1 ').trim();
});
<label for="IBAN">IBAN</label>
<input id="IBAN" maxlength="14" type="text" name="IBAN" />

Find out the 'line' (row) number of the cursor in a textarea

I would like to find out and keep track of the 'line number' (rows) of the cursor in a textarea. (The 'bigger picture' is to parse the text on the line every time a new line is created/modified/selected, if of course the text was not pasted in. This saves parsing the whole text un-necessarily at set intervals.)
There are a couple of posts on StackOverflow however none of them specifically answer my question, most questions are for cursor position in pixels or displaying lines numbers besides the textarea.
My attempt is below, it works fine when starting at line 1 and not leaving the textarea. It fails when clicking out of the textarea and back onto it on a different line. It also fails when pasting text into it because the starting line is not 1.
My JavaScript knowledge is pretty limited.
<html>
<head>
<title>DEVBug</title>
<script type="text/javascript">
var total_lines = 1; // total lines
var current_line = 1; // current line
var old_line_count;
// main editor function
function code(e) {
// declare some needed vars
var keypress_code = e.keyCode; // key press
var editor = document.getElementById('editor'); // the editor textarea
var source_code = editor.value; // contents of the editor
// work out how many lines we have used in total
var lines = source_code.split("\n");
var total_lines = lines.length;
// do stuff on key presses
if (keypress_code == '13') { // Enter
current_line += 1;
} else if (keypress_code == '8') { // Backspace
if (old_line_count > total_lines) { current_line -= 1; }
} else if (keypress_code == '38') { // Up
if (total_lines > 1 && current_line > 1) { current_line -= 1; }
} else if (keypress_code == '40') { // Down
if (total_lines > 1 && current_line < total_lines) { current_line += 1; }
} else {
//document.getElementById('keycodes').innerHTML += keypress_code;
}
// for some reason chrome doesn't enter a newline char on enter
// you have to press enter and then an additional key for \n to appear
// making the total_lines counter lag.
if (total_lines < current_line) { total_lines += 1 };
// putput the data
document.getElementById('total_lines').innerHTML = "Total lines: " + total_lines;
document.getElementById('current_line').innerHTML = "Current line: " + current_line;
// save the old line count for comparison on next run
old_line_count = total_lines;
}
</script>
</head>
<body>
<textarea id="editor" rows="30" cols="100" value="" onkeydown="code(event)"></textarea>
<div id="total_lines"></div>
<div id="current_line"></div>
</body>
</html>
You would want to use selectionStart to do this.
<textarea onkeyup="getLineNumber(this, document.getElementById('lineNo'));" onmouseup="this.onkeyup();"></textarea>
<div id="lineNo"></div>
<script>
function getLineNumber(textarea, indicator) {
indicator.innerHTML = textarea.value.substr(0, textarea.selectionStart).split("\n").length;
}
</script>
This works when you change the cursor position using the mouse as well.
This is tough because of word wrap. It's a very easy thing to count the number of line breaks present, but what happens when the new row is because of word wrap? To solve this problem, it's useful to create a mirror (credit: github.com/jevin). Here's the idea:
Create a mirror of the textarea
Send the content from the beginning of the textarea to the cursor to the mirror
Use the height of the mirror to extract the current row
On JSFiddle
jQuery.fn.trackRows = function() {
return this.each(function() {
var ininitalHeight, currentRow, firstIteration = true;
var createMirror = function(textarea) {
jQuery(textarea).after('<div class="autogrow-textarea-mirror"></div>');
return jQuery(textarea).next('.autogrow-textarea-mirror')[0];
}
var sendContentToMirror = function (textarea) {
mirror.innerHTML = String(textarea.value.substring(0,textarea.selectionStart-1)).replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br />') + '.<br/>.';
calculateRowNumber();
}
var growTextarea = function () {
sendContentToMirror(this);
}
var calculateRowNumber = function () {
if(firstIteration){
ininitalHeight = $(mirror).height();
currentHeight = ininitalHeight;
firstIteration = false;
} else {
currentHeight = $(mirror).height();
}
// Assume that textarea.rows = 2 initially
currentRow = currentHeight/(ininitalHeight/2) - 1;
//remove tracker in production
$('.tracker').html('Current row: ' + currentRow);
}
// Create a mirror
var mirror = createMirror(this);
// Style the mirror
mirror.style.display = 'none';
mirror.style.wordWrap = 'break-word';
mirror.style.whiteSpace = 'normal';
mirror.style.padding = jQuery(this).css('padding');
mirror.style.width = jQuery(this).css('width');
mirror.style.fontFamily = jQuery(this).css('font-family');
mirror.style.fontSize = jQuery(this).css('font-size');
mirror.style.lineHeight = jQuery(this).css('line-height');
// Style the textarea
this.style.overflow = "hidden";
this.style.minHeight = this.rows+"em";
var ininitalHeight = $(mirror).height();
// Bind the textarea's event
this.onkeyup = growTextarea;
// Fire the event for text already present
// sendContentToMirror(this);
});
};
$(function(){
$('textarea').trackRows();
});
This worked for me:
function getLineNumber(textarea) {
return textarea.value.substr(0, textarea.selectionStart) // get the substring of the textarea's value up to the cursor position
.split("\n") // split on explicit line breaks
.map((line) => 1 + Math.floor(line.length / textarea.cols)) // count the number of line wraps for each split and add 1 for the explicit line break
.reduce((a, b) => a + b, 0); // add all of these together
};
Inspired by colab's answer as a starting point, this includes the number of word wraps without having to introduce a mirror (as in bradbarbin's answer).
The trick is simply counting how many times the number of columns textarea.cols can divide the length of each segment between explicit line breaks \n.
Note: this starts counting at 1.

Categories