parentNode.org

The building blocks of a solid frontend.

Working with the Cursor Position

Posted in JavaScript by white on the February 9th, 2006
One question, I’m getting asked almost once a day is:
“Is it possible to insert text at the current cursor position inside a textarea ?”

The answer is yes, for the following browsers

  • Internet Explorer (IE)
  • Any Gecko based (e.g. Firefox)
  • Opera 8+

Any other browser does not support this functionality.
So if you want to keep your page 100% working for everyone out there, you should stop reading here !

 

1. Brainstorming

Enough about this topic - others should discuss that - lets come to the interesting part.
Before we start getting into the sourcecode, we have to be aware of some facts:

  • IE and Gecko have two completely different implementations
  • Both work for Textareas and Inputs !
  • Everything is based on a so-called selection. A selection can also be a simple cursor position. That means, that the selection start and end are the same !
  • We have to accomplish two different tasks:
    • Replacing / Inserting the Text
    • Repositioning the Cursor

 

2. Choosing the implementation

If you are not familiar with object detection, you should consider to read the tutorial written by Munter about Using logic to minimize crossbrowser scripts.

With that information and the knowledge, that for IE everything is based on document.selection, while we will need element.selectionStart inside the Gecko world, we can create the following framework:

	/*	obj is the element, where we want to insert something
		text is the string, which will be inserted
	*/
	function insertAtCaret(obj, text) {
		if(document.selection) {
			// Go the IE way
		} else if(obj.selectionStart) {
			// Go the Gecko way
		} else {
			// Fallback for any other browser
		}
	}

 

3. Going the Gecko way

I guess, I let the Code speak itself. The idea is to find out the start and end position of the selection and than replace everything between these coordinates with the given text.

	/* Find the Start and End Position */
	var start = obj.selectionStart;
	var end   = obj.selectionEnd;

	/* Remember obj is a textarea or input field */
	obj.value = obj.value.substr(0, start)
		+ text
		+ obj.value.substr(end, obj.value.length);

 

4. Don’t forget the <insert something from 0-100>% still using IE !

Now its getting very tricky ! We have to get across different problems, namely:

  • IE does not handle a selection for a given element, but for the whole document
  • Windows based “new lines” (\r\n) are counted as 2 characters, but not when it comes to positioning the cursor
  • There is no easy way to find the cursor position

In terms of sourcecode, a possible solution could look like this:

	/* First of all, focus the object, we want to work with
	   If we do not do so, it is possible, that the selection
	   is not, where we expect it to be
	*/
	obj.focus();

	/* Create a TextRange based on the document.selection
	   This TextRanged can be used to replace the selected
	   Text with the new one
	*/
	var range = document.selection.createRange();

	/* If the range is not part of our Object (remember the
	   textarea or input field), stop processing here
	*/
	if(range.parentElement() != obj) {
		return false;
	}

	/* Save the current value. We will need this value later
	   to find out, where the text has been changed
	*/
	var orig = obj.value.replace(/rn/g, "n");

	/* Replace the Text */
	range.text = text;

	/* Now get the new content and save it into
	   a temporary variable
	*/
	var actual = tmp = obj.value.replace(/rn/g, "n");

	/* Find the first occurance, where the original differs
	   from the actual content. This could be the startposition
	   of our text selection, but it has not to be. Think of the
	   selection "ab" and replacing it with "ac". The first
	   difference would be the "c", while the start position
	   is the "a"
	*/
	for(var diff = 0; diff < orig.length; diff++) {
		if(orig.charAt(diff) != actual.charAt(diff)) break;
	}

	/* To get the real start position, we iterate through
	   the string searching for the whole replacement
	   text - "abc", as long as the first difference is not
	   reached. If you do not understand that logic - no
	   blame to you, just copy & paste it ;)
	*/
	for(var index = 0, start = 0;
		tmp.match(text)
			&& (tmp = tmp.replace(text, ""))
			&& index <= diff;
		index = start + text.length
	) {
		start = actual.indexOf(text, index);
	}

 

5. Positioning the Cursor

That part is much easier now. Its pretty much straight forward, just - who would have thought - there are again two different implementations to do that.

	function setCaretTo(obj, pos) {
		if(obj.createTextRange) {
			/* Create a TextRange, set the internal pointer to
			   a specified position and show the cursor at this
			   position
			*/
			var range = obj.createTextRange();
			range.move("character", pos);
			range.select();
		} else if(obj.selectionStart) {
			/* Gecko is a little bit shorter on that. Simply
			   focus the element and set the selection to a
			   specified position
			*/
			obj.focus();
			obj.setSelectionRange(pos, pos);
		}
	}

 

