Persisting the Changes of Range Objects After Selection in Html

Persisting the changes of range objects after selection in HTML

For each selection, you could serialize the selected range to character offsets and deserialize it again on reload using something like this:

Demo: http://jsfiddle.net/WeWy7/3/

Code:

var saveSelection, restoreSelection;

if (window.getSelection && document.createRange) {
saveSelection = function(containerEl) {
var range = window.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 charIndex = 0, range = document.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 = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document.selection) {
saveSelection = function(containerEl) {
var selectedTextRange = document.selection.createRange();
var preSelectionTextRange = document.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 textRange = document.body.createTextRange();
textRange.moveToElementText(containerEl);
textRange.collapse(true);
textRange.moveEnd("character", savedSel.end);
textRange.moveStart("character", savedSel.start);
textRange.select();
};
}

Can't restore selection after HTML modify, even if it's the same HTML

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();
};
}

Set selection by range in Javascript

The setStart() and setEnd() methods of DOM Range are well specified, so you could find the answer by reading the spec or MDN.

To summarise, if you want to specify a range boundary in terms of character offsets, you need to deal with a text node rather than an element. If your element contains a single text node, changing the first line of your code to the following will work:

var node = document.getElementById("content").firstChild;

contenteditable div issue when restore/saving selection

You have a few issues:

1) Timing: the "click" event is way too late to grab selection (ALWAYS debug this, it's super easy to see the editable DIV has long lost focus and selection by that time). Use "mousedown" instead.

2) You can't store selection range like this - changing the selection context (in your case the innerHTML of the commonAncestorContainer) will wipe that range (for some reason even cloned range objects get wiped for me). If you manage to get a copy (via jQuery.extend for example) it will become invalid because the text node inside is not guaranteed to remain the same. My best guess is to go with storing start/end offset and if needed the related nodes as required by the range. Restore the range properties after the HTML is modified.

3) As with 1) focus is crucial to maintain selection, so that click on the list.. make sure you prevent the default before exiting the handler so focus and you new selection will remain in the DIV.

Can't figure out the exact use case from your code but this is my test sample and you can adjust from here as needed: http://jsfiddle.net/damyanpetev/KWDf6/

Set Selection Start and End in a Contentedible

I've posted a couple of different functions for this. I can't find my most recent one but the following has a link to jsFiddle containing a restoreSelection() function that should be helpful:

https://stackoverflow.com/a/7404126/96100

Update: I've found my more recent version of this code. It should work the same but the internals are a bit more elegant.

https://stackoverflow.com/a/13950376/96100

How can I persist a highlighted selection using Javascript?

You need two things: some means of serializing the selection and some means of saving it. Cookies and/or local storage would do for the saving part. For the serializing/deserializing, I'd suggest creating some kind of path through the DOM using child node index at each level to specify the selection boundaries. See this answer to a similar question: Calculating text selection offsets in nest elements in Javascript

Edit: summary of the linked solution

The user's selection can be expressed as a Range object. A Range has a start and end boundary, each expressed in terms of an offset within a node. What the the answer I linked to is suggesting is serializing each boundary as a path though the DOM that represents the node, coupled with the offset. For example, for the following document with the selection boundaries represented by |:

<html><head><title>Foo</title></head><body>On|e <b>t|wo</b><div>

... you could represent the selection start boundary as "1/0:2", meaning offset 2 within the child node at position 0 of the child node at position 1 in the document root. Similarly the end boundary would be "1/1/0:1". You could represent the whole thing JSON as '{"start":"1/0:2",end:"1/1/0:1"}'.

Get range of selected text, relative to element

The inverse process is simpler: given a range and a parent element, the process is

  1. Create a new range that encompasses the contents of the element
  2. Set the end of that range to the start boundary of the range you're measuring
  3. Get the length of the string returned by calling toString() on the measuring range. This is your start offset.
  4. Get the length of the string returned by calling toString() on the original range and add it to the start offset. This is your end offset.

See the saveSelection function here for an example.



Related Topics



Leave a reply



Submit