How to iterate over every node in a selected range in javascript?
Asked Answered
C

2

18

When implementing a rich text editor in javascript, I need to apply some changes to every text node in selected range. Range object provides interface to get startContainer, endContainer, startOffset, endOffset for the selected range. How can I iterate over every DOM node in between?

var selection = window.getSelection();
var range = selection.getRange(0);
// How can I iterate over every node within the range?
Commutation answered 18/2, 2016 at 8:8 Comment(2)
Does an example on accessing the range DOM get you up to speed?Howitzer
@RogierSpieker That points me in the right direction, thanks!Commutation
L
17

As suggested, you can use NodeIterator to walk inside range.commonAncestorContainer.

Here's a snippet:

var _iterator = document.createNodeIterator(
    range.commonAncestorContainer,
    NodeFilter.SHOW_ALL, // pre-filter
    {
        // custom filter
        acceptNode: function (node) {
            return NodeFilter.FILTER_ACCEPT;
        }
    }
);

var _nodes = [];
while (_iterator.nextNode()) {
    if (_nodes.length === 0 && _iterator.referenceNode !== range.startContainer) continue;
    _nodes.push(_iterator.referenceNode);
    if (_iterator.referenceNode === range.endContainer) break;
}

You should use NodeFilter.SHOW_ALL because your range can contain multiple nodeTypes. If you know what you are selecting, you can check this reference to properly choose NodeFilter.


Edit: I also want to point out document.createTreeWalker().

The key difference is that document.createTreeWalker() allow your acceptNode filter to return both NodeFilter.FILTER_REJECT and NodeFilter.FILTER_SKIP with real differences.

Quote from NodeFilter docs:

FILTER_REJECT:

Value to be returned by the NodeFilter.acceptNode() method when a node should be rejected. For TreeWalker, child nodes are also rejected. For NodeIterator, this flag is synonymous with FILTER_SKIP.

Ps: the NodeFilter.acceptNode() documentation for NodeFilter.FILTER_REJECT is incorrect.

Lallation answered 5/9, 2017 at 13:48 Comment(1)
I might be wrong, but I think not using NodeFilter.SHOW_ALL would potentially result in the first condition always evaluating to false, as referenceNode might never equal startContainer if it was filtered away.Wrestling
S
6

range.commonAncestorContainer will give you the node that encompasses the range. If it gives you a text node, then that's the only node in your range.

If it gives you an element, you can use NodeIterator, or el.querySelectorAll('*') to get the nodes within.

Not all of these will be inside your range, so use range.intersectsNode(el) to confirm.

Sthilaire answered 19/6, 2017 at 0:25 Comment(1)
Range.intersectsNode() has no support on IE (up to and including 11) :( Likewise Selection.containsNode().Departmentalize

© 2022 - 2024 — McMap. All rights reserved.