JavaScript Control Input Field Caret Position or Move to End in Textboxes and Textareas

Long title, but then I felt like specifying parts of what I’ll be covering in this post. Anyway, so I’ll start by a not-so-common issue that some of us have encountered, which is, how to move the input caret (sometimes referred to as the keyboard cursor) to the end of an input field or a textarea on focus.

Method 1: Re-setting the Value

If you try to find the solution around the internet, you’ll see a lot of them talking about re-setting the value of the field, so something like this.value = this.value. Some HTML and JS code will help -

<!-- HTML -->

<input type="text" value="hello world foo bar bak baz more text">
<br><br>
<textarea>
	
	hello world
	foo bar
	bak baz
	more text
	.
</textarea>
// JavaScript

var input = document.querySelector('input');
var textarea = document.querySelector('textarea');
var reset = function() {
	this.value = this.value;
};
input.addEventListener('focus', reset, false);
textarea.addEventListener('focus', reset, false);

On testing, it seems nothing about this approach works. For input fields, IE9-10 just places caret at initial position while other browsers selects entire content. For textareas the caret is either placed at first (Firefox, Chrome, Safari) or last position (IE and Opera).

Now if I add the reset function as listener to the mouseup and keyup events when the focus event is triggered, then that helps a bit.

var reset = function (e) {
	if (e.type === 'focus') {
		this.addEventListener('mouseup', reset, false);
		this.addEventListener('keyup', reset, false);
	}
	else {
		// Remove to not repeat moving the caret to end
		// on further clicks/keypresses
		this.removeEventListener('mouseup', reset, false);
		this.removeEventListener('keyup', reset, false);
	}
	
	var val = this.value;
	this.value = val;
};

The new code just makes the situation a bit better by moving the caret to the end in input fields (not textareas) in the webkit browsers. All other browsers are still messed up.

Apparently, unsetting the value of the field and then setting it fixes a lot of stuff. What I mean is this (inside the reset function) -

var val = this.value;
this.value = ''; // unset first
this.value = val; // re-set next

Now in all browsers, caret ends up at the last position for input and textarea fields. This really works well, but IE9-10 still places the caret at initial position for input fields, but works fine with textarea. I’ve been unable to fix this.

Also there’s a UX flaw with the browsers where it works. When the text is wider than the box’s width, the end portion with the caret won’t be shown, instead it kind of crops the text and shows the initial part which is as wide as the field. A picture will explain better.

Caret on Focus

The blue outlined ones are the fields with focus. On the left you see what it looks like (with flaw) while on the right you see what it should look like for a better experience. Similar behaviour is noticed with textareas.

Method 2: Using setSelectionRange() DOM Method

The previous approach feels more like a hack, while the one that’s going to follow seems more legit. Basically setSelectionRange allows us to make a text selection based on the start and end positions we pass to it.

Let’s look at what the new JS code will be like -

var input = document.querySelector('input');
var textarea = document.querySelector('textarea');

var reset = function (e) {
	if (e.type === 'focus') {
		this.addEventListener('mouseup', reset, false);
		this.addEventListener('keyup', reset, false);
	}
	else {
		this.removeEventListener('mouseup', reset, false);
		this.removeEventListener('keyup', reset, false);
	}
	
	// Following is what we've changed
	var len = this.value.length;
	this.setSelectionRange(len, len);
};

input.addEventListener('focus', reset, false);
textarea.addEventListener('focus', reset, false);

This solution just works great. Caret sticks to end on focus (via click, keyboard or auto focussing) on all browsers and fixes the previouse UX flaw for IE9-10 (for textarea only), Firefox and Opera but not for Chrome/Safari.

Just for reference, in the process I discovered a chrome bug (and reported) where setSelectionRange doesn’t works from inside a focus event handler for both textboxes and textareas.

Stick Caret to End

