I have some HTML that can contain <span>
s with a specific class (let's call it marker
). These spans can be anywhere in the document under a particular <div>
- as direct children, or nested arbitrarily deeply in other nodes.
I then have a particular piece of text selected by the use (so I can use a window.getSelection()
call to find the anchor node from Selection.anchorNode
). What I want to find out, using Javascript, and jQuery as needed, is the last marker
<span>
to occur in the documents before that selection. For example:
<div class="container">
<div>
<div>
<span>Some text<span class="marker">Marker 1</span></span>
</div>
<div>
<div>
<span>Foo</span>
<span>THIS IS THE SELECTION ANCHOR NODE</span>
</div>
<span class="marker">Marker 2</span>
</div>
</div><!-- end of container -->
would find Marker 1
, even though they are "cousins".
Is there a "standard" approach to determining the relative "linear" positions of an element in the DOM so I can decide if one element is "before" the other?
I am not concerned with the position on the page (x, y), so things like CSS order
do not matter.
Things I have thought of, but seem suboptimal:
- traversing the parents of each
.marker
(and the selection span) using[closest()][2]
and constructing some kind of lexicographic ordering of nodes, which seems expensive and error-prone - traversing parents of
.markers
and storing lists of the spans found within
Both of these seem like they need a lot of book-keeping and manual DOM traversal for something that sounds like the DOM already knows (since the document has its specific order).
CodePudding user response:
If I understand you correctly, this should get you there:
let xpath = '(//span[@class="marker"][not(.//preceding::*[contains(.,"SELECTION ANCHOR")])])[last()]',
result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
console.log(result.textContent);
CodePudding user response:
You can use the Node.prototype.compareDocumentPosition
method to find out if an element is prior to an other one:
const anchor = document.getElementById("anchor");
const markers = document.querySelectorAll(".marker"); // get all the markers in DOM order
const previous = [...markers].filter( // get only the markers before
(elem) => anchor.compareDocumentPosition(elem) === Node.DOCUMENT_POSITION_PRECEDING
).pop(); // get the last one
console.log( previous );
<div class="container">
<div>
<div>
<span>Some text<span class="marker">Marker 0</span></span>
<span>Some text<span class="marker">Marker 1</span></span>
</div>
<div>
<div>
<span>Foo</span>
<span id="anchor">THIS IS THE SELECTION ANCHOR NODE</span>
</div>
<span class="marker">Marker 2</span>
</div>
</div><!-- end of container -->