DOM 2 Range with JavaScript

This article describes using the DOM 2 Range with JavaScript and describes its support in mozilla. I put this together rather quickly and it isn’t very robust, but it is better than nothing. Send bugs and comments to mail[@]dylans.org (without the brackets)

Overview:

A Range selects all content between a pair of boundary points, and the Range interface provides methods for accessing and manipulating the document tree at a higher level than similar methods in the Node interface. Each of the methods in the Range interface maps directly to a series of DOM Core Methods, so the Range interface is actually just a set of convenience methods to make such manipulations tolerable.

Currently, all of the examples here will only work in Mozilla-based browsers, as it is not supported in IE 5, 5.5, or 6.0.

Examples

hasFeature:

This Core DOM method returns a boolean for whether the feature is supported or not.

document.implementation.hasFeature(“Range”,”2.0″);

Creating a Range:

Method:
createRange()

Supported in Mozilla

This creates a Range with offset 0 and container is the Document node.

Example:
document.createRange();

Position:

A Range is defined by the position of its two boundary points, the start and end of a range. The position in a Document or DocumentFragment tree is represented by a node and an offset. The boundary points must have a common ancestor container which is either a Document, DocumentFragment, or Attr node. This common ancestor is the root container of the Range, and contains the Range’s context tree.

The boundary point contains a reference to a node, and the offset, which is different for nodes which inherit from the Character Data interface (Text, Comment and CDATASection nodes) and those which do not. For the Character Data interface inheriting nodes, the offset is the 16-bit unit position. For example, an offset of four for a text node that has standard English text would mean that the selection was after the fourth letter. For the other node types, the offset is an integer that describes where it is in the childNodes hierarchy. For example, an offset of 2 would be between the second and third child nodes.

To read the position for the offset and container node, use the following methods:

startContainer, startOffset, endContainer, endOffset

This is currently supported in Mozilla. The valid syntax to retrieve the startContainer for a valid Range testRange is

testRange.startContainer;

This requires our Range to have a start and end position. This will be tested to show that the setting of start and end works correctly.

Selection:

A node or CDATA unit is selected if it is between the two boundary points of a Range. A node is partially selected by a Range if it is an ancestor container to exactly one of the boundary points.

Changing a Range’s Position:

Methods:
setStart(parent,offset)
setEnd(parent,offset)

where parent is the container node and offset is an integer

Supported in Mozilla

Example:
testRange.setStart(document.getElementsByTagName(“div”).item(0),0);
testRange.setEnd(document.getElementsByTagName(“div”).item(0),1);

This is one way to select where a Range starts and ends. You can also set a Range’s position relative to nodes in the tree:

Methods:
setStartBefore(node);
setStartAfter(node);
setEndBefore(node);
setEndAfter(node);

Implemented correctly in Mozilla

Example:
testRange.setStartBefore(document.getElementsByTagName(“div”).item(0));
testRange.setEndAfter(document.getElementsByTagName(“div”).item(0));

You can also collapse a Range to one of its boundary points.

Methods:
collapse(bool)
where bool is “TRUE” to collapse to the start boudnary point, and “FALSE” to collapse to the end boundary point

Attributes:
.collapsed

Supported in Mozilla

Examples:
testRange.collapse(“TRUE”);
alert(testRange.collapsed); // should return true

You can also have a Range directly that will directly select a Node or its contents

Methods:
selectNode(node)
selectNodeContents(node)

supported in Mozilla

Example:
testRange.selectNode(document.getElementsByTagName(“div”).item(0));

Comparing two Ranges by their Boundary Points

Method:
compareBoundaryPoints(how,sourceRange)

how is one of four values, START_TO_START,START_TO_END,END_TO_END,and END_TO_START, which equal the constants 0,1,2,3
the sourceRange is the Range which you are comparing to.

It returns either -1,0,1 dpending on whether the corresponding boundary point is before, equal to, or after the boundary point of sourceRange.

