Range, TextRange and Selection

    Many JavaScript developers had to deal with the objects listed in the title, for example, when solving the following tasks:
    - creating visual editors (wysiwyg),
    - searching in a browser window,
    - setting BB-code,
    etc.

    In this article, the author made an attempt to collect the translation of documentation about these objects in one place + write small accompanying examples. The translation is free, not verbatim, so if you meet inaccuracy or clumsy wording - write in the comments.

    In general, the post is for reference only, and I hope someone will simplify the development.

    1. Range


    Range is an object corresponding to a fragment of a document, which may include nodes and sections of text from this document. Range objects are described in more detail in the DOM Range .

    To understand what we are talking about, we turn to the simplest case of Range objects, which will be discussed in detail below, to selections. In the example below, highlight a few words in a sentence. Thus, you will create objects similar to Range. In our example, we get the text content of the area you select.

    Link to an example

    But such areas can be created not only using custom selection, but also from a JavaScript script, performing certain manipulations with them. However, writing a simple illustrative code will not work right away, because There is one BUT - Internet Explorer. Micsosoft created their own implementation - the TextRange object . We will analyze each implementation separately.

    1.1 DOM implementation of Range Mozilla Firefox supportedChrome supportedOpera supportedSafari supported


    Range consists of two boundary points (boundary-points), corresponding to the beginning and end of the region. The position of any boundary point is defined in the document using two properties: node (node) and offset (offset). A container is a node that contains an endpoint. The container itself and all its ancestors are called the parent containers (ancestor containers) for the boundary point. A parent container that includes both boundary points is called a root container.

    Range nodes

    In the image above, the selection boundary points lie in the text nodes (# text1 and # text2), which are containers. For the left border, the parent containers are # text1, H1, BODY, for the right - # text2, P, BODY. The common parent for both boundary points is BODY, this element is a root container.

    If the container is a text node, then the offset is determined in characters from the beginning of the node. If the container is an element (Document, DocumentFragment, Element ...), then the offset is determined in the child nodes.

    We look at the illustration ( source ): The

    Range example

    boundary points of the Range object s1 are in text nodes, so the offset is specified in characters from the beginning of the node. For s2, the boundary points are arranged so that they include the entire paragraph

    Blah xyz

    , therefore, the container is the BODY element, and the offset is considered at the positions of the child nodes.

    Range objects are created using the creareRange method, which is called in the context of the root container or document. The object is created empty, and the boundary points are set using the setStart and setEnd methods of the object. We look at an example: An example in action . Consider briefly the properties and methods of Range :


      

    Соз|даем Range-объекта


      

    От третье|го символа заголовка до десятого символа это абзаца.




    $domRange = {
      create : function() {
        // Найдем root-контейнер
        var root =  document.getElementById('ex2');
        // Найдем контейнеры граничных точек (в данном случае тестовые)
        var start =  root.getElementsByTagName('h2')[0].firstChild;
        var end =  root.getElementsByTagName('p')[0].firstChild;
        if ( root.createRange ) {
          // Создаем Range
          var rng = root.createRange();
          // Задаем верхнюю граничную точку, передав контейнер и смещение
          rng.setStart( start, 3 );
          // Аналогично для нижней границы
          rng.setEnd( end, 10 );
          // Теперь мы можем вернуть текст, который содержится в полученной области
          return rng.toString();
        } else
          return 'Вероятно, у вас Internet Explorer, смотрите реализацию TextRange ниже';
      }
    }





    • The commonAncestorContainer property will return a link to the most nested root container.
    • The startContainer ( endContainer ) property will return a reference to the container of the upper (lower) boundary point.
    • The startOffset ( endOffset ) property will return the offset for the upper (lower) boundary point.
    • The collapsed property will return true if the boundary points have the same containers and offset (false otherwise).

    • The setStart ( setEnd ) method sets the container (link to the node) and the offset (integer value) for the corresponding boundary points. The example above.
    • The methods setStartBefore , setStartAfter , setEndBefore , setEndAfter take the node reference as the only argument and set the boundary points according to the natural boundary of the transmitted node. For instance:
      First
      Second

      var rng = document.createRange();
      // Установит верхнюю граничную точку по левой границе спана #s1
      rng.setStartBefore( document.getElementById('s1') );
      // Установит нижнюю граничную точку по правой границе спана #s2
      rng.setEndAfter( document.getElementById('s2') );

    • The selectNode and selectNodeContents methods allow you to create a Range object along the boundaries of the node, the link to which they accept as a single argument. When using selectNode, the transmitted node will also go into Range, while selectNodeContents will only create an object from the contents of the node:
      Select node
    • The collapse method combines the boundary points of a Range object. Takes a Boolean value as the only argument (true & mdash to merge at the top, false & mdash at the bottom). True by default.
    • The toString method will return the text content of the Range object.
    • The cloneContents method will return a copy of the contents of the Range object as a fragment of the document.
    • The cloneRange method will return a copy of the Range object itself.
    • The deleteContents method deletes the entire contents of a Range object.
    • The detach method retrieves the current object from the DOM so that it can no longer be referenced.
    • The insertNode method takes as a single argument a link to a node (or document fragment) and inserts it into the contents of the Range object at the starting point.
    • The extractContents method cuts out the contents of a Range object and returns a link to the received fragment of the document.
    • The surroundContents method puts all the contents of the current Range object in the new parent element, the reference to which is taken as the only argument.
    • The compareBoundaryPoints method is used to compare boundary points.


    To distract and consolidate knowledge, we will solve a small problem. Find the phrase in the text node and highlight it with a blue background. An example in action . Experiment with the rest of the properties and methods yourself. Let's move on to the implementation of range in MSIE.


      Найдем в этом тексте слово "бабуля" и подсветим его синим фоном


    $domRange.highlight = function( text ) {
      // Получим текстовый узел
      var root = document.getElementById('ex3').firstChild;
      // и его содержимое
      var content = root.nodeValue;
      // Проверим есть ли совпадения с переданным текстом
      if ( content.indexOf( text ) != -1 ) {
        if ( document.createRange ) {
          // Если есть совпадение, и браузер поддерживает Range, создаем объект
          var rng = document.createRange();
          // Ставим верхнюю границу по индексу совпадения,
          rng.setStart( root, content.indexOf( text ) );
          // а нижнюю по индексу + длина текста
          rng.setEnd( root, content.indexOf( text ) + text.length );
          // Создаем спан с синим фоном
          var highlightDiv = document.createElement('span');
          highlightDiv.style.backgroundColor = 'blue';
          // Обернем наш Range в спан
           rng.surroundContents( highlightDiv );
        } else
          alert('Вероятно, у вас Internet Explorer, смотрите реализацию TextRange ниже');
      } else
        alert('Совпадений не найдено');
    }







    1.2. TextrangeInternet Explorer supported


    The TextRange object in the MSIE implementation is a text range of zero or more lengths. This range also has its own borders, which you can “move” to an integer number of text units: character (character), word (word), sentence (sentence). That is, you can take and shift the border by 2 (5, 8, etc.) words (symbol, sentence) to the right (left). At the same time, the object stores data about the HTML content of the range and there are methods for interacting with the DOM.

    The TextRange object is created using the createTextRange method , which can be called in the context of the following DOM elements:

    BODY, BUTTON, INPUT type=button, INPUT type=hidden, INPUT type=password, INPUT type=reset, INPUT type=submit, INPUT type=text, TEXTAREA

    A simple example with a button: Example in action . Consider the properties and methods of the TextRange object (not all, only the most necessary):



    $ieTextRange = {
      create : function() {
        // Найдем кнопку
        var button = document.getElementById('buttonId');
        // Если мы в ИЕ
        if ( button.createTextRange && button.createTextRange() != undefined ) {
          // Создаем TextRange
          var rng = button.createTextRange();
          // И вернем текстовое содержимое полученного объекта
          return rng.text;
        } else
          return 'Вероятно, у вас не IE (поздравляем!), смотрите реализацию Range выше';
      }
    }





    • The boundingWidth (boundingHeight) property will return the width (height) that the TextRange object occupies in pixels.
    • The boundingTop ( boundingLeft ) property returns the Y (X) coordinate of the upper left corner of the test area relative to the document window.
    • The htmlText property will return the HTML content of the object.
    • The text property will return the text content of the object (see the example above).
    • The offsetTop ( offsetLeft ) property will return the Y (X) coordinate of the upper left corner of the test area relative to the ancestor.

    • The collapse method combines the endpoints of a range. Takes a Boolean value as the only argument (true & mdash to merge at the top, false & mdash at the bottom). True by default.
    • The duplicate method clones an existing text range, returning a new one, exactly the same.
    • The expand method expands the current text range to a unit of text passed as the only text argument: Returns true (false) in case of success (failure).
      'character' — символ
      'word' — слово
      'sentence' — предложение
      'textedit' — сворачивает до первоначального диапазона.


    • The findText method searches in the range of coincidence with the text string passed as the first argument (case insensitive). If a match is found, then the boundaries of the range collapse to it. As a second (optional) argument, you can pass an integer indicating the number of characters from the top point at which to search. Further, as arguments, you can list INT flags that you are unlikely to need .
    • If the call is successful, the getBookmark method returns a string by which it will be possible to restore the current state of the text range using the moveToBookmark method .
    • The inRange method takes another TextRange object as an argument and checks if its text range is in the range of the context object. Returns a boolean value.
    • The isEqual method checks whether the current TextRange object is identical to the one passed as an argument. Returns a boolean value.
    • The move (sUnit [, iCount]) method collapses the current range to zero length and moves by one the text passed as the first argument (character | word | sentence | textedit). As the second (optional) argument, you can pass the number of units by which to move the range.
    • The moveEnd ( moveStart ) method, similarly to the move method, moves the upper (lower) border of the range by a unit of text, the number of which can also be specified with an optional second parameter.
    • The moveToElementText method takes as an argument a reference to the DOM element and sets the bounds of the range of the TextRange object to the borders of the received element.
    • The moveToPoint method takes as two required arguments the X and Y coordinates (in pixels) relative to the upper left corner of the document and transfers the borders of the range there.
    • The parentElement method will return a link to an element that completely contains the range of the TextRange object (or null).
    • The pasteHTML method replaces the HTML content of the current text range with a string passed as a single argument.
    • The select method forms a selection based on the contents of the TextRange object, which we will discuss in more detail below.
    • The setEndPoint method takes as a required argument a text pointer and a link to another TextRange object, setting the range boundary pointer depending on the value. Pointers can be the following: 'StartToEnd', 'StartToStart', 'EndToStart', 'EndToEnd'.


    Commands of the execCommand method are also applicable to TextRange objects .

    To consolidate, we will solve the problem of finding textual content, similar to the one above: An example in action . Experiment with the rest of the properties and methods yourself.


      Найдем в этом тексте слово "бабуля" и подсветим его синим фоном


    $ieTextRange.highlight = function( text ) {
      // Получим ссылку на элемент, в котором будет происходить поиск
      var root = document.getElementById('ex4');
      // Получим значение его текстового потомка
      var content = root.firstChild.nodeValue;
      // Если есть совпадение
      if ( content.indexOf( text ) != -1 ) {
        // и мы в MSIE
        if ( document.body.createTextRange ) {
          // Создадим объект TextRange
          var rng = document.body.createTextRange();
          // Свернем его до root
          rng.moveToElementText( root );
          // Найдем текст и свернем диапазон до него
          if ( rng.findText( text ) )
            // Заменим текстовый фрагмент на span с синим фоном
            rng.pasteHTML( '' + text + '' );
        } else
          alert('Вероятно, у вас не IE (поздравляем!), смотрите реализацию Range выше');
      } else
        alert('Совпадений не найдено');
    }






    2. Selection


    Everyone is familiar with the selection of elements on the page when, holding the left mouse button and moving the cursor, we select the desired fragment. Or hold Shift and click on the keyboard arrows. Or somehow, it doesn’t matter. In this part of the article, we will cross-browser learn how to solve two problems: get custom selection and set your own.

    2.1. Get custom selection


    We already solved this problem at the very beginning of the article in the mix example . Now let's look at the code: All browsers except Internet Explorer support the getSelection method in the window context, which returns an object similar to the Range discussed earlier . This object has an anchor and a focus end point. Points can match. Consider the properties and methods of the Selection object:

    $selection = {
      getText : function() {
        var txt = '';
        if (txt = window.getSelection) // Not IE, используем метод getSelection
          txt = window.getSelection().toString();
        else // IE, используем объект selection
          txt = document.selection.createRange().text;
        return txt;
      }
    }


    Mozilla Firefox supportedChrome supportedOpera supportedSafari supported
    • The anchorNode property will return the container in which the selection begins. I note that the start of the selection is the border from which you started the selection. That is, if you select from right to left, then the right border will be the beginning. This rule works everywhere except for the Opera browser, in which anchorNode will return a link to the node on the left edge of the selection.
    • The anchorOffset property will return the offset to start selection within the anchorNode container.
    • The focusNode and focusOffset properties work similarly for focal points, that is, points for ending selection. Opera has distinguished itself here as well, instead of focusing, returns the node of the right edge of the selection.
    • The rangeCount property returns the number of Range objects that are included in the resulting selection. This property is useful when using the addRange method.

    • The getRangeAt method takes as an argument the index of the Range object and returns the object itself. If rangeCount == 1, then only getRangeAt (0) will work . Thus, we can get a Range object that fully matches the current selection.
    • The collapse method collapses the selection to a point (caret). You can pass the node into which you want to place the caret as the first argument to the method.
    • The extend method takes as arguments the reference to the container and the offset (parentNode, offset), and moves the focal point to this position.
    • The collapseToStart ( collapseToEnd ) method moves the focal (initial) border to the initial (focal) border, thereby minimizing the selection into the caret.
    • The selectAllChildren method takes as a single argument a link to the node and adds all its descendants to the selection.
    • The addRange method takes a Range object as an argument and adds it to the selection. Thus, you can increase the number of Range objects, the number of which the rangeCount property tells us.
    • The removeRange ( removeAllRanges ) method removes the passed (all) Range object from the selection.
    • The toString method will return the text content of the selection.

    Internet Explorer provides its own interface for interacting with selections — the selection object in the document context. The following methods are used to work with this object.Internet Explorer supported
    • The clear method removes the selection along with the content.
    • The createRange method (IMPORTANT! Not to be confused with the createRange DOM method for creating Range objects!) Creates a TextRange object from the selection contents.
    • The empty method removes the selection, but leaves the contents.

    Hopefully now that I’ve become familiar with both implementations of the selections, the code above has become clearer.

    2.1. Setting your own selection


    Let's say you want some text fragment on the page to be highlighted as a user selection. This is necessary for the client implementation of page search and some other tasks. The easiest way to solve this problem is as follows:
    1. Create a Range object (TextRange for IE).
    2. Convert the resulting object to the selection.

    We look at the implementation: An example in action .


      Снова будем выделять бабулю, на этот раз без поиска.


    $selection.set = function() {
      var target = document.getElementById('ex5').getElementsByTagName('span')[0];
      var rng, sel;
      if ( document.createRange ) {
        rng = document.createRange();
        rng.selectNode( target )
        sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange( rng );
      } else {
        var rng = document.body.createTextRange();
        rng.moveToElementText( target );
        rng.select();
      }
    }




    3. Afterword


    Comprehending the API provided during the writing process, the idea came up to create a small library that will teach IE DOM implementations of Range and Selection. Perhaps it even exists, then the author will be grateful to you for the link and saving your own time.

    The API description does not pretend to be complete and accurate, but this is enough for the author for comfortable JavaScript development. You can find the originals in English at the links below.

    Crosspost articles Range, TextRange and Selection from fastcoder.org .

    4. References


    Also popular now: