text: /*
   * 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:
      
      * 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().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;
  }));
  