Fixed in Mozilla implementations after December, 2000. (see bug #58028).. returns -1 instead of 1 and vice versa

Example:
testRange.selectNode(document.getElementsByTagName(“div”).item(0));
testRange2.selectNode(document.getElementsByTagName(“div”).item(1));
alert(testRange.compareBoundaryPoints(0,testRange2)); // should return -1

Deleting Content in a Range:

Method:
deleteContents()

The Range is collapsed upon deletion

This is supported in Mozilla, but there are reported bugs such as #43535 which says sometimes too much is deleted

Example:
testRange.deleteContents();

Extracting Contents (like Cut) from a Range:

Method:
extractContents()

The Range is deleted from the document and is placed and returned in a new DocumentFragment. Partially Selected Nodes are cloned.

Fixed in Mozilla in May, 2001. Refer to bug #58969

Example:
testFragment = testRange.extractContents();

Cloning Content (like Copy) in a Range

Method:
cloneContents()

Clones the contents of a Range into a new DocumentFragment with boundaryPoints equal to those in the existing Range

Fixed in Mozilla in May, 2001. Refer to bug Refer to bug #58969.

Example:
clonedFragment = testRange.cloneContents();

Inserting Content in a Range

Method:
insertNode(node)

Inserts a node into the Range at the starting boundary point, following rules similiar to DOM Core method insertBefore(). If the start of the Range is in a text node, that node will be split, even if the new inserted node is all text.

Fixed in Mozilla in May, 2001. Refer to bug Refer to bug #58972

Example:
testRange.insertNode(document.createTextNode(“Insert This Text “));

Subsuming Content Selected by a Range (Wrapping the Range inside its own node)

Method:
surroundContents(newParent)

Fixed in Mozilla in May, 2001. Refer to bug Refer to bug #58974

Example:

testRange.surroundContents(document.createElement(“div”));

    It is equivalent to the following series of steps:

  1. extractContents()
  2. insert newParent where the Range is collapsed (after extraction) with insertNode()
  3. insert contents of extracted DocumentFragment into newParent using newParent.appendChild()
  4. select newParent and all of its content using newParent.selectNode()

An exception is raised if Range partially selects a non-text node

If newParent has any children, they are removed before its insertion, and if it has a parent, it is removed from its parent’s childNodes list.

Cloning a Range:

Method:
cloneRange()

Clones the Range itself.

This is supported in Mozilla

Example:
testRange2 = testRange.cloneRange();

Getting an ancestor container:

Attribute
commonAncestorContainer

This gives the ancestor for the boundary points of a Range that is furthest down from the root

This is supported in Mozilla

Example:
testRange.commonAncestorContainer;

Get a copy of all character data in a Range:

Method:
toString()

This returns the CDATA in a Range

This is supported in Mozilla

Example:
testRange.toString();

Implementation can remove a Range from resources

Method:
detach()

I do not think JavaScript developers need to worry about this unless they are worried about performance with a lot of Ranges

This is supported in Mozilla

Example:
testRange.detach();
alert(testRange); //should alert “”

Normalization (part of DOM CORE)

Method:
normalize()

Used to combine adjacent text nodes and removed empty text nodes from a node

This is supported in Mozilla

Example:
document.getElementByTagName(“div”).item(0).normalize();

Document Mutations:

This section simply describes what happens to a Range when a document is mutated by insertion or deletion

All Ranges remain valid after an mutation operation, and they select the same portion of the docuemtn after mutation.

Insertion occurs at a single insertion point. The boundary point of the Range changes upon insertion if they have the same container and the offset of the insertion point is less than the offset of the range boundary point. This will change the end boundary point by increasing its offset. The boundary point will be placed at the start of the newly inserted content if the boundary point and insertion point are equivalent.

Deletion will affect the Range is the boundary point of the original Range is within the content being deleted. After deletion, it will be a thte same position as the resulting boundary point of the now collapsed Range used to delete the contents.

This behavior is supported in Mozilla, though it is highly probable that there are some quirks.

Examples

All Reported DOM Range and Traversal bugs in mozilla

7 Responses to “DOM 2 Range with JavaScript”

  1. on 08 Sep 2008 at 21:41Doug

    Is it possible to change the style of a range (i.e. color and background color)? How?

  2. on 09 Sep 2008 at 21:26Dylan

    @Doug, yes, but you pretty much need to take the range/selection, wrap it in nodes, and apply the style to that. Ranges really were not intended with the rich text editor use case in mind unfortunately…

  3. on 10 Sep 2008 at 1:17Doug

    Got it. That complicates things a little, but I think what I want is still doable. Thanks.

  4. on 24 Jan 2009 at 9:56Bill

    Is there a way to change the document selection programmatically to a new range that I have created?

  5. on 04 Apr 2009 at 14:13Veli

    What happens when I want to style a selection which partially selects two non-Text-Only nodes? What is the proper way of doing this? Suppose I have selected a range from the middle of a element to the middle of the next element and I want to style it. How do I do it without breaking the DOM? Thank you in advance.

  6. on 08 Apr 2009 at 1:49Pomax

    Is it possible to get the x/y position of the start and end points?

    For instance, say I have a div with text in it, and when I select some text, want a new div to ‘pop up’, positioned to have its upper left corner at the position of the first character of the selected text (used to visually mark a change or annotation on a webpage, for instance).

  7. on 16 Apr 2009 at 0:31Vincentc

    @ Pomax
    Exactly what i’m searching for actually. Do you find anything about that please ?

Leave a Reply