Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Andy Matthews
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Andy Matthews ( @commadelimited )

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

By 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.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel