2

consider the following selected html snippet:

<span>...</span><span>...</span><span>....</span>
.......||||||||||||||||||||||||||||||.......

The second line represents the user selection (the pipes), spanning across several span tags. Using javascript, I'd like to retrieve the (non-text) nodes partially or completely selected (in this example the 3 span tags).

Thanks,

Alex
  • 21
  • 2

3 Answers3

3

Solution: window.getSelection().getRangeAt(0).cloneContents()
Source: somebody’s deleted comment @ https://stackoverflow.com/questions/43542742/find-text-on-page-and-select-it

Basic example

window.getSelection().addRange(new Range());
var dispray = document.querySelectorAll("[id^=dispray]");
var Go = () => {
    var fragm = window.getSelection().getRangeAt(0).cloneContents();
    dispray[0].innerHTML = "";
    dispray[0].appendChild(fragm);
    dispray[1].innerText = dispray[0].innerHTML;
}
#sample {color: red;}
#sample > span {color: green;}
#panel {height: 5em; border: 3px solid #ccc; resize: vertical; overflow-y: scroll;}
#panel > table {width: 100%; height: 100%; /* border-collapse: collapse; */}
#panel > table td {vertical-align: top; padding: 0.5em;}
#panel > table #disprayA {border-right: 1px solid #ccc;}
<p id="sample" onMouseUp="Go()"><span>Lorem ipsum <b>dolor sit</b> amet.⇥</span
> Text node <span>⇤Maecenas <b>porttitor a felis</b> in pharetra.⇥</span><span
>⇤Nulla accumsan auctor est sit amet finibus.</span></p>

<div id="panel"><table><tr>
<td id="disprayA">Copied selection goes here...</td>
<td><code id="disprayB">Source code of copied selection goes here...</code></td>
</tr></table></div>

Complete example – no bare Text nodes

Your “little” requirement that copy of selection contain no bare Text nodes complicates the code greatly.

window.getSelection().addRange(new Range());
var dispray = document.querySelectorAll("[id^=dispray]");
var Go = () => {
    dispray[0].innerHTML = "";
    var r = window.getSelection().getRangeAt(0);
    var fragm = r.cloneContents();
    if ([...fragm.childNodes].some(child => child.nodeType === Node.TEXT_NODE)) {
        /* if selection contains some bare Text nodes... */
        var ancestorElement = r.commonAncestorContainer;
        if (ancestorElement.nodeType === Node.TEXT_NODE)
            ancestorElement = ancestorElement.parentElement;
        var ancestorElement2 = ancestorElement.cloneNode(false);
        ancestorElement2.appendChild(fragm);
        dispray[0].appendChild(ancestorElement2);
    }
    else dispray[0].appendChild(fragm); 
    dispray[1].innerText = dispray[0].innerHTML;
}
#sample {color: red;}
#sample > span {color: green;}
#panel {height: 5em; border: 3px solid #ccc; resize: vertical; overflow-y: scroll;}
#panel > table {width: 100%; height: 100%; /* border-collapse: collapse; */}
#panel > table td {vertical-align: top; padding: 0.5em;}
#panel > table #disprayA {border-right: 1px solid #ccc;}
<p id="sample" onMouseUp="Go()"><span>Lorem ipsum <b>dolor sit</b> amet.⇥</span
> Text node <span>⇤Maecenas <b>porttitor a felis</b> in pharetra.⇥</span><span
>⇤Nulla accumsan auctor est sit amet finibus.</span></p>

<div id="panel"><table><tr>
<td id="disprayA">Copied selection goes here...</td>
<td><code id="disprayB">Source code of copied selection goes here...</code></td>
</tr></table></div>
Community
  • 1
  • 1
7vujy0f0hy
  • 8,741
  • 1
  • 28
  • 33
2

To do this cross browser (including IE) and quite conveniently, you can use my Rangy library, which provides extended DOM Range and Selection objects in all browsers. The code would look something like:

var sel = rangy.getSelection();
if (sel.rangeCount) {
    var range = sel.getRangeAt(0);
    var selectedElements = range.getNodes([1]); // [1] is an array of node types
    alert("Found " + selectedElements.length + " selected elements");
}
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • 1
    When I do this, I get a "text" object, which isn't what I'd expect. I was expecting to see the div or span that *contains* the text. I can get the parentNode of it, and that's the node i was expecting. Will this always be the case that the node is a "text" object who's parentNode is the dom element containg the text directly? – B T Feb 20 '15 at 08:27
  • @BT: You should only get elements from the above code. In general, the boundary point of the selection can lie within an element or a text node. – Tim Down Feb 20 '15 at 12:25
0

Unfortunately, there is no instant solution for that problem. To solve this you will need to use var sel = window.getSelection() to get an instance of a Selection object which represents the selected elements. From there, you can use sel.anchorNode() to get the element where the selection starts and sel.focusNode() to get the element where it ends. Keep in mind, these will be instances of TextNode and will actually be children of the span that you want.

You will have to come up with a way to determine the shortest path between the two nodes (anchor and focus) which will give you all the information you need.

EDIT: Oh, btw, this is the mozilla/webkit implementation. IE is a completely different beast. You will need to use var sel = document.selection. Check out How can I get the DOM element which contains the current selection? for more information on how to do this in a cross browser way

Community
  • 1
  • 1
jordancpaul
  • 2,954
  • 1
  • 18
  • 27