JavaScript Selection and Range

In this chapter, we are going to explore selection in the document and in form fields such as <input>.

JavaScript is capable of getting the existing selection, selecting or deselecting partially or totally, and so on.

Range

Range is the basic concept of selection. It encompasses a pair of boundary points such as range start and range end.

Every point is represented as a parent DOM node along with the relative offset from the beginning. Once the parent node is an element node, the offset will be a child number.

Let’s start at creating a range, like this:

let r = new Range();

The next step is setting the selection of boundaries with range.setStart(node, offset) and range.setEnd(node, offset) .

A fragment of HTML will look as follows:

<p id="pId">Example:<b>bold</b> and <i>italic</i></p>

While selecting "Example: <b>bold</b>", the two first children of <p> will be:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p id="pId">Example: <b>bold</b> and <i>italic</i></p>
    From
    <input id="start" type="number" value=0> – To
    <input id="end" type="number" value=2>
    <button id="buttonID">Click to select</button>
    <script>
      const button = document.getElementById('buttonID')
      button.onclick = () => {
        let range = new Range();
        range.setStart(pId, Number(document.getElementById('start').value));
        range.setEnd(pId, Number(document.getElementById('end').value));
        // apply a selection explained later
        document.getSelection().removeAllRanges();
        document.getSelection().addRange(range);
      };
    </script>
  </body>
</html>

Now, let’s see a more flexible test stand:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p id="pId">Example: <b>bold</b> and <i>italic</i></p>
    From
    <input id="start" type="number" value=0> – To
    <input id="end" type="number" value=5>
    <button id="buttonID">Click to select</button>
    <script>
      button.onclick = () => {
        let range = new Range();
        range.setStart(pId, start.value);
        range.setEnd(pId, end.value);
        // apply a selection explained later
        document.getSelection().removeAllRanges();
        document.getSelection().addRange(range);
      };
    </script>
  </body>
</html>

For example, if you select from 1 to 4, it will give the range: <i>italic</i> and <b>bold</b>.

Note that the same node shouldn’t be used in both setStart and setEnd. The most important thing is that the end should be after the start.

Selecting Text Nodes Parts

The partial selection of the text will look as follows:

<p id="pId">Example:<b>bold</b> and <i>italic</i></p>

It is necessary to generate a range, which will start from position 2 in the first child of <p> ( it takes all except the two first letters of “Instance”).

Then, it should end at the position 3 in the first child of <i> (takes the first three letters: “italic”).

Here is how the example looks like:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p id="pId">Example: <i>italic</i> and <b>bold</b></p>
    <script>
      let range = new Range();
      range.setStart(pId.firstChild, 1);
      range.setEnd(pId.querySelector('b')
        .firstChild, 3);
      alert(range); // xample: bold and italic
      // use this range for selection, explained later
      window.getSelection().addRange(range);
    </script>
  </body>
</html>

Range Methods

Multiple convenience methods exist for manipulating ranges.

Set range start includes:

  • setStart(node, offset)
  • setStartBefore(node)
  • setStartAfter(node)

Similar methods are included in set range end:

  • setEnd(node, offset)
  • setEndBefore(node)
  • setEndAfter(node)

Node can be both an element node or a text. Offset skips that many characters for text nodes. For element nodes, it skips that many child nodes.

For manipulating within the range, you can use the following methods:

  • Removal of the range content from the document can be implemented with deleteContents().
  • Removal of the range content from the document and returning as DocumentFragment - with extractContents().
  • Cloning the range content and returning as DocumentFragment - with cloneContents().
  • Inserting node into the document at the range start - with insertNode(node).
  • Wrapping node around the range content - with surroundContents(node).

The methods above will allow you to do basically anything inside the selected nodes.

