Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rebuilding a Range with a Node, startOffset and endOffset

I am trying to rebuild a Range() object on a clients browser using websockets.

https://jsfiddle.net/k36goyec/

First I am getting the Range object in my browser and the Node that the range starts in:

var range = window.getSelection().getRangeAt(0);
var node  = range.startContainer

I am passing three parameters over websockets to the range builder function on the clients browser.

var text        = node.parentNode.textContent;
var startOffset = range.startOffset
var endOffset   = range.endOffset

this data is passed to my buildRange function:

/**
 *
 */
buildRange: function(text, startOffset, endOffset){
    var node = this.getNodeByText(text); // get the Node by its contents

    var range = document.createRange();
    range.setStart(node, startOffset);
    range.setEnd(node, endOffset);

    span = document.createElement('span');
    span.style.backgroundColor = this.color;
    $(span).addClass('hl');
    range.surroundContents(span);
},

As you can see below, I am getting the node on the clients browser by looping through all the elements on the page and comparing its content with the text:

/**
 *
 */
getNodeByText: function(text){
    var all = document.getElementsByTagName("*");
    for (var i = 0; i < all.length; i++) {
        if (all[i].textContent === text) {
            return all[i];
        }

    }
},

I am using setStart() and setEnd() to set the range of my selection on the node.

Problems!

The range.startOffset/endOffset spec says the following:

If the startNode is a Node of type Text, Comment, or CDATASection, then startOffset is the number of characters from the start of startNode. For other Node types, startOffset is the number of child nodes between the start of the startNode.

When I select a range of text I get the following error:

IndexSizeError: Index or size is negative or greater than the allowed amount

This is because I am passing in an offset of like 0, 10 (10 characters selected) but the node is an element node not a text node.

I just can't seem to reliably get the text node, I can only get the element node itself...

Q:

How can I reliably rebuild a Range with the node and the offsets?

like image 890
Jethro Hazelhurst Avatar asked Mar 11 '17 13:03

Jethro Hazelhurst


1 Answers

Others have pointed out already that the immediate problem you are facing is that when you save the range data, you get an offset into a text node but when you try to recreate the range you use the offset as an index into an element, and it crashes. If you fix this you'll have fixed your immediate problem.

Your overall approach, however, is brittle.

Consider a document with this:

<p>Farmer John has a thousand <b>goats</b></p>
<p>and his <b>goats</b> ate all his oats.</p>

Some problems, off the top of my head:

  1. If you select the 2nd "goats", your algorithm will recreate the range on the first "goats" because it only looks for an element that has the same text as the original range.

  2. If you select a range that start with "and" in the 2nd line and ends with "ate", the offsets that you get from your origin range are indexing in two different text nodes: one text node that contains the text and his and a second text node that contains the text ate all his oats. Your algorithm assumes that there's only one note to recover at the destination. There may be more than one.

    The same problem happens if you have a selection that starts in one of the bolded words and ends outside.

To have something robust, you need to perform a serialization of the range at the origin that records enough information to ensure a exact deserialization at the destination.

There are many ways to do it. One way could be to ensure that the same DOM structure is present at the origin and destination, and use the serialization module of the Rangy library to perform the serialization and deserialization. If you can ensure the same DOM structure at both ends, this is what I'd use.

If you cannot ensure the same DOM structure at both ends, then you have to roll your own. You have to identify reference points that are the same on both ends and identify the start node/offset and end node/offset of the range relative to those reference points.

like image 96
Louis Avatar answered Oct 14 '22 06:10

Louis