Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Inserting Text At The Last Known Selection / Caret Location In JavaScript

By Ben Nadel on

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>&#x1F642;</button><!-- slightly smiling face -->
		<button>&#x2639;&#xFE0F;</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:

Text being inserted based on caret position of an input in JavaScript.

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.



Reader Comments

What has two thumbs and hopes you leave a comment? This Guy! (Ben Nadel).

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.