Sort an array containing DOM elements according to their position in the DOM

Context

I structured the jQuery plugin that I am working on now, so that I store DOM elements in an array, mainly in order to store additional information next to these elements without the need to use data() not so fast.

This array looks like this:

 [ { element: DOMElement3, additionalData1: …, additionalData2: … }, { element: DOMElement1, additionalData1: …, additionalData2: … }, { element: DOMElement2, additionalData1: …, additionalData2: … }, ] 

The way this plugin works is stopping me from dragging these elements into an array in a predictable order, which means that DOMElement3 may actually end up with an index below DOMElement2 .

However, I need these array elements to sort in the same order as the DOM elements that they contain are displayed in the DOM. The previous sample array, after sorting, will look like this:

 [ { element: DOMElement1, additionalData1: …, additionalData2: … }, { element: DOMElement2, additionalData1: …, additionalData2: … }, { element: DOMElement3, additionalData1: …, additionalData2: … }, ] 

This, of course, if DOMElement1 appears before DOMElement2 in the DOM and DOMElement2 before DOMElement3 .

Discarded solution

The jQuery add() method returns a set of DOM elements in the same order as in the DOM. I could use this, but the requirement is that I work with the jQuery collection, which means that I will have to reorganize a huge piece of the above plugin to use a different storage format. Therefore, I consider this a decision of last resort.

Another solution?

I would suggest that map() and a kind of global DOM index bound to each DOM element would do the trick, but there isn’t such a “global DOM index”.

What approach can you come up with? (When writing code, both vanilla JS and jQuery are welcome.)

+7
javascript jquery dom arrays html
source share
4 answers

There is a very useful compareDocumentPosition function that returns a number based on where the two elements are relative to each other. You can use this in your .sort :

 yourArray.sort(function(a,b) { if( a === b) return 0; if( !a.compareDocumentPosition) { // support for IE8 and below return a.sourceIndex - b.sourceIndex; } if( a.compareDocumentPosition(b) & 2) { // b comes before a return 1; } return -1; }); 
+11
source share

You do not need to do a lot of refactoring to take advantage of jQuery sorting. You can use it as a temporary sorting mechanism. Out of the cuff here:

 function putInDocumentOrder(a) { var elements; // Get elements in order and remember the index of // the entry in `a` elements = $().add(a.map(function(entry, index){ entry.element.__index = index; return entry.element; })); // Build array of entries in element order a = elements.map(function(){ return a[this.__index]; }).get(); return a; } 

This requires an extension, but it does the job. Living example

This has the advantage that it works in all browsers supported by jQuery, and not on your case when you have to deal with extreme cases (for example, IE8 that does not support compareDocumentPosition ).

+1
source share

Until I suggest you go with this approach, using jquery you can get a "global DOM index":

 var $all = $("*"); // use index to get your element index within the entire DOM $all.index($YOUR_EL); 
+1
source share

If you can assume that the visible location on the page is the same, this could be a relatively quick fix:

 function compareByVisibleLocation(a, b) { if (a.offsetTop > b.offsetTop) { return 1; } else if (a.offsetTop < b.offsetTop) { return -1; } if (a.offsetLeft > b.offsetLeft) { return 1; } else if (a.offsetLeft < b.offsetLeft) { return -1; } else { return 0; } } nodes.sort(compareByVisibleLocation); 

Otherwise, you can use querySelectorAll() to quickly create an index (this is a preliminary first-order workaround), or by explicitly marking the nodes that you want to index using the indexed-node classname.

 var nodes = document.querySelectorAll('.indexed-node'); 

Or, capturing all of one node type if this works:

 var nodes = document.querySelectorAll('div'); 

Or just capturing all the nodes:

 var nodes = document.querySelectorAll('*'); 

And then adding an index attribute to each node:

 for (var i = 0; i < nodes.length; i++) { nodes[i].__DomIndex = i; } 

You can sort it as follows:

 var nodes = yourGetMethod().sort(function(a,b) { if (a === b) { return 0; } else if (a.__DomIndex > b.__DomIndex) { return 1; } else { return -1; } }); 

Should work before IE8.

0
source share

All Articles