mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-02 02:18:20 +00:00
356 lines
19 KiB
JavaScript
Executable File
356 lines
19 KiB
JavaScript
Executable File
/**
|
|
* 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 <ron@ronvalstar.nl>
|
|
* @copyright Ron Valstar <ron@ronvalstar.nl>
|
|
* @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('<th class="colspan-compensate">');
|
|
}
|
|
});
|
|
|
|
// 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('<th class="rowspan-compensate">');
|
|
}
|
|
});
|
|
|
|
// 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('<div class="mozilla"></div>');
|
|
$this.children().eq(0).append('<span class="sign ' + localSignClass + '"></span>');
|
|
}
|
|
else {
|
|
$table.find('> thead span.sign').remove();
|
|
$this.append('<span class="sign ' + localSignClass + '"></span>');
|
|
}
|
|
|
|
// 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);
|
|
|
|
});
|