Setting the caret position to an empty node inside a contentEditable element

13,353

Solution 1

IE's selection/range model is based around indexes into text content, disregarding element boundaries. I believe it may be impossible to set the input focus inside an inline element with no text in it. Certainly with your example I cannot set focus inside the last element by clicking or arrow keys.

It almost works if you set each span to display: block, though there's still some highly strange behaviour, dependent on the existence of whitespace in the parent. Hacking the display to look inline with tricks like float, inline-block and absolute position make IE treat each element as a separate editing box. Relative-positioned block elements next to each other work, but that's probably impractical.

If it makes you feel any better, IE9 finally fixes this unpleasantness and adopts the standard range model. (Hooray!)

it's acceptable to have whitespace in the last node if the caret is at the very start.

I'd probably do that, then, unless an IE selection expert can think of anything better. (Calling Tim Down!)

Solution 2

Set the element's innerHTML to a zero-width character:

('element').innerHTML = '&#200B';

Now the carret can go there.

Solution 3

I found a workaround for Webkit, don't know if anybody found this before but instead of programmatically appending a zero-width space, you can do the same thing with the css3 content property in the after psuedo-selector of the elements you want to put the caret in. This has the advantage that the extra characters don't show up in the DOM and the user can't navigate the caret between it. So basically it doesn't need cleaning up.

For this to work for any child element of your content editable element it would be something like this:

#mycontenteditableelement *:after {
    content: '\200B';
}

I didn't check completely, but I suspect this is a full workaround.

Solution 4

For me setting it content of contenteditable div to <br> works. I tried setting it to nbsp; but that creates extra character space in the div before i start editing.

Hope this helps.

Solution 5

