Search code examples
javascriptjqueryrangy

Replacing contenteditable characters on the fly with (or without) rangy


I'm working on a little experimental editor where I would like to visualize the time between typed characters. Therefore I'm using javascript and a contenteditable div to wrap every character with a SPAN and a timestamp attribute. I build a little function with the help of rangy:

function insertAtCursor(char, timestamp) { 
    var sel = rangy.getSelection();
    var range = sel.rangeCount ? sel.getRangeAt(0) : null;
    if (range) {
        var el = document.createElement("span");
        $(el).attr('time', timestamp); 
        el.appendChild(document.createTextNode(char)); 
        range.insertNode(el); 
        range.setStartAfter(el);
        rangy.getSelection().setSingleRange(range); 
    } 
}

Now I'm facing two problems with this concept where I would appreciate some help:

a. With the above function the output ends in nested span's like seen here:

<span time="12345">a
  <span time="12345">b
    <span time="12345">c</span>
  </span>
</span>

b. Even if I could get the above function running, a copy&paste or drag&drop action would possibly also end in some nested span's ... and I wonder if there is a way to avoid that at all?

Thanks, Andreas


Solution

  • I'm not convinced this a good idea overall, particularly if the text could get large. A couple of improvements:

    • time should probably be data-time to validate as HTML5
    • you need to handle the case where some content is selected (adding range.deleteContents() would do).

    However, if you are going to do this, I would suggest checking if the cursor is at the end of a text node inside an existing <span> and appending the new <span> after the text node's parent. Something like this:

    Live demo: http://jsfiddle.net/kWL82/1/

    Code:

    function insertAtCursor(char, timestamp) { 
        var sel = rangy.getSelection();
        var range = sel.rangeCount ? sel.getRangeAt(0) : null;
        var parent;
        if (range) {
            var el = document.createElement("span");
            $(el).attr('data-time', timestamp); 
            el.appendChild(document.createTextNode(char));
    
            // Check if the cursor is at the end of the text in an existing span
            if (range.endContainer.nodeType == 3
                    && (parent = range.endContainer.parentNode)
                    && (parent.tagName == "SPAN")) {
                range.setStartAfter(parent);
            }
    
            range.insertNode(el); 
            range.setStartAfter(el);
            rangy.getSelection().setSingleRange(range); 
        } 
    }