Inserting Text At The Last Known Selection / Caret Location In JavaScript
Over the weekend, I added emoji shortcuts just below my comment form. To do this, I had to render the emoji options using Lucee CFML; and then, I had to apply the selected emoji to the textarea
using JavaScript. In an effort to always do things incrementally and iteratively, my first (and current) implementation always inserts the emoji at the end of the textarea
. But, in my next iteration, I want to insert the emoji at the last known caret / selection location. As such, I wanted to explore that concept in its own little JavaScript demo.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
In the past, I've looked at using the Selection
properties of input elements - selectionStart
and selectionEnd
- such as when replacing double-dashes with em-dashes in JavaScript. But, the big breakthrough that I had while putting this demo together was the realization that an input element retains its selection properties even after the input element itself loses focus. Which means, we can read an input's last known selection even after the users clicks out of the input (such as to click into an emoji option).
Using this new-found understanding, my emoji shortcuts can be easily augmented with the new behavior by making sure that I insert the emoji at whatever the selectionEnd
property is at the time of the interaction. This ends up being great for keyboard accessibility reasons as well.
To this in action, I have a textarea
followed by some button
elements. When you click on one of the button
elements, it will take the textContent
of the button
and insert into the textarea
value at whatever the last known selectionEnd
property contains:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>
Inserting Text At The Last Known Selection / Caret Location In JavaScript
</title>
<link rel="stylesheet" type="text/css" href="./demo.css" />
</head>
<body>
<h1>
Inserting Text At The Last Known Selection / Caret Location In JavaScript
</h1>
<textarea id="input">Me? ... I'm scared of everything. I'm scared of what I saw, of what I did, of who I am. And most of all, I'm scared of walking out of this room and never feeling the rest of my whole life ... the way I feel when I'm with you.";</textarea>
<div id="buttons">
<button>(Meep)</button>
<button>(Moop)</button>
<button>🙂</button><!-- slightly smiling face -->
<button>☹️</button><!-- frowning face -->
</div>
<script type="text/javascript" src="../../vendor/jquery/3.6.0/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
var input = $( "#input" );
var buttons = $( "#buttons" );
// When the user clicks on one of the target text-tokens, we are going to insert
// that token into the textarea at the last known selection location.
buttons.on( "click", "button", handleButtonClick );
function handleButtonClick() {
var inputValue = input.val();
// For the sake of simplicity, we're going to pull the insertable text right
// out of the DOM structure. Fun fact, using .textContent will pull the emoji
// glyph that was generated by HTML entities.
var insertToken = $( this ).text();
// NOTE: Even after the input is blurred, it appears to retain its last-known
// selection properties. As such, we don't have to track those through the
// life-cycle of the page - we can just reference them at any time.
var insertTokenAt = input.prop( "selectionEnd" );
// After we insert the text, we're going to want to re-focus the input.
// However, we're going to want to advance the selection such that it starts
// just after the inserted text.
var nextSelectionEnd = ( insertTokenAt + insertToken.length );
// Insert the text at the given location within the input.
input.val(
inputValue.slice( 0, insertTokenAt ) +
insertToken +
inputValue.slice( insertTokenAt )
);
// Advance the text selection to just after the inserted text.
input
.prop( "selectionStart", nextSelectionEnd )
.prop( "selectionEnd", nextSelectionEnd )
.focus()
;
}
</script>
</body>
</html>
As you can see, when the user clicks on one of the button
elements, all we're doing is grabbing whatever the current selectionEnd
property is and using that to slice-and-dice our input string. Then, we update the selectionEnd
property so that the next focusing of the input will bring the caret to just after our inserted text.
ASIDE: Even through some emoji require more than one codepoint, the
length
property appears to handle our offsets property (since it will show a length of 2 for our multi-codepoint emoji).
Now, if we run this code in the browser, we can move our caret around and then use simple keyboard navigation to insert text back into the input
at the previously-selected location:
As you can see, even after I blur the input using either keyboard navigation or a mouse click, when I go to insert the selected text-token, I'm inserting it at the last known selectionEnd
property. Super elegant! And, so much easier than trying to track and record the last known location using additional events.
Want to use code from this post? Check out the license.
Reader Comments