Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modify selection, select word and then reattach the caret position

So i'm trying to make a wysiwyg editor in javascript. What i'm trying to do is: when I havent made a selection and only stand with the caret on a word, I want to select the word, execute a command on it and then move the caret back to its position.

Example: Before I execute my code (the caret should be here after my function):

make this word b|old

After:

make this word bold|

jsFiddle Example: jsFiddle

My code so far:

    if (window.getSelection && (sel = window.getSelection()).modify) {
        doc.focus(); // the editor
        var range = sel.getRangeAt(0);
        var oldRange = document.createRange();
        oldRange.setStart(range.startContainer, range.startOffset);
        oldRange.setEnd(range.endContainer, range.endOffset);

        sel.collapseToStart();           

        sel.modify("move", "forward", "character");
        sel.modify("move", "backward", "word");
        sel.modify("extend", "forward", "word");

        document.execCommand(command, false, value);

        // Restore selection, Dosen't work as I expected
        sel.removeAllRanges();
        sel.addRange(oldRange);
}
like image 256
Noyce Avatar asked Jan 27 '26 20:01

Noyce


2 Answers

The issue is that right after you run document.execCommand, it resets the range, you can do a console.log on oldRange and comment out the document.execCommand line to confirm. The only way to save the old range is to serialize it, like I've done in the example below. Take a look

Solution

changeStyle = function(command) {
    var sel;  
    var doc = document.getElementById('editor');

    if (window.getSelection && (sel = window.getSelection()).modify) {
      doc.focus();
      var range = sel.getRangeAt(0);
      var oldRange = saveCaret(doc);
    
      sel.collapseToStart();
    
      sel.modify("move", "forward", "character");
      sel.modify("move", "backward", "word");
      sel.modify("extend", "forward", "word");
    
      document.execCommand(command, false, null);
    
      // Restore selection, well, it isn't restoring it
      restoreCaret(doc, oldRange)
    }
}
saveCaret = function(container) {
  var range = window.getSelection().getRangeAt(0);
  var preSelectionRange = range.cloneRange();
  preSelectionRange.selectNodeContents(container);
  preSelectionRange.setEnd(range.startContainer, range.startOffset);
  var start = preSelectionRange.toString().length;

  return {
    start: start,
    end: start + range.toString().length
  };
};
restoreCaret = function(container, position) {
  var charIndex = 0, range = document.createRange();
  range.setStart(container, 0);
  range.collapse(true);
  var nodeStack = [container], node, foundStart = false, stop = false;

  while (!stop && (node = nodeStack.pop())) {
    if (node.nodeType == 3) {
      var nextCharIndex = charIndex + node.length;
      if (!foundStart && position.start >= charIndex && position.start <= nextCharIndex) {
        range.setStart(node, position.start - charIndex);
        foundStart = true;
      }
      if (foundStart && position.end >= charIndex && position.end <= nextCharIndex) {
        range.setEnd(node, position.end - charIndex);
        stop = true;
      }
      charIndex = nextCharIndex;
    } else {
      var i = node.childNodes.length;
      while (i--) {
        nodeStack.push(node.childNodes[i]);
      }
    }
  }

  var sel = window.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
}
<button onclick="changeStyle('bold')">
  Bold
</button>
<div contentEditable=true id="editor">
Try make a word bold without make a selection, the caret will either jump to the start of the word or the end.
It should stay on its position.
</div>
like image 195
AnonymousSB Avatar answered Jan 30 '26 10:01

AnonymousSB


For anyone whoever may stumble upon this issue in the future, the implementation isn't so straightforward - mainly due to the difficulty of tracking and manipulating the caret. Also, cross-browser line-endings are a cause for problem in any attempted implementation.

However, jquery.caret does a pretty good job of enabling caret information and manipulation, so consider checking it out.

like image 45
Shushan Avatar answered Jan 30 '26 10:01

Shushan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!