Recently in one of my projects, it was a requirement to stick the caret at the end no matter where the user moves it to or clicks. Hence, basically whatever is typed should appear at the end. So what I did was, attach the reset function to more events like keydown and make sure none of the handlers attached to any events are removed when fired for the first time (what we were doing before). We attach to keydown so that if someone tries to clicks on a field and tries to type something without releasing the click, the new character that should appear on key press (in the pressed state) will appear after the caret’s position has been moved to the end. While keyup is being used to make sure setSelectionRange works from inside focus event in webkit browsers (as mentioned earlier).

var input = document.querySelector('input');
var textarea = document.querySelector('textarea');

var reset = function (e) {
	var len = this.value.length;
	this.setSelectionRange(len, len);
};

input.addEventListener('focus', reset, false);
input.addEventListener('mouseup', reset, false);
input.addEventListener('keyup', reset, false);
input.addEventListener('keydown', reset, false);

textarea.addEventListener('focus', reset, false);
textarea.addEventListener('mouseup', reset, false);
textarea.addEventListener('keyup', reset, false);
textarea.addEventListener('keydown', reset, false);

Seems to work well as whatever you type will appear at the end of the field, even if you try to move the keyboard cursor using mouse clicks or keyboards or using cursor handlers in touch screens. Although I noticed yet another problem in Android 4.2.2 Chrome 26. You can easily move the cursor using it’s handler and then type something in between. Unlike the iOS counterpart, it works. In order to fix this, I came up with a simple string comparing solution.

var old_val = '';
var reset = function (e) {
	
	var val = this.value
		, len = val.length;
	
	this.setSelectionRange(len, len);
	
	// To fix android chrome bug
	// although good practise just incase
	// user is using some other browser/os combination
	// with similar issue.
	if (e.type === 'keyup' || e.type === 'keydown') {
		var short, long;
		// Find which is shorter and which is longer
		if (old_val.length < val.length) {
			short = old_val;
			long = val;
		}
		else {
			short = val;
			long = old_val;
		}
		
		// Check if shorter version is a substring
		// of the longer one (from the initial position)
		if (long.indexOf(short) !== 0) {
			e.preventDefault();
			// Just exit from the function now
			return false;
		}
	}
	
	old_val = val;
};

So now we compare the current value of the field with the old value and if one of them (the shorter one) is not an exact substring of the other one (longer version) from initial (0th) position, then simply execute e.preventDefault(); which won’t let any character insertion occur. As usual, the this.setSelectionRange(len, len); will move the cursor to the end. This makes the logic in our context more robust although doesn’t exactly fix issues in Android chrome still because it has a bug of not obeying e.preventDefault() for keyup and/or keydown events. I’ve reported this issue too. Sigh!

To fix this glitch, we can add a bit more code to revert the contents of the fields to the old one -

if (long.indexOf(short) !== 0) {
	val = old_val;
	this.value = val;
	this.setSelectionRange(old_val.length, old_val.length);
	
	e.preventDefault();
	// Just exit from the function now
	return false;
}

We just re-set the contents to the old value and execute setSelectionRange once more. A good approach anyway, as it’ll continue to work across different platforms regardless of similar bugs. Here’s a complete testcase.

Controlling Caret Position

By now you already know how to control caret positions via JavaScript. There’s the excellent setSelectionRange method on input fields and textareas that allows you to set a selection range while 2 more properties – setSelectionStart and setSelectionEnd – that you can use to get the current selection’s start and end positions or set them!

Notes

The browsers used for all kinds of testing were IE9-10, Chrome 26 (desktop, iOS 6 and Android 4.2.2), Firefox 20, Safari 6 (Mac and iOS 6).

Unfortunately, I think none of the methods suggested above works well with IE8 and below. I guess for those, you’ve to use a combination of createTextRange, collapse and select methods.

That’s all!

Share:

One thought on “JavaScript Control Input Field Caret Position or Move to End in Textboxes and Textareas

  1. Tiffany

    Neat, I’ve been looking for something like this. My only question is, how do you keep the entire textarea or input editable? If I want to correct text that I’ve entered near the beginning or middle, etc., when I click there, the cursor automatically jumps to the end.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>