0

I am trying to get all the containers in a selection and add them into an array. So far, I have been able to get only the first container using the following code:

function getSelectedNode()
{
    var containers = [];//need to add containers here so we can later loop on it and do the transformations
    if (document.selection)
        return document.selection.createRange().parentElement();
    else
    {
        var selection = window.getSelection();
        if (selection.rangeCount > 0)
            return selection.getRangeAt(0).startContainer.parentNode;
    }
}

So if I had:

<p>
   <b>Here's some more content</b>. 
   <span style="background-color: #ffcccc">Highlight some</span> 
   and press the button. Press the other button to remove all highlights
</p>

and I selected this part of the text: "Here's some more content Highlight" Once I use the container returned by getSelectedNode() and do some transformation on it only "Here's some more content" gets affected correctly and not "Highlight". So is there a way to make it get all containers and not just the first one?

Note: I was also previously looking at this link: How can I get the DOM element which contains the current selection? and someone even commented: "This solution doesn't work for all cases. If you try to select more than one tag, all the subsequent tags except the first one will be ignored."

Sara Kat
  • 378
  • 2
  • 19

3 Answers3

1

Use Range.commonAncestorContainer and Selection.containsNode:

function getSelectedNode()
{
    var containers = [];//need to add containers here so we can later loop on it and do the transformations
    if (document.selection)
        return document.selection.createRange().parentElement();
    else
    {
        var selection = window.getSelection();
        if (selection.rangeCount > 0) {
            var range = selection.getRangeAt(0);
            if (range.startContainer === range.endContainer) {
              containers.push(range.startContainer);
            } else {              
              var children = range.commonAncestorContainer.children;
              containers = Array.from(children || []).filter(node => selection.containsNode(node, true));
            }
        }
    }
  return containers;
}

In your case, all possible "containers" are siblings that have no children, and we are selecting using a mouse or keyboard. In this case, we only have to consider two possibilities: you've selected a single node, or you've selected sibling nodes.

However, if your HTML were more complicated and you considered the possibility of scripts creating multiple selections, we'd have need a different solution. You would have to go through each node in the DOM, looking for ones that were part of something selection.

  • Nice but how can I get the children of interest and disregard the others ? – Sara Kat Sep 19 '19 at 23:01
  • Almost, let's say I had

    content 1 content 2 content 3

    and now what i do is select content 1 only, the code you provided above will unhighlight both content 1 and 3 when we only want to unhighlight 1
    – Sara Kat Sep 19 '19 at 23:16
  • OK, I see what you mean. I changed my answer once more. – Michael Smith Sep 20 '19 at 00:21
0

Maybee i am blondie and old school but if i have to fill a array i use a for next loop and something called push to fill the array. That might not be cool but usually works. I can not see any loop or pushing. So there will be only one element.`

other code 
...
if (selection.rangeCount > 0)
 for (var i;i<selection.rangeCount;i++){
 var  x= selection.getRangeAt(i).startContainer.parentNode ; //make a var
   containers.push(x);//push var to array
 }
 return  containers ;
}`
Thomas Ludewig
  • 696
  • 9
  • 17
  • It means there is just 1 container, See at selection.rangeCount.I guess something with your feeding part is buggy. As usually i suggest instead to uses youngsternerdyi doitallinonelinecauseiamyoungcoolandstupid separate the tasks in single lines. . Split such monster up ! -> x= selection.getRangeAt(i).startContainer.parentNode . Or at least track it down in the debugger. I do not know what it should hold inside and if there. Very often by such constructs sooner or later one component fails. Hard to track down. – Thomas Ludewig Sep 20 '19 at 00:30
0

It seems that you wan't to unhighlight the selected text, it seems easer to go through the highlighted portions and see if they are part of the selection, here is an example:

document.addEventListener('mouseup', event => {
  const sel = document.getSelection();
  if (!sel.isCollapsed) {
    const elms = [...document.querySelectorAll('.highlighted')];
    const selectedElms = elms.filter(e => sel.containsNode(e, true));
    if (selectedElms.length) {
      selectedElms.forEach(e => {
        let prev = e.nextSibling;
        [...e.childNodes].forEach(child => e.parentElement.insertBefore(child, e));
        e.remove();
      });
      sel.empty();
    }
  }
});
.highlighted {
  background-color: #ffcccc
}
<p>
   <b>Here's <span class="highlighted">Highlight <b>some</b></span>  some more content</b>. 
   <span class="highlighted">Highlight some</span> 
   and press the button. Press the <span class="highlighted">Highlight some</span> other button to remove all highlights
</p>

Because I've used true as the second parameter to containsNode(...), this example will unhighlight the elements that are only partially selected.

Titus
  • 22,031
  • 1
  • 23
  • 33