Here is how they will look like in action:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    //Click the buttons to start the selection methods, “resetExample” to reset it.
    <p id="pId">Example: <b>bold</b> and <i>italic</i></p>
    <p id="resultId"></p>
    <script>
      let range = new Range();
      // Each illustrated method is shown here:
      let methods = {
        deleteContents() {
            range.deleteContents()
          },
          extractContents() {
            let content = range.extractContents();
            resultId.innerHTML = "";
            resultId.append("extracted: ", content);
          },
          cloneContents() {
            let content = range.cloneContents();
            resultId.innerHTML = "";
            resultId.append("cloned: ", content);
          },
          insertNode() {
            let newNode = document.createElement('u');
            newNode.innerHTML = "New Node";
            range.insertNode(newNode);
          },
          surroundContents() {
            let newNode = document.createElement('u');
            try {
              range.surroundContents(newNode);
            } catch(e) {
              alert(e)
            }
          },
          resetExample() {
            pId.innerHTML = `Example:  <b>bold</b> and <i>italic</i>`;
            resultId.innerHTML = "";
            range.setStart(pId.firstChild, 1);
            range.setEnd(pId.querySelector('i').firstChild, 3);
            window.getSelection().removeAllRanges();
            window.getSelection().addRange(range);
          }
      };
      for(let m in methods) {
        document.write(`<div><button onclick="methods.${m}()">${m}</button></div>`);
      }
      methods.resetExample();
    </script>
  </body>
</html>

Selection

Range is a generic object that helps to manage selection ranges. Objects like that can be generated, passed around, but they can’t select anything visually on their own.

The document selection can be carried out with the Selection object. It can be obtained as window.getSelection() or document.getSelection().

Any selection can contain zero or more selections.

Selection Properties

Like a range, a selection can have a start (known as “anchor”) and an end (known as “focus”).

The primary selection properties include:

  • anchorNode
  • anchorOffset
  • focusNode
  • focusOffset
  • rangeCount

There are multiple ways of selecting the content. It depends on the user agent: hotkeys, mouse, and so on.

For example, a mouse allows creating the same selection in two directions: right-to-left and left-to-right.

In case the start (anchor) goes in the document before the end (focus), that kind of selection has a forward direction.

So, this is the most essential difference between the selection and range. For range objects, the start can never be after the end.

Selection Events

For keeping track of selection are used events such as:

  • elem.onselectstart: it occurs when a selection begins with elem. The selection won’t start if the default action is prevented.
  • document.onselectionchange: occurs at any time selection is changed. An important note: you can set the handler merely on the document.
An important note: you can set the handler merely on the document.

Selection Tracking Demo

Let’s check out a demo that shows the dynamic changes of the selection boundaries:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p id="pId">Select: <b>bold</b> and <i>italic</i> </p>
    From
    <input id="fromId" disabled> – To
    <input id="toId" disabled>
    <script>
      document.onselectionchange = function() {
        let {
          anchorNode, anchorOffset, focusNode, focusOffset
        } = document.getSelection();
        from.value = `${anchorNode && anchorNode.data}:${anchorOffset}`;
        to.value = `${focusNode && focusNode.data}:${focusOffset}`;
      };
    </script>
  </body>
</html>

Selection Getting Demo

Now, let’s see what is necessary to do for getting the whole selection. First of all, as text, it is required to call document.getSelection().toString(). Then, as DOM, to receive the underlined ranges, calling the cloneContents() method.

Here is how the demo looks like:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p id="p">Select: <b>bold</b> and <i>italic</i></p>
    Clone: <span id="cloneId"></span>
    <br> Text: <span id="textId"></span>
    <script>
      document.onselectionchange = function() {
        let select = document.getSelection();
        cloneId.innerHTML = textId.innerHTML = "";
        // Clone DOM nodes from ranges (multiple selection is supported here)
        for(let i = 0; i < select.rangeCount; i++) {
          cloneId.append(select.getRangeAt(i).cloneContents());
        }
        // Get as text
        textId.innerHTML += select;
      };
    </script>
  </body>
</html>

Selection Methods

It’s better to start at the methods for adding and removing ranges. Among them are:

  • getRangeAt(i)
  • addRange(range)
  • removeRange(range)
  • removeAllRanges()
  • empty()

There exist methods for manipulating the selection range without using Range. Here they are:

  • collapse(node, offset)
  • setPosition(node, offset)
  • collapseToStart()
  • collapseToEnd()
  • extend(node,offset)
  • setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)
  • selectAllChildren(node)
  • deleteFromDocument()
  • containsNode(node, allowPartialContainment = false)

The Selection methods can be called for different tasks without even using the underlying Range object.

Let’s try to select the <p> paragraph’s whole contents:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p id="pId">Select: <b>bold</b> and <i>italic</i></p>
    <script>
      // select from the <p> 0th child  to the last child
      document.getSelection().setBaseAndExtent(pId, 0, pId, pId.childNodes.length);
    </script>
  </body>
