Subversion-Projekte lars-tiefland.ci

Revision

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 debugger
DataTable.select = {};
DataTable.select.version = '1.0.1';

/*

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
        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().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-table
                        if ( $(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 blur
                        if ( $(e.target).parents().filter( dt.table().container() ).length ) {
                                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;
        }

        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 ];

                        // Row
                        if ( 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 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( '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 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', 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 isSelected = dt[type]( idx, { selected: true } ).any();

        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 {
                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 compression
var 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 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( '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 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( '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 destroy
                        dt.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 defaults
        var items = 'row';
        var style = 'api';
        var blurable = false;
        var info = true;
        var selector = 'td, th';

        ctx._select = {};

        // Initialisation customisations
        if ( 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 enable
        if ( $( dt.table().node() ).hasClass( 'selectable' ) ) {
                dt.select.style( 'os' );
        }
} );


}; // /factory


// Define as an AMD module if possible
if ( typeof define === 'function' && define.amd ) {
        define( ['jquery', 'datatables'], factory );
}
else if ( typeof exports === 'object' ) {
    // Node/CommonJS
    factory( require('jquery'), require('datatables') );
}
else if ( jQuery && !jQuery.fn.dataTable.select ) {
        // Otherwise simply initialise as normal, stopping multiple evaluation
        factory( jQuery, jQuery.fn.dataTable );
}


})(window, document);