JavaScript: how to sort DOM elements with data attributes

JavaScript: how to sort DOM elements with data attributes

In this tutorial we're going to see how to sort DOM elements with the aid of data attributes in JavaScript.

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']
        });
    })();

Demo

JavaScript: sort elements