4

I want to insert some html in a contenteditable div.

When editing the content, the user clicks on an icon, a dialog pops and he enters the url & anchor text in the dialog. This causes that the editable div loses its focus and the link is inserted at the beginning of the div, not when the caret was. I tried many things but I'm stuck.

"rte" id of my editable content div

"link_add" id of button in dialog

$('#link_add').click(function (e)
{       
    $('#rte').focus();                  
    document.execCommand('insertHTML', false, 'html content test');         
    close_modal ();
    e.preventDefault();

});

I also tried the solution from set execcommand just for a div, using:

function execCommandOnElement(el, commandName, value)

But this empties the div and just paste the new content.

Community
  • 1
  • 1
earlyriser
  • 419
  • 1
  • 5
  • 13

3 Answers3

3

I take it this is an "inline" dialog (much like StackOverflow's link dialog, for instance), which moves the focus and therefore the selection. The solution seems to be to save and restore the selection. Call getSelection() to get a reference to the selection and save the anchorNode, anchorOffset, focusNode and focusOffset properties and then use collapse(anchorNode, anchorOffset) and extend(focusNode, focusOffset) to restore the selection once you have focused the contenteditable div. (If you're not interested in both nodes you could just collapse(focusNode, focusOffset).)

Neil
  • 54,642
  • 8
  • 60
  • 72
  • Thanks Neil. I need to use 2 fields in the dialog, not just select the text and enter a url. If I don't select anything, does your solution still works? – earlyriser Jul 17 '11 at 20:59
  • If nothing is selected then anchorNode == focusNode and anchorOffset == focusOffset. – Neil Jul 17 '11 at 21:08
  • This is a good solution except in IE < 9, which has a completely different selection object. – Tim Down Jul 18 '11 at 08:23
  • In fact, it won't work in IE 9 either, because it doesn't implement the `extend()` method of `Selection` objects. – Tim Down Jul 18 '11 at 12:17
3

You need to save and restore the selection. IE for one loses it if the selection is collapsed (i.e. just a caret) when the editable element loses focus. Note that IE < 9 has completely different selection and range objects from other browsers. Here is some code for doing this that will work in all major browsers, including older IE. Note that it won't restore the original direction of the selection, however. If you need to do that, the solution becomes more complicated and can't be done in IE.

var saveSelection, restoreSelection;
if (window.getSelection) {
    // IE 9 and non-IE
    saveSelection = function() {
        var sel = window.getSelection(), ranges = [];
        if (sel.rangeCount) {
            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                ranges.push(sel.getRangeAt(i));
            }
        }
        return ranges;
    };

    restoreSelection = function(savedSelection) {
        var sel = window.getSelection();
        sel.removeAllRanges();
        for (var i = 0, len = savedSelection.length; i < len; ++i) {
            sel.addRange(savedSelection[i]);
        }
    };
} else if (document.selection && document.selection.createRange) {
    // IE <= 8
    saveSelection = function() {
        var sel = document.selection;
        return (sel.type != "None") ? sel.createRange() : null;
    };

    restoreSelection = function(savedSelection) {
        if (savedSelection) {
            savedSelection.select();
        }
    };
}

Before opening your dialog:

var savedSel = saveSelection();

After closing the dialog:

restoreSelection(savedSel);

Alternatively, if you were doing a lot of selection/range manipulation you could use the selection save/restore module of my Rangy library, but that would be overkill for just this one use.

Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • Thanks Tim. I'm using the Div more for inserting elements than for select them and modify them (bold, etc) – earlyriser Jul 18 '11 at 00:13
  • @Tim, have you tested this with running the execCommand insertHTML ? – Blowsie Mar 23 '12 at 11:59
  • @Blowsie: I'm not sure if I tested it at the time of writing. I can see that it could well break. If it doesn't work then I'll delete it, although I'm not sure the accepted answer will do much better. – Tim Down Mar 23 '12 at 12:28
  • @Tim, it'd be nice to get to the bottom of the issue and actually resolve it rather than deleting it, its the execCommand insertHTML that breaks it for sure. – Blowsie Mar 23 '12 at 13:02
  • @TimDown Ive asked the question on SO http://stackoverflow.com/questions/9841082/execcommand-inserthtml-breaks-stored-window-getselection – Blowsie Mar 23 '12 at 14:35
  • 1
    @Blowsie: Yes, that really doesn't work. How best to save and restore the selection really depends on what you're doing. For your example, I'd suggest a character index-based approach, such as http://stackoverflow.com/a/5596688/96100 (although this requires my Rangy library, but can be trivially changed to not require it. See jsFiddle: http://jsfiddle.net/Y8pJ7/8/) – Tim Down Mar 23 '12 at 15:05
0

Probably suggested above wouldnt work properly, because of two reasons: first, need to use window.getSelection().getRangeAt(i).cloneRange(), and there is second, - selection and range are living data and refreshes automatically with user related actions, like new selections and focuses.