6. Putting everything together

Thats it, the whole magic is done. Everything missing is putting things together.
You can find the JavaScript Sourcecode in one piece without any comments here.
A sample HTML File is also available.

You can find my object oriented implemenation of this here (not available yet). This version does also cover the solution for placing something around a selection.
This is especialy useful for BBCode insertion.

35 Responses to 'Working with the Cursor Position'

Subscribe to comments with RSS or TrackBack to 'Working with the Cursor Position'.


  1. on March 3rd, 2006 at 10:03 pm

    To fakta:

    1. Jeres syntax-highlighter virker ikke korrekt. Den fjerner backslashes, fx mangler der backslashes på siden:

    http://parentnode.org/javascript/working-with-the-cursor-position/

    2. Jeres script på ovennævnte side er ikke korrekt. Gecko-testen returnerer false, selvom man er i Gecko, hvis cursoren står i starten af teksten (eller hvis man har en markering, der starter i starten af teksten), dvs. obj.selectStart er 0. Man kan i stedet skrive:

    else if(obj.selectionStart >= 0)

    Denne if-sætning er altid true i Gecko false i IE.

  2. yn said,

    on March 23rd, 2006 at 9:39 am

    Uh, your IE code is completely silly, you’ve tried to model it after your Gecko solution because you didn’t realize it offers so much more..

    It’s this simple (once you determined the proper object is focused):

    document.selection.createRange().text=text;

    That’s it. This will replace the selected text with the text you give it. If nothing is selected, it will append the text at the caret. Isn’t IE grand? :P

  3. Karl Dahlén said,

    on April 3rd, 2006 at 9:57 pm

    This does not work in Opera 8.5 (and 8.53).

    The test if(document.selection) is true in Opera. But Opera uses the Gecko code style!

    A test of the range.parentElement object solves the problem.
    If range.parentElement is false then do not use the IE mode of the code.

    A working example of this test.

    function insertAtCaret(obj, text) {
    var NotInIEmode = true;

    if(document.selection) {
    obj.focus();
    var orig = obj.value.replace(/\r\n/g, “\n”);
    var range = document.selection.createRange();

    if(range.parentElement) {
    NotInIEmode = true;

    if(range.parentElement() != obj) {
    return false;
    }

    range.text = text;

    var actual = tmp = obj.value.replace(/\r\n/g, “\n”);

    for(var diff = 0; diff = 0) {
    var start = obj.selectionStart;
    var end = obj.selectionEnd;

    obj.value = obj.value.substr(0, start)
    + text
    + obj.value.substr(end, obj.value.length);
    }
    }

  4. white said,

    on April 6th, 2006 at 9:07 am

    Hi yn,

    yn, I guess, I do not exactly understand what you mean, because what you are saying is exactly what I am already doing ?! See “var range = document.selection.createRange(); ” and “range.text = text; “.

    All the rest of the code is just trying to set the cursor after the replacement, which is not automatically done by IE !

    Hi Karl,

    thanks for your feedback. I realized the same problem some time later too. Opera has both objects implemented but does not handle them correctly :(
    I will update my code with a fix, when I have some to to fix it.

  5. yn said,

    on April 6th, 2006 at 5:49 pm

    white,

    That’s just as easy. This will replace the text and put the caret at the end of the new content:

    var rng=document.selection.createRange();
    rng.text=newtext;
    rng.select();

  6. white said,

    on April 7th, 2006 at 2:35 pm

    lol, allright :) That is very true and works like a charm.

    Thanks, guess I have to give my code a major update soon ;)

  7. redman said,

    on April 9th, 2006 at 2:20 pm

    I am trying to make this code work so that it doesnt replace the highlighted text, it just puts text around it.. I got this to work for IE but I am having trouble getting it to work for Gecko….

    I changed the function header to suit my needs:

    function insertAtCaret(obj,o,e)

    example: o would be like [u], and e would be [/u]

    For the IE version I only changed this line:

    range.text = o + range.text + e;

    My problem is getting this to work for Gecko.. I used the following:

    var start = obj.selectionStart;
    var end = obj.selectionEnd;

    obj.value = obj.value.substr(0, start)
    + o + obj.value.substr(start, end) + e
    + obj.value.substr(end, obj.value.length);

    You would think that would work.. but it just takes the text from the start position and goes to the end of the textarea.. for example:

    textarea contains:
    line1
    line2
    line3
    line4

    and I highlight line3.. it turns into:

    line1
    line2
    [u]line3
    line4[/u]
    line4

    I’ve tried several other things but they dont work either.. any ideas?

  8. Raevel said,

    on April 28th, 2006 at 11:16 am

    The public demands support for Safari! Is it even possible? :)

  9. Kenji said,

    on June 4th, 2006 at 12:25 am

    Hi, I just found this article, and it has saved my day.
    Just wanted to say thanks! Great site.

  10. monkey56657 said,

    on July 31st, 2006 at 9:14 am

    javascript is so complex lol. Much prefer php. Thnx for the great expanations tho

  11. FourPenguins said,

    on August 27th, 2006 at 3:03 am

    As a suggestion, you might want to change
    if(obj.selectionStart)
    to:
    if(obj.selectionStart>=0)

    This allows the script to return the correct value foer gecco browsers even if the selection begins as index 0 (as in an empty textarea)

  12. labilbe said,

    on September 1st, 2006 at 1:16 pm

    Good analyze.
    Great work.

  13. S. T. said,

    on November 1st, 2006 at 5:43 pm

    to redman

    if you are using substr(a,b) then variable b is a number which defines length (not position).
    I suggest you use substring(a, b) instead of substr.
    This should work.
    If you need to read more:
    http://www.quirksmode.org/js/strings.html

  14. Jake said,

    on November 10th, 2006 at 10:16 pm

    Your solution is very good. I’ve come up with a different method. It finds the start and end positions of the selection along with the complete selected text. It does this without modifying the content.

    http://linebyline.blogspot.com/2006/11/textarea-cursor-position-in-internet.html

  15. Karan said,

    on December 8th, 2006 at 1:13 pm

    Hi ……
    How to track Cursor position in DIV element.

    Waiting for Reply

  16. Kai Young said,

    on January 8th, 2007 at 4:44 pm

    Redman,

    I found that using this works for firefox:

    + o
    + obj.value.substr(start, (parseInt(end)-parseInt(start)))
    + e
    + obj.value.substr(end, obj.value.length);

  17. Kai Young said,

    on January 8th, 2007 at 6:02 pm

    Just found with that the cursor doesn’t go into the right place. I have edited it. Simplest way to show is whole source:

    function insertAtCaret(obj, text,text2) {
    if(document.selection) {
    obj.focus();
    var orig = obj.value.replace(/\r\n/g, “\n”);
    var range = document.selection.createRange();
    var rangelength = range.text.length;
    if(range.parentElement() != obj) {
    return false;
    }

    range.text = text+range.text+text2 ;
    var actual = tmp = obj.value.replace(/\r\n/g, “\n”);
    for(var diff = 0; diff

  18. Kai Young said,

    on January 8th, 2007 at 6:03 pm


  19. Kai Young said,

    on January 8th, 2007 at 6:04 pm

    sorry, keeps getting cut off

  20. AF said,

    on January 12th, 2007 at 1:25 am

    Is there a way to modify the code so that it can also work with html links rather than buttons.

    Such as:
    test

    Thanks in advance for any guidance.

  21. AF said,

    on January 12th, 2007 at 1:27 am

    Sorry my example code above turned into a link - basically a hyperlink rather than form button.

  22. Roel said,

    on January 27th, 2007 at 1:05 pm

    IE6/7 has a quirk that makes it difficult to use anything but a button element if you want to restore the cursor. If I’ve analysed correctly, it is like: if you leave a textarea and click a button, the document.selection is still in the textarea; if you leave the textarea to click an element like , the document.selection is directly moved to the newly clicked element. I’ve tried to catch and store the cursor position when the textarea is onblur’red, but to no avail. I’m certain somebody out there has a solution…

    FF and Opera 9 work fine with “buttons” made of spans.

  23. Roel said,

    on January 27th, 2007 at 1:07 pm

    Duh, someone ate my HTML tag. It should read: “…to click an element like span”.

  24. Rick said,

    on February 6th, 2007 at 3:45 am

    Is there not a possible display problem after the insertion, at least in Gecko(I don’t have IE). If you scroll down in the text and insert a word. The TEXTAREA refreshes itself with only the text from the beginning of it’s buffer and doesn’t display the cursor or the text around the cursor. Your cursor area is only brought back when you enter another key.

    Is there any way of insuring the cursor position is always presented in the TEXTAREA after the insertion? Could you do something like dispatch a key event?

  25. andy said,

    on February 18th, 2007 at 1:28 pm

    Hi,

    I’ve been playing with this code and it all works fine except on IE when the seletion is empty, then the range object is not set to have it’s parent as the textarea, and so it’s text isn’t altered - anyone else seen this behaviour?

    Andy

  26. Jamie said,

    on March 6th, 2007 at 4:04 pm

    Hi White,

    Thanks for the code, its great.

    However, I think there is a bug finding the cursor position, if you try enter the same text as is there already.

    My original text in the text area is:
    james is james
    *james ok

    I try to insert the text “James ” without the quotes.

    If I position my cursor after the asterix and click “Insert text” then the new cursor position is incorrect.
    Howver, if I select the asterix and “Insert text” to replace the asterix the new cursor position is correct.

    Thanks,
    Jam.

  27. Bøt said,

    on March 18th, 2007 at 6:36 pm

    Nice indeed! This code solved my problem. However, you do not write a anything about copyrights, so I hope you allow me to copy these functions into my own project?

  28. Dave F said,

    on March 28th, 2007 at 4:34 pm

    Worked great for me except for one thing. In Firefox it inserted text into both textbox and textarea at the cursor.
    IE7 also inserted correctly for textarea.
    However, in IE7, it put the inserted text at the beginning of the textbox, no matter where the cursor was.
    Any thoughts on what to look for to fix this?

  29. Roman said,

    on May 3rd, 2007 at 5:21 pm

    This simply doesn’t work, I’m no expert, but this code does not recognise the browsers’ object model correctly. I’m testing with IE6 and Firefox 2.0.0.3, the code fails to work on both browsers.

    I found a working solution at http://www.laviska.com/view.php?id=138 though. I don’t know if this solution is worth anything in the long run, but for now it does a splendid job and it seems extremely simple too.

  30. SOmeONE said,

    on May 5th, 2007 at 11:28 am

    Very nice tutorial indeed, but if it was much simpler, i could use it.

  31. labilbe said,

    on June 12th, 2007 at 2:46 pm

    Thank you very much with this code which saved my day :-)

  32. Disgrunt said,

    on June 14th, 2007 at 3:21 pm

    This would be nice if it worked. I copied your code right from your site and it just doesn’t work in IE 6 or 7. I am using an input type=text with this and it keeps prepending the text at the beginning of the text box. Nice try though.

  33. steve said,

    on July 26th, 2007 at 6:16 pm

    One thing I just found that was driving me bonkers (in both FIrefox and IE), was that any attempt to select a character range inside a field, will be *severely* hampered, under the following conditions.

    1.) Browser has auto-completion assistance turned on.
    2.) DOM Call on field to fieldObj.setAttribute(’autocomplete’, ‘off’); is made

    I *was* trying to set this attribute dynamically, on fields where I wanted “my own” widget-like behavior. However after extensive hair pulling, I found that doing so, would cause the character range selection to FAIL.

    For #2, setting the attribute directly, behaves a bit better in Firefox. e.g. fieldObj.autocomplete = ‘off’;

    If anyone knows another way to turn it off, that doesn’t affect the selection, I’d be interested in finding out how.

  34. Andrew said,

    on February 8th, 2008 at 4:16 pm

    Hi,

    Your description is not entirely true “Both work for Textareas and Inputs”. In Firefox your code does work for both textareas and inputs. However, it does not work successfully for Inputs in IE6 and IE7.

    If you use an input box in IE the text you are inserting pre-appends the whole phrase.

    I am writing a search program which has buttons to insert boolean operators into a text input. (it was going to be a deprecated feature, but 30% of the users claim to find it helpful) If anyone knows a a fix for this I would be grateful to find out.

  35. Andrew said,

    on February 8th, 2008 at 4:45 pm

    OK literally 5 minutes after I posted the above message I have discovered that setting a width on the Input box fixed the issue for IE. However, I have also discovered during this time a bug with Firefox. If the focus is at the very start of the input box the text is appended to the end of the input box and the input box loses focus. This bug does not have a significant impact on my application, but I will be attempting to find a solution for it.

Leave a Reply