ContentEditable div - set cursor position after updating inner html
Asked Answered
K

2

19

I have a spell check solution that uses a content editable div and inserts span tags around words that are misspelled. Every time the inner html of the div is updated, the cursor moves to the beginning of the div.

I know I can move the cursor to the end of the div if the user adds new words to the end of the sentence (code below).

Old Text: This is a spell checker|

New Text: This is a spell checker soluuution|

var range = document.createRange();
range.selectNodeContents(element[0]);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);

However, I am unable to retain the cursor position if the user adds words in the middle of a sentence.

Old Text: This is a spell checker

New Text: This is a new spell checker|

In the above case, the cursor goes to the end of the div when it should be after "new".

How do I retain the cursor position? Since I am updating the html and adding nodes, saving the range before the update and adding it to the selection object isn't working.

Thanks in advance.

Kelcy answered 21/4, 2017 at 4:28 Comment(6)
Please check if this topic answers your question. Thank you.Dunaj
Thanks, @Dunaj - unfortunately, that approach doesn't work for me as I need to update the innerHtml of the div and hence, any marker nodes are lost.Kelcy
That's a shame. I will try to be on the lookout for something that might help. Using a separate element just for input while updating the div's innerHTML is out of the question, I presume?Dunaj
Unfortunately, yes. I have tried to update the inner html while still keeping the marker node and then select it but I keep getting a "The given range isn't in document" error.Kelcy
What about this? #4768348Dunaj
@HAL9256 can you share any code snippet or fiddle link for more clarification? ThanksUnvoice
L
6

As far as I know, changing the content of the div will always have problem.

So here is the solution that I came with. Please type error word such as helloo, dudeee

This should ideally work for textarea as well.

Solution details:

  • Use a ghost div with same text content
  • Use transparent color for the ghost div
  • Use border-bottom for the ghost div span text
  • Change zIndex so that it does't appear infront

// some mock logic to identify spelling error
const errorWords = ["helloo", "dudeee"];

// Find words from string like '   Helloo   world .. '
// Perhaps you could find a better library you that does this logic.
const getWords = (data) =>{
  console.log("Input: ", data);
  const allWords = data.split(/\b/);
  console.log("Output: ", allWords)
  return allWords;
}

// Simple mock logic to identify errors. Now works only for 
// two words [ 'helloo', 'dudeee']
const containsSpellingError = word => {
  const found = errorWords.indexOf(word) !== -1;
  console.log("spell check:", word, found);
  return found;
}

const processSpellCheck = text => {
  const allWords = getWords(text);
  console.log("Words in the string: ", allWords);
  const newContent = allWords.map((word, index) => {
    var text = word;
    if(containsSpellingError(word.toLowerCase())) {
      console.log("Error word found", word);
      text = $("<span />")
        .addClass("spell-error")
        .text(word);
    }
    return text;
  });
  return newContent;
}

function initalizeSpellcheck(editorRef) {
  var editorSize = editorRef.getBoundingClientRect();
  
  var spellcheckContainer = $("<div />", {})
    .addClass("spell-check")
    .prop("spellcheck", "false");
 
  var spellcheckSpan = $("<span />")
    .addClass("spell-check-text-content")
    .css({
      width: editorSize.width,
      height: editorSize.height,
      position: "absolute",
      zIndex: -1
    });
    
  var text = $(editorRef).text();
  
  var newContent = processSpellCheck(text);
  spellcheckSpan.append(newContent);
  
  spellcheckContainer.append(spellcheckSpan);
  spellcheckContainer.insertBefore(editorRef);
  
  $(editorRef).on("input.spellcheck", function(event) {
      var newText = $(event.target).text();
      var newContent = processSpellCheck(newText); 
      $(".spell-check .spell-check-text-content").text("");
      $(".spell-check .spell-check-text-content").append(newContent);
  });
}


$(document).ready(function() {
  var editor = document.querySelector("#editor");
  initalizeSpellcheck(editor);
});
#editor {
  border: 1px solid black;
  height: 200px;
}

.spell-check {
  color: transparent;
}

.spell-error {
  border-bottom: 3px solid orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


<div id="editor" contenteditable="true" spellcheck="false">
dudeee
</div>
Ludlow answered 8/4, 2019 at 16:34 Comment(1)
Finding words is based on space character.Ludlow
N
0

This answer might work from SitePoint:

Store the selection x, y:

cursorPos=document.selection.createRange().duplicate();
clickx = cursorPos.getBoundingClientRect().left;
clicky = cursorPos.getBoundingClientRect().top;

Restore the selection:

cursorPos = document.body.createTextRange();
cursorPos.moveToPoint(clickx, clicky);
cursorPos.select();

SitePoint Article: Saving/restoring caret position in a contentEditable div

Update 25.10.2019:

The solution mentioned above doesn't work anymore since functions are used that are deprecated. Does chrome supports document.selection?

Nubbly answered 8/4, 2019 at 3:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.