type CmpItem = [string, number?]; // used internally /** * A simple jQuery plugin to sort table body by a selected column * * @license MIT * @author Dumitru Uzun (DUzun.Me) * @version 0.0.2 */ export default function initTableSortable($: JQueryStatic): TableSortablePlugin { const CLICK_TH_SELECTOR = 'th:not(.nosort),td.sort'; let sort_dir: number; let cmp: CmpFunction; const defaults = { cmp: undefined, }; const tableSortable: TableSortablePlugin = function tableSortable(options?: TableSortableOptions) { options = $.extend({}, defaults, options); // Mark the table as initialized this.addClass('table_sortable'); let $thead = this.children('thead'); let $tbody = this.children('tbody'); let $thr = $thead.children('tr'); $thead.on('click', CLICK_TH_SELECTOR, function () { const $th = $(this); let sortOrder = $th.hasClass('asc') ? -1 : 1; const $ths = $thr.children(CLICK_TH_SELECTOR); $ths.removeClass('asc desc'); if (sortOrder === -1) { $th.removeClass('asc').addClass('desc'); } else { $th.removeClass('desc').addClass('asc'); } let cols = $th.data('_cols_'); if (cols == undefined) { calcCols($thr); cols = $th.data('_cols_'); } const rows = $tbody.find('>tr:has(td)') .get() // get an Array .map((tr) => { const $tds = $(tr).children('td'); const ret = cols.map(function (col) { let val = $tds.eq(col).text().toUpperCase(); // ignore case let ret: CmpItem = [val]; let num = parseFloat(val.trim()); if (!isNaN(num)) ret[1] = num; return ret; }); ret.tr = tr; return ret; }) ; sort_dir = sortOrder > 0 ? 1 : -1; cmp = options.cmp; rows.sort(cmpArr); $tbody.append(rows.map((row) => row.tr)); }); return this; }; // Comparison function for [].sort(), almost like narutal sort function cmpArr(_a: any, _b: any): number { let ret; _a.some((a, idx) => { let b = _b[idx]; if (cmp) return ret = cmp(a[0], b[0]); if (1 in a && 1 in b && (ret = a[1] - b[1])) { return ret; } else { let val1 = a[0]; let val2 = b[0]; if (val1 != val2) { return ret = val1 < val2 ? -1 : 1; } } }); return ret * sort_dir; } // Calc _cols_ indexes, respecting colspan and rowspan in headers function calcCols($thr: JQuery) { let headerMatrix = []; $thr.each((rowIdx, tr) => { $(tr) .children('th,td') .each((colIdx, td) => { //@ts-ignore let { rowSpan, colSpan } = td; const cols = []; const colsDict = {}; while (rowSpan-- > 0) { let _rowIdx = rowIdx + rowSpan; let _colSpan = colSpan; while (_colSpan-- > 0) { let _colIdx = colIdx + _colSpan; let rowMatrix = headerMatrix[_rowIdx] || (headerMatrix[_rowIdx] = []); while (rowMatrix[_colIdx]) ++_colIdx; rowMatrix[_colIdx] = td; if (!(_colIdx in colsDict)) { cols.push(_colIdx); colsDict[_colIdx] = _colIdx; } } } cols.sort(); $(td).data('_cols_', cols); }); }); } tableSortable.defaults = defaults; $.fn.tableSortable = tableSortable; return tableSortable; } // Auto-init in browser when jQuery or Zepto is present if ( typeof window !== 'undefined' ) { const $ = window.jQuery || window.Zepto; if ( $ ) { initTableSortable($); } }