Blame | Letzte Änderung | Log anzeigen | RSS feed
/*! Select for DataTables 1.0.1* 2015 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.0.1* @file dataTables.select.js* @author SpryMedia Ltd (www.sprymedia.co.uk)* @contact datatables.net/forums* @copyright Copyright 2015 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(window, document, undefined) {var factory = function( $, DataTable ) {"use strict";// Version information for debuggerDataTable.select = {};DataTable.select.version = '1.0.1';/*Select is a collection of API methods, event handlers, event emitters andbuttons (for the `Buttons` extension) for DataTables. It provides the followingfeatures, with an overview of how they are implemented:## Selection of rows, columns and cells. Whether an item is selected or not isstored in:* rows: a `_select_selected` property which contains a boolean value of theDataTables' `aoData` object for each row* columns: a `_select_selected` property which contains a boolean value of theDataTables' `aoColumns` object for each column* cells: a `_selected_cells` property which contains an array of boolean valuesof the `aoData` object for each row. The array is the same length as thecolumns array, with each element of it representing a cell.This method of using boolean flags allows Select to operate when nodes have notbeen created for rows / cells (DataTables' defer rendering feature).## API methodsA range of API methods are available for triggering selection and de-selectionof rows. Methods are also available to configure the selection events that canbe triggered by an end user (such as which items are to be selected). To a largeextent, these of API methods *is* Select. It is basically a collection of helperfunctions that can be used to select items in a DataTable.Configuration of select is held in the object `_select` which is attached to theDataTables settings object on initialisation. Select being available on a tableis not optional when Select is loaded, but its default is for selection only tobe available via the API - so the end user wouldn't be able to select rowswithout additional configuration.The `_select` object contains the following properties:```{items:string - Can be `rows`, `columns` or `cells`. Defines what itemwill be selected if the user is allowed to activate rowselection using the mouse.style:string - Can be `none`, `single`, `multi` or `os`. Defines theinteraction style when selecting itemsblurable:boolean - If row selection can be cleared by clicking outside ofthe tableinfo:boolean - If the selection summary should be shown in the tableinformation elements}```In addition to the API methods, Select also extends the DataTables selectoroptions for rows, columns and cells adding a `selected` option to the selectoroptions object, allowing the developer to select only selected items orunselected items.## Mouse selection of itemsClicking on items can be used to select items. This is done by a simple eventhandler 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 === endrecord = false;return true;}return record;} );};var selectRows = function ( start, end ) {var indexes = dt.rows( { search: 'applied' } ).indexes();// Which comes first - might need to swapif ( 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 onecolumnIndexes = selectColumns( 0, idx.column );rowIndexes = selectRows( 0 , idx.row );}else {// Get column indexes between old and newcolumnIndexes = 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 rangedt.cells( indexes ).select();}else {// Deselect rangedt.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().body() ).off( 'mousedown.dtSelect', selector ).off( 'mouseup.dtSelect', selector ).off( 'click.dtSelect', selector );$('body').off( 'click.dtSelect' );}/*** 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 body = $( dt.table().body() );var ctx = dt.settings()[0];var selector = ctx._select.selector;body.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 ) {body.css( '-moz-user-select', 'none' ).one('selectstart.dtSelect', selector, function () {return false;} );}} ).on( 'mouseup.dtSelect', selector, function(e) {// Allow text selection to occur again, Mozilla style (tested in FF// 35.0.1 - still required)body.css( '-moz-user-select', '' );} ).on( 'click.dtSelect', selector, function ( e ) {var items = dt.select.items();var cellIndex = dt.cell( this ).index();var idx;var ctx = dt.settings()[0];// Ignore clicks inside a sub-tableif ( $(e.target).closest('tbody')[0] != body[0] ) {return;}// Check the cell actually belongs to the host DataTable (so child rows,// etc, are ignored)if ( ! dt.cell( e.target ).any() ) {return;}if ( items === 'row' ) {idx = cellIndex.row;typeSelect( e, dt, ctx, 'row', idx );}else if ( items === 'column' ) {idx = dt.cell( e.target ).index().column;typeSelect( e, dt, ctx, 'column', idx );}else if ( items === 'cell' ) {idx = dt.cell( e.target ).index();typeSelect( e, dt, ctx, 'cell', idx );}ctx._select_lastCell = cellIndex;} );// Blurable$('body').on( 'click.dtSelect', function ( e ) {if ( ctx._select.blurable ) {// If the click was inside the DataTables container, don't blurif ( $(e.target).parents().filter( dt.table().container() ).length ) {return;}// Don't blur in Editor formif ( $(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;}args.unshift( api );$(api.table().node()).triggerHandler( type+'.dt', 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;}var output = $('<span class="select-info"/>');var add = function ( name, num ) {output.append( $('<span class="select-item"/>').append( api.i18n('select.'+name+'s',{ _: '%d '+name+'s selected', 0: '', 1: '1 '+name+' selected' },num) ) );};add( 'row', api.rows( { selected: true } ).flatten().length );add( 'column', api.columns( { selected: true } ).flatten().length );add( 'cell', api.cells( { selected: true } ).flatten().length );// Internal knowledge of DataTables to loop over all information elements$.each( ctx.aanFeatures.i, function ( i, el ) {el = $(el);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 ];// Rowif ( d._select_selected ) {$( row ).addClass( 'selected' );}// Cells and columns - if separated out, we would need to do two// loops, so it makes sense to combine them into a single onefor ( 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( 'selected' );}}},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 selectedvar 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 itemsapi.one( 'draw.dt.dtSelect', function () {api.rows( rows ).select();// `cells` is not a cell index selector, so it needs a loopif ( cells.any() ) {cells.each( function ( id ) {api.cells( id.row, id.column ).select();} );}} );} );// Update the table information element with selected item summaryapi.on( 'draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt', function () {info( api );} );// Clean up and releaseapi.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 onevar 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 thisindexes.splice( $.inArray( idx, indexes )+1, indexes.length );}else {// reverse so we can shift click 'up' as well as downif ( 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 rangedt[type+'s']( indexes ).select();}else {// Deselect range - need to keep the clicked on row selectedindexes.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 isSelected = dt[type]( idx, { selected: true } ).any();if ( style === 'os' ) {if ( e.ctrlKey || e.metaKey ) {// Add or remove from the selectiondt[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 onlyvar selected = dt[type+'s']( { selected: true } );if ( isSelected && selected.flatten().length === 1 ) {dt[type]( idx ).deselect();}else {selected.deselect();dt[type]( idx ).select();}}}else {dt[ type ]( idx ).select( ! isSelected );}}/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** 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 === undefined ) {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 compressionvar apiRegister = DataTable.Api.register;var apiRegisterPlural = DataTable.Api.registerPlural;apiRegister( 'select()', function () {} );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.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 thatapiRegister( '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 availablevar 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( 'selected' );} );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( 'selected' );$( column.footer() ).addClass( 'selected' );column.nodes().to$().addClass( 'selected' );} );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( 'selected' );}} );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( 'selected' );} );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( 'selected' );$( column.footer() ).removeClass( 'selected' );// 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 themapi.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( 'selected' );}} );} );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( 'selected' );}} );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 );};}$.extend( DataTable.ext.buttons, {selected: {text: i18n( 'selected', 'Selected' ),className: 'buttons-selected',init: function ( dt, button, config ) {var that = this;// .DT namespace listeners are removed by DataTables automatically// on table destroydt.on( 'draw.dt.DT select.dt.DT deselect.dt.DT', function () {var enable = that.rows( { selected: true } ).any() ||that.columns( { selected: true } ).any() ||that.cells( { selected: true } ).any();that.enable( enable );} );this.disable();}},selectedSingle: {text: i18n( 'selectedSingle', 'Selected single' ),className: 'buttons-selected-single',init: function ( dt, button, config ) {var that = this;dt.on( 'draw.dt.DT select.dt.DT deselect.dt.DT', 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();}},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 );}}} );$.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, button, config ) {var that = this;dt.on( 'selectItems.dt.DT', function ( e, ctx, items ) {that.active( items === lc );} );}};} );/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** Initialisation*/// DataTables creation - check if the buttons have been defined for this table,// they will have been if the `B` option was used in `dom`, otherwise we should// create the buttons instance here so they can be inserted into the document// using the API$(document).on( 'init.dt.dtSelect', function (e, ctx, json) {if ( e.namespace !== 'dt' ) {return;}var opts = ctx.oInit.select || DataTable.defaults.select;var dt = new DataTable.Api( ctx );// Set defaultsvar items = 'row';var style = 'api';var blurable = false;var info = true;var selector = 'td, th';ctx._select = {};// Initialisation customisationsif ( opts === true ) {style = 'os';}else if ( typeof opts === 'string' ) {style = opts;}else if ( $.isPlainObject( opts ) ) {if ( opts.blurable !== undefined ) {blurable = opts.blurable;}if ( opts.info !== undefined ) {info = opts.info;}if ( opts.items !== undefined ) {items = opts.items;}if ( opts.style !== undefined ) {style = opts.style;}if ( opts.selector !== undefined ) {selector = opts.selector;}}dt.select.selector( selector );dt.select.items( items );dt.select.style( style );dt.select.blurable( blurable );dt.select.info( info );// If the init options haven't enabled select, but there is a selectable// class name, then enableif ( $( dt.table().node() ).hasClass( 'selectable' ) ) {dt.select.style( 'os' );}} );}; // /factory// Define as an AMD module if possibleif ( typeof define === 'function' && define.amd ) {define( ['jquery', 'datatables'], factory );}else if ( typeof exports === 'object' ) {// Node/CommonJSfactory( require('jquery'), require('datatables') );}else if ( jQuery && !jQuery.fn.dataTable.select ) {// Otherwise simply initialise as normal, stopping multiple evaluationfactory( jQuery, jQuery.fn.dataTable );}})(window, document);