Is there a means of detecting whether an element appears before or after another element in markup? This is regardless of position in DOM. It could be a child, a sibling, a parent or a parent's parent. This is a general question, so no markup to share.
To clarify - this is in regards to the element's position in markup, not its display position. Now that I think about it my question is a bit strange because if you have element X and element Y then you can have these scenarios.
//in regards to y
<x />
<y /> //:after
<y /> //:before
<x />
<x><y /></x> //not really before or after is it?
Yes, sort of. DOM3 introduced Node.compareDocumentPosition
, which allows you to compare the position of two elements. The functionality isn't very friendly: it involves bitmasks: this is a jQuery plugin that should simplify its use.
This code is only tested on Firefox 9 and the current version of Chromium. Certainly it won't work in old versions of IE.
$.fn.docPosition = function(element) {
if (element.jquery) element = element[0];
var position = this[0].compareDocumentPosition(element);
if (position & 0x04) return 'after';
if (position & 0x02) return 'before';
};
Also, an element that contains another is considered to be before it in the structure.
OK, a little Googling gives me this blog post by John Resig (the creator of jQuery), which includes compatibility with IE <9. (It's a little ugly: it uses two non-standard bits of functionality: contains
and sourceIndex
.) This code should be cross-browser:
$.fn.docPosition = function (element) {
function comparePosition(a, b) {
return a.compareDocumentPosition ?
a.compareDocumentPosition(b) :
a.contains ?
(a != b && a.contains(b) && 16) +
(a != b && b.contains(a) && 8) +
(a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
(a.sourceIndex < b.sourceIndex && 4) +
(a.sourceIndex > b.sourceIndex && 2) :
1)
+ 0 : 0;
}
if (element.jquery) element = element[0];
var position = comparePosition(this[0], element);
if (position & 0x04) return 'after';
if (position & 0x02) return 'before';
};
Summary
Compares the position of the current node against another node in any other document.
UPDATE : This does not work in all browsers but there is a fix for that. Thanks for Alnitak (see answer comments) for providing the link: cross browser compare document position
A brute force approach may be to get all elements, then get the index of each element within the set.
var all = $('*');
var a_index = all.index($('#element_a'));
var b_index = all.index($('#element_b'));
if( a_index < b_index )
alert( 'a is first' );
else
alert( 'b is first' );
For a browser compliant non-jQuery solution, you could do this:
function sortInDocumentOrder( a, b ) {
var all = document.getElementsByTagName('*');
for( var i = 0; i < all.length; ++i ) {
if( all[i] === a )
return [a,b];
else if( all[i] === b )
return [b,a];
}
}
Give it two elements, and it will return them in the document order.
var a = document.getElementById('a');
var b = document.getElementById('b');
var inOrder = sortInDocumentOrder( a, b );
I don't have complete code, but the approach I would take (if Node.compareDocumentPosition
isn't available) is:
.parents()
chain of both elementsThere are tricks you could use to make (1) and (2) simpler by having the DOM do some of the work for you:
var $a = $('#a'); // first element
var $b = $('#b'); // first element
$a.parents().andSelf().addClass('search'); // mark A and all of A's ancestors
var $parent = $b.closest('.search'); // find B's first marked ancestor
You're confusing a few things. The location in markup is the location in the DOM. This line shows your confusion:
<x><y /></x> //not really before or after is it?
Of course y
is after x
by any reasonable definition. You should think of the DOM as a tree, not as characters in a text file.
Now, as for determining position, use Node.compareDocumentPosition
:
node.compareDocumentPosition(otherNode)
The return value is a bitmask with the following values:
DOCUMENT_POSITION_DISCONNECTED = 0x01;
DOCUMENT_POSITION_PRECEDING = 0x02;
DOCUMENT_POSITION_FOLLOWING = 0x04;
DOCUMENT_POSITION_CONTAINS = 0x08;
DOCUMENT_POSITION_CONTAINED_BY = 0x16;
Here is the full example with Node.compareDocumentPosition
/**
* Checks if the node1 is before the node2 in the DOM tree
* It is true if
* + node1 is sibling before node2
* + node1 contains node2
* + node1 is in the subtree that is before node2 or the subtree that contains it
*/
public isNodeBefore(node1: Node, node2: Node): boolean {
// https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
return (node2.compareDocumentPosition(node1) & (Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS)) != 0;
}
This question requires a set of rules on what is considered before and after . It's certainly easy to traverse the elements in body
and see what comes next, but is there any sort of hierarchical precedence? What about repeating elements?
<foo>
<bar>
<foobar />
</bar>
</foo>
<foo>
<baz />
</foo>
<foobar />
Does baz
come before foobar
?
I'd create two functions isBefore
and isAfter
; in this case they'd both be true (doing a depth first lookup).
var el = $('*');
function isBefore(elA, elB){
return ( el.index($(elA).first()) < el.index($(elB).last()) );
}
function isAfter(elA, elB){
return ( el.index($(elA).last()) > el.index($(elB).first()) );
}
isBefore('body', 'head'); // false
isAfter('body', 'head'); // true
function isBefore(a, b){
return a.sourceIndex < b.sourceIndex; }
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.