I'm trying to store a selection of a contentEditable element and restore it later.
I want to observe the paste
event and store the HTML as it was before, clear the html and then manually insert the pasted text with some changes at the selected position.
Take a look at this example: jsfiddle.net/gEhjZ
When you select a part of the text, hit store
, remove the selection again and hit restore
, it's working as expected.
But when you first hit store
, then replace the HTML with the exact same HTML by hitting overwrite html
and then try to restore
, nothing happens.
I thought that using .cloneRange()
would make a difference, but it won't. Even a deep copy of the object ($.extend(true, {}, oldRange)
) won't do the trick. As soon as I overwrite the HTML, the selection object sel
is being changed too. It makes sense for me that changing the selection context will wipe the range, but I'm trying to restore it for the exact same HTML.
I know I could use rangy, but I really don't want to use a huge library just for this small feature. What am I missing? Any help would be much appreciated!
Note: only Firefox/Chrome, so no crossbrowser-hacks needed.
@Tim Down's answer works when using a div, but I'm actually using an iframe. When I made that example, I thought it wouldn't make any difference.
Now when I try to restore the iframe's body, i get the following error: TypeError: Value does not implement interface Node.
in the following line preSelectionRange.selectNodeContents(containerEl);
. I didn't get much from googling. I tried to wrap the contents of the body and restore the wrap's html, but I get the same error.
jsfiddle isn't working in this case because it is using iframes to display the results itself, so I put an example here: snipt.org/AJad3
And the same without the wrap: snipt.org/AJaf0
Update 2:
I figured that I have to use editable.get(0)
, of course. But now the start
and end
of the iframe's selection is 0. see snipt.org/AJah2
You could save and restore the character position using functions like these:
https://stackoverflow.com/a/13950376/96100
I've adapted these function slightly to work for an element inside an iframe.
Demo: http://jsfiddle.net/timdown/gEhjZ/4/
Code:
var saveSelection, restoreSelection;
if (window.getSelection && document.createRange) {
saveSelection = function(containerEl) {
var doc = containerEl.ownerDocument, win = doc.defaultView;
var range = win.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
};
restoreSelection = function(containerEl, savedSel) {
var doc = containerEl.ownerDocument, win = doc.defaultView;
var charIndex = 0, range = doc.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = win.getSelection();
sel.removeAllRanges();
sel.addRange(range);
};
} else if (document.selection) {
saveSelection = function(containerEl) {
var doc = containerEl.ownerDocument, win = doc.defaultView || doc.parentWindow;
var selectedTextRange = doc.selection.createRange();
var preSelectionTextRange = doc.body.createTextRange();
preSelectionTextRange.moveToElementText(containerEl);
preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
var start = preSelectionTextRange.text.length;
return {
start: start,
end: start + selectedTextRange.text.length
};
};
restoreSelection = function(containerEl, savedSel) {
var doc = containerEl.ownerDocument, win = doc.defaultView || doc.parentWindow;
var textRange = doc.body.createTextRange();
textRange.moveToElementText(containerEl);
textRange.collapse(true);
textRange.moveEnd("character", savedSel.end);
textRange.moveStart("character", savedSel.start);
textRange.select();
};
}
Provided solution works very well.
replacing that line
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
by
if (!foundStart && savedSel.start >= charIndex && savedSel.start < nextCharIndex) {
prevents Chrome / Edge to select the end of the previous line
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