Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reset content editable caret position

I am currently trying to create a syntax highlighter for Javascript and I currently facing the issue which I have found out is common with creating something like this which is setting the caret position to the end while the user types or edit contentEditable text.

I researched and found this and many other solutions here on SO but none works. It gets the position of the caret but never resets it so I am trying to find a workaround for this problem.

Below is the code I came up with.

html

<div id="editor" contentEditable="true" onkeyup="resetPosition(this)"></div>

<input type="text" onkeyup="resetPosition(this)" />

js

function getPos(e) {
    // for contentedit field
  if (e.isContentEditable) {
    e.focus()
    let _range = document.getSelection().getRangeAt(0)
    let range = _range.cloneRange()
    range.selectNodeContents(e)
    range.setEnd(_range.endContainer, _range.endOffset)

    return range.toString().length;
  }
  // for texterea/input element
  return e.target.selectionStart
}


function setPos(pos, e) {
  // for contentedit field
  if (e.isContentEditable) {
      e.focus()
      document.getSelection().collapse(e, pos);
      return
  }
  e.setSelectionRange(pos, pos)
}

function resetPosition(e) {
  if(e.isContentEditable) {
  let currentPosition = getPos(e);
  e.innerHTML=e.innerHTML.replace(/[0-9]/g, "a");
  setPos(currentPosition, e);

  return;
}

  e.value = e.value.replace(/[0-9]/g, "a");
  setPos(currentPosition, e);

}     

This works fine for text input but not for contentEditable divs.

When I type something like function, I get otincfun.

UPDATE: I was able to fix the setPos function by changing this line from document.getSelection().collapse(e, pos); to document.getSelection().collapse(e.firstChild, pos); but a new bug arose.

When I press ENTER Key, the caret goes back to the first line and first character. Please how do I fix?


Below is the fiddle link

https://jsfiddle.net/oketega/bfeh9nm5/35/

Thanks.

like image 969
codebarz Avatar asked Apr 11 '20 10:04

codebarz


Video Answer


1 Answers

The Problems

document.getSelection().collapse(element, index) collapses the cursor to the child node that index points to, not the character index.

I was able to fix the setPos function by changing this line from document.getSelection().collapse(e, pos); to document.getSelection().collapse(e.firstChild, pos);

That will work if you are only replacing characters, but if you are creating a syntax highlighter, you will want to encase characters in span elements to style them. e.firstChild would then only set the position to an index within e's first child, excluding latter span's

Another thing to consider is that you may want to autocomplete the certain chars. The caret position before you manipulate the text may not be the same as after you do so.

The Solution

I recommend creating a <span id="caret-position"></span> element to track where the caret is.

It would work like this:

function textChanged(element) {
    // 1
    const text = setCursorMarker(element.innerText, element);

    // 2
    const html = manipulate(text);
    element.innerHTML = html;

    // 3
    const index = findCursorIndex(element);
    document.getSelection().collapse(element, index)
}

  1. Every time the user types, you can get the current caret position and slip in the #caret-position element in there.
  2. Overwrite the existing html with the syntax highlighted text
  3. Find out where #caret-position is and put the caret there.

Note: The recommended way to listen for when the user types in the content-editable element is with the oninput listener, not onkeyup. It is possible to insert many characters by holding down a key.

Example

There is a working js fiddle here: https://jsfiddle.net/Vehmloewff/0j8hzevm/132/

Known Issue: After you hit Enter twice, it looses track of where the caret is supposed to be. I am not quite sure why it does that.

like image 73
Vehmloewff Avatar answered Oct 04 '22 11:10

Vehmloewff