/*!
 * jQuery DataTables Checkboxes (https://www.gyrocode.com/projects/jquery-datatables-checkboxes/)
 * Checkboxes extension for jQuery DataTables
 *
 * @version     1.3.0
 * @author      Gyrocode LLC (https://www.gyrocode.com)
 * @copyright   (c) Gyrocode LLC
 * @license     MIT
 */
(function( factory ){
/* eslint-disable */
   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 );
   }
/* eslint-enable */
}(function( $, window, document ) {
   'use strict';
   var DataTable = $.fn.dataTable;


   /**
   * Checkboxes is an extension for the jQuery DataTables library that provides
   * universal solution for working with checkboxes in a table.
   *
   *  @class
   *  @param {object} settings DataTables settings object for the host table
   *  @requires jQuery 1.7+
   *  @requires DataTables 1.10.8+
   *
   *  @example
   *     $('#example').DataTable({
   *        'columnDefs': [
   *           {
   *              'targets': 0,
   *              'checkboxes': true
   *           }
   *        ]
   *     });
   */
   var Checkboxes = function ( settings ) {
      // Sanity check that we are using DataTables 1.10.8 or newer
      if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
         throw 'DataTables Checkboxes requires DataTables 1.10.8 or newer';
      }

      this.s = {
         dt: new DataTable.Api( settings ),
         columns: [],
         data: {},
         dataDisabled: {},
         ignoreSelect: false,
         infoEls: []
      };

      // Determine whether DataTables v2 is being used
      this.s.isDataTablesV2 = DataTable.versionCheck('2');

      // Determine whether DataTables v1 is being used
      this.s.isDataTablesV1 = DataTable.versionCheck('1') && !this.s.isDataTablesV2;

      // Get settings object
      this.s.ctx = this.s.dt.settings()[0];

      // Check if checkboxes have already been initialised on this table
      if ( this.s.ctx.checkboxes ) {
         return;
      }

      settings.checkboxes = this;

      this._constructor();
   };


   Checkboxes.prototype = {
      /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
      * Constructor
      */

      /**
      * Initialise the Checkboxes instance
      *
      * @private
      */
      _constructor: function ()
      {
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;
         var hasCheckboxes = false;
         var hasCheckboxesSelectRow = false;

         for(var i = 0; i < ctx.aoColumns.length; i++){
            if (ctx.aoColumns[i].checkboxes){
               var $colHeader = $(dt.column(i).header());

               //
               // INITIALIZATION
               //

               hasCheckboxes = true;

               if(!$.isPlainObject(ctx.aoColumns[i].checkboxes)){
                  ctx.aoColumns[i].checkboxes = {};
               }

               ctx.aoColumns[i].checkboxes = $.extend(
                  {}, Checkboxes.defaults, ctx.aoColumns[i].checkboxes
               );

               //
               // OPTIONS
               //

               ctx.aoColumns[i].bSortable = ctx.aoColumns[i].orderable = false;
               ctx.aoColumns[i].bSearchable = ctx.aoColumns[i].searchable = false;

               if(ctx.aoColumns[i].sClass === ''){
                  ctx.aoColumns[i].sClass = 'dt-checkboxes-cell';
               } else {
                  ctx.aoColumns[i].sClass = ctx.aoColumns[i].sClass + ' dt-checkboxes-cell';
               }

               if(ctx.aoColumns[i].sWidthOrig === null){
                  ctx.aoColumns[i].width = '1%';
                  ctx.aoColumns[i].sWidthOrig = ctx.aoColumns[i].width;
               }

               if(ctx.aoColumns[i].mRender === null){
                  self._fnColumnOptionsRender(ctx, i);
               }


               // WORKAROUND: Remove "sorting" class
               $colHeader.removeClass('sorting');

               // WORKAROUND: Detach all event handlers for this column
               $colHeader.off('.dt');

               // If table has data source other than Ajax
               if(
                  (self.s.isDataTablesV1 && ctx.sAjaxSource === null)
                  || ctx.ajax === null

               ){
                  // WORKAROUND: Invalidate column data
                  var cells = dt.cells('tr', i);
                  cells.invalidate('data');

                  // WORKAROUND: Add required class to existing cells
                  $(cells.nodes()).addClass(ctx.aoColumns[i].sClass);
               }


               //
               // DATA
               //

               // Initialize object holding data for selected checkboxes
               self.s.data[i] = {};
               self.s.dataDisabled[i] = {};

               // Store column index for easy column selection later
               self.s.columns.push(i);


               //
               // CLASSES
               //

               // If row selection is enabled for this column
               if(ctx.aoColumns[i].checkboxes.selectRow){

                  // If Select extension is enabled
                  if(ctx._select){
                     hasCheckboxesSelectRow = true;

                  // Otherwise, if Select extension is not enabled
                  } else {
                     // Disable row selection for this column
                     ctx.aoColumns[i].checkboxes.selectRow = false;
                  }
               }

               // If "Select all" control is enabled
               if(ctx.aoColumns[i].checkboxes.selectAll){
                  // Save previous HTML content
                  $colHeader.data('html', $colHeader.html());

                  // If "Select all" control markup is provided
                  if(ctx.aoColumns[i].checkboxes.selectAllRender !== null){
                     var selectAllHtml = '';

                     // If "selectAllRender" option is a function
                     if($.isFunction(ctx.aoColumns[i].checkboxes.selectAllRender)){
                        selectAllHtml = ctx.aoColumns[i].checkboxes.selectAllRender();

                     // Otherwise, if "selectAllRender" option is a string
                     } else if(typeof ctx.aoColumns[i].checkboxes.selectAllRender === 'string'){
                        selectAllHtml = ctx.aoColumns[i].checkboxes.selectAllRender;
                     }

                     $colHeader
                        .html(selectAllHtml)
                        .addClass('dt-checkboxes-select-all')
                        .attr('data-col', i);
                  }
               }
            }
         }

         // If table has at least one checkbox column
         if(hasCheckboxes){

            // Load previous state
            self.loadState();

            //
            // EVENT HANDLERS
            //

            var $table = $(dt.table().node());
            var $tableBody = $(dt.table().body());
            var $tableContainer = $(dt.table().container());

            // If there is at least one column that has row selection enabled
            if(hasCheckboxesSelectRow){
               $table.addClass('dt-checkboxes-select');

               // Handle event before row is selected/deselected
               $table.on('user-select.dt.dtCheckboxes', function (e, dt, type, cell , originalEvent){
                  self.onDataTablesUserSelect(e, dt, type, cell , originalEvent);
               });

               // Handle row select/deselect event
               $table.on('select.dt.dtCheckboxes deselect.dt.dtCheckboxes', function(e, api, type, indexes){
                  self.onDataTablesSelectDeselect(e, type, indexes);
               });

               // If displaying of Select extension information is enabled
               if(ctx._select.info){
                  // Disable Select extension information display
                  dt.select.info(false);

                  // Update the table information element with selected item summary
                  //
                  // NOTE: Needed to display correct count of selected rows
                  // when using server-side processing mode
                  $table.on('draw.dt.dtCheckboxes select.dt.dtCheckboxes deselect.dt.dtCheckboxes', function(){
                     self.showInfoSelected();
                  });

                  if (self.s.isDataTablesV2) {
                     // Update the table information element with selected item summary
                     $table.on('info.dt.dtCheckboxes', function (e, ctx, node) {
                        // Store the info node for updating on select / deselect
                        if (!ctx.checkboxes.s.infoEls.includes(node)) {
                           ctx.checkboxes.s.infoEls.push(node);
                        }

                        self.showInfoSelected();
                     });
                  }
               }
            }

            // Handle table draw event
            $table.on('draw.dt.dtCheckboxes', function(e){
               self.onDataTablesDraw(e);
            });

            // Handle checkbox click event
            $tableBody.on('click.dtCheckboxes', 'input.dt-checkboxes', function(e){
               self.onClick(e, this);
            });

            // Handle click on "Select all" control
            $tableContainer.on('click.dtCheckboxes', 'thead th.dt-checkboxes-select-all input[type="checkbox"]', function(e){
               self.onClickSelectAll(e, this);
            });

            // Handle click on heading containing "Select all" control
            $tableContainer.on('click.dtCheckboxes', 'thead th.dt-checkboxes-select-all', function() {
               $('input[type="checkbox"]', this).not(':disabled').trigger('click');
            });

            // If row selection is disabled
            if(!hasCheckboxesSelectRow){
               // Handle click on cell containing checkbox
               $tableContainer.on('click.dtCheckboxes', 'tbody td.dt-checkboxes-cell', function() {
                  $('input[type="checkbox"]', this).not(':disabled').trigger('click');
               });
            }

            // Handle click on label node in heading containing "Select all" control
            // and in cell containing checkbox
            $tableContainer.on('click.dtCheckboxes', 'thead th.dt-checkboxes-select-all label, tbody td.dt-checkboxes-cell label', function(e) {
               // Prevent default behavior
               e.preventDefault();
            });

            // Handle click on "Select all" control in floating fixed header
            $(document).on('click.dtCheckboxes', '.fixedHeader-floating thead th.dt-checkboxes-select-all input[type="checkbox"]', function(e){
               // If FixedHeader is enabled in this instance
               if(ctx._fixedHeader){
                  // If header is floating in this instance
                  if(ctx._fixedHeader.dom['header'].floating){
                     self.onClickSelectAll(e, this);
                  }
               }
            });

            // Handle click on heading containing "Select all" control in floating fixed header
            $(document).on('click.dtCheckboxes', '.fixedHeader-floating thead th.dt-checkboxes-select-all', function() {
               // If FixedHeader is enabled in this instance
               if(ctx._fixedHeader){
                  // If header is floating in this instance
                  if(ctx._fixedHeader.dom['header'].floating){
                     $('input[type="checkbox"]', this).trigger('click');
                  }
               }
            });

            // Handle table initialization event
            $table.on('init.dt.dtCheckboxes', function(){
               // Use delay to handle initialization event
               // because certain extensions (FixedColumns) are initialized
               // only when initialization event is triggered.
               setTimeout(function(){
                   self.onDataTablesInit();
               }, 0);
            });

            // Handle state saving event
            $table.on('stateSaveParams.dt.dtCheckboxes', function (e, settings, data) {
               self.onDataTablesStateSave(e, settings, data);
            });

            // Handle table destroy event
            $table.one('destroy.dt.dtCheckboxes', function(e, settings){
               self.onDataTablesDestroy(e, settings);
            });
         }
      },

      // Apply render options for a column
      _fnColumnOptionsRender: function (ctx, i){
         var self = this;

         var _fnGetObjectDataFn = self.s.isDataTablesV1
            ? DataTable.ext.internal._fnGetObjectDataFn
            : DataTable.util.get;

         ctx.aoColumns[i].mRender = ctx.aoColumns[i].render = function(){
            return '<input type="checkbox" class="dt-checkboxes" autocomplete="off">';
         };

         ctx.aoColumns[i]._render = _fnGetObjectDataFn(ctx.aoColumns[i].mRender);

         var mDataSrc = ctx.aoColumns[i].mData;
         var mData = _fnGetObjectDataFn(mDataSrc);

         ctx.aoColumns[i].fnGetData = function (rowData, type, meta) {
            var innerData = mData(rowData, type, undefined, meta);

            return ctx.aoColumns[i]._render && type
               ? ctx.aoColumns[i]._render(innerData, type, rowData, meta)
               : innerData;
         };
      },

      // Handles DataTables initialization event
      onDataTablesInit: function(){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // If server-side processing mode is not enabled
         // NOTE: Needed to avoid duplicate call to updateStateCheckboxes() in onDataTablesDraw()
         if(!ctx.oFeatures.bServerSide){

            // If state saving is enabled
            if(ctx.oFeatures.bStateSave){
               self.updateState();
            }

            // Handle Ajax request completion event
            // NOTE: Needed to update table state
            // if table is reloaded via ajax.reload() API method
            $(dt.table().node()).on('xhr.dt.dtCheckboxes', function ( e, settings , json, xhr ) {
               self.onDataTablesXhr(e. settings, json, xhr);
            });
         }
      },

      // Handles DataTables user initiated select event
      onDataTablesUserSelect: function ( e, dt, type, cell /*, originalEvent*/ ){
         var self = this;

         var cellIdx = cell.index();
         var rowIdx = cellIdx.row;
         var colIdx = self.getSelectRowColIndex();
         var cellData = dt.cell({ row: rowIdx, column: colIdx }).data();

         // If checkbox in the cell cannot be checked
         if(!self.isCellSelectable(colIdx, cellData)){
            // Prevent row selection
            e.preventDefault();
         }
      },

      // Handles DataTables row select/deselect event
      onDataTablesSelectDeselect: function(e, type, indexes){
         var self = this;
         var dt = self.s.dt;

         if(self.s.ignoreSelect){ return; }

         if(type === 'row'){
            // Get index of the first column that has checkbox and row selection enabled
            var colIdx = self.getSelectRowColIndex();
            if(colIdx !== null){
               var cells = dt.cells(indexes, colIdx);

               self.updateData(cells, colIdx, (e.type === 'select') ? true : false);
               self.updateCheckbox(cells, colIdx, (e.type === 'select') ? true : false);
               self.updateSelectAll(colIdx);
            }
         }
      },

      // Handles DataTables state save event
      onDataTablesStateSave: function (e, settings, data) {
         var self = this;
         var ctx = self.s.ctx;

         // For every column where checkboxes are enabled
         $.each(self.s.columns, function(index, colIdx){
            // If checkbox state saving is enabled
            if(ctx.aoColumns[colIdx].checkboxes.stateSave){
               // If checkboxes state hasn't been saved before
               if(!Object.prototype.hasOwnProperty.call(data, 'checkboxes')){
                  // Initialize array to save checkboxes state for each column
                  data.checkboxes = [];
               }

               // Save checkboxes state
               data.checkboxes[colIdx] = self.s.data[colIdx];
            }
         });
      },

      // Handles DataTables destroy event
      onDataTablesDestroy: function(){
         var self = this;
         var dt = self.s.dt;

         // Get table elements
         var $table = $(dt.table().node());
         var $tableBody = $(dt.table().body());
         var $tableContainer = $(dt.table().container());

         // Detach event handlers
         $(document).off('click.dtCheckboxes');
         $tableContainer.off('.dtCheckboxes');
         $tableBody.off('.dtCheckboxes');
         $table.off('.dtCheckboxes');

         // Clear data
         //
         // NOTE: Needed only to reduce memory footprint
         // in case user saves instance of DataTable object.
         self.s.data = {};
         self.s.dataDisabled = {};

         // Remove added elements
         $('.dt-checkboxes-select-all', $table).each(function(index, el){
            $(el)
               .html($(el).data('html'))
               .removeClass('dt-checkboxes-select-all');
         });
      },

      // Handles DataTables draw event
      onDataTablesDraw: function(){
         var self = this;
         var ctx = self.s.ctx;

         // If server-side processing is enabled
         // or deferred render is enabled
         //
         // TODO: it's not optimal to update state of checkboxes
         // for already created rows in deferred rendering mode
         if(ctx.oFeatures.bServerSide || ctx.oFeatures.bDeferRender){
            self.updateStateCheckboxes({ page: 'current', search: 'none' });
         }

         $.each(self.s.columns, function(index, colIdx){
            self.updateSelectAll(colIdx);
         });
      },

      // Handles DataTables Ajax request completion event
      onDataTablesXhr: function( /* e, settings , json, xhr */ ){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // Get table elements
         var $table = $(dt.table().node());

         // For every column where checkboxes are enabled
         $.each(self.s.columns, function(index, colIdx){
            // Reset data
            self.s.data[colIdx] = {};
            self.s.dataDisabled[colIdx] = {};
         });

         // If state saving is enabled
         if(ctx.oFeatures.bStateSave){
            // Load previous state
            self.loadState();

            // Update table state on next redraw
            $table.one('draw.dt.dtCheckboxes', function(){
               self.updateState();
            });
         }
      },

      // Updates array holding data for selected checkboxes
      updateData: function(cells, colIdx, isSelected){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // If Checkboxes extension is enabled for this column
         if(ctx.aoColumns[colIdx].checkboxes){
            var cellsData = cells.data();
            cellsData.each(function(cellData){
               // If checkbox is checked
               if(isSelected){
                  ctx.checkboxes.s.data[colIdx][cellData] = 1;

               // Otherwise, if checkbox is not checked
               } else {
                  delete ctx.checkboxes.s.data[colIdx][cellData];
               }
            });

            // If state saving is enabled
            if(ctx.oFeatures.bStateSave){
               // If checkbox state saving is enabled
               if(ctx.aoColumns[colIdx].checkboxes.stateSave){
                  // Save state
                  dt.state.save();
               }
            }
         }
      },

      // Updates row selection
      updateSelect: function(selector, isSelected){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // If Select extension is enabled
         if(ctx._select){
            // Disable select event hanlder temporarily
            self.s.ignoreSelect = true;

            if(isSelected){
               dt.rows(selector).select();
            } else {
               dt.rows(selector).deselect();
            }

            // Re-enable select event handler
            self.s.ignoreSelect = false;
         }
      },

      // Updates state of single checkbox
      updateCheckbox: function(cells, colIdx, isSelected){
         var self = this;
         var ctx = self.s.ctx;

         var cellNodes = cells.nodes ? cells.nodes() : [ cells.node() ];
         if(cellNodes.length){
            $('input.dt-checkboxes', cellNodes).not(':disabled').prop('checked', isSelected);

            // If selectCallback is a function
            if($.isFunction(ctx.aoColumns[colIdx].checkboxes.selectCallback)){
               ctx.aoColumns[colIdx].checkboxes.selectCallback(cellNodes, isSelected);
            }
         }
      },

      // Update table state
      updateState: function(){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         self.updateStateCheckboxes({ page: 'all', search: 'none' });

         // If FixedColumns v3 extension is enabled
         if(ctx._oFixedColumns){
            // Use delay to let FixedColumns construct the header
            // before we update the "Select all" checkbox
            setTimeout(function(){
               // For every column where checkboxes are enabled
               $.each(self.s.columns, function(index, colIdx){
                  self.updateSelectAll(colIdx);
               });
            }, 0);
         }
      },

      // Updates state of multiple checkboxes
      updateStateCheckboxes: function(opts){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // Enumerate all cells
         dt.cells('tr', self.s.columns, opts).every(function(rowIdx, colIdx){
            // Get cell data
            var cellData = this.data();

            // Determine if checkbox in the cell can be selected
            var isCellSelectable = self.isCellSelectable(colIdx, cellData);

            // If checkbox is checked
            if(
               Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.data, colIdx)
               && Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.data[colIdx], cellData)
            ) {
               // If row selection is enabled
               // and checkbox can be checked
               if(ctx.aoColumns[colIdx].checkboxes.selectRow && isCellSelectable){
                  self.updateSelect(rowIdx, true);
               }

               self.updateCheckbox(this, colIdx, true);
            }

            // If checkbox is disabled
            if(!isCellSelectable){
               $('input.dt-checkboxes', this.node()).prop('disabled', true);
            }
         });
      },

      // Handles checkbox click event
      onClick: function(e, ctrl){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         var cellSelector;

         // Get cell
         var $cell = $(ctrl).closest('td');

         // If cell is in a fixed column using FixedColumns v3 extension
         if($cell.parents('.DTFC_Cloned').length){
            cellSelector = dt.fixedColumns().cellIndex($cell);

         } else {
            cellSelector = $cell;
         }

         var cell    = dt.cell(cellSelector);
         var cellIdx = cell.index();
         var colIdx  = cellIdx.column;
         var rowIdx  = cellIdx.row;

         // If row selection is not enabled
         // NOTE: if row selection is enabled, checkbox selection/deselection
         // would be handled by onDataTablesSelectDeselect event handler instead
         if(!ctx.aoColumns[colIdx].checkboxes.selectRow){
            cell.checkboxes.select(ctrl.checked);

            // Prevent click event from propagating to parent
            e.stopPropagation();

         } else {

            // If Select extension is enabled
            if(ctx._select){
               // If style is set to "os"
               if(ctx._select.style === 'os'){

                  // WORKAROUND:
                  // See https://github.com/gyrocode/jquery-datatables-checkboxes/issues/128

                  // Prevent click event from propagating to parent
                  e.stopPropagation();

                  // Select/deselect individual row
                  cell.checkboxes.select(ctrl.checked);

               // Otherwise, if style is set to other than "os"
               } else {
                  // WORKAROUND:
                  // Select extension may keep the row selected
                  // when checkbox is unchecked with SHIFT key.
                  //
                  // We need to update the state of the checkbox AFTER handling
                  // select/deselect event from Select extension.
                  //
                  // Call to setTimeout is needed to let select/deselect event handler
                  // update the data first.
                  setTimeout(function(){
                     // Get cell data
                     var cellData = cell.data();

                     // Determine whether data is in the list
                     var hasData = (
                        Object.prototype.hasOwnProperty.call(self.s.data, colIdx)
                        && Object.prototype.hasOwnProperty.call(self.s.data[colIdx], cellData)
                     );

                     // If state of the checkbox needs to be updated
                     if(hasData !== ctrl.checked){
                        self.updateCheckbox(cell, colIdx, hasData);
                        self.updateSelectAll(colIdx);
                     }
                  }, 0);
               }
            }
         }
      },

      // Handles checkbox click event
      onClickSelectAll: function(e, ctrl){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // Calculate column index
         var colIdx = null;
         var $th = $(ctrl).closest('th');

         // If column is fixed using FixedColumns v3 extension
         if($th.parents('.DTFC_Cloned').length){
            var cellIdx = dt.fixedColumns().cellIndex($th);
            colIdx = cellIdx.column;
         } else {
            colIdx = dt.column($th).index();
         }

         // Indicate that state of "Select all" control has been changed
         $(ctrl).data('is-changed', true);

         dt.column(colIdx, {
            page: (
               (ctx.aoColumns[colIdx].checkboxes && ctx.aoColumns[colIdx].checkboxes.selectAllPages)
                  ? 'all'
                  : 'current'
            ),
            search: 'applied'
         }).checkboxes.select(ctrl.checked);

         // Prevent click event from propagating to parent
         e.stopPropagation();
      },

      // Loads previosly saved sate
      loadState: function () {
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // If state saving is enabled
         if(ctx.oFeatures.bStateSave){
            // Retrieve stored state
            var state = dt.state.loaded();

            // For every column where checkboxes are enabled
            $.each(self.s.columns, function(index, colIdx){
               // If state is loaded and contains data for this column
               if(state && state.checkboxes && state.checkboxes.hasOwnProperty(colIdx)){
                  // If checkbox state saving is enabled
                  if(ctx.aoColumns[colIdx].checkboxes.stateSave){
                     // Load previous state
                     self.s.data[colIdx] = state.checkboxes[colIdx];
                  }
               }
            });
         }
      },

      // Updates state of the "Select all" controls
      updateSelectAll: function(colIdx){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // If Checkboxes extension is enabled for this column
         // and "Select all" control is enabled for this column
         if(ctx.aoColumns[colIdx].checkboxes && ctx.aoColumns[colIdx].checkboxes.selectAll){
            var cells = dt.cells('tr', colIdx, {
               page: (
                  (ctx.aoColumns[colIdx].checkboxes.selectAllPages)
                     ? 'all'
                     : 'current'
               ),
               search: 'applied'
            });

            var $tableContainer = dt.table().container();
            var $checkboxesSelectAll = $('.dt-checkboxes-select-all[data-col="' + colIdx + '"] input[type="checkbox"]', $tableContainer);

            var countChecked = 0;
            var countDisabled = 0;
            var cellsData = cells.data();
            $.each(cellsData, function(index, cellData){
               // If checkbox is not disabled
               if(self.isCellSelectable(colIdx, cellData)){
                  if(
                     Object.prototype.hasOwnProperty.call(self.s.data, colIdx)
                     && Object.prototype.hasOwnProperty.call(self.s.data[colIdx], cellData)
                  ) {
                     countChecked++;
                  }

               // Otherwise, if checkbox is disabled
               } else {
                  countDisabled++;
               }
            });

            // If FixedHeader is enabled in this instance
            if(ctx._fixedHeader){
               // If header is floating in this instance
               if(ctx._fixedHeader.dom['header'].floating){
                  $checkboxesSelectAll = $('.fixedHeader-floating .dt-checkboxes-select-all[data-col="' + colIdx + '"] input[type="checkbox"]');
               }
            }

            var isSelected;
            var isIndeterminate;

            // If none of the checkboxes are checked
            if (countChecked === 0){
               isSelected      = false;
               isIndeterminate = false;

            // If all of the checkboxes are checked
            } else if ((countChecked + countDisabled) === cellsData.length){
               isSelected      = true;
               isIndeterminate = false;

            // If some of the checkboxes are checked
            } else {
               isSelected      = true;
               isIndeterminate = true;
            }

            var isChanged          = $checkboxesSelectAll.data('is-changed');
            var isSelectedNow      = $checkboxesSelectAll.prop('checked');
            var isIndeterminateNow = $checkboxesSelectAll.prop('indeterminate');

            // If state of "Select all" control has been changed
            if(isChanged || isSelectedNow !== isSelected || isIndeterminateNow !== isIndeterminate){
               // Reset "Select all" control state flag
               $checkboxesSelectAll.data('is-changed', false);

               $checkboxesSelectAll.prop({
                  // NOTE: If checkbox has indeterminate state,
                  // "checked" property must be set to false.
                  'checked': isIndeterminate ? false : isSelected,
                  'indeterminate': isIndeterminate
               });

               // If selectAllCallback is a function
               if($.isFunction(ctx.aoColumns[colIdx].checkboxes.selectAllCallback)){
                  ctx.aoColumns[colIdx].checkboxes.selectAllCallback($checkboxesSelectAll.closest('th').get(0), isSelected, isIndeterminate);
               }
            }
         }
      },

      // Updates the information element of the DataTable showing information about the
      // items selected. Based on info() method of Select extension.
      showInfoSelected: function(){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         if (!ctx.oFeatures.bInfo) {
            return;
         }

         // Get index of the first column that has checkbox and row selection enabled
         var colIdx = self.getSelectRowColIndex();

         // If there is a column that has checkbox and row selection enabled
         if(colIdx !== null){
            // Count number of selected rows
            var countRows = 0;
            for (var cellData in ctx.checkboxes.s.data[colIdx]){
               if(
                  Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.data, colIdx)
                  && Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.data[colIdx], cellData)
               ) {
                  countRows++;
               }
            }

            var add = function($el, name, num){
               $el.append( $('<span class="select-item"/>').append( dt.i18n(
                  'select.'+name+'s',
                  { _: '%d '+name+'s selected', 0: '', 1: '1 '+name+' selected' },
                  num
               ) ) );
            };

            var infoEls = (ctx.checkboxes.s.isDataTablesV1)
               ? ctx.aanFeatures.i
               : ctx.checkboxes.s.infoEls;

            // Internal knowledge of DataTables to loop over all information elements
            $.each(infoEls, function ( i, el ) {
               var $el = $(el);

               var $output  = $('<span class="select-info"/>');
               add($output, 'row', countRows);

               var $existing = $el.children('span.select-info');
               if($existing.length){
                  $existing.remove();
               }

               if($output.text() !== ''){
                  $el.append($output);
               }
            });
         }
      },

      // Determines whether checkbox in the cell can be checked
      isCellSelectable: function(colIdx, cellData){
         var self = this;
         var ctx = self.s.ctx;

         // If data is in the list of disabled elements
         if(
            Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.dataDisabled, colIdx)
            && Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.dataDisabled[colIdx], cellData)
         ) {
            return false;

         // Otherwise, if checkbox can be selected
         } else {
            return true;
         }
      },

      // Gets cell index
      getCellIndex: function(cell){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // If FixedColumns v3 extension is available
         if(ctx._oFixedColumns){
            return dt.fixedColumns().cellIndex(cell);

         } else {
            return dt.cell(cell).index();
         }
      },

      // Gets index of the first column that has checkbox and row selection enabled
      getSelectRowColIndex: function(){
         var self = this;
         var ctx = self.s.ctx;

         var colIdx = null;

         for(var i = 0; i < ctx.aoColumns.length; i++){
            // If Checkboxes extension is enabled
            // and row selection is enabled for this column
            if(ctx.aoColumns[i].checkboxes && ctx.aoColumns[i].checkboxes.selectRow){
               colIdx = i;
               break;
            }
         }

         return colIdx;
      },

      // Updates fixed column if FixedColumns v3 extension is enabled
      // and given column is inside a fixed column
      updateFixedColumn: function(colIdx){
         var self = this;
         var dt = self.s.dt;
         var ctx = self.s.ctx;

         // If FixedColumns v3 extension is enabled
         if(ctx._oFixedColumns){
            var leftCols = ctx._oFixedColumns.s.iLeftColumns;
            var rightCols = ctx.aoColumns.length - ctx._oFixedColumns.s.iRightColumns - 1;

            if (colIdx < leftCols || colIdx > rightCols){
               // Update the data shown in the fixed column
               dt.fixedColumns().update();

               // Use delay to let FixedColumns construct the header
               // before we update the "Select all" checkbox
               setTimeout(function(){
                  // For every column where checkboxes are enabled
                  $.each(self.s.columns, function(index, colIdx){
                     self.updateSelectAll(colIdx);
                  });
               }, 0);
            }
         }
      }
   };


   /**
   * Checkboxes default settings for initialisation
   *
   * @namespace
   * @name Checkboxes.defaults
   * @static
   */
   Checkboxes.defaults = {
      /**
      * Enable / disable checkbox state loading/saving if state saving is enabled globally
      *
      * @type {Boolean}
      * @default `true`
      */
      stateSave: true,

      /**
      * Enable / disable row selection
      *
      * @type {Boolean}
      * @default `false`
      */
      selectRow: false,

      /**
      * Enable / disable "select all" control in the header
      *
      * @type {Boolean}
      * @default `true`
      */
      selectAll: true,

      /**
      * Enable / disable ability to select checkboxes from all pages
      *
      * @type {Boolean}
      * @default `true`
      */
      selectAllPages: true,

      /**
      * Checkbox select/deselect callback
      *
      * @type {Function}
      * @default  `null`
      */
      selectCallback: null,

      /**
      * "Select all" control select/deselect callback
      *
      * @type {Function}
      * @default  `null`
      */
      selectAllCallback: null,

      /**
      * "Select all" control markup
      *
      * @type {mixed}
      * @default `<input type="checkbox">`
      */
      selectAllRender: '<input type="checkbox" autocomplete="off">'
   };


   /*
   * API
   */
   var Api = $.fn.dataTable.Api;

   // Doesn't do anything - work around for a bug in DT... Not documented
   Api.register( 'checkboxes()', function () {
      return this;
   } );

   Api.registerPlural( 'columns().checkboxes.select()', 'column().checkboxes.select()', function ( state ) {
      if(typeof state === 'undefined'){ state = true; }

      return this.iterator( 'column-rows', function ( ctx, colIdx, i, j, rowsIdx ) {
         // If Checkboxes extension is enabled for this column
         if(ctx.aoColumns[colIdx].checkboxes){
            // Prepare a list of all cells
            var selector = [];
            $.each(rowsIdx, function(index, rowIdx){
               selector.push({ row: rowIdx, column: colIdx });
            });

            var cells = this.cells(selector);
            var cellsData = cells.data();

            // Prepare a list of cells that contain checkboxes that can be selected
            var rowsSelectableIdx = [];
            selector = [];
            $.each(cellsData, function(index, cellData){
               // If checkbox in the cell can be selected
               if(ctx.checkboxes.isCellSelectable(colIdx, cellData)){
                  selector.push({ row: rowsIdx[index], column: colIdx });
                  rowsSelectableIdx.push(rowsIdx[index]);
               }
            });

            cells = this.cells(selector);

            ctx.checkboxes.updateData(cells, colIdx, state);

            // If row selection is enabled
            if(ctx.aoColumns[colIdx].checkboxes.selectRow){
               ctx.checkboxes.updateSelect(rowsSelectableIdx, state);
            }

            ctx.checkboxes.updateCheckbox(cells, colIdx, state);

            ctx.checkboxes.updateSelectAll(colIdx);

            ctx.checkboxes.updateFixedColumn(colIdx);
         }
      }, 1 );
   } );

   Api.registerPlural( 'cells().checkboxes.select()', 'cell().checkboxes.select()', function ( state ) {
      if(typeof state === 'undefined'){ state = true; }

      return this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
         // If Checkboxes extension is enabled for this column
         if(ctx.aoColumns[colIdx].checkboxes){
            var cells = this.cells([{ row: rowIdx, column: colIdx }]);
            var cellData = this.cell({ row: rowIdx, column: colIdx }).data();

            // If checkbox in the cell can be selected
            if(ctx.checkboxes.isCellSelectable(colIdx, cellData)){
               ctx.checkboxes.updateData(cells, colIdx, state);

               // If row selection is enabled
               if(ctx.aoColumns[colIdx].checkboxes.selectRow){
                  ctx.checkboxes.updateSelect(rowIdx, state);
               }

               ctx.checkboxes.updateCheckbox(cells, colIdx, state);

               ctx.checkboxes.updateSelectAll(colIdx);

               ctx.checkboxes.updateFixedColumn(colIdx);
            }
         }
      }, 1 );
   } );

   Api.registerPlural( 'cells().checkboxes.enable()', 'cell().checkboxes.enable()', function ( state ) {
      if(typeof state === 'undefined'){ state = true; }

      return this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
         // If Checkboxes extension is enabled for this column
         if(ctx.aoColumns[colIdx].checkboxes){
            var cell = this.cell({ row: rowIdx, column: colIdx });

            // Get cell data
            var cellData = cell.data();

            // If checkbox should be enabled
            if(state){
               delete ctx.checkboxes.s.dataDisabled[colIdx][cellData];

            // Otherwise, if checkbox should be disabled
            } else {
               ctx.checkboxes.s.dataDisabled[colIdx][cellData] = 1;
            }

            // Determine if cell node is available
            // (deferRender is not enabled or cell has been already created)
            var cellNode = cell.node();
            if(cellNode){
               $('input.dt-checkboxes', cellNode).prop('disabled', !state);
            }

            // If row selection is enabled
            // and checkbox can be checked
            if(ctx.aoColumns[colIdx].checkboxes.selectRow){
               // If data is in the list
               if(
                  Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.data, colIdx)
                  && Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.data[colIdx], cellData)
               ) {
                  // Update selection based on current state:
                  // if checkbox is enabled then select row;
                  // otherwise, deselect row
                  ctx.checkboxes.updateSelect(rowIdx, state);
               }
            }
         }
      }, 1 );
   } );

   Api.registerPlural( 'cells().checkboxes.disable()', 'cell().checkboxes.disable()', function ( state ) {
      if(typeof state === 'undefined'){ state = true; }
      return this.checkboxes.enable(!state);
   } );

   Api.registerPlural( 'columns().checkboxes.deselect()', 'column().checkboxes.deselect()', function ( state ) {
      if(typeof state === 'undefined'){ state = true; }
      return this.checkboxes.select(!state);
   } );

   Api.registerPlural( 'cells().checkboxes.deselect()', 'cell().checkboxes.deselect()', function ( state ) {
      if(typeof state === 'undefined'){ state = true; }
      return this.checkboxes.select(!state);
   } );

   Api.registerPlural( 'columns().checkboxes.deselectAll()', 'column().checkboxes.deselectAll()', function () {
      return this.iterator( 'column', function (ctx, colIdx){
         // If Checkboxes extension is enabled for this column
         if(ctx.aoColumns[colIdx].checkboxes){
            ctx.checkboxes.s.data[colIdx] = {};

            this.column(colIdx).checkboxes.select(false);
         }
      }, 1 );
   } );

   Api.registerPlural( 'columns().checkboxes.selected()', 'column().checkboxes.selected()', function () {
      return this.iterator( 'column-rows', function ( ctx, colIdx, i, j, rowsIdx ) {

         // If Checkboxes extension is enabled for this column
         if(ctx.aoColumns[colIdx].checkboxes){
            var data = [];

            // If server-side processing mode is enabled
            if(ctx.oFeatures.bServerSide){
               $.each(ctx.checkboxes.s.data[colIdx], function(cellData){
                  // If checkbox in the cell can be checked
                  if(ctx.checkboxes.isCellSelectable(colIdx, cellData)){
                     data.push(cellData);
                  }
               });

            // Otherwise, if server-side processing mode is not enabled
            } else {
               // Prepare a list of all cells
               var selector = [];
               $.each(rowsIdx, function(index, rowIdx){
                  selector.push({ row: rowIdx, column: colIdx });
               });

               // Get all cells data
               var cells = this.cells(selector);
               var cellsData = cells.data();

               // Enumerate all cells data
               $.each(cellsData, function(index, cellData){
                  // If checkbox is checked
                  if(
                     Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.data, colIdx)
                     && Object.prototype.hasOwnProperty.call(ctx.checkboxes.s.data[colIdx], cellData)
                  ) {
                     // If checkbox in the cell can be selected
                     if(ctx.checkboxes.isCellSelectable(colIdx, cellData)){
                        data.push(cellData);
                     }
                  }
               });
            }

            return data;

         } else {
            return [];
         }
      }, 1 );
   } );


   /**
    * Version information
    *
    * @name Checkboxes.version
    * @static
    */
   Checkboxes.version = '1.3.0';


   $.fn.DataTable.Checkboxes = Checkboxes;
   $.fn.dataTable.Checkboxes = Checkboxes;


   // Attach a listener to the document which listens for DataTables initialisation
   // events so we can automatically initialise
   $(document).on( 'preInit.dt.dtCheckboxes', function (e, settings /*, json */ ) {
      if ( e.namespace !== 'dt' ) {
         return;
      }

      new Checkboxes( settings );
   } );


   return Checkboxes;
}));
