Snippets, lines, and complaints. If you find a solution you are fortunate.

Friday, November 10, 2006

Textarea Cursor Position in Internet Explorer

Background...I'm building a content management type website and wanted to include a nice WYSIWYG editor. Unfortunately all the editors I tested are either too unpredictable and erratic, cost a fortune, or are not customizable enough for my needs (out of the box anyways).

In the end I just didn't want to deal with the awful (x)html that they produce. I also didn't want to deal with my end users, whom have never heard of HTML and won't want to "View Source" to fix the HTML when the WYSIWYG fails.

So, I decided to build my own. After a smidgen of research and testing I decided not to build my own. Now I'm going for a Wiki style code editor with a toolbar. With this I can generate clean consistent simple markup. It's not WYSIWYG but at least it will be consistent and predictable.

I quickly found that I'd need to be able to retrieve and adjust the current text selection in the textarea control. So, after not finding any satisfactory solutions via Google I've come up with my own.

Textarea Cursor Position in IE

First off I'd like to give credit to the sites that provided good tips, suggestions, and solutions.

It trims \r\n from the end of the TextRange.text so you don't know the real selection length.

My solution discovers the correct index of the start and end positions of the selection which can't be done by just using the TextRange.text.length. Once you have the positions you can use textarea.value.substring(start,end) to get the actual selected text rather than the trimmed TextRange.text property.

All the string methods (substring, indexOf, length, etc.) treat \r\n as 2 characters, however, the TextRange methods (moveStart, moveEnd, etc.) treat \r\n as 1 character. So just moving the TextRange until we reach the beginning and counting how many times we moved will be off by exactly the number of new lines passed along the way.

A Word About Changing the Selection

The TextRange.text \r\n trim issue also causes problems when attempting to change the selection's position. You need to count the newlines between the beginning and the target index and then subtract count from the target index. Then you can use this adjusted index with the TextRange.moveStart and TextRange.moveEnd methods to get the correct selection.

The Code

In my code I use 3 TextRanges (instead of 2 as described above) so that I can re-assemble the entire string and compare it to the original textarea.value. I do this to make sure I performed the untrimming correctly.

You will also notice that I only use a single do..while loop to untrim all the TextRanges. I could have done this with 3 separate loops. Even better, I probably should move the untrim bit to a separate function. I purposely made these design decisions but it could be done differently.

Also note that this bit of code was only tested in IE 6.0. Please don't complain because it doesn't work in Opera, Firefox, Safari or Lynx.

if (selection_range.parentElement() == textarea) { // Check that the selection is actually in our textarea // Create three ranges, one containing all the text before the selection, // one containing all the text in the selection (this already exists), and one containing all // the text after the selection. var before_range = document.body.createTextRange(); before_range.moveToElementText(textarea); // Selects all the text before_range.setEndPoint("EndToStart", selection_range); // Moves the end where we need it

var after_range = document.body.createTextRange(); after_range.moveToElementText(textarea); // Selects all the text after_range.setEndPoint("StartToEnd", selection_range); // Moves the start where we need it

Here is my solution, it moves a bookmark from the document.selection textrange to an element textrange. It correctly calculates the start and end positions regardless of where the element is in the page.