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);
}
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
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With