tags:

views:

374

answers:

1

I want to get all of the elements in a user highlighted area. The problem is that I don't know how to traverse to different parts of the DOM, when the elements are on a different part of the tree. My code so far can only get the elements off the startContainer of the selection range. Do I need some sort of recursion? Here is my code:

    getSelectedElementTags:function() 
{
 var range, sel, container;
 sel = content.window.getSelection();
 if (sel.getRangeAt) 
 {
  if (sel.rangeCount > 0) 
  {
   range = sel.getRangeAt(0);    
  }
 } 
 else 
 {  
  range = content.window.createRange();
  range.setStart(sel.anchorNode, sel.anchorOffset);
  range.setEnd(sel.focusNode, sel.focusOffset);
  alert("range created");
 }


 if (range)
 {
  container = range["startContainer"];
  var elms = container.parentNode.getElementsByTagName("*");   
  elmlist = "parent: "+container.parentNode.tagName + " (" + elms.length + ")\n";
  for (i in elms)
  {
   if (elms[i].tagName != null)
   {
    elmlist += elms[i].tagName+"\n";
   }
  }

  alert(elmlist);
 }
},
A: 

You could use a TreeWalker using document.createTreeWalker. An example is below. It lists all elements that are partially or fully selected. You can easily change the behaviour by modifying the parameters passed to document.createTreeWalker.

Note that in Firefox you don't need to check for the existence of the getRangeAt method of a selection. This check is only required for older versions of WebKit.

Edit Fixed as per comments below.

function rangeIntersectsNode(range, node) {
    var nodeRange;
    if (range.intersectsNode) {
        return range.intersectsNode(node);
    } else {
        nodeRange = node.ownerDocument.createRange();
        try {
            nodeRange.selectNode(node);
        } catch (e) {
            nodeRange.selectNodeContents(node);
        }

        return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 &&
            range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
    }
}

function getSelectedElementTags(win) {
    var range, sel, elmlist, treeWalker, containerElement;
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
        range = sel.getRangeAt(0);
    }

    if (range) {
        containerElement = range.commonAncestorContainer;
        if (containerElement.nodeType != 1) {
            containerElement = containerElement.parentNode;
        }

        treeWalker = win.document.createTreeWalker(
            containerElement,
            NodeFilter.SHOW_ELEMENT,
            function(node) { return rangeIntersectsNode(range, node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; },
            false
        );

        elmlist = [treeWalker.currentNode];
        while (treeWalker.nextNode()) {
            elmlist.push(treeWalker.currentNode);
        }

        console.log(elmlist);
    }
}

<input type="button" onclick="getSelectedElementTags(window)" value="Get selected elements">
Tim Down
Thanks alot Tim and it works great. Just one small problem... when one element is selected, the list is empty.
John Sheares
Not sure if this is the best way, but I solved it by testing if the startContainer and endContainer of the range were the same. If they were the same, I would skip doing the treewalker and just get the parentNode of the startContainer.
John Sheares
You're right. There were two problems: first, I'd forgotten to include the first node in the TreeWalker in the list, and second that if the whole selection was contained within a single text node, no elements would be returned. Both now fixed in the answer.
Tim Down
Tim, it works perfectly now. Thanks again!
John Sheares