Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
201 views
in Technique[技术] by (71.8m points)

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

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.

Update:

@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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...