282 lines
9.2 KiB
JavaScript
282 lines
9.2 KiB
JavaScript
// Stupid jQuery table plugin.
|
|
|
|
(function($) {
|
|
$.fn.stupidtable = function(sortFns) {
|
|
return this.each(function() {
|
|
var $table = $(this);
|
|
sortFns = sortFns || {};
|
|
sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
|
|
$table.data('sortFns', sortFns);
|
|
$table.stupidtable_build();
|
|
|
|
$table.on("click.stupidtable", "thead th", function() {
|
|
$(this).stupidsort();
|
|
});
|
|
|
|
// Sort th immediately if data-sort-onload="yes" is specified. Limit to
|
|
// the first one found - only one default sort column makes sense anyway.
|
|
var $th_onload_sort = $table.find("th[data-sort-onload=yes]").eq(0);
|
|
$th_onload_sort.stupidsort();
|
|
});
|
|
};
|
|
|
|
// ------------------------------------------------------------------
|
|
// Default settings
|
|
// ------------------------------------------------------------------
|
|
$.fn.stupidtable.default_settings = {
|
|
should_redraw: function(sort_info){
|
|
return true;
|
|
},
|
|
will_manually_build_table: false
|
|
};
|
|
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
|
|
$.fn.stupidtable.default_sort_fns = {
|
|
"int": function(a, b) {
|
|
return parseInt(a, 10) - parseInt(b, 10);
|
|
},
|
|
"float": function(a, b) {
|
|
return parseFloat(a) - parseFloat(b);
|
|
},
|
|
"string": function(a, b) {
|
|
return a.toString().localeCompare(b.toString());
|
|
},
|
|
"string-ins": function(a, b) {
|
|
a = a.toString().toLocaleLowerCase();
|
|
b = b.toString().toLocaleLowerCase();
|
|
return a.localeCompare(b);
|
|
}
|
|
};
|
|
|
|
// Allow specification of settings on a per-table basis. Call on a table
|
|
// jquery object. Call *before* calling .stuidtable();
|
|
$.fn.stupidtable_settings = function(settings) {
|
|
return this.each(function() {
|
|
var $table = $(this);
|
|
var final_settings = $.extend({}, $.fn.stupidtable.default_settings, settings);
|
|
$table.stupidtable.settings = final_settings;
|
|
});
|
|
};
|
|
|
|
|
|
// Expects $("#mytable").stupidtable() to have already been called.
|
|
// Call on a table header.
|
|
$.fn.stupidsort = function(force_direction){
|
|
var $this_th = $(this);
|
|
var datatype = $this_th.data("sort") || null;
|
|
|
|
// No datatype? Nothing to do.
|
|
if (datatype === null) {
|
|
return;
|
|
}
|
|
|
|
var dir = $.fn.stupidtable.dir;
|
|
var $table = $this_th.closest("table");
|
|
|
|
var sort_info = {
|
|
$th: $this_th,
|
|
$table: $table,
|
|
datatype: datatype
|
|
};
|
|
|
|
|
|
// Bring in default settings if none provided
|
|
if(!$table.stupidtable.settings){
|
|
$table.stupidtable.settings = $.extend({}, $.fn.stupidtable.default_settings);
|
|
}
|
|
|
|
sort_info.compare_fn = $table.data('sortFns')[datatype];
|
|
sort_info.th_index = calculateTHIndex(sort_info);
|
|
sort_info.sort_dir = calculateSortDir(force_direction, sort_info);
|
|
|
|
$this_th.data("sort-dir", sort_info.sort_dir);
|
|
$table.trigger("beforetablesort", {column: sort_info.th_index, direction: sort_info.sort_dir, $th: $this_th});
|
|
|
|
// More reliable method of forcing a redraw
|
|
$table.css("display");
|
|
|
|
// Run sorting asynchronously on a timout to force browser redraw after
|
|
// `beforetablesort` callback. Also avoids locking up the browser too much.
|
|
setTimeout(function() {
|
|
if(!$table.stupidtable.settings.will_manually_build_table){
|
|
$table.stupidtable_build();
|
|
}
|
|
var table_structure = sortTable(sort_info);
|
|
var trs = getTableRowsFromTableStructure(table_structure, sort_info);
|
|
|
|
if(!$table.stupidtable.settings.should_redraw(sort_info)){
|
|
return;
|
|
}
|
|
$table.children("tbody").append(trs);
|
|
|
|
updateElementData(sort_info);
|
|
$table.trigger("aftertablesort", {column: sort_info.th_index, direction: sort_info.sort_dir, $th: $this_th});
|
|
$table.css("display");
|
|
|
|
}, 10);
|
|
return $this_th;
|
|
};
|
|
|
|
// Call on a sortable td to update its value in the sort. This should be the
|
|
// only mechanism used to update a cell's sort value. If your display value is
|
|
// different from your sort value, use jQuery's .text() or .html() to update
|
|
// the td contents, Assumes stupidtable has already been called for the table.
|
|
$.fn.updateSortVal = function(new_sort_val){
|
|
var $this_td = $(this);
|
|
if($this_td.is('[data-sort-value]')){
|
|
// For visual consistency with the .data cache
|
|
$this_td.attr('data-sort-value', new_sort_val);
|
|
}
|
|
$this_td.data("sort-value", new_sort_val);
|
|
return $this_td;
|
|
};
|
|
|
|
|
|
$.fn.stupidtable_build = function(){
|
|
return this.each(function() {
|
|
var $table = $(this);
|
|
var table_structure = [];
|
|
var trs = $table.children("tbody").children("tr");
|
|
trs.each(function(index,tr) {
|
|
|
|
// ====================================================================
|
|
// Transfer to using internal table structure
|
|
// ====================================================================
|
|
var ele = {
|
|
$tr: $(tr),
|
|
columns: [],
|
|
index: index
|
|
};
|
|
|
|
$(tr).children('td').each(function(idx, td){
|
|
var sort_val = $(td).data("sort-value");
|
|
|
|
// Store and read from the .data cache for display text only sorts
|
|
// instead of looking through the DOM every time
|
|
if(typeof(sort_val) === "undefined"){
|
|
var txt = $(td).text();
|
|
$(td).data('sort-value', txt);
|
|
sort_val = txt;
|
|
}
|
|
ele.columns.push(sort_val);
|
|
});
|
|
table_structure.push(ele);
|
|
});
|
|
$table.data('stupidsort_internaltable', table_structure);
|
|
});
|
|
};
|
|
|
|
// ====================================================================
|
|
// Private functions
|
|
// ====================================================================
|
|
var sortTable = function(sort_info){
|
|
var table_structure = sort_info.$table.data('stupidsort_internaltable');
|
|
var th_index = sort_info.th_index;
|
|
var $th = sort_info.$th;
|
|
|
|
var multicolumn_target_str = $th.data('sort-multicolumn');
|
|
var multicolumn_targets;
|
|
if(multicolumn_target_str){
|
|
multicolumn_targets = multicolumn_target_str.split(',');
|
|
}
|
|
else{
|
|
multicolumn_targets = [];
|
|
}
|
|
var multicolumn_th_targets = $.map(multicolumn_targets, function(identifier, i){
|
|
return get_th(sort_info.$table, identifier);
|
|
});
|
|
|
|
table_structure.sort(function(e1, e2){
|
|
var multicolumns = multicolumn_th_targets.slice(0); // shallow copy
|
|
var diff = sort_info.compare_fn(e1.columns[th_index], e2.columns[th_index]);
|
|
while(diff === 0 && multicolumns.length){
|
|
var multicolumn = multicolumns[0];
|
|
var datatype = multicolumn.$e.data("sort");
|
|
var multiCloumnSortMethod = sort_info.$table.data('sortFns')[datatype];
|
|
diff = multiCloumnSortMethod(e1.columns[multicolumn.index], e2.columns[multicolumn.index]);
|
|
multicolumns.shift();
|
|
}
|
|
// Sort by position in the table if values are the same. This enforces a
|
|
// stable sort across all browsers. See https://bugs.chromium.org/p/v8/issues/detail?id=90
|
|
if (diff === 0)
|
|
return e1.index - e2.index;
|
|
else
|
|
return diff;
|
|
|
|
});
|
|
|
|
if (sort_info.sort_dir != $.fn.stupidtable.dir.ASC){
|
|
table_structure.reverse();
|
|
}
|
|
return table_structure;
|
|
};
|
|
|
|
var get_th = function($table, identifier){
|
|
// identifier can be a th id or a th index number;
|
|
var $table_ths = $table.find('th');
|
|
var index = parseInt(identifier, 10);
|
|
var $th;
|
|
if(!index && index !== 0){
|
|
$th = $table_ths.siblings('#' + identifier);
|
|
index = $table_ths.index($th);
|
|
}
|
|
else{
|
|
$th = $table_ths.eq(index);
|
|
}
|
|
return {index: index, $e: $th};
|
|
};
|
|
|
|
var getTableRowsFromTableStructure = function(table_structure, sort_info){
|
|
// Gather individual column for callbacks
|
|
var column = $.map(table_structure, function(ele, i){
|
|
return [[ele.columns[sort_info.th_index], ele.$tr, i]];
|
|
});
|
|
|
|
/* Side effect */
|
|
sort_info.column = column;
|
|
|
|
// Replace the content of tbody with the sorted rows. Strangely
|
|
// enough, .append accomplishes this for us.
|
|
return $.map(table_structure, function(ele) { return ele.$tr; });
|
|
|
|
};
|
|
|
|
var updateElementData = function(sort_info){
|
|
var $table = sort_info.$table;
|
|
var $this_th = sort_info.$th;
|
|
var sort_dir = $this_th.data('sort-dir');
|
|
var th_index = sort_info.th_index;
|
|
|
|
|
|
// Reset siblings
|
|
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
|
|
$this_th.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
|
|
};
|
|
|
|
var calculateSortDir = function(force_direction, sort_info){
|
|
var sort_dir;
|
|
var $this_th = sort_info.$th;
|
|
var dir = $.fn.stupidtable.dir;
|
|
|
|
if(!!force_direction){
|
|
sort_dir = force_direction;
|
|
}
|
|
else{
|
|
sort_dir = force_direction || $this_th.data("sort-default") || dir.ASC;
|
|
if ($this_th.data("sort-dir"))
|
|
sort_dir = $this_th.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
|
|
}
|
|
return sort_dir;
|
|
};
|
|
|
|
var calculateTHIndex = function(sort_info){
|
|
var th_index = 0;
|
|
var base_index = sort_info.$th.index();
|
|
sort_info.$th.parents("tr").find("th").slice(0, base_index).each(function() {
|
|
var cols = $(this).attr("colspan") || 1;
|
|
th_index += parseInt(cols,10);
|
|
});
|
|
return th_index;
|
|
};
|
|
|
|
})(jQuery);
|