Athium Athium - 28 days ago 16
jQuery Question

How could I store caret position in an editable div?

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)

Answer

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;
}

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

document.getElementById("test").onkeyup = showCaretPos;
document.getElementById("test").onmouseup = showCaretPos;
<div id="test" contenteditable="true">
  This is an editable div
</div>
<div id="caretPos"></div>