Tuesday, November 27, 2007

Mozilla/Firefox Range - Part 1: setEndAfter Mythic

This article is one of the cycle about Range behavior in Mozilla Firefox. There are lots of nuances in Range manipulation (especially when you need to write some custom behavior that is tightly bounded to page DOM). This entry describes nonobvious behavior of setEnd Range method

Range endOffset Property and setEnd Method
Range setEndAfter Method
The setEndAfter Mythics


Range endOffset Property and setEnd Method

Let's consider next Range properties:
  • range.endContainer – points to the node where the range ends.
  • range.endOffset – is shift inside the endContainer to point exactly to the place where range ends
  • range.setEnd(newEndContainer, newEndOffset) – sets new end boundary of the range.

The prevalent errors are made here when misunderstanding endOffset property.

EndOffset is the index of node or character right before which is the end of the range and this node or character does not belong to the range itself (not the index of last range node or character!!)

The second important point here is that this node or character before the end may not even exist.

Example:





Suppose some range contains some text node (dayNode.nodeType = “#text”).
For example dayRange contains text node with textContent ‘day’.

dayRange has next properties set:

  • commonAncestorContainer - Text node ‘day’
  • startContainer - Text node ‘day’
  • startOffset - 0
  • endContainer - Text node ‘day’
  • endOffset - 3

Note that endOffset is 3, but dayNode.textContent.charAt(3) doesn’t exist.

Range setEndAfter Method

setEndAfter method, moves range’s right boundary after the node passed in parameter.

Lets suppose dayNode is a child of a dayWrapperNode (the only one child).

After calling

dayRange.setEndAfter(dayNode);

dayRange.endContainer will point to second (nonexisting in our case) child of
dayWrapperNode: dayWrapperNode.childNodes[1].

The dayRange is now:
  • commonAncestorContainer - dayWrapperNode
  • startContainer - Text node 'day'
  • startOffset - 0
  • endContainer - dayWrapperNode
  • endOffset - 1

Note that commonAncestorContainer has also changed to dayWrapperNode.

It might seem strange that range whose text content ex facte is completely in text node, (dayRange) is reached out from the dayNode to next sibling of the parent node. Cool yeah? But it is the way how it is implemented.

The setEndAfter Mythic

It is hard to understand, but something mythical happens with setEndAfter method.

Suppose dayWrapperNode a child of some dayWrapperWrapperNode and
dayRange.startContainer == dayRange.endContainer == dayNode.

Question 1: what is expected result of calling:

dayRange.setEndAfter(dayRange.startContainer);?

Question 2: what is expected result of calling:

dayRange.setEndAfter(dayRange.endContainer);?

As dayRange.endContainer and dayRange.startContainer point to same node, it is natural to expect that results of execution any of those operations will be the same.

But… no – they, are not the same.

In the former case dayRange.startContainer will point to dayWpapperNode.childNodes[1]:

In the latter case dayRange.endContainer will point to dayWrapperWrapperNode.childNodes[1]:

Question 3: what is expected result of executing this:

var endNode = dayRange.endContainer;
dayRange.setEndAfter(endNode);


?

The answer is awesome!
The result is same as calling

dayRange.setEndAfter(dayRange.startContainer);

i.e. it will point to dayWpapperNode.childNodes[1].

And now... will somebody tell me that Javascript programming isn't great? There are so many unknown and hidden features to take over in order to be a master....

No comments: