1207 lines
32 KiB
JavaScript
1207 lines
32 KiB
JavaScript
|
/*! Select for DataTables 1.3.1
|
||
|
* 2015-2019 SpryMedia Ltd - datatables.net/license/mit
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @summary Select for DataTables
|
||
|
* @description A collection of API methods, events and buttons for DataTables
|
||
|
* that provides selection options of the items in a DataTable
|
||
|
* @version 1.3.1
|
||
|
* @file dataTables.select.js
|
||
|
* @author SpryMedia Ltd (www.sprymedia.co.uk)
|
||
|
* @contact datatables.net/forums
|
||
|
* @copyright Copyright 2015-2019 SpryMedia Ltd.
|
||
|
*
|
||
|
* This source file is free software, available under the following license:
|
||
|
* MIT license - http://datatables.net/license/mit
|
||
|
*
|
||
|
* This source file is distributed in the hope that it will be useful, but
|
||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
|
||
|
*
|
||
|
* For details please refer to: http://www.datatables.net/extensions/select
|
||
|
*/
|
||
|
(function( factory ){
|
||
|
if ( typeof define === 'function' && define.amd ) {
|
||
|
// AMD
|
||
|
define( ['jquery', 'datatables.net'], function ( $ ) {
|
||
|
return factory( $, window, document );
|
||
|
} );
|
||
|
}
|
||
|
else if ( typeof exports === 'object' ) {
|
||
|
// CommonJS
|
||
|
module.exports = function (root, $) {
|
||
|
if ( ! root ) {
|
||
|
root = window;
|
||
|
}
|
||
|
|
||
|
if ( ! $ || ! $.fn.dataTable ) {
|
||
|
$ = require('datatables.net')(root, $).$;
|
||
|
}
|
||
|
|
||
|
return factory( $, root, root.document );
|
||
|
};
|
||
|
}
|
||
|
else {
|
||
|
// Browser
|
||
|
factory( jQuery, window, document );
|
||
|
}
|
||
|
}(function( $, window, document, undefined ) {
|
||
|
'use strict';
|
||
|
var DataTable = $.fn.dataTable;
|
||
|
|
||
|
|
||
|
// Version information for debugger
|
||
|
DataTable.select = {};
|
||
|
|
||
|
DataTable.select.version = '1.3.1';
|
||
|
|
||
|
DataTable.select.init = function ( dt ) {
|
||
|
var ctx = dt.settings()[0];
|
||
|
var init = ctx.oInit.select;
|
||
|
var defaults = DataTable.defaults.select;
|
||
|
var opts = init === undefined ?
|
||
|
defaults :
|
||
|
init;
|
||
|
|
||
|
// Set defaults
|
||
|
var items = 'row';
|
||
|
var style = 'api';
|
||
|
var blurable = false;
|
||
|
var toggleable = true;
|
||
|
var info = true;
|
||
|
var selector = 'td, th';
|
||
|
var className = 'selected';
|
||
|
var setStyle = false;
|
||
|
|
||
|
ctx._select = {};
|
||
|
|
||
|
// Initialisation customisations
|
||
|
if ( opts === true ) {
|
||
|
style = 'os';
|
||
|
setStyle = true;
|
||
|
}
|
||
|
else if ( typeof opts === 'string' ) {
|
||
|
style = opts;
|
||
|
setStyle = true;
|
||
|
}
|
||
|
else if ( $.isPlainObject( opts ) ) {
|
||
|
if ( opts.blurable !== undefined ) {
|
||
|
blurable = opts.blurable;
|
||
|
}
|
||
|
|
||
|
if ( opts.toggleable !== undefined ) {
|
||
|
toggleable = opts.toggleable;
|
||
|
}
|
||
|
|
||
|
if ( opts.info !== undefined ) {
|
||
|
info = opts.info;
|
||
|
}
|
||
|
|
||
|
if ( opts.items !== undefined ) {
|
||
|
items = opts.items;
|
||
|
}
|
||
|
|
||
|
if ( opts.style !== undefined ) {
|
||
|
style = opts.style;
|
||
|
setStyle = true;
|
||
|
}
|
||
|
else {
|
||
|
style = 'os';
|
||
|
setStyle = true;
|
||
|
}
|
||
|
|
||
|
if ( opts.selector !== undefined ) {
|
||
|
selector = opts.selector;
|
||
|
}
|
||
|
|
||
|
if ( opts.className !== undefined ) {
|
||
|
className = opts.className;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dt.select.selector( selector );
|
||
|
dt.select.items( items );
|
||
|
dt.select.style( style );
|
||
|
dt.select.blurable( blurable );
|
||
|
dt.select.toggleable( toggleable );
|
||
|
dt.select.info( info );
|
||
|
ctx._select.className = className;
|
||
|
|
||
|
|
||
|
// Sort table based on selected rows. Requires Select Datatables extension
|
||
|
$.fn.dataTable.ext.order['select-checkbox'] = function ( settings, col ) {
|
||
|
return this.api().column( col, {order: 'index'} ).nodes().map( function ( td ) {
|
||
|
if ( settings._select.items === 'row' ) {
|
||
|
return $( td ).parent().hasClass( settings._select.className );
|
||
|
} else if ( settings._select.items === 'cell' ) {
|
||
|
return $( td ).hasClass( settings._select.className );
|
||
|
}
|
||
|
return false;
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// If the init options haven't enabled select, but there is a selectable
|
||
|
// class name, then enable
|
||
|
if ( ! setStyle && $( dt.table().node() ).hasClass( 'selectable' ) ) {
|
||
|
dt.select.style( 'os' );
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
|
||
|
Select is a collection of API methods, event handlers, event emitters and
|
||
|
buttons (for the `Buttons` extension) for DataTables. It provides the following
|
||
|
features, with an overview of how they are implemented:
|
||
|
|
||
|
## Selection of rows, columns and cells. Whether an item is selected or not is
|
||
|
stored in:
|
||
|
|
||
|
* rows: a `_select_selected` property which contains a boolean value of the
|
||
|
DataTables' `aoData` object for each row
|
||
|
* columns: a `_select_selected` property which contains a boolean value of the
|
||
|
DataTables' `aoColumns` object for each column
|
||
|
* cells: a `_selected_cells` property which contains an array of boolean values
|
||
|
of the `aoData` object for each row. The array is the same length as the
|
||
|
columns array, with each element of it representing a cell.
|
||
|
|
||
|
This method of using boolean flags allows Select to operate when nodes have not
|
||
|
been created for rows / cells (DataTables' defer rendering feature).
|
||
|
|
||
|
## API methods
|
||
|
|
||
|
A range of API methods are available for triggering selection and de-selection
|
||
|
of rows. Methods are also available to configure the selection events that can
|
||
|
be triggered by an end user (such as which items are to be selected). To a large
|
||
|
extent, these of API methods *is* Select. It is basically a collection of helper
|
||
|
functions that can be used to select items in a DataTable.
|
||
|
|
||
|
Configuration of select is held in the object `_select` which is attached to the
|
||
|
DataTables settings object on initialisation. Select being available on a table
|
||
|
is not optional when Select is loaded, but its default is for selection only to
|
||
|
be available via the API - so the end user wouldn't be able to select rows
|
||
|
without additional configuration.
|
||
|
|
||
|
The `_select` object contains the following properties:
|
||
|
|
||
|
```
|
||
|
{
|
||
|
items:string - Can be `rows`, `columns` or `cells`. Defines what item
|
||
|
will be selected if the user is allowed to activate row
|
||
|
selection using the mouse.
|
||
|
style:string - Can be `none`, `single`, `multi` or `os`. Defines the
|
||
|
interaction style when selecting items
|
||
|
blurable:boolean - If row selection can be cleared by clicking outside of
|
||
|
the table
|
||
|
toggleable:boolean - If row selection can be cancelled by repeated clicking
|
||
|
on the row
|
||
|
info:boolean - If the selection summary should be shown in the table
|
||
|
information elements
|
||
|
}
|
||
|
```
|
||
|
|
||
|
In addition to the API methods, Select also extends the DataTables selector
|
||
|
options for rows, columns and cells adding a `selected` option to the selector
|
||
|
options object, allowing the developer to select only selected items or
|
||
|
unselected items.
|
||
|
|
||
|
## Mouse selection of items
|
||
|
|
||
|
Clicking on items can be used to select items. This is done by a simple event
|
||
|
handler that will select the items using the API methods.
|
||
|
|
||
|
*/
|
||
|
|
||
|
|
||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
* Local functions
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Add one or more cells to the selection when shift clicking in OS selection
|
||
|
* style cell selection.
|
||
|
*
|
||
|
* Cell range is more complicated than row and column as we want to select
|
||
|
* in the visible grid rather than by index in sequence. For example, if you
|
||
|
* click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1
|
||
|
* should also be selected (and not 1-3, 1-4. etc)
|
||
|
*
|
||
|
* @param {DataTable.Api} dt DataTable
|
||
|
* @param {object} idx Cell index to select to
|
||
|
* @param {object} last Cell index to select from
|
||
|
* @private
|
||
|
*/
|
||
|
function cellRange( dt, idx, last )
|
||
|
{
|
||
|
var indexes;
|
||
|
var columnIndexes;
|
||
|
var rowIndexes;
|
||
|
var selectColumns = function ( start, end ) {
|
||
|
if ( start > end ) {
|
||
|
var tmp = end;
|
||
|
end = start;
|
||
|
start = tmp;
|
||
|
}
|
||
|
|
||
|
var record = false;
|
||
|
return dt.columns( ':visible' ).indexes().filter( function (i) {
|
||
|
if ( i === start ) {
|
||
|
record = true;
|
||
|
}
|
||
|
|
||
|
if ( i === end ) { // not else if, as start might === end
|
||
|
record = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return record;
|
||
|
} );
|
||
|
};
|
||
|
|
||
|
var selectRows = function ( start, end ) {
|
||
|
var indexes = dt.rows( { search: 'applied' } ).indexes();
|
||
|
|
||
|
// Which comes first - might need to swap
|
||
|
if ( indexes.indexOf( start ) > indexes.indexOf( end ) ) {
|
||
|
var tmp = end;
|
||
|
end = start;
|
||
|
start = tmp;
|
||
|
}
|
||
|
|
||
|
var record = false;
|
||
|
return indexes.filter( function (i) {
|
||
|
if ( i === start ) {
|
||
|
record = true;
|
||
|
}
|
||
|
|
||
|
if ( i === end ) {
|
||
|
record = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return record;
|
||
|
} );
|
||
|
};
|
||
|
|
||
|
if ( ! dt.cells( { selected: true } ).any() && ! last ) {
|
||
|
// select from the top left cell to this one
|
||
|
columnIndexes = selectColumns( 0, idx.column );
|
||
|
rowIndexes = selectRows( 0 , idx.row );
|
||
|
}
|
||
|
else {
|
||
|
// Get column indexes between old and new
|
||
|
columnIndexes = selectColumns( last.column, idx.column );
|
||
|
rowIndexes = selectRows( last.row , idx.row );
|
||
|
}
|
||
|
|
||
|
indexes = dt.cells( rowIndexes, columnIndexes ).flatten();
|
||
|
|
||
|
if ( ! dt.cells( idx, { selected: true } ).any() ) {
|
||
|
// Select range
|
||
|
dt.cells( indexes ).select();
|
||
|
}
|
||
|
else {
|
||
|
// Deselect range
|
||
|
dt.cells( indexes ).deselect();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Disable mouse selection by removing the selectors
|
||
|
*
|
||
|
* @param {DataTable.Api} dt DataTable to remove events from
|
||
|
* @private
|
||
|
*/
|
||
|
function disableMouseSelection( dt )
|
||
|
{
|
||
|
var ctx = dt.settings()[0];
|
||
|
var selector = ctx._select.selector;
|
||
|
|
||
|
$( dt.table().container() )
|
||
|
.off( 'mousedown.dtSelect', selector )
|
||
|
.off( 'mouseup.dtSelect', selector )
|
||
|
.off( 'click.dtSelect', selector );
|
||
|
|
||
|
$('body').off( 'click.dtSelect' + _safeId(dt.table().node()) );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attach mouse listeners to the table to allow mouse selection of items
|
||
|
*
|
||
|
* @param {DataTable.Api} dt DataTable to remove events from
|
||
|
* @private
|
||
|
*/
|
||
|
function enableMouseSelection ( dt )
|
||
|
{
|
||
|
var container = $( dt.table().container() );
|
||
|
var ctx = dt.settings()[0];
|
||
|
var selector = ctx._select.selector;
|
||
|
var matchSelection;
|
||
|
|
||
|
container
|
||
|
.on( 'mousedown.dtSelect', selector, function(e) {
|
||
|
// Disallow text selection for shift clicking on the table so multi
|
||
|
// element selection doesn't look terrible!
|
||
|
if ( e.shiftKey || e.metaKey || e.ctrlKey ) {
|
||
|
container
|
||
|
.css( '-moz-user-select', 'none' )
|
||
|
.one('selectstart.dtSelect', selector, function () {
|
||
|
return false;
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
if ( window.getSelection ) {
|
||
|
matchSelection = window.getSelection();
|
||
|
}
|
||
|
} )
|
||
|
.on( 'mouseup.dtSelect', selector, function() {
|
||
|
// Allow text selection to occur again, Mozilla style (tested in FF
|
||
|
// 35.0.1 - still required)
|
||
|
container.css( '-moz-user-select', '' );
|
||
|
} )
|
||
|
.on( 'click.dtSelect', selector, function ( e ) {
|
||
|
var items = dt.select.items();
|
||
|
var idx;
|
||
|
|
||
|
// If text was selected (click and drag), then we shouldn't change
|
||
|
// the row's selected state
|
||
|
if ( matchSelection ) {
|
||
|
var selection = window.getSelection();
|
||
|
|
||
|
// If the element that contains the selection is not in the table, we can ignore it
|
||
|
// This can happen if the developer selects text from the click event
|
||
|
if ( ! selection.anchorNode || $(selection.anchorNode).closest('table')[0] === dt.table().node() ) {
|
||
|
if ( selection !== matchSelection ) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var ctx = dt.settings()[0];
|
||
|
var wrapperClass = $.trim(dt.settings()[0].oClasses.sWrapper).replace(/ +/g, '.');
|
||
|
|
||
|
// Ignore clicks inside a sub-table
|
||
|
if ( $(e.target).closest('div.'+wrapperClass)[0] != dt.table().container() ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var cell = dt.cell( $(e.target).closest('td, th') );
|
||
|
|
||
|
// Check the cell actually belongs to the host DataTable (so child
|
||
|
// rows, etc, are ignored)
|
||
|
if ( ! cell.any() ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var event = $.Event('user-select.dt');
|
||
|
eventTrigger( dt, event, [ items, cell, e ] );
|
||
|
|
||
|
if ( event.isDefaultPrevented() ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var cellIndex = cell.index();
|
||
|
if ( items === 'row' ) {
|
||
|
idx = cellIndex.row;
|
||
|
typeSelect( e, dt, ctx, 'row', idx );
|
||
|
}
|
||
|
else if ( items === 'column' ) {
|
||
|
idx = cell.index().column;
|
||
|
typeSelect( e, dt, ctx, 'column', idx );
|
||
|
}
|
||
|
else if ( items === 'cell' ) {
|
||
|
idx = cell.index();
|
||
|
typeSelect( e, dt, ctx, 'cell', idx );
|
||
|
}
|
||
|
|
||
|
ctx._select_lastCell = cellIndex;
|
||
|
} );
|
||
|
|
||
|
// Blurable
|
||
|
$('body').on( 'click.dtSelect' + _safeId(dt.table().node()), function ( e ) {
|
||
|
if ( ctx._select.blurable ) {
|
||
|
// If the click was inside the DataTables container, don't blur
|
||
|
if ( $(e.target).parents().filter( dt.table().container() ).length ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Ignore elements which have been removed from the DOM (i.e. paging
|
||
|
// buttons)
|
||
|
if ( $(e.target).parents('html').length === 0 ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Don't blur in Editor form
|
||
|
if ( $(e.target).parents('div.DTE').length ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
clear( ctx, true );
|
||
|
}
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Trigger an event on a DataTable
|
||
|
*
|
||
|
* @param {DataTable.Api} api DataTable to trigger events on
|
||
|
* @param {boolean} selected true if selected, false if deselected
|
||
|
* @param {string} type Item type acting on
|
||
|
* @param {boolean} any Require that there are values before
|
||
|
* triggering
|
||
|
* @private
|
||
|
*/
|
||
|
function eventTrigger ( api, type, args, any )
|
||
|
{
|
||
|
if ( any && ! api.flatten().length ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( typeof type === 'string' ) {
|
||
|
type = type +'.dt';
|
||
|
}
|
||
|
|
||
|
args.unshift( api );
|
||
|
|
||
|
$(api.table().node()).trigger( type, args );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the information element of the DataTable showing information about the
|
||
|
* items selected. This is done by adding tags to the existing text
|
||
|
*
|
||
|
* @param {DataTable.Api} api DataTable to update
|
||
|
* @private
|
||
|
*/
|
||
|
function info ( api )
|
||
|
{
|
||
|
var ctx = api.settings()[0];
|
||
|
|
||
|
if ( ! ctx._select.info || ! ctx.aanFeatures.i ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( api.select.style() === 'api' ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var rows = api.rows( { selected: true } ).flatten().length;
|
||
|
var columns = api.columns( { selected: true } ).flatten().length;
|
||
|
var cells = api.cells( { selected: true } ).flatten().length;
|
||
|
|
||
|
var add = function ( el, name, num ) {
|
||
|
el.append( $('<span class="select-item"/>').append( api.i18n(
|
||
|
'select.'+name+'s',
|
||
|
{ _: '%d '+name+'s selected', 0: '', 1: '1 '+name+' selected' },
|
||
|
num
|
||
|
) ) );
|
||
|
};
|
||
|
|
||
|
// Internal knowledge of DataTables to loop over all information elements
|
||
|
$.each( ctx.aanFeatures.i, function ( i, el ) {
|
||
|
el = $(el);
|
||
|
|
||
|
var output = $('<span class="select-info"/>');
|
||
|
add( output, 'row', rows );
|
||
|
add( output, 'column', columns );
|
||
|
add( output, 'cell', cells );
|
||
|
|
||
|
var exisiting = el.children('span.select-info');
|
||
|
if ( exisiting.length ) {
|
||
|
exisiting.remove();
|
||
|
}
|
||
|
|
||
|
if ( output.text() !== '' ) {
|
||
|
el.append( output );
|
||
|
}
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialisation of a new table. Attach event handlers and callbacks to allow
|
||
|
* Select to operate correctly.
|
||
|
*
|
||
|
* This will occur _after_ the initial DataTables initialisation, although
|
||
|
* before Ajax data is rendered, if there is ajax data
|
||
|
*
|
||
|
* @param {DataTable.settings} ctx Settings object to operate on
|
||
|
* @private
|
||
|
*/
|
||
|
function init ( ctx ) {
|
||
|
var api = new DataTable.Api( ctx );
|
||
|
|
||
|
// Row callback so that classes can be added to rows and cells if the item
|
||
|
// was selected before the element was created. This will happen with the
|
||
|
// `deferRender` option enabled.
|
||
|
//
|
||
|
// This method of attaching to `aoRowCreatedCallback` is a hack until
|
||
|
// DataTables has proper events for row manipulation If you are reviewing
|
||
|
// this code to create your own plug-ins, please do not do this!
|
||
|
ctx.aoRowCreatedCallback.push( {
|
||
|
fn: function ( row, data, index ) {
|
||
|
var i, ien;
|
||
|
var d = ctx.aoData[ index ];
|
||
|
|
||
|
// Row
|
||
|
if ( d._select_selected ) {
|
||
|
$( row ).addClass( ctx._select.className );
|
||
|
}
|
||
|
|
||
|
// Cells and columns - if separated out, we would need to do two
|
||
|
// loops, so it makes sense to combine them into a single one
|
||
|
for ( i=0, ien=ctx.aoColumns.length ; i<ien ; i++ ) {
|
||
|
if ( ctx.aoColumns[i]._select_selected || (d._selected_cells && d._selected_cells[i]) ) {
|
||
|
$(d.anCells[i]).addClass( ctx._select.className );
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
sName: 'select-deferRender'
|
||
|
} );
|
||
|
|
||
|
// On Ajax reload we want to reselect all rows which are currently selected,
|
||
|
// if there is an rowId (i.e. a unique value to identify each row with)
|
||
|
api.on( 'preXhr.dt.dtSelect', function () {
|
||
|
// note that column selection doesn't need to be cached and then
|
||
|
// reselected, as they are already selected
|
||
|
var rows = api.rows( { selected: true } ).ids( true ).filter( function ( d ) {
|
||
|
return d !== undefined;
|
||
|
} );
|
||
|
|
||
|
var cells = api.cells( { selected: true } ).eq(0).map( function ( cellIdx ) {
|
||
|
var id = api.row( cellIdx.row ).id( true );
|
||
|
return id ?
|
||
|
{ row: id, column: cellIdx.column } :
|
||
|
undefined;
|
||
|
} ).filter( function ( d ) {
|
||
|
return d !== undefined;
|
||
|
} );
|
||
|
|
||
|
// On the next draw, reselect the currently selected items
|
||
|
api.one( 'draw.dt.dtSelect', function () {
|
||
|
api.rows( rows ).select();
|
||
|
|
||
|
// `cells` is not a cell index selector, so it needs a loop
|
||
|
if ( cells.any() ) {
|
||
|
cells.each( function ( id ) {
|
||
|
api.cells( id.row, id.column ).select();
|
||
|
} );
|
||
|
}
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
// Update the table information element with selected item summary
|
||
|
api.on( 'draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt info.dt', function () {
|
||
|
info( api );
|
||
|
} );
|
||
|
|
||
|
// Clean up and release
|
||
|
api.on( 'destroy.dtSelect', function () {
|
||
|
disableMouseSelection( api );
|
||
|
api.off( '.dtSelect' );
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add one or more items (rows or columns) to the selection when shift clicking
|
||
|
* in OS selection style
|
||
|
*
|
||
|
* @param {DataTable.Api} dt DataTable
|
||
|
* @param {string} type Row or column range selector
|
||
|
* @param {object} idx Item index to select to
|
||
|
* @param {object} last Item index to select from
|
||
|
* @private
|
||
|
*/
|
||
|
function rowColumnRange( dt, type, idx, last )
|
||
|
{
|
||
|
// Add a range of rows from the last selected row to this one
|
||
|
var indexes = dt[type+'s']( { search: 'applied' } ).indexes();
|
||
|
var idx1 = $.inArray( last, indexes );
|
||
|
var idx2 = $.inArray( idx, indexes );
|
||
|
|
||
|
if ( ! dt[type+'s']( { selected: true } ).any() && idx1 === -1 ) {
|
||
|
// select from top to here - slightly odd, but both Windows and Mac OS
|
||
|
// do this
|
||
|
indexes.splice( $.inArray( idx, indexes )+1, indexes.length );
|
||
|
}
|
||
|
else {
|
||
|
// reverse so we can shift click 'up' as well as down
|
||
|
if ( idx1 > idx2 ) {
|
||
|
var tmp = idx2;
|
||
|
idx2 = idx1;
|
||
|
idx1 = tmp;
|
||
|
}
|
||
|
|
||
|
indexes.splice( idx2+1, indexes.length );
|
||
|
indexes.splice( 0, idx1 );
|
||
|
}
|
||
|
|
||
|
if ( ! dt[type]( idx, { selected: true } ).any() ) {
|
||
|
// Select range
|
||
|
dt[type+'s']( indexes ).select();
|
||
|
}
|
||
|
else {
|
||
|
// Deselect range - need to keep the clicked on row selected
|
||
|
indexes.splice( $.inArray( idx, indexes ), 1 );
|
||
|
dt[type+'s']( indexes ).deselect();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clear all selected items
|
||
|
*
|
||
|
* @param {DataTable.settings} ctx Settings object of the host DataTable
|
||
|
* @param {boolean} [force=false] Force the de-selection to happen, regardless
|
||
|
* of selection style
|
||
|
* @private
|
||
|
*/
|
||
|
function clear( ctx, force )
|
||
|
{
|
||
|
if ( force || ctx._select.style === 'single' ) {
|
||
|
var api = new DataTable.Api( ctx );
|
||
|
|
||
|
api.rows( { selected: true } ).deselect();
|
||
|
api.columns( { selected: true } ).deselect();
|
||
|
api.cells( { selected: true } ).deselect();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Select items based on the current configuration for style and items.
|
||
|
*
|
||
|
* @param {object} e Mouse event object
|
||
|
* @param {DataTables.Api} dt DataTable
|
||
|
* @param {DataTable.settings} ctx Settings object of the host DataTable
|
||
|
* @param {string} type Items to select
|
||
|
* @param {int|object} idx Index of the item to select
|
||
|
* @private
|
||
|
*/
|
||
|
function typeSelect ( e, dt, ctx, type, idx )
|
||
|
{
|
||
|
var style = dt.select.style();
|
||
|
var toggleable = dt.select.toggleable();
|
||
|
var isSelected = dt[type]( idx, { selected: true } ).any();
|
||
|
|
||
|
if ( isSelected && ! toggleable ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( style === 'os' ) {
|
||
|
if ( e.ctrlKey || e.metaKey ) {
|
||
|
// Add or remove from the selection
|
||
|
dt[type]( idx ).select( ! isSelected );
|
||
|
}
|
||
|
else if ( e.shiftKey ) {
|
||
|
if ( type === 'cell' ) {
|
||
|
cellRange( dt, idx, ctx._select_lastCell || null );
|
||
|
}
|
||
|
else {
|
||
|
rowColumnRange( dt, type, idx, ctx._select_lastCell ?
|
||
|
ctx._select_lastCell[type] :
|
||
|
null
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// No cmd or shift click - deselect if selected, or select
|
||
|
// this row only
|
||
|
var selected = dt[type+'s']( { selected: true } );
|
||
|
|
||
|
if ( isSelected && selected.flatten().length === 1 ) {
|
||
|
dt[type]( idx ).deselect();
|
||
|
}
|
||
|
else {
|
||
|
selected.deselect();
|
||
|
dt[type]( idx ).select();
|
||
|
}
|
||
|
}
|
||
|
} else if ( style == 'multi+shift' ) {
|
||
|
if ( e.shiftKey ) {
|
||
|
if ( type === 'cell' ) {
|
||
|
cellRange( dt, idx, ctx._select_lastCell || null );
|
||
|
}
|
||
|
else {
|
||
|
rowColumnRange( dt, type, idx, ctx._select_lastCell ?
|
||
|
ctx._select_lastCell[type] :
|
||
|
null
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
dt[ type ]( idx ).select( ! isSelected );
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
dt[ type ]( idx ).select( ! isSelected );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function _safeId( node ) {
|
||
|
return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-');
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
* DataTables selectors
|
||
|
*/
|
||
|
|
||
|
// row and column are basically identical just assigned to different properties
|
||
|
// and checking a different array, so we can dynamically create the functions to
|
||
|
// reduce the code size
|
||
|
$.each( [
|
||
|
{ type: 'row', prop: 'aoData' },
|
||
|
{ type: 'column', prop: 'aoColumns' }
|
||
|
], function ( i, o ) {
|
||
|
DataTable.ext.selector[ o.type ].push( function ( settings, opts, indexes ) {
|
||
|
var selected = opts.selected;
|
||
|
var data;
|
||
|
var out = [];
|
||
|
|
||
|
if ( selected !== true && selected !== false ) {
|
||
|
return indexes;
|
||
|
}
|
||
|
|
||
|
for ( var i=0, ien=indexes.length ; i<ien ; i++ ) {
|
||
|
data = settings[ o.prop ][ indexes[i] ];
|
||
|
|
||
|
if ( (selected === true && data._select_selected === true) ||
|
||
|
(selected === false && ! data._select_selected )
|
||
|
) {
|
||
|
out.push( indexes[i] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return out;
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
|
||
|
var selected = opts.selected;
|
||
|
var rowData;
|
||
|
var out = [];
|
||
|
|
||
|
if ( selected === undefined ) {
|
||
|
return cells;
|
||
|
}
|
||
|
|
||
|
for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
|
||
|
rowData = settings.aoData[ cells[i].row ];
|
||
|
|
||
|
if ( (selected === true && rowData._selected_cells && rowData._selected_cells[ cells[i].column ] === true) ||
|
||
|
(selected === false && ( ! rowData._selected_cells || ! rowData._selected_cells[ cells[i].column ] ) )
|
||
|
) {
|
||
|
out.push( cells[i] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return out;
|
||
|
} );
|
||
|
|
||
|
|
||
|
|
||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
* DataTables API
|
||
|
*
|
||
|
* For complete documentation, please refer to the docs/api directory or the
|
||
|
* DataTables site
|
||
|
*/
|
||
|
|
||
|
// Local variables to improve compression
|
||
|
var apiRegister = DataTable.Api.register;
|
||
|
var apiRegisterPlural = DataTable.Api.registerPlural;
|
||
|
|
||
|
apiRegister( 'select()', function () {
|
||
|
return this.iterator( 'table', function ( ctx ) {
|
||
|
DataTable.select.init( new DataTable.Api( ctx ) );
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
apiRegister( 'select.blurable()', function ( flag ) {
|
||
|
if ( flag === undefined ) {
|
||
|
return this.context[0]._select.blurable;
|
||
|
}
|
||
|
|
||
|
return this.iterator( 'table', function ( ctx ) {
|
||
|
ctx._select.blurable = flag;
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
apiRegister( 'select.toggleable()', function ( flag ) {
|
||
|
if ( flag === undefined ) {
|
||
|
return this.context[0]._select.toggleable;
|
||
|
}
|
||
|
|
||
|
return this.iterator( 'table', function ( ctx ) {
|
||
|
ctx._select.toggleable = flag;
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
apiRegister( 'select.info()', function ( flag ) {
|
||
|
if ( info === undefined ) {
|
||
|
return this.context[0]._select.info;
|
||
|
}
|
||
|
|
||
|
return this.iterator( 'table', function ( ctx ) {
|
||
|
ctx._select.info = flag;
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
apiRegister( 'select.items()', function ( items ) {
|
||
|
if ( items === undefined ) {
|
||
|
return this.context[0]._select.items;
|
||
|
}
|
||
|
|
||
|
return this.iterator( 'table', function ( ctx ) {
|
||
|
ctx._select.items = items;
|
||
|
|
||
|
eventTrigger( new DataTable.Api( ctx ), 'selectItems', [ items ] );
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
// Takes effect from the _next_ selection. None disables future selection, but
|
||
|
// does not clear the current selection. Use the `deselect` methods for that
|
||
|
apiRegister( 'select.style()', function ( style ) {
|
||
|
if ( style === undefined ) {
|
||
|
return this.context[0]._select.style;
|
||
|
}
|
||
|
|
||
|
return this.iterator( 'table', function ( ctx ) {
|
||
|
ctx._select.style = style;
|
||
|
|
||
|
if ( ! ctx._select_init ) {
|
||
|
init( ctx );
|
||
|
}
|
||
|
|
||
|
// Add / remove mouse event handlers. They aren't required when only
|
||
|
// API selection is available
|
||
|
var dt = new DataTable.Api( ctx );
|
||
|
disableMouseSelection( dt );
|
||
|
|
||
|
if ( style !== 'api' ) {
|
||
|
enableMouseSelection( dt );
|
||
|
}
|
||
|
|
||
|
eventTrigger( new DataTable.Api( ctx ), 'selectStyle', [ style ] );
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
apiRegister( 'select.selector()', function ( selector ) {
|
||
|
if ( selector === undefined ) {
|
||
|
return this.context[0]._select.selector;
|
||
|
}
|
||
|
|
||
|
return this.iterator( 'table', function ( ctx ) {
|
||
|
disableMouseSelection( new DataTable.Api( ctx ) );
|
||
|
|
||
|
ctx._select.selector = selector;
|
||
|
|
||
|
if ( ctx._select.style !== 'api' ) {
|
||
|
enableMouseSelection( new DataTable.Api( ctx ) );
|
||
|
}
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
|
||
|
|
||
|
apiRegisterPlural( 'rows().select()', 'row().select()', function ( select ) {
|
||
|
var api = this;
|
||
|
|
||
|
if ( select === false ) {
|
||
|
return this.deselect();
|
||
|
}
|
||
|
|
||
|
this.iterator( 'row', function ( ctx, idx ) {
|
||
|
clear( ctx );
|
||
|
|
||
|
ctx.aoData[ idx ]._select_selected = true;
|
||
|
$( ctx.aoData[ idx ].nTr ).addClass( ctx._select.className );
|
||
|
} );
|
||
|
|
||
|
this.iterator( 'table', function ( ctx, i ) {
|
||
|
eventTrigger( api, 'select', [ 'row', api[i] ], true );
|
||
|
} );
|
||
|
|
||
|
return this;
|
||
|
} );
|
||
|
|
||
|
apiRegisterPlural( 'columns().select()', 'column().select()', function ( select ) {
|
||
|
var api = this;
|
||
|
|
||
|
if ( select === false ) {
|
||
|
return this.deselect();
|
||
|
}
|
||
|
|
||
|
this.iterator( 'column', function ( ctx, idx ) {
|
||
|
clear( ctx );
|
||
|
|
||
|
ctx.aoColumns[ idx ]._select_selected = true;
|
||
|
|
||
|
var column = new DataTable.Api( ctx ).column( idx );
|
||
|
|
||
|
$( column.header() ).addClass( ctx._select.className );
|
||
|
$( column.footer() ).addClass( ctx._select.className );
|
||
|
|
||
|
column.nodes().to$().addClass( ctx._select.className );
|
||
|
} );
|
||
|
|
||
|
this.iterator( 'table', function ( ctx, i ) {
|
||
|
eventTrigger( api, 'select', [ 'column', api[i] ], true );
|
||
|
} );
|
||
|
|
||
|
return this;
|
||
|
} );
|
||
|
|
||
|
apiRegisterPlural( 'cells().select()', 'cell().select()', function ( select ) {
|
||
|
var api = this;
|
||
|
|
||
|
if ( select === false ) {
|
||
|
return this.deselect();
|
||
|
}
|
||
|
|
||
|
this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
|
||
|
clear( ctx );
|
||
|
|
||
|
var data = ctx.aoData[ rowIdx ];
|
||
|
|
||
|
if ( data._selected_cells === undefined ) {
|
||
|
data._selected_cells = [];
|
||
|
}
|
||
|
|
||
|
data._selected_cells[ colIdx ] = true;
|
||
|
|
||
|
if ( data.anCells ) {
|
||
|
$( data.anCells[ colIdx ] ).addClass( ctx._select.className );
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
this.iterator( 'table', function ( ctx, i ) {
|
||
|
eventTrigger( api, 'select', [ 'cell', api[i] ], true );
|
||
|
} );
|
||
|
|
||
|
return this;
|
||
|
} );
|
||
|
|
||
|
|
||
|
apiRegisterPlural( 'rows().deselect()', 'row().deselect()', function () {
|
||
|
var api = this;
|
||
|
|
||
|
this.iterator( 'row', function ( ctx, idx ) {
|
||
|
ctx.aoData[ idx ]._select_selected = false;
|
||
|
$( ctx.aoData[ idx ].nTr ).removeClass( ctx._select.className );
|
||
|
} );
|
||
|
|
||
|
this.iterator( 'table', function ( ctx, i ) {
|
||
|
eventTrigger( api, 'deselect', [ 'row', api[i] ], true );
|
||
|
} );
|
||
|
|
||
|
return this;
|
||
|
} );
|
||
|
|
||
|
apiRegisterPlural( 'columns().deselect()', 'column().deselect()', function () {
|
||
|
var api = this;
|
||
|
|
||
|
this.iterator( 'column', function ( ctx, idx ) {
|
||
|
ctx.aoColumns[ idx ]._select_selected = false;
|
||
|
|
||
|
var api = new DataTable.Api( ctx );
|
||
|
var column = api.column( idx );
|
||
|
|
||
|
$( column.header() ).removeClass( ctx._select.className );
|
||
|
$( column.footer() ).removeClass( ctx._select.className );
|
||
|
|
||
|
// Need to loop over each cell, rather than just using
|
||
|
// `column().nodes()` as cells which are individually selected should
|
||
|
// not have the `selected` class removed from them
|
||
|
api.cells( null, idx ).indexes().each( function (cellIdx) {
|
||
|
var data = ctx.aoData[ cellIdx.row ];
|
||
|
var cellSelected = data._selected_cells;
|
||
|
|
||
|
if ( data.anCells && (! cellSelected || ! cellSelected[ cellIdx.column ]) ) {
|
||
|
$( data.anCells[ cellIdx.column ] ).removeClass( ctx._select.className );
|
||
|
}
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
this.iterator( 'table', function ( ctx, i ) {
|
||
|
eventTrigger( api, 'deselect', [ 'column', api[i] ], true );
|
||
|
} );
|
||
|
|
||
|
return this;
|
||
|
} );
|
||
|
|
||
|
apiRegisterPlural( 'cells().deselect()', 'cell().deselect()', function () {
|
||
|
var api = this;
|
||
|
|
||
|
this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
|
||
|
var data = ctx.aoData[ rowIdx ];
|
||
|
|
||
|
data._selected_cells[ colIdx ] = false;
|
||
|
|
||
|
// Remove class only if the cells exist, and the cell is not column
|
||
|
// selected, in which case the class should remain (since it is selected
|
||
|
// in the column)
|
||
|
if ( data.anCells && ! ctx.aoColumns[ colIdx ]._select_selected ) {
|
||
|
$( data.anCells[ colIdx ] ).removeClass( ctx._select.className );
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
this.iterator( 'table', function ( ctx, i ) {
|
||
|
eventTrigger( api, 'deselect', [ 'cell', api[i] ], true );
|
||
|
} );
|
||
|
|
||
|
return this;
|
||
|
} );
|
||
|
|
||
|
|
||
|
|
||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
* Buttons
|
||
|
*/
|
||
|
function i18n( label, def ) {
|
||
|
return function (dt) {
|
||
|
return dt.i18n( 'buttons.'+label, def );
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Common events with suitable namespaces
|
||
|
function namespacedEvents ( config ) {
|
||
|
var unique = config._eventNamespace;
|
||
|
|
||
|
return 'draw.dt.DT'+unique+' select.dt.DT'+unique+' deselect.dt.DT'+unique;
|
||
|
}
|
||
|
|
||
|
function enabled ( dt, config ) {
|
||
|
if ( $.inArray( 'rows', config.limitTo ) !== -1 && dt.rows( { selected: true } ).any() ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( $.inArray( 'columns', config.limitTo ) !== -1 && dt.columns( { selected: true } ).any() ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( $.inArray( 'cells', config.limitTo ) !== -1 && dt.cells( { selected: true } ).any() ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var _buttonNamespace = 0;
|
||
|
|
||
|
$.extend( DataTable.ext.buttons, {
|
||
|
selected: {
|
||
|
text: i18n( 'selected', 'Selected' ),
|
||
|
className: 'buttons-selected',
|
||
|
limitTo: [ 'rows', 'columns', 'cells' ],
|
||
|
init: function ( dt, node, config ) {
|
||
|
var that = this;
|
||
|
config._eventNamespace = '.select'+(_buttonNamespace++);
|
||
|
|
||
|
// .DT namespace listeners are removed by DataTables automatically
|
||
|
// on table destroy
|
||
|
dt.on( namespacedEvents(config), function () {
|
||
|
that.enable( enabled(dt, config) );
|
||
|
} );
|
||
|
|
||
|
this.disable();
|
||
|
},
|
||
|
destroy: function ( dt, node, config ) {
|
||
|
dt.off( config._eventNamespace );
|
||
|
}
|
||
|
},
|
||
|
selectedSingle: {
|
||
|
text: i18n( 'selectedSingle', 'Selected single' ),
|
||
|
className: 'buttons-selected-single',
|
||
|
init: function ( dt, node, config ) {
|
||
|
var that = this;
|
||
|
config._eventNamespace = '.select'+(_buttonNamespace++);
|
||
|
|
||
|
dt.on( namespacedEvents(config), function () {
|
||
|
var count = dt.rows( { selected: true } ).flatten().length +
|
||
|
dt.columns( { selected: true } ).flatten().length +
|
||
|
dt.cells( { selected: true } ).flatten().length;
|
||
|
|
||
|
that.enable( count === 1 );
|
||
|
} );
|
||
|
|
||
|
this.disable();
|
||
|
},
|
||
|
destroy: function ( dt, node, config ) {
|
||
|
dt.off( config._eventNamespace );
|
||
|
}
|
||
|
},
|
||
|
selectAll: {
|
||
|
text: i18n( 'selectAll', 'Select all' ),
|
||
|
className: 'buttons-select-all',
|
||
|
action: function () {
|
||
|
var items = this.select.items();
|
||
|
this[ items+'s' ]().select();
|
||
|
}
|
||
|
},
|
||
|
selectNone: {
|
||
|
text: i18n( 'selectNone', 'Deselect all' ),
|
||
|
className: 'buttons-select-none',
|
||
|
action: function () {
|
||
|
clear( this.settings()[0], true );
|
||
|
},
|
||
|
init: function ( dt, node, config ) {
|
||
|
var that = this;
|
||
|
config._eventNamespace = '.select'+(_buttonNamespace++);
|
||
|
|
||
|
dt.on( namespacedEvents(config), function () {
|
||
|
var count = dt.rows( { selected: true } ).flatten().length +
|
||
|
dt.columns( { selected: true } ).flatten().length +
|
||
|
dt.cells( { selected: true } ).flatten().length;
|
||
|
|
||
|
that.enable( count > 0 );
|
||
|
} );
|
||
|
|
||
|
this.disable();
|
||
|
},
|
||
|
destroy: function ( dt, node, config ) {
|
||
|
dt.off( config._eventNamespace );
|
||
|
}
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
$.each( [ 'Row', 'Column', 'Cell' ], function ( i, item ) {
|
||
|
var lc = item.toLowerCase();
|
||
|
|
||
|
DataTable.ext.buttons[ 'select'+item+'s' ] = {
|
||
|
text: i18n( 'select'+item+'s', 'Select '+lc+'s' ),
|
||
|
className: 'buttons-select-'+lc+'s',
|
||
|
action: function () {
|
||
|
this.select.items( lc );
|
||
|
},
|
||
|
init: function ( dt ) {
|
||
|
var that = this;
|
||
|
|
||
|
dt.on( 'selectItems.dt.DT', function ( e, ctx, items ) {
|
||
|
that.active( items === lc );
|
||
|
} );
|
||
|
}
|
||
|
};
|
||
|
} );
|
||
|
|
||
|
|
||
|
|
||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
* Initialisation
|
||
|
*/
|
||
|
|
||
|
// DataTables creation - check if select has been defined in the options. Note
|
||
|
// this required that the table be in the document! If it isn't then something
|
||
|
// needs to trigger this method unfortunately. The next major release of
|
||
|
// DataTables will rework the events and address this.
|
||
|
$(document).on( 'preInit.dt.dtSelect', function (e, ctx) {
|
||
|
if ( e.namespace !== 'dt' ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DataTable.select.init( new DataTable.Api( ctx ) );
|
||
|
} );
|
||
|
|
||
|
|
||
|
return DataTable.select;
|
||
|
}));
|