Introduction to Range

See also the Range Compatibility Table.

This page gives an introduction to the Range objects. Using these, you can select any part of an HTML document and do something with this information. The most common Range is a user selection.

This page concentrates on getting the user selection and converting this selection to a W3C Range or Microsoft Text Range object, although we'll treat the programmatic creation of Range objects, too.

What is a Range?

A Range is an arbitrary part of the content of an HTML document. A Range can start and end at any point, and the start and end point may even be the same (in which case you have an empty Range). The most common Range is a user text selection. As soon as the user has selected (part of) the text on an HTML page, you can convert this selection to a Range. However, you can also define Ranges programmatically.

Let's take this bit of HTML from my linklog as an example. Suppose the user selects a bit of text:

<h4 id="entry1196"><a
	href="http://radar.oreilly.com/archives/2007/03/call_for_a_blog_1.html"
	class="external">Call for a Blogger's Code of Conduct</a></h4>

<p>Tim O'Reilly calls for a Blogger Code of Conduct. His proposals are:</p>

<ol>
	<li>Take responsibility not just for your own words, but for the
		comments you allow on your blog.</li>
	<li>Label your tolerance level for abusive comments.</li>
	<li>Consider eliminating anonymous comments.</li>
</ol>

You can convert this user selection to a Range object (details below), which contains the bit of text the user has selected. Through the Range object you can find the start and end point of this Range, and if you so desire you can copy or delete it, or substitute it by another text, or even a bit of HTML.

This is about the simplest Range object you can get, because it only contains text. Let's move to a more complicated example, where the user-generated Range spans several HTML elements:

<h4 id="entry1196"><a
	href="http://radar.oreilly.com/archives/2007/03/call_for_a_blog_1.html"
	class="external">Call for a Blogger's Code of Conduct</a></h4>

<p>Tim O'Reilly calls for a Blogger Code of Conduct. His proposals are:</p>

<ol>
	<li>Take responsibility not just for your own words, but for the
		comments you allow on your blog.</li>
	<li>Label your tolerance level for abusive comments.</li>
	<li>Consider eliminating anonymous comments.</li>
</ol>

Again, a Range object is created, one that contains HTML. The problem is that the user selection (and thus the Range) crosses a few boundaries from one HTML element to the next. Without more action, it would look like this:

calls for a Blogger Code of Conduct. His proposals are:</p>

<ol>
	<li>Take responsibility not just for your own words, but for the
		comments you allow on your blog.</li>
	<li>Label your toleran

This is violently invalid HTML. Fortunately all browsers intervene: they adjust the HTML so that the snippet becomes valid:

<p>calls for a Blogger Code of Conduct. His proposals are:</p>

<ol>
	<li>Take responsibility not just for your own words, but for the
		comments you allow on your blog.</li>
	<li>Label your toleran</li></ol>

As you see, the browsers add the minimum amount of HTML to make the Range valid. If you copy or move the Range, you copy or move this valid HTML snippet.

Browser compatibility - overview

Before continuing we have to have an overview of the rather severe browser incompatibilities in this part of JavaScript. The main problem is that there are no less than three Range-like objects, and you have to understand them all.

Module Explorer 6/7 Firefox 2 Safari 1.3 Opera 9
W3C Range
Specification
No Yes Yes Yes
The W3C Range object is the only officially specified one. In the main it treats the Range as a document fragment that contains a DOM tree.
Mozilla Selection
Specification
No Yes Incomplete Yes
The Mozilla Selection object is a bit of a leftover. It is there to provide backward compatibility with Netscape 4. (One is left to wonder why we need that at all.) It resembles the W3C Range object and is also DOM tree-based.
Microsoft Text Range
Specification
Yes No No Incomplete
The Microsoft Text Range object is profoundly different from the other two, because it's string-based. In fact, it is extremely hard to jump from the string contained by Text Range to a DOM node.

In general the Mozilla Selection object is rather pointless; it would be far better to promote any user selection to a full Range object immediately; possibly with a few extra methods and properties to remain backward compatible with Netscape 4. Unfortunately all browsers save IE use this unnecessary Selection object.

Accessing the user selection

The first step in manipulating a user-generated Range is accessing the user selection. This immediately requires a code branch: Internet Explorer uses the Microsoft way, while the other browsers use the Mozilla way:

var userSelection;
if (window.getSelection) {
	userSelection = window.getSelection();
}
else if (document.selection) { // should come last; Opera!
	userSelection = document.selection.createRange();
}

In Mozilla, Safari and Opera userSelection now is a Selection object, while in Internet Explorer it's a Text Range object. This difference will remain valid for the rest of your script: Internet Explorer's Text Ranges are fundamentally different from Mozilla's Selection and W3C's Range objects, and all other code that you write will require a branch for IE and a branch for all other browsers.

