/**
* adding sorting ability to HTML tables with Bootstrap styling
* @summary HTML tables sorting ability
* @version 2.0.1
* @requires tinysort, moment.js, jQuery
* @license MIT
* @author Matus Brlit (drvic10k)
* @copyright Matus Brlit (drvic10k), bootstrap-sortable contributors
*/
/**
* TinySort is a small script that sorts HTML elements. It sorts by text- or attribute value, or by that of one of it's children.
* @summary A nodeElement sorting script.
* @version 2.3.6
* @license MIT
* @author Ron Valstar
* @copyright Ron Valstar
* @namespace tinysort
*/
!function (e, t) { "use strict"; function r() { return t } "function" == typeof define && define.amd ? define("tinysort", r) : e.tinysort = t }(this, function () { "use strict"; function e(e, n) { function s() { 0 === arguments.length ? v({}) : t(arguments, function (e) { v(x(e) ? { selector: e } : e) }), d = $.length } function v(e) { var t = !!e.selector, n = t && ":" === e.selector[0], o = r(e || {}, m); $.push(r({ hasSelector: t, hasAttr: !(o.attr === l || "" === o.attr), hasData: o.data !== l, hasFilter: n, sortReturnNumber: "asc" === o.order ? 1 : -1 }, o)) } function S() { t(e, function (e, t) { M ? M !== e.parentNode && (k = !1) : M = e.parentNode; var r = $[0], n = r.hasFilter, o = r.selector, a = !o || n && e.matchesSelector(o) || o && e.querySelector(o), l = a ? R : V, s = { elm: e, pos: t, posn: l.length }; B.push(s), l.push(s) }), D = R.slice(0) } function y(e, t, r) { for (var n = r(e.toString()), o = r(t.toString()), a = 0; n[a] && o[a]; a++) if (n[a] !== o[a]) { var l = Number(n[a]), s = Number(o[a]); return l == n[a] && s == o[a] ? l - s : n[a] > o[a] ? 1 : -1 } return n.length - o.length } function N(e) { for (var t, r, n = [], o = 0, a = -1, l = 0; t = (r = e.charAt(o++)).charCodeAt(0) ;) { var s = 46 == t || t >= 48 && 57 >= t; s !== l && (n[++a] = "", l = s), n[a] += r } return n } function C(e, r) { var n = 0; for (0 !== p && (p = 0) ; 0 === n && d > p;) { var l = $[p], s = l.ignoreDashes ? f : u; if (t(h, function (e) { var t = e.prepare; t && t(l) }), l.sortFunction) n = l.sortFunction(e, r); else if ("rand" == l.order) n = Math.random() < .5 ? 1 : -1; else { var c = a, g = w(e, l), m = w(r, l), v = "" === g || g === o, S = "" === m || m === o; if (g === m) n = 0; else if (l.emptyEnd && (v || S)) n = v && S ? 0 : v ? 1 : -1; else { if (!l.forceStrings) { var C = x(g) ? g && g.match(s) : a, b = x(m) ? m && m.match(s) : a; if (C && b) { var A = g.substr(0, g.length - C[0].length), F = m.substr(0, m.length - b[0].length); A == F && (c = !a, g = i(C[0]), m = i(b[0])) } } n = g === o || m === o ? 0 : l.natural && (isNaN(g) || isNaN(m)) ? y(g, m, N) : m > g ? -1 : g > m ? 1 : 0 } } t(h, function (e) { var t = e.sort; t && (n = t(l, c, g, m, n)) }), n *= l.sortReturnNumber, 0 === n && p++ } return 0 === n && (n = e.pos > r.pos ? 1 : -1), n } function b() { var e = R.length === B.length; if (k && e) O ? R.forEach(function (e, t) { e.elm.style.order = t }) : M ? M.appendChild(A()) : console.warn("parentNode has been removed"); else { var t = $[0], r = t.place, n = "org" === r, o = "start" === r, a = "end" === r, l = "first" === r, s = "last" === r; if (n) R.forEach(F), R.forEach(function (e, t) { E(D[t], e.elm) }); else if (o || a) { var c = D[o ? 0 : D.length - 1], i = c && c.elm.parentNode, u = i && (o && i.firstChild || i.lastChild); u && (u !== c.elm && (c = { elm: u }), F(c), a && i.appendChild(c.ghost), E(c, A())) } else if (l || s) { var f = D[l ? 0 : D.length - 1]; E(F(f), A()) } } } function A() { return R.forEach(function (e) { q.appendChild(e.elm) }), q } function F(e) { var t = e.elm, r = c.createElement("div"); return e.ghost = r, t.parentNode.insertBefore(r, t), e } function E(e, t) { var r = e.ghost, n = r.parentNode; n.insertBefore(t, r), n.removeChild(r), delete e.ghost } function w(e, t) { var r, n = e.elm; return t.selector && (t.hasFilter ? n.matchesSelector(t.selector) || (n = l) : n = n.querySelector(t.selector)), t.hasAttr ? r = n.getAttribute(t.attr) : t.useVal ? r = n.value || n.getAttribute("value") : t.hasData ? r = n.getAttribute("data-" + t.data) : n && (r = n.textContent), x(r) && (t.cases || (r = r.toLowerCase()), r = r.replace(/\s+/g, " ")), null === r && (r = g), r } function x(e) { return "string" == typeof e } x(e) && (e = c.querySelectorAll(e)), 0 === e.length && console.warn("No elements to sort"); var D, M, q = c.createDocumentFragment(), B = [], R = [], V = [], $ = [], k = !0, z = e.length && e[0].parentNode, L = z.rootNode !== document, O = e.length && (n === o || n.useFlex !== !1) && !L && -1 !== getComputedStyle(z, null).display.indexOf("flex"); return s.apply(l, Array.prototype.slice.call(arguments, 1)), S(), R.sort(C), b(), R.map(function (e) { return e.elm }) } function t(e, t) { for (var r, n = e.length, o = n; o--;) r = n - o - 1, t(e[r], r) } function r(e, t, r) { for (var n in t) (r || e[n] === o) && (e[n] = t[n]); return e } function n(e, t, r) { h.push({ prepare: e, sort: t, sortBy: r }) } var o, a = !1, l = null, s = window, c = s.document, i = parseFloat, u = /(-?\d+\.?\d*)\s*$/g, f = /(\d+\.?\d*)\s*$/g, h = [], d = 0, p = 0, g = String.fromCharCode(4095), m = { selector: l, order: "asc", attr: l, data: l, useVal: a, place: "org", returns: a, cases: a, natural: a, forceStrings: a, ignoreDashes: a, sortFunction: l, useFlex: a, emptyEnd: a }; return s.Element && function (e) { e.matchesSelector = e.matchesSelector || e.mozMatchesSelector || e.msMatchesSelector || e.oMatchesSelector || e.webkitMatchesSelector || function (e) { for (var t = this, r = (t.parentNode || t.document).querySelectorAll(e), n = -1; r[++n] && r[n] != t;); return !!r[n] } }(Element.prototype), r(n, { loop: t }), r(e, { plugin: n, defaults: m }) }());
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery', 'tinysort', 'moment'], factory);
} else {
factory(global.jQuery, global.tinysort, global.moment || undefined);
}
})(this, function ($, tinysort, moment) {
var $document = $(document),
signClass,
sortEngine,
emptyEnd;
$.bootstrapSortable = function (options) {
if (options == undefined) {
initialize({});
}
else if (options.constructor === Boolean) {
initialize({ applyLast: options });
}
else if (options.sortingHeader !== undefined) {
sortByColumn(options.sortingHeader);
}
else {
initialize(options);
}
};
function initialize(options) {
// Check if moment.js is available
var momentJsAvailable = (typeof moment !== 'undefined');
// Set class based on sign parameter
signClass = !options.sign ? "arrow" : options.sign;
// Set sorting algorithm
if (options.customSort == 'default')
options.customSort = defaultSortEngine;
sortEngine = options.customSort || sortEngine || defaultSortEngine;
emptyEnd = options.emptyEnd;
// Set attributes needed for sorting
$('table.sortable').each(function () {
var $this = $(this);
var applyLast = (options.applyLast === true);
$this.find('span.sign').remove();
// Add placeholder cells for colspans
$this.find('> thead [colspan]').each(function () {
var colspan = parseFloat($(this).attr('colspan'));
for (var i = 1; i < colspan; i++) {
$(this).after('');
}
});
// Add placeholder cells for rowspans in header
$this.find('> thead [rowspan]').each(function () {
var $cell = $(this);
var rowspan = parseFloat($cell.attr('rowspan'));
for (var i = 1; i < rowspan; i++) {
var parentRow = $cell.parent('tr');
var nextRow = parentRow.next('tr');
var index = parentRow.children().index($cell);
nextRow.children().eq(index).before(' | ');
}
});
// Set indexes to header cells
$this.find('> thead tr').each(function (rowIndex) {
$(this).find('th').each(function (columnIndex) {
var $header = $(this);
$header.addClass('nosort').removeClass('up down');
$header.attr('data-sortcolumn', columnIndex);
$header.attr('data-sortkey', columnIndex + '-' + rowIndex);
});
});
// Cleanup placeholder cells
$this.find('> thead .rowspan-compensate, .colspan-compensate').remove();
// Initialize sorting values specified in header
$this.find('th').each(function () {
var $header = $(this);
if ($header.attr('data-dateformat') !== undefined && momentJsAvailable) {
var colNumber = parseFloat($header.attr('data-sortcolumn'));
$this.find('td:nth-child(' + (colNumber + 1) + ')').each(function () {
var $cell = $(this);
$cell.attr('data-value', moment($cell.text(), $header.attr('data-dateformat')).format('YYYY/MM/DD/HH/mm/ss'));
});
}
else if ($header.attr('data-valueprovider') !== undefined) {
var colNumber = parseFloat($header.attr('data-sortcolumn'));
$this.find('td:nth-child(' + (colNumber + 1) + ')').each(function () {
var $cell = $(this);
$cell.attr('data-value', new RegExp($header.attr('data-valueprovider')).exec($cell.text())[0]);
});
}
});
// Initialize sorting values
$this.find('td').each(function () {
var $cell = $(this);
if ($cell.attr('data-dateformat') !== undefined && momentJsAvailable) {
$cell.attr('data-value', moment($cell.text(), $cell.attr('data-dateformat')).format('YYYY/MM/DD/HH/mm/ss'));
}
else if ($cell.attr('data-valueprovider') !== undefined) {
$cell.attr('data-value', new RegExp($cell.attr('data-valueprovider')).exec($cell.text())[0]);
}
else {
$cell.attr('data-value') === undefined && $cell.attr('data-value', $cell.text());
}
});
var context = lookupSortContext($this),
bsSort = context.bsSort;
$this.find('> thead th[data-defaultsort!="disabled"]').each(function (index) {
var $header = $(this);
var $sortTable = $header.closest('table.sortable');
$header.data('sortTable', $sortTable);
var sortKey = $header.attr('data-sortkey');
var thisLastSort = applyLast ? context.lastSort : -1;
bsSort[sortKey] = applyLast ? bsSort[sortKey] : $header.attr('data-defaultsort');
if (bsSort[sortKey] !== undefined && (applyLast === (sortKey === thisLastSort))) {
bsSort[sortKey] = bsSort[sortKey] === 'asc' ? 'desc' : 'asc';
doSort($header, $sortTable);
}
});
});
}
// Clean up placeholder cells for rowspans in body
function removeRowspanPlaceholders(table) {
table.find('> tbody [rowspan-group]').each(function () {
var $this = $(this);
var id = $this.attr('rowspan-group');
var parentRow = $this.parent('tr');
var index = parentRow.children().index($this);
while(true) {
var nextRow = parentRow.next('tr');
if (!nextRow.is('tr'))
break;
var nextCell = nextRow.children().eq(index);
if (nextCell.attr('rowspan-group') === id) {
var rowspan = parseFloat($this.attr('rowspan')) || 1;
$this.attr('rowspan', rowspan + 1);
nextCell.remove();
} else {
break;
}
parentRow = nextRow;
}
});
}
// Add placeholder cells for rowspans in body
function addRowspanPlaceholders(table) {
table.find('> tbody [rowspan]').each(function () {
var $cell = $(this);
var rowspan = parseFloat($cell.attr('rowspan'));
$cell.removeAttr('rowspan');
var rowSpanId = $cell.attr('rowspan-group') || guid();
$cell.attr('rowspan-group', rowSpanId);
$cell.attr('rowspan-value', rowspan);
var parentRow = $cell.parent('tr');
var index = parentRow.children().index($cell);
for (var i = 1; i < rowspan; i++) {
var compemnsationCell = $cell.clone(false);
var nextRow = parentRow.next('tr');
nextRow.children().eq(index).before(compemnsationCell);
parentRow = nextRow;
}
});
}
// Add click event to table header
$document.on('click', 'table.sortable>thead th[data-defaultsort!="disabled"]', function (e) {
sortByColumn(this);
});
// element is the header of the column to sort (the clicked header)
function sortByColumn(element) {
var $this = $(element), $table = $this.data('sortTable') || $this.closest('table.sortable');
doSort($this, $table);
}
// Look up sorting data appropriate for the specified table (jQuery element).
// This allows multiple tables on one page without collisions.
function lookupSortContext($table) {
var context = $table.data("bootstrap-sortable-context");
if (context === undefined) {
context = { bsSort: [], lastSort: undefined };
$table.find('> thead th[data-defaultsort!="disabled"]').each(function (index) {
var $this = $(this);
var sortKey = $this.attr('data-sortkey');
context.bsSort[sortKey] = $this.attr('data-defaultsort');
if (context.bsSort[sortKey] !== undefined) {
context.lastSort = sortKey;
}
});
$table.data("bootstrap-sortable-context", context);
}
return context;
}
function defaultSortEngine(rows, sortingParams) {
tinysort(rows, sortingParams);
}
// Sorting mechanism separated
function doSort($this, $table) {
$table.trigger('before-sort');
addRowspanPlaceholders($table);
var sortColumn = parseFloat($this.attr('data-sortcolumn')),
context = lookupSortContext($table),
bsSort = context.bsSort;
var colspan = $this.attr('colspan');
if (colspan) {
var mainSort = parseFloat($this.data('mainsort')) || 0;
var rowIndex = parseFloat($this.data('sortkey').split('-').pop());
// If there is one more row in header, delve deeper
if ($table.find('> thead tr').length - 1 > rowIndex) {
doSort($table.find('[data-sortkey="' + (sortColumn + mainSort) + '-' + (rowIndex + 1) + '"]'), $table);
return;
}
// Otherwise, just adjust the sortColumn
sortColumn = sortColumn + mainSort;
}
var localSignClass = $this.attr('data-defaultsign') || signClass;
// update arrow icon
$table.find('> thead th').each(function () {
$(this).removeClass('up').removeClass('down').addClass('nosort');
});
if ($.browser.mozilla) {
var moz_arrow = $table.find('> thead div.mozilla');
if (moz_arrow !== undefined) {
moz_arrow.find('.sign').remove();
moz_arrow.parent().html(moz_arrow.html());
}
$this.wrapInner('');
$this.children().eq(0).append('');
}
else {
$table.find('> thead span.sign').remove();
$this.append('');
}
// sort direction
var sortKey = $this.attr('data-sortkey');
var initialDirection = $this.attr('data-firstsort') !== 'desc' ? 'desc' : 'asc';
var newDirection = (bsSort[sortKey] || initialDirection);
if (context.lastSort === sortKey || bsSort[sortKey] === undefined) {
newDirection = newDirection === 'asc' ? 'desc' : 'asc';
}
bsSort[sortKey] = newDirection;
context.lastSort = sortKey;
if (bsSort[sortKey] === 'desc') {
$this.find('span.sign').addClass('up');
$this.addClass('up').removeClass('down nosort');
} else {
$this.addClass('down').removeClass('up nosort');
}
// remove rows that should not be sorted
var rows = $table.children('tbody').children('tr');
var fixedRows = [];
$(rows.filter('[data-disablesort="true"]').get().reverse()).each(function (index, fixedRow) {
var $fixedRow = $(fixedRow);
fixedRows.push({ index: rows.index($fixedRow), row: $fixedRow });
$fixedRow.remove();
});
// sort rows
var rowsToSort = rows.not('[data-disablesort="true"]');
if (rowsToSort.length != 0) {
var emptySorting = bsSort[sortKey] === 'asc' ? emptyEnd : false;
sortEngine(rowsToSort, { emptyEnd: emptySorting, selector: 'td:nth-child(' + (sortColumn + 1) + ')', order: bsSort[sortKey], data: 'value' });
}
// add back the fixed rows
$(fixedRows.reverse()).each(function (index, row) {
if (row.index === 0) {
$table.children('tbody').prepend(row.row);
} else {
$table.children('tbody').children('tr').eq(row.index - 1).after(row.row);
}
});
// add class to sorted column cells
$table.find('> tbody > tr > td.sorted,> thead th.sorted').removeClass('sorted');
rowsToSort.find('td:eq(' + sortColumn + ')').addClass('sorted');
$this.addClass('sorted');
removeRowspanPlaceholders($table);
$table.trigger('sorted');
}
// jQuery 1.9 removed this object
if (!$.browser) {
$.browser = { chrome: false, mozilla: false, opera: false, msie: false, safari: false };
var ua = navigator.userAgent;
$.each($.browser, function (c) {
$.browser[c] = ((new RegExp(c, 'i').test(ua))) ? true : false;
if ($.browser.mozilla && c === 'mozilla') { $.browser.mozilla = ((new RegExp('firefox', 'i').test(ua))) ? true : false; }
if ($.browser.chrome && c === 'safari') { $.browser.safari = false; }
});
}
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
// Initialise on DOM ready
$($.bootstrapSortable);
}); |