What’s the best way to set cursor/caret position?

If I’m inserting content into a textarea that TinyMCE has co-opted, what’s the best way to set the position of the cursor/caret?

I’m using tinyMCE.execCommand("mceInsertRawHTML", false, content); to insert the content, and I’d like set the cursor position to the end of the content.

Read More

Both document.selection and myField.selectionStart won’t work for this, and I feel as though this is going to be supported by TinyMCE (through something I can’t find on their forum) or it’s going to be a really ugly hack.

Later: It gets better; I just figured out that, when you load TinyMCE in WordPress, it loads the entire editor in an embedded iframe.

Later (2): I can use document.getElementById('content_ifr').contentDocument.getSelection(); to get the selection as a string, but not a Selection Object that I can use getRangeAt(0) on. Making progress little by little.

Related posts

Leave a Reply

12 comments

  1. First what you should do is add a span at the end of the content you want to create.

    var ed = tinyMCE.activeEditor;
    
    //add an empty span with a unique id
    var endId = tinymce.DOM.uniqueId();
    ed.dom.add(ed.getBody(), 'span', {'id': endId}, '');
    
    //select that span
    var newNode = ed.dom.select('span#' + endId);
    ed.selection.select(newNode[0]);
    

    This is a strategy used by the TinyMCE developers themselves in writing Selection.js. Reading the underlying source can be massively helpful for this kind of problem.

  2. After spending over 15 hours on this issue (dedication, I know), I found a partial solution that works in FF and Safari, but not in IE. For the moment, this is good enough for me although I might continue working on it in the future.

    The solution: When inserting HTML at the current caret position, the best function to use is:

    tinyMCE.activeEditor.selection.setContent(htmlcontent);

    In Firefox and Safari, this function will insert the content at the current caret position within the iframe that WordPress uses as a TinyMCE editor. The issue with IE 7 and 8 is that the function seems to add the content to the top of the page, not the iframe (i.e. it completely misses the text editor). To address this issue, I added a conditional statement based on this code that will use this function instead for IE:

    tinyMCE.activeEditor.execCommand("mceInsertRawHTML", false, htmlcontent);

    The issue for this second function, however, is that the caret position is set to the beginning of the post area after it has been called (with no hope of recalling it based on the browser range, etc.). Somewhere near the end I discovered that this function works to restore the caret position at the end of the inserted content with the first function:

    tinyMCE.activeEditor.focus();

    In addition, it restores the caret position to the end of the inserted content without having to calculate the length of the inserted text. The downside is that it only works with the first insertion function which seems to cause problems in IE 7 and IE 8 (which might be more of a WordPress fault than TinyMCE).

    A wordy answer, I know. Feel free to ask questions for clarification.

  3. It is so simple to move the cursor to the end that I can’t believe the awful kludges that have been posted elsewhere online to do this. Mr. Spocke’s answer wasn’t initially helpful but in the end the API docs provided me with the answer. “Select all content and then collapse the selection”:

    ed.selection.select(ed.getBody(), true); // ed is the editor instance
    
    ed.selection.collapse(false);
    

    I hope this helps someone else as this thread is one of the first to come up in Google.

  4. The best way that I have found is to insert the content with a temp id and then give that focus using some trickery I found in the advlink plugin.

    Hope this helps.

    var ed = tinyMCE.activeEditor;
    
    ed.execCommand('mceInsertContent', false, '<a id="_mce_temp_rob" href="http://robubu.com">robubu.com</a>');
    
    //horrible hack to put the cursor in the right spot
    ed.focus(); //give the editor focus
    ed.selection.select(ed.dom.select('#_mce_temp_rob')[0]); //select the inserted element
    ed.selection.collapse(0); //collapses the selection to the end of the range, so the cursor is after the inserted element
    ed.dom.setAttrib('_mce_temp_rob', 'id', ''); //remove the temp id
    

    If you’re doing this from a popup I think you also need to add

    tinyMCEPopup.storeSelection();
    

    before closing the popup.

    For anyone trying to insert content from a WordPress custom meta box into the TinyMCE editor, this is the solution that works. I tried a ton of other scripts, including another on this page, the answer above by Gary Tan, which worked for me when installing custom TinyMCE buttons but didnt work out in this scenario.

  5. The correct solution is to use the tinyMCE api tinymce.dom.Selection

    http://www.tinymce.com/wiki.php/API3:class.tinymce.dom.Selection

    the syntaxe is something like :

    var rng = tinymce.DOM.createRng();  // the range obj
    var newNode = editor.dom.select(...    // find the correct selector so that newNode is the element of your editor text
    rng.setStart(newNode.firstChild, 0); // 0 is the offset : here it will be at the beginning of the line.
    rng.setEnd(newNode.firstChild, 0);
    editor.selection.setRng(rng);
    

    it works I use this myself.

  6. So, here is solution from the our project. Our objective was to change (+)/(-)/(?) strings to the appropriate images “on the fly” while user is typing.

    There was a problem: every time after changing string to image, cursor went the beginning of the image, not to the end as was expected.

    We added bulk of the code which moves cursor to the end of the image, so user can type continuously w/o interrupting on “jumpy” cursor.

    Key part: Instead of using editor.getBody, there is a more elegant way to create the temporary span and furthermore, delete it immediately after the necessary actions.

    if (value.match(/([+?-])/)) {
            // set current cursor via span
            editor.selection.setContent('<span id="temp-span"/>');
    
            // refresh Content with new span
            value = editor.getContent();
    
            // changing (+)/(-)/(?) to the appropriate image "on the fly"
            value = value.replace('(+)', ' <img src="https://url_here/static/task_manager/img/add.png">&nbsp;');
            value = value.replace('(-)', ' <img src="https://url_here/static/task_manager/img/forbidden.png">&nbsp;');
            value = value.replace('(?)', ' <img src="https://url_here/static/task_manager/img/help_16.png">&nbsp;');
            if (this.state.editor) {
                editor.setContent(value);
    
                // move cursor to the latter span
                var newNode = editor.dom.select('span#temp-span');
                editor.selection.select(newNode[0]);
                editor.selection.setContent('');
            }
            // and refreshing content again w/o span
            value = editor.getContent();
        }
    
  7. I’m inserting html content at current caret position (without staying in that html parent element) like this:

    editor.insertContent( '<div>My html inserted code here...</div>' + '&nbsp;' , {format: 'html'} );
    

    That blank space at the end will ensure that the further typed text will be placed outside the inserted div element.

  8. Insert a DOM node at the current selection/caret location.

    tinyMCE.activeEditor.selection.setNode(tinyMCE.activeEditor.dom.create('img', {src : 'some.gif', title : 'some title'}));
    
  9. Just throwing in my own 2 cents here. None of these answered my question. I was not putting in an HTML element, but a block of text ([code][/code] for example) and needed the cursor to go between the two.

    Using part of @robyates answer, what I did was put a temporary HTML element in between the 2 [code] tags, focus on it, then removed the HTML element completely. Here is my code:

    setup : function(ed) {
        // Add a custom button
        ed.addButton('codeblock', {
            title : 'Code Block',
            text : '[code/]',
            icon: false,
            onclick : function() {
                // Add you own code to execute something on click
                ed.focus();
                //ed.selection.setContent('[code][/code]');
                ed.execCommand('mceInsertContent', false, '[code]<span id="_cursor" />[/code]');
    
                ed.selection.select(ed.dom.select('#_cursor')[0]); //select the inserted element
                ed.selection.collapse(0); //collapses the selection to the end of the range, so the cursor is after the inserted element
                ed.dom.remove('_cursor'); //remove the element
            }
        });
    }
    
  10. I’ve plagiarized this from here.

    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); 
        } 
    } 
    
  11. This is the solution that works for me:

    // params: 
    //   $node: jquery node with tinymce loaded 
    //   text: text to set at then before caret
    function setTextAndCaretToEnd($node, text) {
         var ed = window.tinyMCE.get($node.attr("id"));
         ed.focus();
         ed.selection.setContent(text);
    }