In this tutorial we're going to see how to sort DOM elements with the aid of data attributes in JavaScript.
We need to think in terms of data types contained in such HTML5 attributes. The most common data type is the numeric one, namely integers and floats. Even dates can be converted into integers if we consider them as Unix timestamps.
JavaScript provides us with the sort()
method that can also be applied to a NodeList
when we convert it into an actual array through the
Array.from()
method. Then we need to make our comparison by using the dataset
object attached to every single element
within our list.
Once this array of DOM elements has been properly sorted, we only need to update the DOM structure by looping through the elements and invoking
the appendChild()
method of their parent.
If the data attributes are not already attached to the page elements, we need to insert such attributes by inspecting the value of each element's text node and assigning to such DOM element a key/value pair made up of the data type and its actual value.
We can define the following class:
'use strict';
class Sorter {
constructor({ wrapper, sorters, elements, skipIndex, dataMap, keyMap }) {
this.wrapper = wrapper;
this.sorters = sorters;
this.elements = elements;
this.skipIndex = skipIndex;
this.dataMap = dataMap;
this.keyMap = keyMap;
this.setUpElements();
this.sort();
}
setDataType(element, type, key) {
let text = element.innerText;
let value = text;
switch (type) {
case 'date':
value = Date.parse(text);
break;
case 'float':
value = value.replace(/[^0-9\.]+/g, '');
break;
default:
break;
}
element.parentNode.setAttribute('data-' + key, value);
}
sort() {
const self = this;
self.sorters.forEach(srtr => {
let ascBtn = srtr.querySelector('[data-sort="asc"]');
let descBtn = srtr.querySelector('[data-sort="desc"]');
ascBtn.addEventListener('click', e => {
e.preventDefault();
self.sortElements(self.wrapper.querySelectorAll('tbody tr'), ascBtn.dataset.sort, ascBtn.parentNode.dataset.key, ascBtn.parentNode.dataset.type);
}, false);
descBtn.addEventListener('click', e => {
e.preventDefault();
self.sortElements(self.wrapper.querySelectorAll('tbody tr'), descBtn.dataset.sort, descBtn.parentNode.dataset.key, descBtn.parentNode.dataset.type);
}, false);
});
}
setUpElements() {
const self = this;
if(this.elements.length === 0) {
return false;
}
self.sorters.forEach((sorter, i) => {
sorter.setAttribute('data-type', self.dataMap[i]);
sorter.setAttribute('data-key', self.keyMap[i]);
});
self.elements.forEach(element => {
element.querySelectorAll('td').forEach((cell, index) => {
if(index > self.skipIndex) {
cell.classList.add('sortable');
}
});
element.querySelectorAll('.sortable').forEach((sortable, ind) => {
self.setDataType(sortable, self.dataMap[ind], self.keyMap[ind]);
});
});
}
sortElements(elements, order, key, type) {
const comparator = (a, b) => {
let v1 = a.dataset[key];
let v2 = b.dataset[key];
if(type === 'integer' || type === 'date') {
v1 = parseInt(v1, 10);
v2 = parseInt(v2, 10);
}
if(type === 'float') {
v1 = parseFloat(v1);
v2 = parseFloat(v2);
}
if(order === 'asc') {
return v1 - v2;
} else {
return v2 - v1;
}
};
const items = Array.from(elements);
const sorted = items.sort(comparator);
sorted.forEach(el => {
el.parentNode.appendChild(el);
});
}
}
We set a click
event handler on every button placed in the table's headings. sortElements()
sorts elements based on a key and its data type.
Keys are here used to access the correct property within the dataset
object,
while the data type allows us to make the proper conversion from a string to the target data type before we can perform the actual comparison
between values.
We can use our class as follows:
(function () {
const sorter = new Sorter({
wrapper: document.querySelector('.data'),
sorters: document.querySelectorAll('.sort'),
elements: document.querySelectorAll('.data tbody tr'),
skipIndex: 0,
dataMap: ['integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'float', 'date'],
keyMap: ['logicalwidth', 'logicalheight', 'physicalwidth', 'physicalheight', 'ppi', 'scalefactor', 'screendiagonal', 'release']
});
})();