Source: components/table-sort.js

;(function () {
    'use strict';

    /**
     * @namespace
     */
    sh.libs.tableSort = (function($, moment){

        var name = 'tableSort';

        /**
         * Compare strings
         * @private
         * @memberof sh.libs.tableSort
         * @param {object} a
         * @param {object} b
         * @return {number}
         */
        function compareString(a, b){
            if(a.value < b.value) return -1;
            if(a.value > b.value) return 1;
            return 0;
        }

        /**
         * Compare numbers
         * @private
         * @memberof sh.libs.tableSort
         * @param {object} a
         * @param {object} b
         * @return {number}
         */
        function compareNumbers(a, b){
            return a.value - b.value;
        }

        /**
         * Compare dates
         * @private
         * @memberof sh.libs.tableSort
         * @param {external:moment} a
         * @param {external:moment} b
         * @return {number}
         */
        function compareDate(a, b){
            return a.value.isAfter(b.value);
        }

        /**
         * Return value from element as number
         * @param {jQuery|string} el The element from where to read value
         * @return {number}
         */
        function getNumber(el){
            return parseInt( $(el).text().toLowerCase() );
        }

        /**
         * Return value from element as text
         * @param {jQuery|string} el The element from where to read value
         * @return {string}
         */
        function getString(el){
            return $(el).text().toLowerCase();
        }

        /**
         * Return value from element as moment.js date
         * @param {jQuery|string} el The element from where to read value
         * @return {external:moment}
         */
        function getDate(el){
            return moment( $(el).text().toLowerCase() );
        }

        /**
         * Get the data from the column to sort
         * @private
         * @memberof sh.libs.tableSort
         * @param {external:jQuery|string} id   Selector or jQuery object for table
         * @param {object} options
         * @param {number} options.index=0      Index of column to sort
         * @param {string} options.type=number  Type of data to sort. Accepts 'number', 'string' or 'date'
         * @return {array} Returns array of objects where value is the value to sort after and target is the row
         */
        function getSortable(id, options){
            var $table = $(id);
            var data = [];

            $table.find('tbody tr').each(function(x, row){
                var $cell = $(row).find('td:nth-child(' + (options.index + 1) + ')');
                var item = {};

                if(options.type == 'string'){
                    item.value = getString($cell);
                } else if(options.type == 'date'){
                    item.value = getDate($cell);
                } else {
                    item.value = getNumber($cell);
                }

                item.target = $(row);
                data.push(item);
            });

            return data;
        }

        /**
         * Sort table by a given column
         * @public
         * @memberof sh.libs.tableSort
         * @param {external:jQuery|string} id   Selector or jQuery object for table
         * @param {object} options
         * @param {number} options.index=0      Index of column to sort
         * @param {string} options.sort=desc    Order of sort. Accepts 'asc' or 'desc'
         * @param {string} options.type=number  Type of data to sort. Accepts 'number', 'string' or 'date'
         * @return {jQuery}
         */
        function sortBy(id, options){
            var $table = $(id);

            var defaults = {
                index : 0,
                sort : 'desc',
                type : 'number'
            };
            options = $.extend({}, defaults, options);

            var data = getSortable(id, options);

            if(options.type == 'string'){
                data.sort(compareString);
            } else if(options.type == 'date'){
                data.sort(compareDate);
            } else {
                data.sort(compareNumbers);
            }

            if(options.sort == 'desc'){
                data.reverse();
            }

            var $tbody = $('<tbody></tbody>');

            $.each(data, function(k, v){
                $tbody.append(v.target);
            });

            $table.find('tbody').replaceWith($tbody);

            return $table;
        }

        function init(){
            $('table').each(function(i, table){
                if( !sh.utils.isInitialized(table, name) ){
                    $(table).find('thead td a[data-sortable]').each(function(i, el){
                        $(el).on('click', function(event){
                            var $el = $(event.currentTarget);
                            var params = {};

                            $(table).find('thead td a[data-sortable]').removeClass('active');
                            $el.toggleClass('asc desc').addClass('active');

                            if( $el.hasClass('asc') ){ params.sort = 'asc'; }
                            if( $el.attr('data-sortable').length !== 0 ){ params.type = $el.attr('data-sortable'); }
                            params.index = $el.parent().index();

                            sortBy(table, params);
                        });
                    });
                    sh.utils.initialized(table, name);
                }
            });
        }

        return {
            init: init,
            reflow: init
        };

    })(jQuery, moment);

})();