</html>

The same action can be taken with ranges, too:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p id="pId">Select: <b>bold</b> and <i>italic</i></p>
    <script>
      let r = new Range();
      r.selectNodeContents(pId); // or selectNode(p) to select the <p> tag
      document.getSelection().removeAllRanges(); // clear existing selection 
      document.getSelection().addRange(r);
    </script>
  </body>
</html>

Selection in Form Controls

For the form elements such as textarea and input, there is a specific API for selection. It allows acting without using the Range and Selection objects. There is no need in those objects, as the input value is a pure text.

The properties are input.selectionStart, input.selectionEnd, and input.selectionDirection.

Once something is selected the input.onselect event occurs.

Now, let’s get to the methods:

  • input.select()– selecting everything inside the text control
  • input.setSelectionRange(start, end, [direction]) – changing the selection for spanning from position start until the end, in the specific direction.
  • input.setRangeText(replacement, [start], [end], [selectionMode]) – replacing a part of text the with the new one.

The selectionMode argument specifies how the selection is going to be set after replacing the text. Here are the main possible values: "select", "start", "end", and "preserve".

Further, you can find the methods above in action.

Tracking Selection (Example)

For tracking selection, the following code applies onselect, like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <textarea id="txtId" style="width:60%;height:50px">
      The selection in this text updates values below.
    </textarea>
    <br> From
    <input id="from" disabled> – To
    <input id="to" disabled>
    <script>
      textId.onselect = function() {
        from.value = txtId.selectionStart;
        to.value = txtId.selectionEnd;
      };
    </script>
  </body>
</html>

Here, there are specific facts to be emphasized:

  • the onselect event occurs when something is selected but not when it’s removed.
  • the document.onselectionchange event may not occur for selections within a form control.

Moving Cursor (Example)

When you set selectionStart and selectionEnd, the cursor is moved.

For instance:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <textarea id="txtId" style="width:60%;height:50px">
      Focus on me, the cursor will be at position 5.
    </textarea>
    <script>
      textId.onfocus = () => {
        // zero delay setTimeout to run after browser action "focus"  finishes
        setTimeout(() => {
          // if start=end, the cursor is in this place
          txtId.selectionStart = txtId.selectionEnd = 5;
        });
      };
    </script>
  </body>
</html>

Modifying Selection (Example)

The input.setRangeText() method is used for modifying the selection content. However, it is a complicated method. It can replace the user selected range and remove the selection in its simplest one-argument form.

Here is how it will look like:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <input id="inputId" style="width:200px" value="Select here and click the button">
    <button id="buttonId">Selection in stars /*...*/</button>
    <script>
      buttonId.onclick = () => {
        if(inputId.selectionStart == inputId.selectionEnd) {
          return; // nothing is selected
        }
        let selected = inputId.value.slice(inputId.selectionStart, inputId.selectionEnd);
        inputId.setRangeText(`/*${selected}*/`);
      };
    </script>
  </body>
</html>

Insert at Cursor (Example)

In case, you have inserted nothing or apply start and end within setRangeText , the new text will be inserted without the removal of anything. Also, you can use setRangeText .for inserting something at the cursor. Let’s take a look at an example:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <input id="inputId" style="width:200px" value="Text Text Text Text Text">
    <button id="buttonId">Insert "WELCOME" at cursor</button>
    <script>
      buttonId.onclick = () => {
        inputId.setRangeText("WELCOME", inputId.selectionStart, inputId.selectionEnd, "end");
        inputId.focus();
      };
    </script>
  </body>
</html>

Summary

In this chapter, we demonstrated two APIs that are used for selection. Within the framework of the first one we explored Selection and Range objects used for document. Several additional methods covered in this chapter are used for textarea and input .

The second API is simpler and is used mainly for texts. The most commonly used receipts are considered getting the selection and setting the selection. And, finally, we learn about the cursor. Its position in <textarea> is constantly either at the start or at the end of the selection. It can be used for getting the cursor position or moving the cursor with elem.selectionStart and elem.selectionEnd .

Practice Your Knowledge

Which of the following actions are correct about Range and Selection methods in JavaScript, according to W3docs?

Quiz Time: Test Your Skills!

Ready to challenge what you've learned? Dive into our interactive quizzes for a deeper understanding and a fun way to reinforce your knowledge.

Do you find this helpful?