Reset content editable caret position
Asked Answered
P

1

8

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.

Pallaten answered 11/4, 2020 at 10:26 Comment(7)
You rather use something ready like github.com/codemirror/codemirrorUnicorn
Yea i know it exists but this is a learning curve for me and I need to understand how to do thisPallaten
Well, i spent some time trying to understand why it didn't count the LF, and what i see is that the number is the same if you are on the end of the first line, or on the beginning of the second. so i believe the only way of doing this is splitting the element line by line.Unicorn
Please can you explain what you mean by "splitting the element line by line" ?Pallaten
the caret position sets to end while user types by default , you don't need any JS code for setting caret position . it acts like any other text field by defaultLorenzalorenzana
I don't understand what you're trying to achieve. Maybe you should add a brief description of what your project is and what steps are you following, so that a greater number of users can help you. As, in the current scenario, it is really difficult for me to be helpful :\Oculist
Isn't caret by default at the end?Nakano
I
5

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.

Indeed answered 18/4, 2020 at 2:51 Comment(2)
Thanks for this but there are still issues from the fiddle I saw but I really appreciate the attemptPallaten
@WhiteHox It is very hard to do what you are trying to do. You will probably have to mirror a textarea to get the results you are looking for.Indeed

© 2022 - 2024 — McMap. All rights reserved.