10

I have this contentedittable div

<div contenteditable="true" id="text">minubyv<img src="images/smiley/Emoji Smiley-01.png" class="emojiText" />iubyvt</div>

Here is an image description of the code output enter image description here

so I want to get the caret position of the div and lets assume that the cursor is after the last character. And this is my code for getting the caret position

function getCaretPosition(editableDiv) {
  var caretPos = 0,
    sel, range;
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.rangeCount) {
      range = sel.getRangeAt(0);
      if (range.commonAncestorContainer.parentNode == editableDiv) {
        caretPos = range.endOffset;
      }
    }
  } else if (document.selection && document.selection.createRange) {
    range = document.selection.createRange();
    if (range.parentElement() == editableDiv) {
      var tempEl = document.createElement("span");
      editableDiv.insertBefore(tempEl, editableDiv.firstChild);
      var tempRange = range.duplicate();
      tempRange.moveToElementText(tempEl);
      tempRange.setEndPoint("EndToEnd", range);
      caretPos = tempRange.text.length;
    }
  }
  return caretPos;
}

var update = function() {
  console.log(getCaretPosition(this));
};
$('#text').on("mousedown mouseup keydown keyup", update);

But the problem is that it returns 6 instead of 14. The caret position goes back to 0 after the image. Please is there a way I can get the caret position to be 14 in this case.

EDIT

I want to also insert some element starting from the caret position. so this is my function to do that

selectStart = 0;
var update = function() {
  selectStart = getCaretPosition(this);
};
function insertEmoji(svg){
    input = $('div#text').html();
    beforeCursor = input.substring(0, selectStart);
    afterCursor = input.substring(selectStart, input.length);
    emoji = '<img src="images/smiley/'+svg+'.png" class="emojiText" />';
    $('div#text').html(beforeCursor+emoji+afterCursor);
}
doggie brezy
  • 289
  • 3
  • 16

1 Answers1

12

See Tim Down's answer on Get a range's start and end offset's relative to its parent container.

Try to use the function he has to get the selection index with nested elements like this:

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 update = function() {
  console.log(getCaretCharacterOffsetWithin(this));
};
$('#text').on("mousedown mouseup keydown keyup", update);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable="true" id="text">minubyv<img src="https://themeforest.net/images/smileys/happy.png" class="emojiText" />iubyvt</div>

I wrote my own function, based on Tim Down's, that works like you want it. I changed the treeWalker to filter NodeFilter.ELEMENT_NODE insted of NodeFilter.SHOW_TEXT, and now <img/> elements also get processed inside our loop. I start by storing the range.startOffset and then recurse through all the selection tree nodes. If it finds an img node, then it adds just 1 to the position; if the current node element is different than our range.startContainer, then it adds that node's length. The position is altered by a different variable lastNodeLength that is adds to the charCount at each loop. Finally, it adds whatever is left in the lastNodeLength to the charCount when it exists the loop and we have the correct final caret position, including image elements.

Final working code (it returns 14 at the end, exactly as it should and you want)

function getCharacterOffsetWithin_final(range, node) {
    var treeWalker = document.createTreeWalker(
        node,
        NodeFilter.ELEMENT_NODE,
        function(node) {
            var nodeRange = document.createRange();
            nodeRange.selectNodeContents(node);
            return nodeRange.compareBoundaryPoints(Range.END_TO_END, range) < 1 ?
                NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        },
        false
    );

    var charCount = 0, lastNodeLength = 0;

    if (range.startContainer.nodeType == 3) {
        charCount += range.startOffset;
    }

    while (treeWalker.nextNode()) {
        charCount += lastNodeLength;
        lastNodeLength = 0;
        
        if(range.startContainer != treeWalker.currentNode) {
            if(treeWalker.currentNode instanceof Text) {
                lastNodeLength += treeWalker.currentNode.length;
            } else if(treeWalker.currentNode instanceof HTMLBRElement ||
                      treeWalker.currentNode instanceof HTMLImageElement /* ||
                      treeWalker.currentNode instanceof HTMLDivElement*/)
            {
                lastNodeLength++;
            }
        }
    }
    
    return charCount + lastNodeLength;
}

var update = function() {
    var el = document.getElementById("text");
    var range = window.getSelection().getRangeAt(0);
    console.log("Caret pos: " + getCharacterOffsetWithin_final(range, el))
};
$('#text').on("mouseup keyup", update);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable="true" id="text">minubyv<img contenteditable="true" src="https://themeforest.net/images/smileys/happy.png" class="emojiText" />iubyvt</div>
Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • Your code is cool but i need to detect all html contents of the div not onle the text, because I will be inserting some characters into the div at the caret position. Look at my question edit. – doggie brezy Nov 23 '16 at 05:42
  • @doggiebrezy I made it work like you want it. See my updated answer. I've tested it on latest Chrome and Firefox. It does not work on IE though. – Christos Lytras Nov 23 '16 at 14:55
  • thanks for your edit but please help me with one final thing. Now I want to insert some text or image at the caret position and so I split the div.html but the issue is that assuming the caret position is 11, the inserted text doesnt count that image as one but the whole img.html there by misplacing the point at which it is meant to be inserted, please can you help me count how many string in the div.html including the img element – doggie brezy Nov 23 '16 at 15:24
  • @doggiebrezy I think that's easier to just add something (text or image) to the `currentNode` of the selection. I'll work on it when I find some time soon. – Christos Lytras Nov 23 '16 at 22:33
  • Thanks @christoslytras. I did figure out a way. and it works perfect. Thanks so much for your contribution, I appreciate – doggie brezy Nov 24 '16 at 08:24
  • add some line breaks and the caret position = NaN :( – Sangar82 Feb 21 '18 at 08:12
  • @Sangar82 I have updated the code so it counts the `BR` elements, but it misses the first break. I don't have much time to investigate why is this happening and fix it, but now it works and there is no `NaN` results. Please let me know if you find a solution for the missing `BR` after each line. – Christos Lytras Feb 21 '18 at 12:07
  • I´m crazy searching the solution... I changed few things and I noticed too that it misses the first break, but I'm looking to fix it, but at this moment, I didn't found a solution. If you have time, I would appreciate your help. Thanks!! – Sangar82 Feb 21 '18 at 13:39
  • @doggie brezy Can you please tell me how you added the img/text at the end – Murtuza Jul 10 '19 at 13:36