6

The Problem

I am trying to figure out the offset of a selection from a particular node with javascript.

Say I have the following HTML

<p>Hi there. This <strong>is blowing my mind</strong> with difficulty.</p>

If I select from blowing to difficulty, it gives me the offset from the #text node inside of the <strong>. I need the string offset from the <p>'s innerHTML and the length of the selection. In this case, the offset would be 26 and the length would be 40.

My first thought was to do something with string offsets, etc. but you could easily have something like

<p> Hi there. This <strong>is awesome</strong>. For real. It <strong>is awesome</strong>.</p>

which would break that method because there are identical nodes. I also need the option to throw out nodes. Say I have something like this

<p>Hi there. <a href="#" rel="inserted">This <strong>is blowing</a> my mind</strong> with difficulty.</p>

I want to throw out an elements with rel="inserted" when I do the calculation. I still want 26 and 40 as the result.

What I'm looking for

The solution needs to be recursive. If there was a <span> with a <strong> in it, it would still need to traverse to the <p>.

The solution needs to remove the length of any element with rel="inserted". The contents are important, but the tags themselves are not. All other tags are important. I'd strongly prefer not to remove any elements from the DOM when I do all of this.

I am using document.getSelection() to get the selection object. This solution only has to work in WebKit. jQuery is an option, but I'd prefer to it without it if possible.

Any ideas would be greatly appreciated.

I have no control over the HTML I doing all of this on.

Sam Soffes
  • 14,831
  • 9
  • 76
  • 80
  • 1
    This is unclear to me. What do you mean by "I need the offset from the `

    `"? Do you mean an offset within the text content of the `

    ` (as represented by `innerText` in IE or `textContent` in other browsers)? Or an offset within a string representation of the HTML content of the `

    `? If it's any of these, I'm interested to know why: if you deal with the DOM as nodes, then the `Range` objects obtained from a selection are perfectly adequate for any purpose.

    – Tim Down Jul 30 '10 at 16:00
  • I have clarified above. The length of the string before the selection of the p's innerHTML. – Sam Soffes Jul 30 '10 at 16:14
  • Maybe I could do something traversing through parent nodes until I get to the

    . When I get there I can follow the path back down using `childNodes` and build the length. There has to be a better way though.

    – Sam Soffes Jul 30 '10 at 16:24
  • It's why you want this particular kind of offset that puzzles me. – Tim Down Jul 30 '10 at 16:28
  • I need to persist the selection in a database and then be able to load it back again easily. Would you recommend another method? – Sam Soffes Jul 30 '10 at 18:31

5 Answers5

4

I think I solved my issue. I ended not calculating the offset like I originally planned. I am storing the "path" from the chunk (aka <p>). Here is the code:

function isChunk(node) {
  if (node == undefined || node == null) {
    return false;
  }
  return node.nodeName == "P";
}

function pathToChunk(node) {
  var components = new Array();

  // While the last component isn't a chunk
  var found = false;
  while (found == false) {
    var childNodes = node.parentNode.childNodes;
    var children = new Array(childNodes.length);
    for (var i = 0; i < childNodes.length; i++) {
      children[i] = childNodes[i];
    }        
    components.unshift(children.indexOf(node));

    if (isChunk(node.parentNode) == true) {
      found = true
    } else {
      node = node.parentNode;
    }
  }

  return components.join("/");
}

function nodeAtPathFromChunk(chunk, path) {
  var components = path.split("/");
  var node = chunk;
  for (i in components) {
    var component = components[i];
    node = node.childNodes[component];
  }
  return node;
}

With all of that, you can do something like this:

var p = document.getElementsByTagName('p')[0];
var piece = nodeAtPathFromChunk(p, "1/0"); // returns desired node
var path = pathToChunk(piece); // returns "1/0"

Now I just need to expand all of that to support the beginning and the end of a selection. This is a great building block though.

Sam Soffes
  • 14,831
  • 9
  • 76
  • 80
3

What does this offset actually mean? An offset within the innerHTML of an element is going to be extremely fragile: any insertion of a new node or change to an attribute of an element preceding the point in the document the offset represents is going to make that offset invalid.

I strongly recommend using the browser's built-in support for this in the form of DOM Range. You can get hold of a range representing the current selection as follows:

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

If you're going to be manipulating the DOM based on this offset that you want, you're best off doing so using nodes instead of string representations of those nodes.

Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • 1
    I agree that Range is the way to go on the Javascript side. The main thing is I need to extract the data and store it in a database, and then be able to recreate the range on subsequent loads. After looking at the Range documentation, I didn't see a good way to do this. Any ideas? That's what I'm trying to solve with all of this. I'm not set on a specific way. – Sam Soffes Jul 30 '10 at 18:49
0

you can use the following java script code:

var text = window.getSelection();
var start = text.anchorOffset;
alert(start);
var end = text.focusOffset - text.anchorOffset;
alert(end);
antyrat
  • 27,479
  • 9
  • 75
  • 76
Mohit kumar
  • 248
  • 3
  • 12
  • this will not work properly, as inside of the node text.anchorOffset will return offset from the start of current node, not from the start of root node where we have our text – Kirk Hammett Jun 17 '15 at 07:13
-1

Just check if your selected element is a paragraph, and if not use something like Prototype's Element.up() method to select the first paragraph parent.

For example:

if(selected_element.nodeName != 'P') {
  parent_paragraph = $(selected_element).up('p');
}

Then just find the difference between the parent_paragraph's text offset and your selected_element's text offset.

  • I'm not sure I follow. How do you see the "text offset" from a give element to its parent. In my example, how would I find the text offset from the first strong to the beginning of the first character in the p. – Sam Soffes Jul 30 '10 at 16:16
-3

Maybe you could use the jQuery selectors to ignore the rel="inserted"?

$('a[rel!=inserted]').doSomething();

http://api.jquery.com/attribute-not-equal-selector/

What code are you using now to select from blowing to difficulty?

  • I'm using `document.getSelection()` to get the selection object. That's a neat idea, but I still need to do stuff with those nodes, I just need to remove them from the calculation. – Sam Soffes Jul 30 '10 at 16:02