3

For example, if I have the HTML:

<div id="foo">
<p>Some text in a paragraph</p>
<p>More text in a paragraph</p>
</div>

And someone selects from the start of "in" (paragraph 1) to the end of "More" (paragraph 2), I want to get the selection info like:

{
    "object": [div #foo],  /* commonAncestorContainer DOM element of the selection */
    "source": "<div id="foo">\n<p>Some text in a paragraph</p>\n<p>More text in a paragraph</p>\n</div>",  /* outerHTML of the commonAncestorContainer */
    "startOffset": 28,  /* offset of selection starting point in the source code */
    "endOffset": 54  /* offset of selection ending point in the source code */
}

Here are some problems when I attempt to do this:

  1. We can use Range.commonAncestorContainer to get commonAncestorContainer of a range. However how do we get the real commonAncestorContainer if a selection contains multiple ranges?

  2. How to get the startOffset and endOffset of the selection range in the source code fragment?

Danny Lin
  • 2,050
  • 1
  • 20
  • 34
  • 1
    Recursion until common ancestor of 1..n are all the same? What have you tried? – vol7ron Jul 20 '14 at 03:15
  • Note that in regular HTML DOM elements don't preserve position from source... You may need to re-think your requirements if you are trying to write scrip in browser. – Alexei Levenkov Jul 20 '14 at 03:46

2 Answers2

3
  1. You may want to check out a related question on finding common ancestors in stack overflow. When the selection contains multiple ranges, you could use the common ancestor algo to the get the common ancestor of all the range.commonAncestorContainer.

  2. Here is a demo of the code to get the start and end offset within the source. You may want to test and extend it as needed.

    function getPosition(node, child, childOffset) {
        if (!node.contains(child)) {
            return -1;
        }
        var children = node.childNodes;
        var pos = 0;
        for (var i = 0; i< children.length; i++) {
            if (children[i] === child) {
                pos += childOffset;
                break;
            } else if (children[i].contains(child)) {
                pos += getPosition(children[i], child, childOffset);
                break;
            } else if (children[i].nodeName === "#text") {
                pos += children[i].textContent.length;
            } else {
                pos += children[i].outerHTML.length;
            }
        }
        if (node.nodeName !== "#text") {
            pos += node.outerHTML.lastIndexOf(node.innerHTML);
        }
        return pos;
    }
    
Community
  • 1
  • 1
ViRa
  • 714
  • 5
  • 7
  • Thank you very much. It seems to be almost what I'm looking for. I'll do more test and check whether there are further issues. – Danny Lin Jul 20 '14 at 07:27
  • I found a problem: if there are text nodes containing something like " ", we get the wrong range. Since startOffset and endOffset counts raw text " ", while innerHTML uses " ". – Danny Lin Jul 20 '14 at 11:00
  • Hey, I have updated the code to construct the html of the code without using outerHTML. Here is the [demo](http://jsfiddle.net/breWL/3/) for it. Hope this solves your   problem. – ViRa Jul 20 '14 at 13:07
  • The code doesn't work because children[i].getHtml() is wrong. Anyway, currently I made the code work (at least for my purpose). There were several fixes I did, including a textToHtmlOffset function (an ugly solution by creating a new span and a text node with the substring before startOffset and then get the innerHTML), fix of comment nodes (which have no .outerHTML defined), and fix of a case that the child isn't a text node (and the childOffset means the nth child of its parent). I'm still looking for potential issues but generally speaking it works. Thank you for the illustration again. – Danny Lin Jul 20 '14 at 15:58
0

Try this function returning an object of information you need

function getInfo(selector)
{
    var element = $(selector);
    var html = element.clone().wrap('<p>').parent().html();
    var Result = new Object();
    Result.object = "[" + element.prop("tagName") + " " + selector + "]";
    Result.source = html;
    Result.startOffset = $("body").html().indexOf(html);
    Result.endOffset = Result.startOffset + html.length;
    return Result;
}

The argument is the selector so you need to call the function like this:

var info = getInfo("#foo");
Khalid
  • 4,730
  • 5
  • 27
  • 50