Where's that cursor?

In my CMS, there's a "source view" and a "design view". This is a pretty common feature found in WYSIWYG editors. However, when switching between the two views the cursor will usually default to the very beginning or the very end of the text. The user then has to locate where they were last.

There is a way around this! Using a placeholder while switching between views. The placeholder is made of a unique character sequence that is unlikely to occur. As you switch between views, you locate your placeholder and then remove it. In doing so, your cursor is now in the location of the placeholder.

Example

In my example, I use the DEC as my "design view" and a textarea as my "source view".

Switching from "design view" (oEditor) to "code view" (oContent):

/* set cursor position */
var sel = oEditor.DOM.selection;
var rng = sel.createRange();
if(sel.type.toLowerCase() == "control"){
   rng.item(0).insertAdjacentText("afterEnd","##_CURSORPOSITION_##");
} else {
   rng.collapse(false);
   rng.pasteHTML("##_CURSORPOSITION_##");
}

We create a range in the editor so we know where to put the placeholder. We check if the range is a control in case we have a table or an image selected. We then put the placeholder after that control. If it's text that we have selected then we collapse the range and insert our placeholder.

/* swap source code and views */
oContent.style.display = "";
oEditor.style.display = "none";
oContent.focus();
oContent.value=oEditor.DOM.body.innerHTML;

We just switch between the "design view" and the "code view".

/* restore cursor position */
rng = oContent.createTextRange();
rng.collapse();
rng.findText("##_CURSORPOSITION_##",100000000);
rng.text=""; rng.select();

Once we've switched views, we create a textrange within the textarea. We collapse the range to the beginning and then go looking for the placeholder. Once we've found it, we remove it and set the cursor.

Switching from "source view" (oContent) to "design view" (oEditor):

/* set cursor position */
oContent.focus();
var rng = document.selection.createRange();
rng.collapse(false);

Focus is given to the textarea and then a textrange is created within the document. If text is selected then the range is collapsed so as not to overwrite the selected text with the placeholder.

