18

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.

Community
  • 1
  • 1
F.S.
  • 333
  • 1
  • 3
  • 13
  • Please check if [this topic](http://stackoverflow.com/questions/4834793/set-caret-position-right-after-the-inserted-element-in-a-contenteditable-div) answers your question. Thank you. – Pyromonk Apr 21 '17 at 05:03
  • 1
    Thanks, @Pyromonk - 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. – F.S. Apr 24 '17 at 13:59
  • 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? – Pyromonk Apr 24 '17 at 14:02
  • 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. – F.S. Apr 24 '17 at 14:10
  • What about this? http://stackoverflow.com/questions/4767848/get-caret-cursor-position-in-contenteditable-area-containing-html-content – Pyromonk Apr 24 '17 at 23:15
  • 2
    @HAL9256 can you share any code snippet or fiddle link for more clarification? Thanks – Hassan Siddiqui Apr 05 '19 at 10:50

2 Answers2

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>
Sivasankar
  • 753
  • 8
  • 22
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?

Christian Meyer
  • 1,501
  • 1
  • 14
  • 19
Andrew
  • 121
  • 11