Get caret index in contenteditable div including tags
Asked Answered
M

2

14

I have a contentEditable div in which I have multiple tags (br, b, u, i) and text.

I need to get the caret index position relative to the div, including all the tags.

For example:

<div id="h" contenteditable="true">abc<b>def<br>ghi</b>jkl</div>

If the cursor is between g and h, I need the caret index position to be 14. The problem is that the found methods that use a treeWalker do not work in this case. The bold tag is not found... probably because it isn't closed. Also I have tried several methods but still no luck.

I need it to work in Firefox. Thank you.

Murderous answered 24/5, 2013 at 13:54 Comment(3)
Why do you need this number? It's essentially meaningless since there many ways to represent the same DOM with different HTML which will affect the number (e.g. by using <br /> in stead of <br>). I'm assuming you want this so you can get an offset relative to the HTML string you're sending from the server to the browser.Reliance
yes... that's exactly why I need the number. I have tried several means but still no success.Murderous
There is no way of getting back the original HTML string via the DOM, so you'll need to re-request the page via Ajax, parse the HTML and match it up to the current DOM. It's going to be quite tricky.Reliance
O
23

Have you tried this? Get a range's start and end offset's relative to its parent container

Direct link to the jsfiddle: https://jsfiddle.net/TjXEG/1/

Function code:

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

function showCaretPos() {
    var el = document.getElementById("test");
    var caretPosEl = document.getElementById("caretPos");
    caretPosEl.innerHTML = "Caret position: " + getCaretCharacterOffsetWithin(el);
}

document.body.onkeyup = showCaretPos;
document.body.onmouseup = showCaretPos;
Odo answered 24/5, 2013 at 14:42 Comment(3)
Thank you for your answer. The problem is that the <br> is not taken into consideration when determining the caret position.Murderous
@Murderous I wonder if you managed to find a solution that includes the tags.Bedmate
is there any function that includes length of the tags as well . ?Jacinthe
B
12

just had to do this so there is some working solution (some testing may be required)

basic idea is to:

  1. get textContent position using this method: Get caret (cursor) position in contentEditable area containing HTML content

  2. iterate through innerHTML of an element to the textContent position

  3. if html tag or entity is encountered, iterate through it until normal char, then continue

sample code here:

function getCaretPosition (node) {
    var range = window.getSelection().getRangeAt(0),
        preCaretRange = range.cloneRange(),
        caretPosition,
        tmp = document.createElement("div");

    preCaretRange.selectNodeContents(node);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    tmp.appendChild(preCaretRange.cloneContents());
    caretPosition = tmp.innerHTML.length;
    return caretPosition;
}

function getHTMLCaretPosition(element) {
var textPosition = getCaretPosition(element),
    htmlContent = element.innerHTML,
    textIndex = 0,
    htmlIndex = 0,
    insideHtml = false,
    htmlBeginChars = ['&', '<'],
    htmlEndChars = [';', '>'];


if (textPosition == 0) {
  return 0;
}

while(textIndex < textPosition) {

  htmlIndex++;

  // check if next character is html and if it is, iterate with htmlIndex to the next non-html character
  while(htmlBeginChars.indexOf(htmlContent.charAt(htmlIndex)) > -1) {
    // console.log('encountered HTML');
    // now iterate to the ending char
    insideHtml = true;

    while(insideHtml) {
      if (htmlEndChars.indexOf(htmlContent.charAt(htmlIndex)) > -1) {
        if (htmlContent.charAt(htmlIndex) == ';') {
          htmlIndex--; // entity is char itself
        }
        // console.log('encountered end of HTML');
        insideHtml = false;
      }
      htmlIndex++;
    }
  }
  textIndex++;
}

//console.log(htmlIndex);
//console.log(textPosition);
// in htmlIndex is caret position inside html
return htmlIndex;
}
Bombe answered 1/3, 2017 at 12:27 Comment(5)
You are my hero. This worked beautifully and saved me hours of tears.Thalweg
What function is being referenced for getCaretPosition(element)? It doesn't appear you've included its definition.Pigmy
@Pigmy read first point of the answer, there is link for that functionBombe
How to get selected text?Huffish
it works! life saving @Bombe thanks manDecigram

© 2022 - 2024 — McMap. All rights reserved.