Created
March 28, 2017 22:41
-
-
Save denised/a7bc5d8cb578f782bad5a4be75b88cb8 to your computer and use it in GitHub Desktop.
Left-justify columns in html tables (datatables implementation, generally applicable concept)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Create a justified table, while *also* enabling wrapable columns to mainain their width. | |
* This is not possible today with pure css, so we resort to javascript. | |
* This code is a tweak of the way datatables computes column widths (no way would I have been | |
* able to write this from scratch!). | |
* | |
* Usage: give the header of one column in the table the class 'dt-rowFiller'. As the most | |
* common use case, to have a natural looking, left-justified, table, add an extra empty column | |
* at the end, with this class. Then invoke one of the functions: fillRows() will cause the filling | |
* to happen a single time, while autoFillRows() watches for events that may cause table resizing and | |
* automatically resizes when they happen. | |
* | |
* This functionality replaces the datatable column sizing functionality, so the | |
* datatable should have the option autoWidth: false set. (And it probably doesn't work with any | |
* features or extensions that depend on autowidth.) | |
* | |
* How it works. Fillrows() works by creating a temporary, hidden, copy of part of the table, | |
* enough to determine it's natural width, and then using that to determine what widths all the | |
* columns should have. If the table is naturally as wide or wider than the space available to it, | |
* nothing happens. But if the table is naturally smaller than the desired width, the rowFiller | |
* column gets enough extra width to make the table fill it's space. | |
* | |
* Like datatables column sizing, fillRows sets these widths as style properties on the header | |
* cells of the table. This can cause some strange behaviors when changes happen to the table | |
* that make the widths no longer correct. If/when this happens, another call to fillRows() | |
* will fix it. | |
*/ | |
/** | |
* @param dt datatables api instance, as returned by the DataTables constructor | |
*/ | |
function autoFillRows(dt) { | |
var $ = jQuery; | |
var resize_timer; | |
fillRows(dt); | |
dt.draw('page'); | |
// Watch for window resizing | |
$(window).on('resize',function() { // this gizmo waits until resize has 'stopped' to redraw | |
clearTimeout(resize_timer); | |
resize_timer = setTimeout(function() { | |
fillRows(dt); | |
dt.draw('page'); | |
},200); | |
}); | |
// Also watch for changes to column visibility | |
dt.on('column-visibility.dt',function() { fillRows(dt); dt.draw('page'); }); | |
} | |
/* | |
* This is a modified copy of _fnCalculateColumnWidths | |
*/ | |
function fillRows(dt) { | |
var | |
$ = jQuery, | |
fns = dt.context[0].oApi, | |
oSettings = dt.settings()[0], | |
table = oSettings.nTable, | |
columns = oSettings.aoColumns, | |
scroll = oSettings.oScroll, | |
scrollY = scroll.sY, | |
scrollX = scroll.sX, | |
visibleColumns = fns._fnGetColumns( oSettings, 'bVisible' ), | |
headerCells = fns._fnGetUniqueThs( oSettings, oSettings.nTHead ), | |
tableWidthAttr = table.getAttribute('width'), // from DOM element | |
tableContainer = table.parentNode, | |
desiredWidth, | |
i, column, columnIdx, width, | |
browser = oSettings.oBrowser, | |
fillerColumnIdx = -1; // in visible-column/header-cell space | |
for ( i=0 ; i<visibleColumns.length ; i++ ) { | |
if( $(headerCells[i]).hasClass('dt-rowFiller') ) { | |
fillerColumnIdx = i; | |
break; | |
} | |
} | |
if ( fillerColumnIdx < 0 ) { | |
// no filler column, no filling to do. | |
//console.log('no filler column'); | |
return; | |
} | |
var styleWidth = table.style.width; | |
if ( styleWidth && styleWidth.indexOf('%') !== -1 ) { | |
tableWidthAttr = styleWidth; | |
} | |
/* Convert any user input sizes into pixel sizes */ | |
for ( i=0 ; i<visibleColumns.length ; i++ ) { | |
column = columns[ visibleColumns[i] ]; | |
if ( column.sWidth !== null ) { | |
column.sWidth = fns._fnConvertToWidth( column.sWidthOrig, tableContainer ); | |
} | |
} | |
// Construct a single row, worst case, table with the widest | |
// node in the data, assign any user defined widths, then insert it into | |
// the DOM and allow the browser to do all the hard work of calculating | |
// table widths | |
var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table | |
.css( 'visibility', 'hidden' ) | |
.removeAttr( 'id' ); | |
// Clean up the table body | |
tmpTable.find('tbody tr').remove(); | |
var tr = $('<tr/>').appendTo( tmpTable.find('tbody') ); | |
// Clone the table header and footer - we can't use the header / footer | |
// from the cloned table, since if scrolling is active, the table's | |
// real header and footer are contained in different table tags | |
tmpTable.find('thead, tfoot').remove(); | |
tmpTable | |
.append( $(oSettings.nTHead).clone() ) | |
.append( $(oSettings.nTFoot).clone() ); | |
// Remove any assigned widths from the footer (from scrolling) | |
tmpTable.find('tfoot th, tfoot td').css('width', ''); | |
// Apply custom sizing to the cloned header | |
headerCells = fns._fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] ); | |
for ( i=0 ; i<visibleColumns.length ; i++ ) { | |
column = columns[ visibleColumns[i] ]; | |
if ( i == fillerColumnIdx ) { | |
headerCells[i].style.width = '0px'; | |
} | |
else { | |
headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ? | |
fns._fnStringToCss( column.sWidthOrig ) : | |
''; | |
// For scrollX we need to force the column width otherwise the | |
// browser will collapse it. If this width is smaller than the | |
// width the column requires, then it will have no effect | |
if ( column.sWidthOrig && scrollX ) { | |
$( headerCells[i] ).append( $('<div/>').css( { | |
width: column.sWidthOrig, | |
margin: 0, | |
padding: 0, | |
border: 0, | |
height: 1 | |
} ) ); | |
} | |
} | |
} | |
// Find the widest cell for each column and put it into the table | |
if ( oSettings.aoData.length ) { | |
for ( i=0 ; i<visibleColumns.length ; i++ ) { | |
if ( i == fillerColumnIdx ) continue; | |
columnIdx = visibleColumns[i]; | |
column = columns[ columnIdx ]; | |
$( fns._fnGetWidestNode( oSettings, columnIdx ) ) | |
.clone( false ) | |
.append( column.sContentPadding ) | |
.appendTo( tr ); | |
} | |
} | |
// Tidy the temporary table - remove name attributes so there aren't | |
// duplicated in the dom (radio elements for example) | |
$('[name]', tmpTable).removeAttr('name'); | |
// Table has been built, attach to the document so we can work with it. | |
// A holding element is used, positioned at the top of the container | |
// with minimal height, so it has no effect on if the container scrolls | |
// or not. Otherwise it might trigger scrolling when it actually isn't | |
// needed | |
var holder = $('<div/>').css( scrollX || scrollY ? | |
{ | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
height: 1, | |
right: 0, | |
overflow: 'hidden' | |
} : | |
{} | |
) | |
.append( tmpTable ) | |
.appendTo( tableContainer ); | |
// remove constraints on width. | |
tmpTable.css( 'width', 'auto' ); | |
tmpTable.removeAttr('width'); | |
desiredWidth = ( tableWidthAttr || tableContainer.clientWidth ); | |
//console.log('desired width ' + desiredWidth); | |
//console.log('actual width ' + tmpTable.width()); | |
// Is the table still at least as big as it's space? | |
// If so, there is no filling to be done. | |
if ( tmpTable.width() >= desiredWidth ) { | |
holder.remove(); | |
//console.log('...is less than ' + tmpTable.width() + ' so skipping'); | |
// remove any previously assigned width. | |
// todo: restore any user-asigned width instead? | |
columns[ visibleColumns[fillerColumnIdx] ].nTh.style.width = ''; | |
return; | |
} | |
// Get the width of each column in the constructed table - we need to | |
// know the inner width (so it can be assigned to the other table's | |
// cells) and the outer width so we can calculate the full width of the | |
// table. This is safe since DataTables requires a unique cell for each | |
// column, but if ever a header can span multiple columns, this will | |
// need to be modified. | |
var total = 0; | |
for ( i=0 ; i<visibleColumns.length ; i++ ) { | |
if ( i == fillerColumnIdx ) continue; | |
var cell = $(headerCells[i]); | |
var border = cell.outerWidth() - cell.width(); | |
// Use getBounding... where possible (not IE8-) because it can give | |
// sub-pixel accuracy, which we then want to round up! | |
var bounding = browser.bBounding ? | |
Math.ceil( headerCells[i].getBoundingClientRect().width ) : | |
cell.outerWidth(); | |
// Total is tracked to remove any sub-pixel errors as the outerWidth | |
// of the table might not equal the total given here (IE!). | |
total += bounding; | |
// Width for each regular column to use | |
columns[ visibleColumns[i] ].sWidth = fns._fnStringToCss( bounding - border ); | |
} | |
//console.log('total from columns is ' + total); | |
// Finished with the table - ditch it | |
holder.remove(); | |
// Copy over to the table css. | |
var cIdx = visibleColumns[fillerColumnIdx]; | |
for ( i=0 ; i<columns.length ; i++ ) { | |
if ( i == cIdx ) continue; | |
//console.log('Assigned width ' + columns[i].sWidth + ' to column ' + i); | |
columns[i].nTh.style.width = columns[i].sWidth; | |
} | |
// and set the filler column to have the leftovers. | |
//console.log('filler column gets ' + (desiredWidth-total)); | |
columns[cIdx].nTh.style.width = fns._fnStringToCss( desiredWidth-total ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment