mirror of
https://github.com/Mottie/tablesorter.git
synced 2024-11-15 23:54:22 +00:00
e8bc2bef2e
The "refreshColumnSelector" method can now update the contents of the container in case the header text, column priorities, etc change
392 lines
14 KiB
JavaScript
392 lines
14 KiB
JavaScript
/* Widget: columnSelector (responsive table widget) - updated 3/5/2015 (v2.21.0) *//*
|
|
* Requires tablesorter v2.8+ and jQuery 1.7+
|
|
* by Justin Hallett & Rob Garrison
|
|
*/
|
|
/*jshint browser:true, jquery:true, unused:false */
|
|
/*global jQuery: false */
|
|
;(function($){
|
|
'use strict';
|
|
|
|
var ts = $.tablesorter,
|
|
namespace = '.tscolsel',
|
|
tsColSel = ts.columnSelector = {
|
|
|
|
queryAll : '@media only all { [columns] { display: none; } } ',
|
|
queryBreak : '@media all and (min-width: [size]) { [columns] { display: table-cell; } } ',
|
|
|
|
init: function(table, c, wo) {
|
|
var $t, colSel;
|
|
|
|
// abort if no input is contained within the layout
|
|
$t = $(wo.columnSelector_layout);
|
|
if (!$t.find('input').add( $t.filter('input') ).length) {
|
|
if (c.debug) {
|
|
console.error('ColumnSelector: >> ERROR: Column Selector aborting, no input found in the layout! ***');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// unique table class name
|
|
c.$table.addClass( c.namespace.slice(1) + 'columnselector' );
|
|
|
|
// build column selector/state array
|
|
colSel = c.selector = { $container : $(wo.columnSelector_container || '<div>') };
|
|
colSel.$style = $('<style></style>').prop('disabled', true).appendTo('head');
|
|
colSel.$breakpoints = $('<style></style>').prop('disabled', true).appendTo('head');
|
|
|
|
colSel.isInitializing = true;
|
|
tsColSel.setupSelector(c, wo);
|
|
|
|
if (wo.columnSelector_mediaquery) {
|
|
tsColSel.setupBreakpoints(c, wo);
|
|
}
|
|
|
|
colSel.isInitializing = false;
|
|
if (colSel.$container.length) {
|
|
tsColSel.updateCols(c, wo);
|
|
} else if (c.debug) {
|
|
console.warn('ColumnSelector: >> container not found');
|
|
}
|
|
|
|
c.$table
|
|
.off('refreshColumnSelector' + namespace)
|
|
/* $('table').trigger('refreshColumnSelector', arguments ); showing arguments below
|
|
undefined = refresh current settings (update css hiding columns)
|
|
'selectors' = update container contents (replace inputs/labels)
|
|
[ [2,3,4] ] = set visible columns; turn off "auto" mode.
|
|
[ 'columns', [2,3,4] ] = set visible columns; turn off "auto" mode.
|
|
[ 'auto', [2,3,4] ] = set visible columns; turn on "auto" mode.
|
|
true = turn on "auto" mode.
|
|
*/
|
|
.on('refreshColumnSelector' + namespace, function( e, optName, optState ){
|
|
// make sure we're using current config settings
|
|
tsColSel.refreshColumns( this.config, optName, optState );
|
|
});
|
|
|
|
},
|
|
|
|
refreshColumns: function( c, optName, optState ) {
|
|
var i, arry,
|
|
isArry = $.isArray(optState || optName),
|
|
wo = c.widgetOptions;
|
|
// see #798
|
|
if (typeof optName !== 'undefined' && c.selector.$container.length) {
|
|
// pass "selectors" to update the all of the container contents
|
|
if ( optName === 'selectors' ) {
|
|
c.selector.$container.empty();
|
|
tsColSel.setupSelector(c, wo);
|
|
tsColSel.setupBreakpoints(c, wo);
|
|
// if optState is undefined, maintain the current "auto" state
|
|
if ( typeof optState === 'undefined' ) {
|
|
optState = c.selector.auto;
|
|
}
|
|
}
|
|
// pass an array of column zero-based indexes to turn off auto mode & toggle selected columns
|
|
if (isArry) {
|
|
arry = optState || optName;
|
|
// make sure array contains numbers
|
|
$.each(arry, function(i, v){
|
|
arry[i] = parseInt(v, 10);
|
|
});
|
|
for (i = 0; i < c.columns; i++) {
|
|
c.selector.$container
|
|
.find('input[data-column=' + i + ']')
|
|
.prop('checked', $.inArray( i, arry ) >= 0 );
|
|
}
|
|
}
|
|
// if passing an array, set auto to false to allow manual column selection & update columns
|
|
// refreshColumns( c, 'auto', true ) === refreshColumns( c, true );
|
|
tsColSel
|
|
.updateAuto( c, wo, c.selector.$container.find('input[data-column="auto"]')
|
|
.prop('checked', optState === true || optName === true || optName === 'auto' && optState !== false) );
|
|
} else {
|
|
tsColSel.updateBreakpoints(c, wo);
|
|
tsColSel.updateCols(c, wo);
|
|
}
|
|
},
|
|
|
|
setupSelector: function(c, wo) {
|
|
var name,
|
|
colSel = c.selector,
|
|
$container = colSel.$container,
|
|
useStorage = wo.columnSelector_saveColumns && ts.storage,
|
|
// get stored column states
|
|
saved = useStorage ? ts.storage( c.table, 'tablesorter-columnSelector' ) : [],
|
|
state = useStorage ? ts.storage( c.table, 'tablesorter-columnSelector-auto') : {};
|
|
|
|
// initial states
|
|
colSel.auto = $.isEmptyObject(state) || $.type(state.auto) !== 'boolean' ? wo.columnSelector_mediaqueryState : state.auto;
|
|
colSel.states = [];
|
|
colSel.$column = [];
|
|
colSel.$wrapper = [];
|
|
colSel.$checkbox = [];
|
|
// populate the selector container
|
|
c.$table.children('thead').find('tr:first th', c.table).each(function() {
|
|
var $this = $(this),
|
|
// if no data-priority is assigned, default to 1, but don't remove it from the selector list
|
|
priority = $this.attr(wo.columnSelector_priority) || 1,
|
|
colId = $this.attr('data-column'),
|
|
state = ts.getData(this, c.headers[colId], 'columnSelector');
|
|
|
|
// if this column not hidable at all
|
|
// include getData check (includes 'columnSelector-false' class, data attribute, etc)
|
|
if ( isNaN(priority) && priority.length > 0 || state === 'disable' ||
|
|
( wo.columnSelector_columns[colId] && wo.columnSelector_columns[colId] === 'disable') ) {
|
|
return true; // goto next
|
|
}
|
|
|
|
// set default state; storage takes priority
|
|
colSel.states[colId] = saved && typeof saved[colId] !== 'undefined' ?
|
|
saved[colId] : typeof wo.columnSelector_columns[colId] !== 'undefined' ?
|
|
wo.columnSelector_columns[colId] : (state === 'true' || state !== 'false');
|
|
colSel.$column[colId] = $(this);
|
|
|
|
// set default col title
|
|
name = $this.attr(wo.columnSelector_name) || $this.text();
|
|
if ($container.length) {
|
|
colSel.$wrapper[colId] = $(wo.columnSelector_layout.replace(/\{name\}/g, name)).appendTo($container);
|
|
colSel.$checkbox[colId] = colSel.$wrapper[colId]
|
|
// input may not be wrapped within the layout template
|
|
.find('input').add( colSel.$wrapper[colId].filter('input') )
|
|
.attr('data-column', colId)
|
|
.toggleClass( wo.columnSelector_cssChecked, colSel.states[colId] )
|
|
.prop('checked', colSel.states[colId])
|
|
.on('change', function(){
|
|
colSel.states[colId] = this.checked;
|
|
tsColSel.updateCols(c, wo);
|
|
}).change();
|
|
}
|
|
});
|
|
|
|
},
|
|
|
|
setupBreakpoints: function(c, wo){
|
|
var colSel = c.selector;
|
|
|
|
// add responsive breakpoints
|
|
if (wo.columnSelector_mediaquery) {
|
|
// used by window resize function
|
|
colSel.lastIndex = -1;
|
|
tsColSel.updateBreakpoints(c, wo);
|
|
c.$table
|
|
.off('updateAll' + namespace)
|
|
.on('updateAll' + namespace, function(){
|
|
tsColSel.updateBreakpoints(c, wo);
|
|
tsColSel.updateCols(c, wo);
|
|
});
|
|
}
|
|
|
|
if (colSel.$container.length) {
|
|
// Add media queries toggle
|
|
if (wo.columnSelector_mediaquery) {
|
|
colSel.$auto = $( wo.columnSelector_layout.replace(/\{name\}/g, wo.columnSelector_mediaqueryName) ).prependTo(colSel.$container);
|
|
colSel.$auto
|
|
// needed in case the input in the layout is not wrapped
|
|
.find('input').add( colSel.$auto.filter('input') )
|
|
.attr('data-column', 'auto')
|
|
.prop('checked', colSel.auto)
|
|
.toggleClass( wo.columnSelector_cssChecked, colSel.auto )
|
|
.on('change', function(){
|
|
tsColSel.updateAuto(c, wo, $(this));
|
|
}).change();
|
|
}
|
|
// Add a bind on update to re-run col setup
|
|
c.$table.off('update' + namespace).on('update' + namespace, function() {
|
|
tsColSel.updateCols(c, wo);
|
|
});
|
|
}
|
|
},
|
|
|
|
updateAuto: function(c, wo, $el) {
|
|
var colSel = c.selector;
|
|
colSel.auto = $el.prop('checked') || false;
|
|
$.each( colSel.$checkbox, function(i, $cb){
|
|
if ($cb) {
|
|
$cb[0].disabled = colSel.auto;
|
|
colSel.$wrapper[i].toggleClass('disabled', colSel.auto);
|
|
}
|
|
});
|
|
if (wo.columnSelector_mediaquery) {
|
|
tsColSel.updateBreakpoints(c, wo);
|
|
}
|
|
tsColSel.updateCols(c, wo);
|
|
// copy the column selector to a popup/tooltip
|
|
if (c.selector.$popup) {
|
|
c.selector.$popup.find('.tablesorter-column-selector')
|
|
.html( colSel.$container.html() )
|
|
.find('input').each(function(){
|
|
var indx = $(this).attr('data-column');
|
|
$(this).prop( 'checked', indx === 'auto' ? colSel.auto : colSel.states[indx] );
|
|
});
|
|
}
|
|
if (wo.columnSelector_saveColumns && ts.storage) {
|
|
ts.storage( c.$table[0], 'tablesorter-columnSelector-auto', { auto : colSel.auto } );
|
|
}
|
|
// trigger columnUpdate if auto is true (it gets skipped in updateCols()
|
|
if (colSel.auto) {
|
|
c.$table.trigger('columnUpdate');
|
|
}
|
|
},
|
|
|
|
updateBreakpoints: function(c, wo) {
|
|
var priority, column, breaks, temp,
|
|
colSel = c.selector,
|
|
prefix = c.namespace + 'columnselector',
|
|
mediaAll = [],
|
|
breakpts = '';
|
|
if (wo.columnSelector_mediaquery && !colSel.auto) {
|
|
colSel.$breakpoints.prop('disabled', true);
|
|
colSel.$style.prop('disabled', false);
|
|
return;
|
|
}
|
|
|
|
// only 6 breakpoints (same as jQuery Mobile)
|
|
for (priority = 0; priority < 6; priority++){
|
|
/*jshint loopfunc:true */
|
|
breaks = [];
|
|
c.$headers.filter('[' + wo.columnSelector_priority + '=' + (priority + 1) + ']').each(function(){
|
|
column = parseInt($(this).attr('data-column'), 10) + 1;
|
|
temp = ' col:nth-child(' + column + ')';
|
|
breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp);
|
|
temp = ' tr th:nth-child(' + column + ')';
|
|
breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp);
|
|
temp = ' tr td:nth-child(' + column + ')';
|
|
breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp);
|
|
});
|
|
if (breaks.length) {
|
|
mediaAll = mediaAll.concat( breaks );
|
|
breakpts += tsColSel.queryBreak
|
|
.replace(/\[size\]/g, wo.columnSelector_breakpoints[priority])
|
|
.replace(/\[columns\]/g, breaks.join(','));
|
|
}
|
|
}
|
|
if (colSel.$style) {
|
|
colSel.$style.prop('disabled', true);
|
|
}
|
|
if (mediaAll.length) {
|
|
colSel.$breakpoints
|
|
.prop('disabled', false)
|
|
.html( tsColSel.queryAll.replace(/\[columns\]/g, mediaAll.join(',')) + breakpts );
|
|
}
|
|
},
|
|
|
|
updateCols: function(c, wo) {
|
|
if (wo.columnSelector_mediaquery && c.selector.auto || c.selector.isInitializing) {
|
|
return;
|
|
}
|
|
var column, temp,
|
|
colSel = c.selector,
|
|
styles = [],
|
|
prefix = c.namespace + 'columnselector';
|
|
colSel.$container.find('input[data-column]').filter('[data-column!="auto"]').each(function(){
|
|
if (!this.checked) {
|
|
column = parseInt( $(this).attr('data-column'), 10 ) + 1;
|
|
temp = ' col:nth-child(' + column + ')';
|
|
styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp);
|
|
temp = ' tr th:nth-child(' + column + ')';
|
|
styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp);
|
|
temp = ' tr td:nth-child(' + column + ')';
|
|
styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp);
|
|
}
|
|
$(this).toggleClass( wo.columnSelector_cssChecked, this.checked );
|
|
});
|
|
if (wo.columnSelector_mediaquery){
|
|
colSel.$breakpoints.prop('disabled', true);
|
|
}
|
|
if (colSel.$style) {
|
|
colSel.$style.prop('disabled', false).html( styles.length ? styles.join(',') + ' { display: none; }' : '' );
|
|
}
|
|
if (wo.columnSelector_saveColumns && ts.storage) {
|
|
ts.storage( c.$table[0], 'tablesorter-columnSelector', colSel.states );
|
|
}
|
|
c.$table.trigger('columnUpdate');
|
|
},
|
|
|
|
attachTo : function(table, elm) {
|
|
table = $(table)[0];
|
|
var colSel, wo, indx,
|
|
c = table.config,
|
|
$popup = $(elm);
|
|
if ($popup.length && c) {
|
|
if (!$popup.find('.tablesorter-column-selector').length) {
|
|
// add a wrapper to add the selector into, in case the popup has other content
|
|
$popup.append('<span class="tablesorter-column-selector"></span>');
|
|
}
|
|
colSel = c.selector;
|
|
wo = c.widgetOptions;
|
|
$popup.find('.tablesorter-column-selector')
|
|
.html( colSel.$container.html() )
|
|
.find('input').each(function(){
|
|
var indx = $(this).attr('data-column'),
|
|
isChecked = indx === 'auto' ? colSel.auto : colSel.states[indx];
|
|
$(this)
|
|
.toggleClass( wo.columnSelector_cssChecked, isChecked )
|
|
.prop( 'checked', isChecked );
|
|
});
|
|
colSel.$popup = $popup.on('change', 'input', function(){
|
|
// data input
|
|
indx = $(this).toggleClass( wo.columnSelector_cssChecked, this.checked ).attr('data-column');
|
|
// update original popup
|
|
colSel.$container.find('input[data-column="' + indx + '"]')
|
|
.prop('checked', this.checked)
|
|
.trigger('change');
|
|
});
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
ts.addWidget({
|
|
id: 'columnSelector',
|
|
priority: 10,
|
|
options: {
|
|
// target the column selector markup
|
|
columnSelector_container : null,
|
|
// column status, true = display, false = hide
|
|
// disable = do not display on list
|
|
columnSelector_columns : {},
|
|
// remember selected columns
|
|
columnSelector_saveColumns: true,
|
|
|
|
// container layout
|
|
columnSelector_layout : '<label><input type="checkbox">{name}</label>',
|
|
// data attribute containing column name to use in the selector container
|
|
columnSelector_name : 'data-selector-name',
|
|
|
|
/* Responsive Media Query settings */
|
|
// enable/disable mediaquery breakpoints
|
|
columnSelector_mediaquery: true,
|
|
// toggle checkbox name
|
|
columnSelector_mediaqueryName: 'Auto: ',
|
|
// breakpoints checkbox initial setting
|
|
columnSelector_mediaqueryState: true,
|
|
// responsive table hides columns with priority 1-6 at these breakpoints
|
|
// see http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/#Applyingapresetbreakpoint
|
|
// *** set to false to disable ***
|
|
columnSelector_breakpoints : [ '20em', '30em', '40em', '50em', '60em', '70em' ],
|
|
// data attribute containing column priority
|
|
// duplicates how jQuery mobile uses priorities:
|
|
// http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/
|
|
columnSelector_priority : 'data-priority',
|
|
// class name added to checked checkboxes - this fixes an issue with Chrome not updating FontAwesome
|
|
// applied icons; use this class name (input.checked) instead of input:checked
|
|
columnSelector_cssChecked : 'checked'
|
|
|
|
},
|
|
init: function(table, thisWidget, c, wo) {
|
|
tsColSel.init(table, c, wo);
|
|
},
|
|
remove: function(table, c, wo, refreshing) {
|
|
if (refreshing) { return; }
|
|
var csel = c.selector;
|
|
csel.$container.empty();
|
|
if (csel.$popup) { csel.$popup.empty(); }
|
|
csel.$style.remove();
|
|
csel.$breakpoints.remove();
|
|
c.$table.off('updateAll' + namespace + ' update' + namespace);
|
|
}
|
|
|
|
});
|
|
|
|
})(jQuery);
|