TinyMCE and cursor position

Our latest product, Treaty, is built around the web-based rich text editor TinyMCE.  We chose TinyMCE as the editor because it seemed to have the most robust API.

Due to the nature of the product, one main function we needed was the ability to set the position of the caret or cursor in the editor.  Unfortunately, there is no easily accessible method of setting or getting the position of the cursor.  After countless hours of researching on the web and combing through the documentation, I was unable to find a suitable, reliable, cross browser method.

TinyMCE does provide a number of functions related to the editor’s current selection, including a bookmarking function.  When using a bookmark, you can bookmark the current position of the cursor and reset the cursor back to that position.  This will save your place, but doesn’t offer a way to change the position of the cursor.

Another popular method of setting the cursor is to add in an empty <span> tag, use the selection api to select that span and then remove the span.  The problem with this method is that it doesn’t work reliably in Internet Explorer.  Apparently, IE handles selections differently than the other browsers.

After many many hours struggling with this, I decided to combine these techniques.  It seems that using the parameter 0 with tinymce.selection.getBookmark() will bookmark the position of the cursor by adding in a magical meta tag into the html of the editor.  This meta tag is a span with a unique ID and a unique attribute.  Ah-ha!  We can add a bookmark, move the span, and move the cursor back to the bookmark.

I’ll cut to the chase here and just provide you with the functions I use:

setCursorPosition : function(editor, index) {
        //get the content in the editor before we add the bookmark... 
        //use the format: html to strip out any existing meta tags
        var content = editor.getContent({format: "html"});

        //split the content at the given index
        var part1 = content.substr(0, index);
        var part2 = content.substr(index);

        //create a bookmark... bookmark is an object with the id of the bookmark
        var bookmark = editor.selection.getBookmark(0);

        //this is a meta span tag that looks like the one the bookmark added... just make sure the ID is the same
        var positionString = '<span id="'+bookmark.id+'_start" data-mce-type="bookmark" data-mce-style="overflow:hidden;line-height:0px"></span>';
        //cram the position string inbetween the two parts of the content we got earlier
        var contentWithString = part1 + positionString + part2;

        //replace the content of the editor with the content with the special span
        //use format: raw so that the bookmark meta tag will remain in the content
        editor.setContent(contentWithString, ({format: "raw"}));

        //move the cursor back to the bookmark
        //this will also strip out the bookmark metatag from the html
        editor.selection.moveToBookmark(bookmark);

        //return the bookmark just because
        return bookmark;
}

For good measure, I’ll also include the function I use to get the cursor position.  This basically follows the reverse of the set function.

getCursorPosition : function(editor) {
        //set a bookmark so we can return to the current position after we reset the content later
        var bm = editor.selection.getBookmark(0);    

        //select the bookmark element
        var selector = "[data-mce-type=bookmark]";
        var bmElements = editor.dom.select(selector);

        //put the cursor in front of that element
        editor.selection.select(bmElements[0]);
        editor.selection.collapse();

        //add in my special span to get the index...
        //we won't be able to use the bookmark element for this because each browser will put id and class attributes in different orders.
        var elementID = "######cursor######");
        var positionString = '<span id="'+elementID+'"></span>';
        editor.selection.setContent(positionString);

        //get the content with the special span but without the bookmark meta tag
        var content = editor.getContent({format: "html"});
        //find the index of the span we placed earlier
        var index = content.indexOf(positionString);

        //remove my special span from the content
        editor.dom.remove(elementID, false);            

        //move back to the bookmark
        editor.selection.moveToBookmark(bm);

        return index;
}

These two functions have served me well thus far.  A little convoluted, but they work.  The only problem left is that occasionally, if you change the content in the editor, the index that you’re setting your cursor at may be inside another html tag.  The index will not automatically change with your content.

For example, if your original cursor position in the following HTML is 10:

<span>foobar</span>

Adding some content at the beginning of this chunk of text will set your cursor’s index of 10 inside the closing span tag:

<span>F</span><span>oobar</span>

Using the setCursorPosition function will screw up all your content if you don’t adjust your index.  The following regular expression and loop will move the index forward until it is no longer inside an html tag.

    var content = editor.getContent({format: "html"});
    var cursor = 10;
    var p = new RegExp("(.*)(<[^>]*$)",[]);
    var m = p.exec(content.substr(0, cursor));
    while(m != null)
    {
        newcur ++;
        m = p.exec(content.substr(0, cursor));
    }

I purposely left this code out of the setCursorPosition() function because, imho, the content checking ought to happen closer to where you change the content. However, it would be fairly trivial to add this directly in the setCursorPosition function.

As stated previously, after extensive searching, I didn’t find any good techniques for setting the cursor position, so hopefully this can help someone out in the future.

 

2 thoughts on “TinyMCE and cursor position

  1. Hi, thanks for your code.

    I have to replace a string in a textarea so how I can use your functions in this context ?

    Something like that :

    var editor = tinymce.get(‘c_txaNewsBody’);
    var content = editor.getContent();
    setCursorPosition(editor);
    var new_content = content.replace(stringToReplace, ”);
    getCursorPosition(editor);
    editor.setContent(new_content);

    It’s correct ?

    Many thanks.

    • Close. You need to save the cursor position to a variable and then you want to swap the set and get functions and reset the cursor position after you set the new content. It’s the editor.setContent() function that makes you lose the cursor position:

      var editor = tinymce.get(‘c_txaNewsBody’);
      var content = editor.getContent();
      var cursorIndex = getCursorPosition(editor);
      var new_content = content.replace(stringToReplace, ”);
      editor.setContent(new_content);
      setCursorPosition(editor, cursorIndex);

      Hope this helps.

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>