Working with the Cursor Position
“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.
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.
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
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);
}
}
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.
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();
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 ;)
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?
on April 28th, 2006 at 11:16 am
The public demands support for Safari! Is it even possible? :)
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.
on July 31st, 2006 at 9:14 am
javascript is so complex lol. Much prefer php. Thnx for the great expanations tho
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)
on September 1st, 2006 at 1:16 pm
Good analyze.
Great work.
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
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
on December 8th, 2006 at 1:13 pm
Hi ……
How to track Cursor position in DIV element.
Waiting for Reply
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);
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
on January 8th, 2007 at 6:03 pm
on January 8th, 2007 at 6:04 pm
sorry, keeps getting cut off
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.
on January 12th, 2007 at 1:27 am
Sorry my example code above turned into a link - basically a hyperlink rather than form button.
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.
on January 27th, 2007 at 1:07 pm
Duh, someone ate my HTML tag. It should read: “…to click an element like span”.
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?
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
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.
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?
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?
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.
on May 5th, 2007 at 11:28 am
Very nice tutorial indeed, but if it was much simpler, i could use it.
on June 12th, 2007 at 2:46 pm
Thank you very much with this code which saved my day :-)
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.
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.
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.
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.