Question

Here is my HTML:

<ul contenteditable>
  <li>Hi there 1</li>
  <li>HI 2 2 2 2 2</li>
  <ul><li>hi 3</li></ul> <!-- I know it's invalid, it's what document.execCommand('indent') yields -->
  <li> hi 4 hi there people 4 here now </li>
</ul>

(you can see it at http://www.webdevout.net/test?06&raw for the next week)

I am attempting to determine whether the currently selected text (in IE8) is within one LI or spans multiple LIs. When I select the entirety of LI's one and two, then enter the following into the console document.selection.createRange().parentElement().innerHTML, the contents of only the second LI (HI 2 2 2 2 2 2) is returned.

Why is TextRange.parentElement returning the final element in the range rather than the parent of the entire range?

The docs say "If the text range spans text in more than one element, this method returns the smallest element that encloses all the elements." My ultimate goal is to determine whether more than one LI is selected; I thought "check if parentElement().nodeName.toUppercase === "LI"" would do it but I can't do this if parentElement() isn't returning the parentElement.

Was it helpful?

Solution

I've seen this kind of thing before and it's a bug in IE. The workaround I use in my Rangy library is to use the innermost common ancestor of three elements:

  • the parentElement() of the TextRange
  • the parentElement() of the TextRange after calling collapse(true)
  • the parentElement() of the TextRange after calling collapse(false)

Here's the code from Rangy:

/*
 This is a workaround for a bug where IE returns the wrong container
 element from the TextRange's parentElement() method. For example,
 in the following (where pipes denote the selection boundaries):

 <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>

 var range = document.selection.createRange();
 alert(range.parentElement().id); // Should alert "ul" but alerts "b"

 This method returns the common ancestor node of the following:
 - the parentElement() of the textRange
 - the parentElement() of the textRange after calling collapse(true)
 - the parentElement() of the textRange after calling collapse(false)
 */
var getTextRangeContainerElement = function(textRange) {
    var parentEl = textRange.parentElement();

    var range = textRange.duplicate();
    range.collapse(true);
    var startEl = range.parentElement();

    range = textRange.duplicate();
    range.collapse(false);
    var endEl = range.parentElement();

    var startEndContainer = (startEl == endEl) ?
        startEl : dom.getCommonAncestor(startEl, endEl);

    return startEndContainer == parentEl ?
        startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top