DOM 2 Range with JavaScript
December 7th, 2002 by Dylan
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.
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:
- extractContents()
- insert newParent where the Range is collapsed (after extraction) with insertNode()
- insert contents of extracted DocumentFragment into newParent using newParent.appendChild()
- 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.
Is it possible to change the style of a range (i.e. color and background color)? How?
@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…
Got it. That complicates things a little, but I think what I want is still doable. Thanks.
Is there a way to change the document selection programmatically to a new range that I have created?
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.
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).
@ Pomax
Exactly what i’m searching for actually. Do you find anything about that please ?