Set caret position in contenteditable div
Asked Answered
L

1

1

Intro

When editing the content of a contenteditable DOM Object, different browsers have different behaviours. For instance, Firefox 18.0 creates a new paragraph (<p>) or a line break <br/> in some instances while Chrome 24 creates a <div>.

In order to cope with this, I'm listening to the DOMNodeInserted event and replacing the new inserted nodes with p tags.


Problem

The problem is placing the caret in place. I've read tons of posts in SO regarding this same subject but none of the provided answers have worked, at least in Chrome 24.


Code

JSFiddle

obj.addEventListener("DOMNodeInserted", onNodeInsert, false);

function onNodeInsert(e) {
    var range    = document.createRange(),
        sel      = window.getSelection(),
        newNode  = e.target,
        tagName  = newNode.tagName.toLowerCase(),
        lnbrNode = document.createElement('br'),
        pNode    = document.createElement('p');

    if (tagName === 'div' && newNode.getAttribute("id") === null) {
        // First we remove the event listener so that it doesn't get triggered again
        this.removeEventListener('DOMNodeInserted', onNodeInsert, false);

        // Creates a p node and removes the div
        newNode.parentNode.replaceChild(pNode, newNode);
        pNode.appendChild(lnbrNode);

        // Places the caret where it belongs
        range.setStart(pNode, 0);
        sel.removeAllRanges();
        sel.addRange(range);    

        //We can restore the event listener now
        this.addEventListener("DOMNodeInserted", onNodeInsert, false);
    }
}
Linnea answered 15/1, 2013 at 21:41 Comment(5)
WebKit simply doesn't let you place the caret just anywhere: it'll always normalize the caret position to a position it considers valid. The only solution is to hack it with zero-width spaces or something even more grim. See bugs.webkit.org/show_bug.cgi?id=23189Yellowlegs
Thank you for your comment. Seems in webkit it's flawed by design. My workaround for the particular issue you mentioned was to always create a paragraph tag at the beginning of the text, Since this is actually the desired behaviour for this particular project.Linnea
@TimDown Can you please explain the hack for webkit? or atleast provide a link which explains the same.Arie
@Mr_Green: The hack would be to add a zero-width space character (Unicode U+200B), either by adding a new text node or inserting into an existing one, at the position you wish to place the caret and then place the caret immediately after this zero-width space. The user will not see the difference (unless they use the left arrow key). You then have the problem of removing this space afterwards when it's no longer necessary.Yellowlegs
@TimDown I am really new to all this stuff. learning slowly.. Can you please direct me to one of your links which explains the same?Arie
L
0

For those who might encounter the same problem as I do, here's my dirty fix...


Relevant code

JSFiddle

obj.addEventListener("DOMNodeInserted", onNodeInsert, false);

function onNodeInsert(e) {
    var range    = document.createRange(),
        sel      = window.getSelection(),
        newNode  = e.target,
        tagName  = newNode.tagName.toLowerCase(),
        lnbrNode = document.createElement('br'),
        pNode    = document.createElement('p');

    if (tagName === 'div' && newNode.getAttribute("id") === null) {
        // First we remove the event listener so that it doesn't get triggered again
        this.removeEventListener('DOMNodeInserted', onNodeInsert, false);

        // Creates a p node and removes the div
        newNode.parentNode.replaceChild(pNode, newNode);
        pNode.appendChild(lnbrNode);

        // Places the caret where it belongs
        var placeCursor = function () {
            range.setStart(pNode, 0);
            sel.removeAllRanges();
            sel.addRange(range);    
        }

        //placeCursor(); // DOES NOT WORK (cursor disappears)
        setTimeout(placeCursor,1); // WORKS

        //We can restore the event listener now
        this.addEventListener("DOMNodeInserted", onNodeInsert, false);
    }
}
Linnea answered 16/1, 2013 at 0:46 Comment(2)
@Arie The fiddle is just this code. The gimmick is this line: setTimeout(placeCursor,1); If you set a timeout, it should workLinnea
Thank you for response. I am poor in javascript. can you please update your fiddle. So that I can use it in my project :). (I tried changing setTimeout to following setTimeout(placeCursor,500); no use). no errors.Arie

© 2022 - 2024 — McMap. All rights reserved.