IE TextRange select method not working properly
internet-explorer, javascript
Solution
I've figured out a few methods for dealing with IE ranges like this.
If all you want to do is save where the cursor is, and then restore it, you can use the pasteHTML method to insert an empty span at the current position of the cursor, and then use the moveToElementText method to put it back at that position again:
// Save position of cursor
range.pasteHTML('<span id="caret"></span>')
...
// Create new cursor and put it in the old position
var caretSpan = iframe.contentWindow.document.getElementById("caret");
var selection = iframe.contentWindow.document.selection;
newRange = selection.createRange();
newRange.moveToElementText(caretSpan);
Alternatively, you can count how many characters precede the current cursor position and save that number:
var selection = iframe.contentWindow.document.selection;
var range = selection.createRange().duplicate();
range.moveStart('sentence', -1000000);
var cursorPosition = range.text.length;
To restore the cursor, you set it to the beginning and then move it that number of characters:
var newRange = selection.createRange();
newRange.move('sentence', -1000000);
newRange.move('character', cursorPosition);
Hope this helps.
Problem
I'm having an unusual problem with an IE document with contentEditable set to true. Calling select() on a range that is positioned at the end of a text node that immediately precedes a block element causes the selection to be shifted to the right one character and appear where it shouldn't. I've submitted a bug to Microsoft against IE8. If you can, please vote for this issue so that it can be fixed. https://connect.microsoft.com/IE/feedback/ViewFeedback.aspx?FeedbackID=390995 I've written a test case to demonstrate the effect: ``` <html> <body> <iframe id="editable"> <html> <body> <div id="test"> Click to the right of this line -> <p id="par">Block Element</p> </div> </body> </html> </iframe> <input id="mytrigger" type="button" value="Then Click here to Save and Restore" /> <script type="text/javascript"> window.onload = function() { var iframe = document.getElementById('editable'); var doc = iframe.contentDocument || iframe.contentWindow.document; // An IFRAME without a source points to a blank document. Here we'll // copy the content we stored in between the IFRAME tags into that // document. It's a hack to allow us to use only one HTML file for this // test. doc.body.innerHTML = iframe.textContent || iframe.innerHTML; // Marke the IFRAME as an editable document if (doc.body.contentEditable) { doc.body.contentEditable = true; } else { var mydoc = doc; doc.designMode = 'On'; } // A function to demonstrate the bug. var myhandler = function() { // Step 1 Get the current selection var selection = doc.selection || iframe.contentWindow.getSelection(); var range = selection.createRange ? selection.createRange() : selection.getRangeAt(0); // Step 2 Restore the selection if (range.select) { range.select(); } else { selection.removeAllRanges(); selection.addRange(range); doc.body.focus(); } } // Set up the button to perform the test code. var button = document.getElementById('mytrigger'); if (button.addEventListener) { button.addEventListener('click', myhandler, false); } else { button.attachEvent('onclick', myhandler); } } </script> </body> </html> ``` The problem is exposed in the myhandler function. This is all that I'm doing, there is no Step 3 in between the saving and restoring the selection, and yet the cursor moves. It doesn't seem to happen unless the selection is empty (ie. I have a blinking cursor, but no text), and it only seems to happen whenever the cursor is at the end of a text node that immediately precedes a block node. It seems that the range is still in the correct position (if I call parentElement on the range it returns the div), but if I get a new range from the current selection, the new range is inside the paragraph tag, and that is its parentElement. How do I work around this and consistently save and restore the selection in internet explorer?