5

I have turned a plain textarea which previously stored the users caret position and returned it when they reopened my Chrome extension. I've now changed the text area into an editable div to enable the use of basic text formatting but the caret position storage does not work.

I current have working code to store the caret position within a text area but I now need to find out what I'd need to change for it to work within an editable div instead.

(function($) {
$.fn.caret = function(pos) {
    var target = this[0];
    var isContentEditable = target.contentEditable === 'true';
    if (arguments.length == 0) {
        if (window.getSelection) {
            if (isContentEditable) {
                target.focus();
                var range1 = window.getSelection().getRangeAt(0),
                    range2 = range1.cloneRange();
                range2.selectNodeContents(target);
                range2.setEnd(range1.endContainer, range1.endOffset);
                return range2.toString().length;
            }
            return target.selectionStart;
        }
        if (document.selection) {
            target.focus();
            if (isContentEditable) {
                var range1 = document.selection.createRange(),
                    range2 = document.body.createTextRange();
                range2.moveToElementText(target);
                range2.setEndPoint('EndToEnd', range1);
                return range2.text.length;
            }
            var pos = 0,
                range = target.createTextRange(),
                range2 = document.selection.createRange().duplicate(),
                bookmark = range2.getBookmark();
            range.moveToBookmark(bookmark);
            while (range.moveStart('character', -1) !== 0) pos++;
            return pos;
        }
        return 0;
    }
    if (pos == -1)
        pos = this[isContentEditable ? 'text' : 'val']().length;
    if (window.getSelection) {
        if (isContentEditable) {
            target.focus();
            window.getSelection().collapse(target.firstChild, pos);
        } else
            target.setSelectionRange(pos, pos);
    } else if (document.body.createTextRange) {
        var range = document.body.createTextRange();
        range.moveToElementText(target);
        range.moveStart('character', pos);
        range.collapse(true);
        range.select();
    }
    if (!isContentEditable)
        target.focus();
    return pos;
}
})(jQuery)
Xan
  • 74,770
  • 16
  • 179
  • 206
Alex
  • 376
  • 1
  • 17

1 Answers1

6

Take a look at this snippet (credit to this answer whose fiddle I have copied here):

This code listens for the mouseup and keyup events to recalculate the position of the caret. You could store it at these points.

function getCaretCharacterOffsetWithin(element) {
  var caretOffset = 0;
  var doc = element.ownerDocument || element.document;
  var win = doc.defaultView || doc.parentWindow;
  var sel;
  if (typeof win.getSelection != "undefined") {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      var range = win.getSelection().getRangeAt(0);
      var preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  } else if ((sel = doc.selection) && sel.type != "Control") {
    var textRange = sel.createRange();
    var preCaretTextRange = doc.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return caretOffset;
}

var lastCaretPos = 10;

function showCaretPos() {
  /* You could store the position when you call this function */
  var el = document.getElementById("test");
  lastCaretPos = getCaretCharacterOffsetWithin(el);
  var caretPosEl = document.getElementById("caretPos");
  caretPosEl.innerHTML = "Caret position: " + lastCaretPos;
}

function restoreCaretPos() {
  var node = document.getElementById("test");
  node.focus();
  var textNode = node.firstChild;
  var range = document.createRange();
  range.setStart(textNode, lastCaretPos);
  range.setEnd(textNode, lastCaretPos);
  var sel = window.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
}

document.getElementById("test").onkeyup = showCaretPos;
document.getElementById("test").onmouseup = showCaretPos;
document.getElementById("button").onclick = restoreCaretPos;
<div id="test" contenteditable="true">This is an editable div</div>
<div id="caretPos">Caret position: 10</div>
<input type="button" id="button" value="restore caret" />
Community
  • 1
  • 1
jcuenod
  • 55,835
  • 14
  • 65
  • 102
  • 1
    If I click off the extension though and then back onto it, how would I have autofocus on the div and therefore the caret displayed (flashing at its stored location)? – Alex Nov 08 '16 at 17:18
  • 1
    @Athium take a look at my updated snippet. I have added a restore button. – jcuenod Nov 08 '16 at 22:44