A bit late to the party but I used Unicodes Zero-Width No Break Space (&#65279;) . Then on an event thats called after the users input takes place, remove the Zero-Width No Break Space.

I had many issues with using the Unicode space issue, mostly bad UX. This prevents the user from ever knowing there was an issue.

Share:
13,353
Mike
Author by

Mike

Updated on June 17, 2022

Comments

  • Mike
    Mike about 2 years

    My task is to set a text caret to appear inside an empty span node within a contentEditable div.

    The following gives me no problems on Firefox 3.6:

    <!DOCTYPE html>
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <script type="text/javascript" src="js/jquery-1.4.3.min.js">
            </script>
            <style>
                #multiple {
                    border: 1px solid #ccc;
                    width: 800px;
                    min-height: 20px;
                    padding: 5px;
                    outline: none;
                }
            </style>
            <script>
                $(document).ready(function(){
                    var contentEditable = document.getElementById('multiple');
                    var lastItem = contentEditable.getElementsByTagName('span').item(2);
        
                    var selectElementText = function(el, win){
                        win = win || window;
                        var doc = win.document, sel, range;
                        if (win.getSelection && doc.createRange) {                    
                            range = doc.createRange();
                            range.selectNodeContents(el);
                            range.collapse(false);
                            sel = win.getSelection();
                            sel.removeAllRanges();
                            sel.addRange(range);
                        }
                        else 
                            if (doc.body.createTextRange) {
                                range = doc.body.createTextRange();
                                range.moveToElementText(el);
                                range.select();
                            }
                    }
    
                    contentEditable.focus();
                    selectElementText(lastItem);
                });
            </script>
            <title>Range Selection Tasks (Make Me Want to Cry)</title>
        </head>
        <body>
            <div id="multiple" contentEditable="true">
                <span style="color:red">First</span><span style="color:green">Second</span><span style="color:blue"></span>
            </div>
        </body>
    </html>

    ... but on Webkit and IE, the focus is set to the penultimate span. No idea why. It works if I put a space inside the last span, but then I get a one-character range selection.

    Having said that, it's acceptable to have whitespace in the last node if the caret is at the very start.

    Any help with this would be greatly appreciated. Thank you in advance.

  • Mike
    Mike over 13 years
    Thanks for your prompt and considered answer. You won't believe how many different hacks and workarounds I've attempted while trying to get this to work. Actually, if you have experience of IE, you probably have a fair idea. Good to hear that IE9 finally adopts the standard model. With any luck I should have all my users migrated by 2019...
  • Tim Down
    Tim Down over 13 years
    Hello! I'm pretty certain that it's impossible to place the caret inside an empty inline element in IE. More generally, in IE it's impossible to place the caret at the start of the first text node within an element. This is also true in WebKit: bugs.webkit.org/show_bug.cgi?id=15256#c4. Adding whitespace is as good as it gets, I think. I have resorted to inserting a Unicode BOM character (U+FEFF, renders as zero width in all major browsers) at the start of a text node, which works but leaves you the logistical nightmare of removing the BOMs when the caret moves elsewhere.
  • Mike
    Mike over 13 years
    Indeed. Your guidance and Tim's encouraged me to throw in the towel and just add whitespace programatically. Thank you both.
  • bwindels
    bwindels over 13 years
    Use at own risk: someone from webkit recommended against using this technique because using the after psuedo-selector is supported badly in contenteditable areas and can cause webkit to crash or the reach an inconsistent state. Again, see webkit bugzilla: bugs.webkit.org/show_bug.cgi?id=15256
  • Nico Burns
    Nico Burns almost 12 years
    @TimDown Ack! I nearly managed to crack this using .move(). I have managed to get a TextRange to report that it is in an inline element (when it is at the very start) jsfiddle.net/GJLPN/13 Unfortunately, typing doesn't place text inside the node. Ah well.
  • Nico Burns
    Nico Burns almost 12 years
    And it's cracked!! jsfiddle.net/GJLPN/31 Adding a character to the start of the element, using the above move technique, selecting the character using .moveEnd(), and then setting .text to "" seems to do the trick :). This technique doesn't seem to work with empty inline elements though.
  • Tim Down
    Tim Down almost 12 years
    @NicoBurns: The caret disappears for me, but typing a character (that does indeed come out bold) brings it back.
  • Tim Down
    Tim Down almost 12 years
    @NicoBurns: Good job on getting this far. Some thoughts: changing the DOM by adding a character may be a bad idea because it breaks the built-in undo stack, although Rangy's TextRange-to-Range algorithm also does this, so I can't really criticise. Doing it by changing the innerHTML will break event listeners and any references to descendant nodes so I'd suggest inserting a text node instead. The following seems to have the same effect: jsfiddle.net/GJLPN/34
  • Tim Down
    Tim Down almost 12 years
    @NicoBurns: I haven't been able to solve the problem of disappearing caret so far.
  • Nico Burns
    Nico Burns almost 12 years
    @TimDown Yes, inserting a text node seems to work well, and is certainly a cleaner approach. I only used innerHTML because I was trying lots of different things, and it's quicker to modify. I have a feeling the disappearing caret may be an unsolvable problem. I tried quite a few things including calling .focus() on the element, and any execCommand commands that looked like they might do anything. The only thing I can think of is something like a filter which might cause the element to rerender, but I would expect that to remove the caret if anything.
  • Nico Burns
    Nico Burns almost 12 years
    In any case, what we have seems to be somewhat helpful, and the same technique also allows for reliably selecting the start of a block element (as opposed to accidentally selecting the end of the previous one), which was the problem I originally had.
  • Nevir
    Nevir over 11 years
    To fix the disappearing caret, give your element a min width of a couple pixels
  • Tim Down
    Tim Down about 11 years
    @Nevir: Which element? In the example jsFiddles in this thread, the element in question is not empty and changing the min-width has no effect.
  • duma
    duma over 10 years
    This worked great; I used &#8203; in my markup though (&#200B; didn't render correctly).
  • daw
    daw about 9 years
    @TimDown U+FEFF works for Chrome\FF but not IE11 - the contenteditable won't take focus. \u200B works for all three.
  • daw
    daw about 9 years
    This answer mixes up the html and unicode identifier. Use either "&#8203;" or "\u200B" to refer to a zero length non-breaking space.
  • Tim Down
    Tim Down about 9 years
    @daw: I favour U+200B these days; I think U+FEFF was only necessary for IE 6 and possibly 7.
  • Mike
    Mike over 5 years
    And, as predicted, I do indeed now have all my users migrated. It only took 8 years...
  • cipherdragon
    cipherdragon over 3 years
    Make sure to remove zero-width character once user enters real text. Unless users will notice the existence of zero-width character when they navigate through text using arrow keys.