/** * Scripts to display simple data tables. * 2/1/2020 Mack Pexton (mack@mackpexton.com) * * This is a collection of scripts from the web that are intended to enhance usage of html data tables. * There are three main scripts bundled below. They provide: * 1) sorting the table on a column * 2) freezing the table header at the top of the screen when it scrolls out of view * 3) resizing table columns * * These tools are for simple data tables on the main scrollable page with a structure like the following: * *
Column 1 | *Column 2 | * ... *
---|
data attributes modifying script behavior are: * data-sortable -- If false, do not sort column (sortable.js) * data-sortable-type -- Column type "numeric", "date", or "alpha" (sortable.js). Custom types also available. * data-resizable -- If false, do not allow user to resize the column. (colResizable.js) * * The optional | data attribute is:
* data-value -- value of column to use for sorting (sortable.js)
*
* To setup newly created tables trigger the custom event "tableSetup". To refresh the table settings with run-time changes,
* trigger the custom event "tableRefresh". Both the table size and settings will be refreshed upon the window "resize" event.
*
* To remove DOM elements added to the tables, trigger the custom event "tableUnsetup". That can be useful before editing
* a table after which the "tableSetup" event can be triggered to re-setup the table(s).
*
* @credits
* Table sorting is from sortable.js by Adam Schwartz $/i,'') )
: $(el).clone(true);
$tr.append($th);
});
this.prepend( $thead.append($tr) );
$firstrow.remove();
}
// Remove colgroup section if it exists (it confuses colResizable).
if ( $('colgroup',this).length ) $('colgroup',this).remove();
// Wrap table with bounding div for touch devices horizontal scroll.
if (this.hasClass('table-fixed-header') && ('ontouchstart' in document.documentElement)) {
// Ensure table is wrapped with table-wrapper div.
if (! this.parent().hasClass('table-wrapper')) {
this.wrap( $(' | ').addClass('table-wrapper') );
}
}
this.data('simpleDataTable',true);
return this;
};
})(jQuery);
/*---------------- sortable.js -----------------------------------------------------------------*/
/* sortable.js by Adam Schwartz tags to only those in sections.
* Identify sortable tables with the class "table-sortable" in addition to it's default "data-sortable" attribute.
* Added compatibility with table-fixed-header.js library. Used jQuery.on() for events so they can be cloned.
* Added \n'
);
//##setTimeout(sortable.init, 0);
sortable.init(); // init() needs to run first before fixedHeader() below
//## Add event handler to set up dynamically generated tables.
$(document).on('tableSetup',function(e){ sortable.init(); });
//## Add event handler to unset sortable on tables.
$(document).on('tableUnsetup',function(e){
// Remove settings initially setup by sortable.
$('table.table-sortable,table[data-sortable]')
.each(function(){
$('thead tr th,thead tr td',this).each(function(){
$(this).removeAttr('data-sorted');
});
})
.removeAttr('data-sortable-initialized');
});
if (typeof define === 'function' && define.amd) {
define(function() {
return sortable;
});
} else if (typeof exports !== 'undefined') {
module.exports = sortable;
} else {
window.Sortable = sortable;
}
//##}).call(this);
});
/*---------------- table-fixed-header.js -------------------------------------------------------*/
/*
* table-fixed-header - Freeze table headers when they get to the top of page.
*
* Usage: add "table-fixed-header" class to table with a thead header.
* If needed, add "data-topoffset" attribute to the table with the offset from top of screen.
* Optional "data-topheader" attribute has id of fixed top header whose bottom coordinate gives the top offset.
* Optional window.TopOffset global variable with the offset from the top of screen.
*
* Derived from: http://github.com/oma/table-fixed-header
* Note: this adds a second thead section to the table which violates HTML5 standard but seems to work everywhere.
* Changes: mack:
* Added auto-invocation code at bottom automatically setting up tables with class "table-fixed-header".
* Got rid of thead selectors by using closures.
* Corrected resizing cloned header when expanding table widths.
* Add class table-header-fixed to the thead tag that's fixed to top of page.
* Removed comments and hacks by jeffen@pactera, made into prepHeaderCopy().
* Added compatibility with sortable.js library.
* Change o variable to $table for consistency
* Added data-topOffset and data-bgColor attributes for setting parameters on auto-setup tables.
* Dynamically read data-topoffset when showing the fixed header. Removed bgcolor config, use class css instead.
* Hide header if scrolled past bottom of table.
* Implement data-topheader selector for dynamically setting top offset to selector element's bottom coordinate when visible.
* Added "tableRefresh" custom event to recompute parameters. Helpful when table was invisible when initially setup.
* Added "tableSetup" and "tableUnsetup" custom events to setup tables with fixed headers and to remove fixed headers.
* Added window.TopOffset global for dynamically setting top offset. Can set by giving an element the class "top-header".
*/
(function($) {
var defaults = {
topOffset: 0, // offset from top of screen
topHeader: null, // selector for fixed element at top of screen
topHeaderClass: '.top-header', // selector of element where if found is used for topHeader
className: 'table-header-fixed', // class name assigned to cloned thead
};
$.fn.fixedHeader = function(arg) {
var config;
if (typeof arg === 'object') {
config = $.extend({}, defaults, arg);
} else if ($.isNumeric(arg)) {
config = $.extend({}, defaults, {topOffset:arg});
} else {
config = $.extend({}, defaults);
}
return this.each(function() {
var $table = $(this)
, $win = $(window)
, $head = $('thead',$table).first()
, isFixed = false
, $copy
// Dynamic table parameters
, tableTopOffset
, headTop
, headHeight
, tableHeight
, tableBottomOffset;
if (! $head.length) return; // no thead sections found in table
if ($('.'+config.className,$table).length) return; // already has a fixed header
// Store dynamic parameters with table element.
if ($table.data('topheader') == null) {
if (config['topHeader'] != null) {
$table.data('topheader',config['topHeader']);
}
else if ($(config['topHeaderClass']).length > 0) {
$table.data('topheader',config['topHeaderClass']);
}
}
if ($table.data('topoffset') == null) $table.data('topoffset', config['topOffset']);
function processScroll() {
if (! $table.is(':visible')) return;
var scrollTop = $win.scrollTop() + tableTopOffset;
if (!isFixed && (scrollTop > headTop && scrollTop < (headTop + tableHeight))) {
isFixed = true;
} else if (isFixed && (scrollTop <= headTop || scrollTop >= (headTop + tableHeight))) {
isFixed = false;
}
if (isFixed) {
if (! $copy.is(':visible')) showHeaderCopy();
$copy.offset({ 'top': (tableBottomOffset < scrollTop ? tableBottomOffset : scrollTop), 'left': $head.offset().left });
} else {
if ($copy.is(':visible')) hideHeaderCopy();
}
}
function processResize() {
if (isFixed) {
prepHeader(); // copy changes to hidden table header
prepHeaderCopy(); // copy new table column widths back to fixed header
getParam(); // recompute global variables for scroll
processScroll();
}
}
function showHeaderCopy() { prepHeaderCopy(); $copy.show(); getParam(); }
function hideHeaderCopy() { prepHeader(); getParam(); $copy.hide(); }
function prepHeaderCopy() {
// Transfer run-time changes made to table header to the cloned header copy.
$copy.css('top',tableTopOffset).width($head.width());
$('tr>th',$head).each(function(i, h) {
var w
, $h = $(h)
//##, $c = $("tr>th:eq("+i+")",$copy);
, $c = $("tr>th:nth-child("+(i+1)+")",$copy);
$c.attr('class', $h.attr('class'));
// Make column widths the same.
//$c.width($h.width()); // fixed width tables
// Following is for compatibility with colResizable.js
// For dynamic tables where their widths can grow or shrink, we must set
// the min and max width too in order to force the table to grow and to allow
// columns to be narrower than a word.
// We should look at colResizable overflow option to determine whether table size
// is dynamic or not. Here we just assume that all tables use the overflow option.
w = $h.width() + 'px';
$c.css({'width':w, 'min-width':w, 'max-width':w}); // dynamic width tables
// Following is for compatibility with sortable.js
$c.attr('data-sorted', $h.attr('data-sorted'));
$c.attr('data-sortable-type', $h.attr('data-sortable-type'));
if ($h.attr('data-sorted-direction')) {
$c.attr('data-sorted-direction',$h.attr('data-sorted-direction'));
} else {
$c.removeAttr('data-sorted-direction'); /* remove to clear possible css styling */
}
});
}
function prepHeader() {
// Transfer run-time changes made to cloned header back to the table header.
$('tr>th',$copy).each(function(i, c) {
var $c = $(c)
//##, $h = $("tr>th:eq("+i+")",$head);
, $h = $("tr>th:nth-child("+(i+1)+")",$head);
$h.attr('class', $c.attr('class'));
// Following is for compatibility with sortable.js
$h.attr('data-sorted', $c.attr('data-sorted'));
$h.attr('data-sortable-type', $c.attr('data-sortable-type'));
if ($c.attr('data-sorted-direction')) {
$h.attr('data-sorted-direction',$c.attr('data-sorted-direction'));
} else {
$h.removeAttr('data-sorted-direction'); /* remove to clear possible css styling */
}
});
}
function getParam() {
// Lookup and set global variables of table parameters used to trigger fixed header.
headTop = $head.offset().top;
headHeight = $head.height();
tableHeight = $table.height();
tableBottomOffset = headTop + tableHeight - headHeight;
tableTopOffset = getTopOffset();
}
function getTopOffset() {
if (window.TopOffset != null) return window.TopOffset;
var $topHeader = $($table.data('topheader'));
if ($topHeader.length && $topHeader.css('position').match(/(fixed|stickey)/)) {
return $topHeader.scrollTop()+$topHeader.height();
}
return $table.data('topoffset') || 0;
}
if (! $copy || ! $table.data('fixedHeaderSetup')) {
$copy = $head.clone(true)
.addClass(config['className'])
.css({
'position': 'fixed',
'border-spacing': '0',
'display': 'none'})
.appendTo($table);
$table.data('fixedHeaderSetup',true);
}
getParam();
$win.on('scroll.fixedHeader', processScroll);
$win.on('resize.fixedHeader', processResize);
//## Add custom event to recompute parameters. Helpful when table was invisible when initially setup.
$(document).on('tableRefresh', function(){getParam();processResize();});
//## Define custom event for setting up dynamically generated tables.
$(document).on('tableSetup',function(e){ $('.table-fixed-header').setupSimpleDataTable().fixedHeader() });
//## Define custom event for removing extra DOM elements added by $.fn.fixedHeader().
$(document).on('tableUnsetup',function(e){ $('.table-fixed-header').removeFixedHeader() });
//## Add touch horizontal scroll.
if ('ontouchstart' in document.documentElement) $('.table-wrapper').on('scroll.fixedHeader',processScroll);
//## trigger scroll to show table headers if page is initially positioned in middle of page.
setTimeout(function(){$win.trigger('scroll.fixedHeader')},10);
});
};
//## Added removeFixedHeader to remove added thead sections in tables.
$.fn.removeFixedHeader = function(){
return this.each(function(){
var $table = $(this);
$('thead',$table).each(function(i){
// Remove added thead sections.
if (i > 0) $(this).remove();
});
$table.data('fixedHeaderSetup',false);
});
};
//## Automatically setup tables having the table-fixed-header class.
$(function(){
$('.table-fixed-header').setupSimpleDataTable().fixedHeader();
});
//## To enable fixedHeaders for touch, the table's container overflow css needs to be set. Helps horizontal scrolling.
//## Note: the following clips off popup menus at bottom of table that are used in file-list.
//## We can get by just by assigning overflow to any element such as .backdrop.
//##if ('ontouchstart' in document.documentElement) {
//## $('head').append('\n');
//##}
})(jQuery);
/*---------------- doubletap handler -----------------------------------------------------------*/
/* Provide a handler for touch devices to double-tap column grips in colResizable.js */
/*
* jQuery Double Tap
* Developer: Sergey Margaritov (sergey@margaritov.net)
* Date: 22.10.2013
* Based on jquery documentation http://learn.jquery.com/events/event-extensions/
*/
(function($){
$.event.special.doubletap = {
bindType: 'touchend',
delegateType: 'touchend',
handle: function(event) {
var handleObj = event.handleObj,
targetData = jQuery.data(event.target),
now = new Date().getTime(),
delta = targetData.lastTouch ? now - targetData.lastTouch : 0,
delay = delay == null ? 300 : delay;
if (delta < delay && delta > 30) {
targetData.lastTouch = null;
event.type = handleObj.origType;
['clientX', 'clientY', 'pageX', 'pageY'].forEach(function(property) {
event[property] = event.originalEvent.changedTouches[0][property];
})
// let jQuery handle the triggering of "doubletap" event handlers
handleObj.handler.apply(this, arguments);
} else {
targetData.lastTouch = now;
}
}
};
})(jQuery);
/*---------------- colResizable.js -------------------------------------------------------------*/
/* Usage: http://www.bacubacu.com/colresizable/#usage
/**
_ _____ _ _ _
| | __ \ (_) | | | |
___ ___ | | |__) |___ ___ _ ______ _| |__ | | ___
/ __/ _ \| | _ // _ \/ __| |_ / _` | '_ \| |/ _ \
| (_| (_) | | | \ \ __/\__ \ |/ / (_| | |_) | | __/
\___\___/|_|_| \_\___||___/_/___\__,_|_.__/|_|\___|
v1.6 - jQuery plugin created by Alvaro Prieto Lauroba
Licences: MIT & GPL
Feel free to use or modify this plugin as far as my full name is kept
If you are going to use this plug-in in production environments it is
strongly recommended to use its minified version: colResizable.min.js
*/
/*
* Changes: mack:
* Changed style settings so grid lines z-index are above fixed header.
* Replaced jQuery deprecated bind() function with on().
* Integrate colResizable with fixedHeader (copy settings, sync with other invisible header).
* Added code automatically setting up tables with the class name "table-col-resizable".
* Flush saved settings if the parameter "reset" is found in url.
* Track which columns are changed by user.
* On resize reset non-user specified column widths to auto to allow browser to resize widths.
* Changed interpretation of overflow option to be more dynamic where table width can expand and contract.
* Added double-click handler on grips to reset column width back to it's initial setting.
* Saved original column width percentage of total width to use when resizing table.
* Removed tracking of user changed columns above. Use saved original widths instead.
* Added custom event tableRefresh to recompute grips and headers.
* Removed need for marginLeft and marginRight options.
* Added option touchBeforeDrag and onGripTouchStart handler to activate grips first before dragging.
* Added custom event tableUnsetup to remove DOM elements added by colResizable to the document.
* Added double-click handler on last table grip to reset all table columns to their original width.
* Sync'ed grip visibility to column visibilty to allow dynamically displayed columns in responsive designs.
*
* Because browsers have latitude in adjusting tables on their own, resize was changed to first
* set table parameters, let the browser render the results, and then read the resulting column
* widths for accurate placement of grips and guidelines.
*/
(function($){
var d = $(document); //window object
var h = $("head"); //head object
var drag = null; //reference to the current grip that is being dragged
var tables = {}; //object of the already processed tables (table.id as key)
var count = 0; //internal count to create unique IDs when needed.
//common strings for packing
var ID = "id";
var PX = "px";
var SIGNATURE ="JColResizer";
var FLEX = "JCLRFlex";
//short-cuts
var I = parseInt;
var M = Math;
var ie = navigator.userAgent.indexOf('Trident/4.0')>0;
var S;
try{S = sessionStorage;}catch(e){} //Firefox crashes when executed as local file system
//append required CSS rules
//##h.append("");
//## Change z-index to be one more than fixed header in table-fixed-header.js above.
//## Removed table td and th padding settings.
//## Removed JCLRFlex{width:auto!important} as width:100% preferred.
//## Expanded width of grips to better facilitate touch devices
//## Changed grip resizer's class to JCLResizer as JColResizer is used for the table class name.
//## Changed JCLRgrip and JCLRResizer css to center the gridline over the cell borders.
//## Changed e-resize to col-resize
h.append(''
+ '\n'
);
/**
* Function to allow column resizing for table objects. It is the starting point to apply the plugin.
* @param {DOM node} tb - reference to the DOM table object to be enhanced
* @param {Object} options - some customization values
*/
var init = function( tb, options){
var t = $(tb); //the table object is wrapped
t.opt = options; //each table has its own options available at anytime
t.mode = options.resizeMode; //shortcuts
t.dc = t.opt.disabledColumns;
if(t.opt.disable) return destroy(t); //the user is asking to destroy a previously colResized table
var id = t.id = t.attr(ID) || SIGNATURE+count++; //its id is obtained, if null new one is generated
count++; //## always increase in case old ID's are reused (re-setup)
t.p = t.opt.postbackSafe; //short-cut to detect postback safe
t.tm = null; //## timer for setting grips after table resize
t.uc = false; //## true if user changed column sizes
if(!t.is("table") || tables[id] && !t.opt.partialRefresh) return; //if the object is not a table or if it was already processed then it is ignored.
//##if(t.opt.hoverCursor !== 'e-resize') h.append(""); //if hoverCursor has been set, append the style
if(t.opt.hoverCursor !== 'col-resize') h.append(""); //if hoverCursor has been set, append the style
t.addClass(SIGNATURE).attr(ID, id).before(''); //the grips container object is added. Signature class forces table rendering in fixed-layout mode to prevent column's min-width
t.g = []; t.c = []; t.w = t.width(); t.gc = t.prev(); t.f=t.opt.fixed; //t.c and t.g are arrays of columns and grips respectively
//## Following options not needed anymore as syncGC() used instead.
//##if(options.marginLeft) t.gc.css("marginLeft", options.marginLeft); //if the table contains margins, it must be specified
//##if(options.marginRight) t.gc.css("marginRight", options.marginRight); //since there is no (direct) way to obtain margin values in its original units (%, em, ...)
t.cs = I(ie? tb.cellSpacing || tb.currentStyle.borderSpacing :t.css('border-spacing'))||2; //table cellspacing (not even jQuery is fully cross-browser)
t.b = I(ie? tb.border || tb.currentStyle.borderLeftWidth :t.css('borderLeftWidth'))||1; //outer border width (again cross-browser issues)
// if(!(tb.style.width || tb.width)) t.width(t.width()); //I am not an IE fan at all, but it is a pity that only IE has the currentStyle attribute working as expected. For this reason I can not check easily if the table has an explicit width or if it is rendered as "auto"
tables[id] = t; //the table object is stored using its id as key
syncGC(t); //## sync grip container
createGrips(t); //grips are created
};
/**
* This function allows to remove any enhancements performed by this plugin on a previously processed table.
* @param {jQuery ref} t - table object
*/
var destroy = function(t){
var id=t.attr(ID), t=tables[id]; //its table object is found
if(!t||!t.is("table")) return; //if none, then it wasn't processed
t.removeClass(SIGNATURE+" "+FLEX).gc.remove(); //class and grips are removed
delete tables[id]; //clean up data
};
/**
* Function to create all the grips associated with the table given by parameters
* @param {jQuery ref} t - table object
*/
var createGrips = function(t){
//##var th = t.find(">thead>tr:first>th,>thead>tr:first>td"); //table headers are obtained
//##if(!th.length) th = t.find(">tbody>tr:first>th,>tr:first>th,>tbody>tr:first>td, >tr:first>td"); //but headers can also be included in different ways
var th = t.find(">thead>tr:first-child>th,>thead>tr:first-child>td"); //table headers are obtained
if(!th.length) th = t.find(">tbody>tr:first-child>th,>tr:first-child>th,>tbody>tr:first-child>td, >tr:first-child>td"); //but headers can also be included in different ways
th = th.filter(":visible"); //filter invisible columns
t.cg = t.find("col"); //a table can also contain a colgroup with col elements
t.ln = th.length; //table length is stored
t.owt = t.w; //## saved original table width
t.ow = []; th.each(function(i){t.ow[i] = $(this).width();}); //## save original column widths
if(t.p && S && S[t.id])memento(t,th); //if 'postbackSafe' is enabled and there is data for the current table, its coloumn layout is restored
th.each(function(i){ //iterate through the table column headers
var c = $(this); //jquery wrap for the current column
var dc = t.dc.indexOf(i)!=-1; //is this a disabled column?
var g = $(t.gc.append('')[0].lastChild); //add the visual node to be used as grip
//## SIGNATURE as a class name is also attached to the table element. Use JCLRResizer here instead.
//##g.append(dc ? "": t.opt.gripInnerHtml).append('');
g.append(dc ? "": t.opt.gripInnerHtml).append('');
if(i == t.ln-1){ //if the current grip is the last one
g.addClass("JCLRLastGrip"); //add a different css class to stlye it in a different way if needed
if(t.f) g.html(""); //if the table resizing mode is set to fixed, the last grip is removed since table with can not change
}
//## following is duplicated below
//##g.on('touchstart mousedown', onGripMouseDown); //bind the mousedown event to start dragging
if(!dc){
//if normal column bind the mousedown event to start dragging, if disabled then apply its css class
//##g.removeClass('JCLRdisabledGrip').on('touchstart mousedown', onGripMouseDown);
g.removeClass('JCLRdisabledGrip')
.on('mousedown',onGripMouseDown)
.on('touchstart',onGripTouchStart) //## activate grip before dragging
.on('dblclick doubletap',onGripDoubleClick); //## reset column width to default
}else{
g.addClass('JCLRdisabledGrip');
}
g.t = t; g.i = i; g.c = c; c.w =c.width(); //some values are stored in the grip's node data as shortcut
t.g.push(g); t.c.push(c); //the current grip and column are added to its table object
if(!dc) c.width(c.w).removeAttr("width"); //the width of the column is converted into pixel-based measurements
c.pl = I(c.css('paddingLeft')); //## store data as shortcut
c.pr = I(c.css('paddingRight')); //## store data as shortcut
c.bl = I(c.css('borderLeftWidth')); //## store data as shortcut
c.br = I(c.css('borderRightWidth')); //## store data as shortcut
c.wp = 100*(c.w+c.pl+c.pr+c.bl+c.br)/t.w; //## compute column width percent for resize
g.data(SIGNATURE, {i:i, t:t.attr(ID), last: i == t.ln-1}); //grip index and its table name are stored in the HTML
});
t.cg.removeAttr("width"); //remove the width attribute from elements in the colgroup
t.find('td, th').not(th).not('table th, table td').each(function(){
$(this).removeAttr('width'); //the width attribute is removed from all table cells which are not nested in other tables and dont belong to the header
});
if(!t.f){
t.removeAttr('width').addClass(FLEX); //if not fixed, let the table grow as needed
}
syncGrips(t); //the grips are positioned according to the current table layout
//there is a small problem, some cells in the table could contain dimension values interfering with the
//width value set by this plugin. Those values are removed
//## Note: original column width settings are now preserved and restored.
};
/**
* Function to allow the persistence of columns dimensions after a browser postback. It is based in
* the HTML5 sessionStorage object, which can be emulated for older browsers using sessionstorage.js
* @param {jQuery ref} t - table object
* @param {jQuery ref} th - reference to the first row elements (only set in deserialization)
*/
var memento = function(t, th){
var w,m=0,i=0,aux =[],tw;
if(th){ //in deserialization mode (after a postback)
t.cg.removeAttr("width");
if(t.opt.flush){ S[t.id] =""; return;} //if flush is activated, stored data is removed
w = S[t.id].split(";"); //column widths is obtained
//## Unnamed tables or tables with same name share same storage. Ensure number of table columns are the same.
if(w.length != t.ln+2){ S[t.id]=""; return;} //## flush if number of table columns are different
tw = w[t.ln+1];
if(!t.f && tw){ //if not fixed and table width data available its size is restored
t.width(tw*=1);
if(t.opt.overflow) { //if overflow flag is set, restore table width also as table min-width
t.css('min-width', tw + PX);
}
}
//## For overflow tables, use actual pixel widths (not percentage)
if(t.opt.overflow){
for(;i | |
---|