rng.text="##_CURSORPOSITION_##";
var cursphrase = /(<[^>]*?)(##_CURSORPOSITION_##)([^<]*?>)/;
if(oContent.value.search(cursphrase) != -1)
   oContent.value = oContent.value.replace(cursphrase, "$1$3$2");

The placeholder is put into the source code. The problem is, when in source view, what if the placeholder is placed within an HTML tag? In this case, a regular expression search is done; if it finds it within an HTML tag then it moves the placeholder outside of the tag.

/* swap source code and views */
oContent.style.display = "none";
oEditor.style.display = "";
oEditor.DOM.focus();
oEditor.DOM.body.innerHTML=oContent.value;

Again, we just switch between the "source view" and the "design view".

/* restore cursor position */
rng = oEditor.DOM.selection.createRange();
rng.expand("textedit");rng.select();rng.collapse();rng.select();
if(rng.findText("##_CURSORPOSITION_##",100000000)){
   rng.text=""; rng.select();
}else{
   oEditor.DOM.body.innerHTML = oEditor.DOM.body.innerHTML.replace('##_CURSORPOSITION_##','');
}

Once we've switched views, we create a new textrange and reset it to the beginning. We use findText to find the placeholder and remove it. In a situation where it doesn't find the placeholder in the text then it strips it out of the HTML source, just in case.

Other ideas

This functionality could be extended in various ways.

  • create a begin and end placeholder so that a selection of text could be retained between views
  • create placeholders for javascript to retain script format between views
  • revise it to work with view switching between two instances of MSHTML
Published October 09, 2003 · Updated September 17, 2005
Categorized as MSHTML and DEC
Short URL: https://snook.ca/s/25

Conversation

19 Comments · RSS feed
Daniel said on July 15, 2004

This article sux! This is exactly the topic I was looking for, but even after reading it I have no clue how to retain cursor position. Next time you write a possibly useful article, try to be more clear and general.

For example, write a functions rememberCursorPosition( obj ) and resetLastCursorPosition( obj ). All the other plumbing is only specific to your project, which nobody cares about.

And please, write inline comments. Some people want to actually understand what each line of code does.

If you can't write something people can use, just don't write it!!!

THIS IS USELESS!

Jonathan Snook said on July 15, 2004

Sorry you didn't get much use out of the article. It's hard to extend this into generic functions because some people use the DEC, others use MSHTML. Some implement them into IFRAMEs and others using HTCs.

This article SHOULD give you enough information to take the code and implement it into your project. This article DEFINITELY gives developers a direction as to how to approach the problem. Something that wouldn't have existed had I not written it. If you have a question regarding your specific implementation, I'll try to help.

bronius said on August 27, 2004

Daniel-
have a heart (and some tact)

Jonathan-
nice article. I appreciate the in-depth examples.

ps said on September 17, 2004

Hi Jonathan,

Thanks so much. This was very helpful. Your article gave me the clue as to how to approach this issue which I could not get elsewhere. However, I use bookmarks instead of text as it produces flickering in my tool.

Regards,
ps

Diederik Stenvers said on December 01, 2004

Well, it worked after a few changes (obvious ones, like the name of controls), i am very happy with this article that rocks!

Arjan Haverkamp said on December 18, 2004

Jonathan,

the regular expression you use to determine whether the cursor is inside a HTML-tag is incorrect.

You have this:
var cursphrase = /(>[^<]*?)(##_CURSORPOSITION_##)([^>]*?<)/

While it should be:
var cursphrase = /(<[^>]*?)(##_CURSORPOSITION_##)([^<]*?>)/;

Jonathan Snook said on December 20, 2004

Thanks a bunch Arjan. I guess I mistyped the gt and lt's when I did the article.

Benyi Robert said on January 11, 2005

Can this be done in simple javascript?
I could not done it.
I don't even think it's impossible, but i would be quite glad if someone could contradict me.

Jonathan Snook said on January 11, 2005

Benyi: I'm not sure what you're reffering to. The cursor position code in the article is in javascript.

brendan said on January 23, 2005

var rng = sel.createRange();

Does anyone know what the return type is for this? i'm using c# and u can't just declare var.

thanks a lot.

brendan said on January 23, 2005

oh dont worry i found it :)

mshtml.IHTMLTxtRange

thanks

Andy said on February 02, 2005

Hi Jonathon,

I have only just come across your site in the last few days whilst doing some research into MSHTML. Just wanted to say thanks as all the stuff you have on here has been very helpul.

Also wanted to say(as you are clearly to nice to do it yourself) that if Daniel doesn't understand the articles, he's an idiot and should find himself another career that doesn't involve the use of a computer. I know his post was a long time ago now but people submitting abusive posts to such a helpful site is really offensive.

You have my full appreciation, respect and support.

Randy said on July 29, 2005

Hi Jonathan,

Not sure if I can use this code for my purpose but it seems like it can be tweaked. I don't really need to switch between design view and source. What I do need it when I select a word or more in the design view is to figure out is what the character position of the word I selected in Javascript.

Example:

"The big brown fox jumped over the lazy"

I select 'fox' which is the 16th character. I what 16 to be returned.

This is a great site. Keep it up. And that Daniel-what an asshole.

Christoph said on August 22, 2005

Great help. I am working on a html editor. This was the help i needed to solve my problem. Thank you very much and keep the good work up please.
Also i cant understand Daniel, i think he just needs more time and calmness while reading your article. I needed too, but finally it worked :-)
Greetings from germany
Christoph

Adrenalin said on September 04, 2005

Your arcticle help me a lot!
Thank you, i'm from Moldova :)
And this is very original site..

Mike McCartney said on December 18, 2005

Robert Benyi, amigo mio, puta su madre!!

lololol

Amol said on January 09, 2006

it's very useful content

vutuanduc said on May 21, 2006

Hi Jonathan,
Thanks for your arcticle, it's help me solved my problem. Your arctical is very useful.

I'm from VietNam.
VuTuanDuc

Joshua Li said on August 16, 2006

Thank you. It helps me a lot.

Sorry, comments are closed for this post. If you have any further questions or comments, feel free to send them to me directly.