Note the order of the branches: the Mozilla Selection should come first! The reason is that Opera supports both objects; if you use window.getSelection() to read out the user selection, Opera creates a Selection object, while if you use document.selection it creates a Text Range object.

Since Opera's support for Mozilla Selection and W3C Range is excellent, but its support for the Microsoft Text Range is spotty, it is desirable to place Opera with the standard compliant browsers by checking window.getSelection() first.

The contents of userSelection

The userSelection variable is now either a Mozilla Selection or a Microsoft Text Range object. As such it grants access to all methods and properties defined on such objects.

However, the Mozilla Selection object that userSelection refers to in W3C-compliant browsers also contains the text the user has selected (as text, not as HTML). Doing this

alert(userSelection)

therefore alerts the following (though formatting details differ per browser):

calls for a Blogger Code of Conduct. His proposals are: Take responsibility
not just for your own words, but for the comments you allow on your blog.
Label your toleran

In order to get the same information from a Microsoft Text Range object you have to use userSelection.text. So to read out the text the user has selected, do this:

var selectedText = userSelection;
if (userSelection.text)
	selectedText = userSelection.text;

Now selectedText contains the text the user has selected. If that's enough information for you, you're ready now.

Creating a Range object from a Selection object

Most of the time, though, you want to play around with the Range object that represents the user selection. In the Microsoft model this is already possible: userSelection is a Text Range. In the W3C-compliant browsers, though, userSelection is still a Selection object, and the time has come to create a Range object that spans the same content as the Selection object.

This is done as follows:

var rangeObject = getRangeObject(userSelection);

function getRangeObject(selectionObject) {
	if (selectionObject.getRangeAt)
		return selectionObject.getRangeAt(0);
	else { // Safari!
		var range = document.createRange();
		range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
		range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);
		return range;
	}
}

Ideally, we access the W3C Range object through the Selection object's getRangeAt() method. This method returns the Range object at the given index; and as usual in JavaScript the first Range has index 0. (getRangeAt() has been designed for situations in which there are several Ranges defined. In simpler scripts you'll rarely encounter such situations.)

Programmatically creating a Range

Unfortunately Safari (1.3) does not support getRangeAt(). Therefore we have to create a new Range object that spans the same HTML selection as the user selection. This is a useful exercise; it'll teach you how to create your own Ranges.

Obviously we start by creating a Range object:

var range = document.createRange();

Now we have an empty Range that floats somewhere in DOM hyperspace. In order to insert it into the document we need to define its start and end points through the thoughtfully provided setStart() and setEnd() methods.

These methods need two parameters:

  1. In which DOM node does the Range start or end?
  2. At which text offset does the Range start or end? The text offset is the position of the first or last character in the text node that's part of the Range.

Let's study the second Range example again:

<h4 id="entry1196"><a
	href="http://radar.oreilly.com/archives/2007/03/call_for_a_blog_1.html"
	class="external">Call for a Blogger's Code of Conduct</a></h4>

<p>Tim O'Reilly calls for a Blogger Code of Conduct. His proposals are:</p>

<ol>
	<li>Take responsibility not just for your own words, but for the
		comments you allow on your blog.</li>
	<li>Label your tolerance level for abusive comments.</li>
	<li>Consider eliminating anonymous comments.</li>
</ol>

The Range starts in the <p> node, and its text offset is 13, since the 14th character of the text node in the <p> is the first character in the Range. (As usual, the first character in a text node has index 0.)

The Range ends in the second <li> node, and its text offset is 17, since the 18th character of the text node in the <li> is the last character in the Range.

So this is the way to create this range:

var startPar = [the p node];
var endLi = [the second li node];
range.setStart(startPar,13);
range.setEnd(endLi,17);

(Note that this newly created Range is not visible to the user; it's wholly internal to the browser.)

Now that we have created a Range, we can read out its start and end points, too. The startContainer and startOffset properties define the starting point of the Range, while the endContainer and endOffset properties define its end.

Reading out start and end of a Selection

Unfortunately, when working with user selections you don't know which part of the page the user has selected. Therefore you have to read out the start and end points of the user selection first; and this has to be done in the Selection object,since there's no Range object yet (and yes, this is needlessly complicated).

We just saw that any Range object has four properties that define its start and end points. A Selection objects has four similar properties that, however, go by other names: anchorNode/anchorOffset define the start of the Selection, while focusNode/focusOffset define its end. (Don't ask me why the Selection object use different property names.)

Therefore, in order to create a Range that spans the same content as the user selection, we read out the start and end points through the Selection object and use this data to set the start and end points of the Range object:

range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);

Continue (later)

Now we have a W3C Range object and a Microsoft Text Range object to play around with. Future pages will give some examples of how to use them, and how to work around the grave incompatibilities between them.