Search code examples
javascriptrangeselectioncontenteditable

Replace word in contenteditable div and properly set caret position


I've been searching through a lot of Range and Selection related questions (mostly answered by @tim-down), but I can't quite get what I need, although I come close.

I want to search the currently focused text node for the word foo. If I find it - replace it with bar and set the caret position at the end of the replaced word. For example:

"Lorem ipsum dolor foo amet, consectetur adipiscing elit."

Turns into:

"Lorem ipsum dolor bar amet, consectetur adipiscing elit."
// -------------------^--- caret position

My attempt

What I currently have works only halfway - it removes the text, but doesn't add anything. I'm not sure it's the best approach, though:

function replacer(search, replace) {
  var sel = window.getSelection();
  if (!sel.focusNode) {
    return;
  }

  var startIndex = sel.focusNode.nodeValue.indexOf(search);
  var endIndex = startIndex + search.length;

  if (startIndex === -1) {
    return;
  }

  var range = document.createRange();
  range.setStart(sel.focusNode, startIndex);
  range.setEnd(sel.focusNode, endIndex);
  range.insertNode(document.createTextNode("bar"));

  sel.removeAllRanges();
  sel.addRange(range);
}

document.addEventListener("keypress", function() {
  replacer("foo", "bar");
});
<div contenteditable="true" style="width: 600px; height: 300px;">Lorem ipsum dolor foo amet, consectetur adipiscing elit.</div>

Note: I only care for compatibility with Chrome.


Solution

  • Explain in code comment and console log.
    See about selection

    function replacer(search, replace) {
      var sel = window.getSelection();
      if (!sel.focusNode) {
        return;
      }
    
      var startIndex = sel.focusNode.nodeValue.indexOf(search);
      var endIndex = startIndex + search.length;
      if (startIndex === -1) {
        return;
      }
      console.log("first focus node: ", sel.focusNode.nodeValue);
      var range = document.createRange();
      //Set the range to contain search text
      range.setStart(sel.focusNode, startIndex);
      range.setEnd(sel.focusNode, endIndex);
      //Delete search text
      range.deleteContents();
      console.log("focus node after delete: ", sel.focusNode.nodeValue);
      //Insert replace text
      range.insertNode(document.createTextNode(replace));
      console.log("focus node after insert: ", sel.focusNode.nodeValue);
      //Move the caret to end of replace text
      sel.collapse(sel.focusNode, 0);
    }
    
    document.addEventListener("keypress", function() {
      replacer("foo", "bar");
    });
    <div contenteditable="true" style="width: 600px; height: 300px;" id='content'>Lorem ipsum dolor foo amet, consectetur adipiscing elit.</div>