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.
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.
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.
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.
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.
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.)
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:
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.
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);
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.