/*

* MDBootstrap integration with Datatables
* Learn more: https://mdbootstrap.com/docs/jquery/tables/datatables/
* About MDBootstrap: https://mdbootstrap.com/
*
* Select for DataTables 1.2.7
* 2015-2018 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.2.7
* @file        dataTables.select.js
* @author      SpryMedia Ltd (www.sprymedia.co.uk)
* @contact     datatables.net/forums
* @copyright   Copyright 2015-2018 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.2.7';

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 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.info !== undefined ) {
                info = opts.info;
        }

        if ( opts.items !== undefined ) {
                items = opts.items;
        }

        if ( opts.style !== undefined ) {
                style = opts.style;
                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.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:

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().container() )
        .off( 'mousedown.dtSelect', selector )
        .off( 'mouseup.dtSelect', selector )
        .off( 'click.dtSelect', selector );

$('body').off( 'click.dtSelect' + dt.table().node().id );

}

/**

* 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 ( window.getSelection ) {
                        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 = 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' + dt.table().node().id, 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 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 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 );
}

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

* 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.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; }));