text: /*
   * Copyright (c) 2019. The copyright is reserved by Ghode of Harbin Institute
   * of Technology. Users are free to copy, change or remove. Because no one
   * will read this. Only I know is that Repeaters are the best of the world.
   * Only I know is that Repeaters are the best of the world. Only I know is
   * that Repeaters are the best of the world. Maybe a long copyright text
   * seems professional. Therefore this text will be a bit lengthy. However,
   * the author seems to be afraid that one day, this text may be uploaded to
   * business projects. That is the time you can contact with author via email
   * ghode@cirnocraft.im or directly ignore this, which will be interesting.
   */
  
  /*! Scroller 1.4.2
   * ©2011-2016 SpryMedia Ltd - datatables.net/license
   */
  
  (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;
  
  
      /**
       * Scroller is a virtual rendering plug-in for DataTables which allows large
       * datasets to be drawn on screen every quickly. What the virtual rendering means
       * is that only the visible portion of the table (and a bit to either side to make
       * the scrolling smooth) is drawn, while the scrolling container gives the
       * visual impression that the whole table is visible. This is done by making use
       * of the pagination abilities of DataTables and moving the table around in the
       * scrolling container DataTables adds to the page. The scrolling container is
       * forced to the height it would be for the full table display using an extra
       * element.
       *
       * Note that rows in the table MUST all be the same height. Information in a cell
       * which expands on to multiple lines will cause some odd behaviour in the scrolling.
       *
       * Scroller is initialised by simply including the letter 'S' in the sDom for the
       * table you want to have this feature enabled on. Note that the 'S' must come
       * AFTER the 't' parameter in `dom`.
       *
       * Key features include:
       *   <ul class="limit_length">
       *     <li>Speed! The aim of Scroller for DataTables is to make rendering large data sets fast</li>
       *     <li>Full compatibility with deferred rendering in DataTables for maximum speed</li>
       *     <li>Display millions of rows</li>
       *     <li>Integration with state saving in DataTables (scrolling position is saved)</li>
       *     <li>Easy to use</li>
       *   </ul>
       *
       *  @class
       *  @constructor
       *  @global
       *  @param {object} dt DataTables settings object or API instance
       *  @param {object} [opts={}] Configuration object for FixedColumns. Options
       *    are defined by {@link Scroller.defaults}
       *
       *  @requires jQuery 1.7+
       *  @requires DataTables 1.10.0+
       *
       *  @example
       *    $(document).ready(function() {
       *        $('#example').DataTable( {
       *            "scrollY": "200px",
       *            "ajax": "media/dataset/large.txt",
       *            "dom": "frtiS",
       *            "deferRender": true
       *        } );
       *    } );
       */
      var Scroller = function (dt, opts) {
          /* Sanity check - you just know it will happen */
          if (!(this instanceof Scroller)) {
              alert("Scroller warning: Scroller must be initialised with the 'new' keyword.");
              return;
          }
  
          if (opts === undefined) {
              opts = {};
          }
  
          /**
           * Settings object which contains customisable information for the Scroller instance
           * @namespace
           * @private
           * @extends Scroller.defaults
           */
          this.s = {
              /**
               * DataTables settings object
               *  @type     object
               *  @default  Passed in as first parameter to constructor
               */
              "dt": $.fn.dataTable.Api(dt).settings()[0],
  
              /**
               * Pixel location of the top of the drawn table in the viewport
               *  @type     int
               *  @default  0
               */
              "tableTop": 0,
  
              /**
               * Pixel location of the bottom of the drawn table in the viewport
               *  @type     int
               *  @default  0
               */
              "tableBottom": 0,
  
              /**
               * Pixel location of the boundary for when the next data set should be loaded and drawn
               * when scrolling up the way.
               *  @type     int
               *  @default  0
               *  @private
               */
              "redrawTop": 0,
  
              /**
               * Pixel location of the boundary for when the next data set should be loaded and drawn
               * when scrolling down the way. Note that this is actually calculated as the offset from
               * the top.
               *  @type     int
               *  @default  0
               *  @private
               */
              "redrawBottom": 0,
  
              /**
               * Auto row height or not indicator
               *  @type     bool
               *  @default  0
               */
              "autoHeight": true,
  
              /**
               * Number of rows calculated as visible in the visible viewport
               *  @type     int
               *  @default  0
               */
              "viewportRows": 0,
  
              /**
               * setTimeout reference for state saving, used when state saving is enabled in the DataTable
               * and when the user scrolls the viewport in order to stop the cookie set taking too much
               * CPU!
               *  @type     int
               *  @default  0
               */
              "stateTO": null,
  
              /**
               * setTimeout reference for the redraw, used when server-side processing is enabled in the
               * DataTables in order to prevent DoSing the server
               *  @type     int
               *  @default  null
               */
              "drawTO": null,
  
              heights: {
                  jump: null,
                  page: null,
                  virtual: null,
                  scroll: null,
  
                  /**
                   * Height of rows in the table
                   *  @type     int
                   *  @default  0
                   */
                  row: null,
  
                  /**
                   * Pixel height of the viewport
                   *  @type     int
                   *  @default  0
                   */
                  viewport: null
              },
  
              topRowFloat: 0,
              scrollDrawDiff: null,
              loaderVisible: false
          };
  
          // @todo The defaults should extend a `c` property and the internal settings
          // only held in the `s` property. At the moment they are mixed
          this.s = $.extend(this.s, Scroller.oDefaults, opts);
  
          // Workaround for row height being read from height object (see above comment)
          this.s.heights.row = this.s.rowHeight;
  
          /**
           * DOM elements used by the class instance
           * @private
           * @namespace
           *
           */
          this.dom = {
              "force": document.createElement('div'),
              "scroller": null,
              "table": null,
              "loader": null
          };
  
          // Attach the instance to the DataTables instance so it can be accessed in
          // future. Don't initialise Scroller twice on the same table
          if (this.s.dt.oScroller) {
              return;
          }
  
          this.s.dt.oScroller = this;
  
          /* Let's do it */
          this._fnConstruct();
      };
  
  
      $.extend(Scroller.prototype, {
          /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
           * Public methods
           * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
          /**
           * Calculate the pixel position from the top of the scrolling container for
           * a given row
           *  @param {int} iRow Row number to calculate the position of
           *  @returns {int} Pixels
           *  @example
           *    $(document).ready(function() {
           *      $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sAjaxSource": "media/dataset/large.txt",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "fnInitComplete": function (o) {
           *          // Find where row 25 is
           *          alert( o.oScroller.fnRowToPixels( 25 ) );
           *        }
           *      } );
           *    } );
           */
          "fnRowToPixels": function (rowIdx, intParse, virtual) {
              var pixels;
  
              if (virtual) {
                  pixels = this._domain('virtualToPhysical', rowIdx * this.s.heights.row);
              } else {
                  var diff = rowIdx - this.s.baseRowTop;
                  pixels = this.s.baseScrollTop + (diff * this.s.heights.row);
              }
  
              return intParse || intParse === undefined ?
                  parseInt(pixels, 10) :
                  pixels;
          },
  
  
          /**
           * Calculate the row number that will be found at the given pixel position
           * (y-scroll).
           *
           * Please note that when the height of the full table exceeds 1 million
           * pixels, Scroller switches into a non-linear mode for the scrollbar to fit
           * all of the records into a finite area, but this function returns a linear
           * value (relative to the last non-linear positioning).
           *  @param {int} iPixels Offset from top to calculate the row number of
           *  @param {int} [intParse=true] If an integer value should be returned
           *  @param {int} [virtual=false] Perform the calculations in the virtual domain
           *  @returns {int} Row index
           *  @example
           *    $(document).ready(function() {
           *      $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sAjaxSource": "media/dataset/large.txt",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "fnInitComplete": function (o) {
           *          // Find what row number is at 500px
           *          alert( o.oScroller.fnPixelsToRow( 500 ) );
           *        }
           *      } );
           *    } );
           */
          "fnPixelsToRow": function (pixels, intParse, virtual) {
              var diff = pixels - this.s.baseScrollTop;
              var row = virtual ?
                  this._domain('physicalToVirtual', pixels) / this.s.heights.row :
                  (diff / this.s.heights.row) + this.s.baseRowTop;
  
              return intParse || intParse === undefined ?
                  parseInt(row, 10) :
                  row;
          },
  
  
          /**
           * Calculate the row number that will be found at the given pixel position (y-scroll)
           *  @param {int} iRow Row index to scroll to
           *  @param {bool} [bAnimate=true] Animate the transition or not
           *  @returns {void}
           *  @example
           *    $(document).ready(function() {
           *      $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sAjaxSource": "media/dataset/large.txt",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "fnInitComplete": function (o) {
           *          // Immediately scroll to row 1000
           *          o.oScroller.fnScrollToRow( 1000 );
           *        }
           *      } );
           *
           *      // Sometime later on use the following to scroll to row 500...
           *          var oSettings = $('#example').dataTable().fnSettings();
           *      oSettings.oScroller.fnScrollToRow( 500 );
           *    } );
           */
          "fnScrollToRow": function (iRow, bAnimate) {
              var that = this;
              var ani = false;
              var px = this.fnRowToPixels(iRow);
  
              // We need to know if the table will redraw or not before doing the
              // scroll. If it will not redraw, then we need to use the currently
              // displayed table, and scroll with the physical pixels. Otherwise, we
              // need to calculate the table's new position from the virtual
              // transform.
              var preRows = ((this.s.displayBuffer - 1) / 2) * this.s.viewportRows;
              var drawRow = iRow - preRows;
              if (drawRow < 0) {
                  drawRow = 0;
              }
  
              if ((px > this.s.redrawBottom || px < this.s.redrawTop) && this.s.dt._iDisplayStart !== drawRow) {
                  ani = true;
                  px = this.fnRowToPixels(iRow, false, true);
              }
  
              if (typeof bAnimate == 'undefined' || bAnimate) {
                  this.s.ani = ani;
                  $(this.dom.scroller).animate({
                      "scrollTop": px
                  }, function () {
                      // This needs to happen after the animation has completed and
                      // the final scroll event fired
                      setTimeout(function () {
                          that.s.ani = false;
                      }, 25);
                  });
              } else {
                  $(this.dom.scroller).scrollTop(px);
              }
          },
  
  
          /**
           * Calculate and store information about how many rows are to be displayed
           * in the scrolling viewport, based on current dimensions in the browser's
           * rendering. This can be particularly useful if the table is initially
           * drawn in a hidden element - for example in a tab.
           *  @param {bool} [bRedraw=true] Redraw the table automatically after the recalculation, with
           *    the new dimensions forming the basis for the draw.
           *  @returns {void}
           *  @example
           *    $(document).ready(function() {
           *      // Make the example container hidden to throw off the browser's sizing
           *      document.getElementById('container').style.display = "none";
           *      var oTable = $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sAjaxSource": "media/dataset/large.txt",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "fnInitComplete": function (o) {
           *          // Immediately scroll to row 1000
           *          o.oScroller.fnScrollToRow( 1000 );
           *        }
           *      } );
           *
           *      setTimeout( function () {
           *        // Make the example container visible and recalculate the scroller sizes
           *        document.getElementById('container').style.display = "block";
           *        oTable.fnSettings().oScroller.fnMeasure();
           *      }, 3000 );
           */
          "fnMeasure": function (bRedraw) {
              if (this.s.autoHeight) {
                  this._fnCalcRowHeight();
              }
  
              var heights = this.s.heights;
  
              if (heights.row) {
                  heights.viewport = $(this.dom.scroller).height();
                  this.s.viewportRows = parseInt(heights.viewport / heights.row, 10) + 1;
                  this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer;
              }
  
              if (bRedraw === undefined || bRedraw) {
                  this.s.dt.oInstance.fnDraw(false);
              }
          },
  
  
          /**
           * Get information about current displayed record range. This corresponds to
           * the information usually displayed in the "Info" block of the table.
           *
           * @returns {object} info as an object:
           *  {
           *      start: {int}, // the 0-indexed record at the top of the viewport
           *      end:   {int}, // the 0-indexed record at the bottom of the viewport
           *  }
           */
          "fnPageInfo": function () {
              var
                  dt = this.s.dt,
                  iScrollTop = this.dom.scroller.scrollTop,
                  iTotal = dt.fnRecordsDisplay(),
                  iPossibleEnd = Math.ceil(this.fnPixelsToRow(iScrollTop + this.s.heights.viewport, false, this.s.ani));
  
              return {
                  start: Math.floor(this.fnPixelsToRow(iScrollTop, false, this.s.ani)),
                  end: iTotal < iPossibleEnd ? iTotal - 1 : iPossibleEnd - 1
              };
          },
  
  
          /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
           * Private methods (they are of course public in JS, but recommended as private)
           * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
          /**
           * Initialisation for Scroller
           *  @returns {void}
           *  @private
           */
          "_fnConstruct": function () {
              var that = this;
  
              /* Sanity check */
              if (!this.s.dt.oFeatures.bPaginate) {
                  this.s.dt.oApi._fnLog(this.s.dt, 0, 'Pagination must be enabled for Scroller');
                  return;
              }
  
              /* Insert a div element that we can use to force the DT scrolling container to
               * the height that would be required if the whole table was being displayed
               */
              this.dom.force.style.position = "relative";
              this.dom.force.style.top = "0px";
              this.dom.force.style.left = "0px";
              this.dom.force.style.width = "1px";
  
              this.dom.scroller = $('div.' + this.s.dt.oClasses.sScrollBody, this.s.dt.nTableWrapper)[0];
              this.dom.scroller.appendChild(this.dom.force);
              this.dom.scroller.style.position = "relative";
  
              this.dom.table = $('>table', this.dom.scroller)[0];
              this.dom.table.style.position = "absolute";
              this.dom.table.style.top = "0px";
              this.dom.table.style.left = "0px";
  
              // Add class to 'announce' that we are a Scroller table
              $(this.s.dt.nTableWrapper).addClass('DTS');
  
              // Add a 'loading' indicator
              if (this.s.loadingIndicator) {
                  this.dom.loader = $('<div class="dataTables_processing DTS_Loading">' + this.s.dt.oLanguage.sLoadingRecords + '</div>')
                      .css('display', 'none');
  
                  $(this.dom.scroller.parentNode)
                      .css('position', 'relative')
                      .append(this.dom.loader);
              }
  
              /* Initial size calculations */
              if (this.s.heights.row && this.s.heights.row != 'auto') {
                  this.s.autoHeight = false;
              }
              this.fnMeasure(false);
  
              /* Scrolling callback to see if a page change is needed - use a throttled
               * function for the save save callback so we aren't hitting it on every
               * scroll
               */
              this.s.ingnoreScroll = true;
              this.s.stateSaveThrottle = this.s.dt.oApi._fnThrottle(function () {
                  that.s.dt.oApi._fnSaveState(that.s.dt);
              }, 500);
              $(this.dom.scroller).on('scroll.DTS', function (e) {
                  that._fnScroll.call(that);
              });
  
              /* In iOS we catch the touchstart event in case the user tries to scroll
               * while the display is already scrolling
               */
              $(this.dom.scroller).on('touchstart.DTS', function () {
                  that._fnScroll.call(that);
              });
  
              /* Update the scroller when the DataTable is redrawn */
              this.s.dt.aoDrawCallback.push({
                  "fn": function () {
                      if (that.s.dt.bInitialised) {
                          that._fnDrawCallback.call(that);
                      }
                  },
                  "sName": "Scroller"
              });
  
              /* On resize, update the information element, since the number of rows shown might change */
              $(window).on('resize.DTS', function () {
                  that.fnMeasure(false);
                  that._fnInfo();
              });
  
              /* Add a state saving parameter to the DT state saving so we can restore the exact
               * position of the scrolling
               */
              var initialStateSave = true;
              this.s.dt.oApi._fnCallbackReg(this.s.dt, 'aoStateSaveParams', function (oS, oData) {
                  /* Set iScroller to saved scroll position on initialization.
                   */
                  if (initialStateSave && that.s.dt.oLoadedState) {
                      oData.iScroller = that.s.dt.oLoadedState.iScroller;
                      oData.iScrollerTopRow = that.s.dt.oLoadedState.iScrollerTopRow;
                      initialStateSave = false;
                  } else {
                      oData.iScroller = that.dom.scroller.scrollTop;
                      oData.iScrollerTopRow = that.s.topRowFloat;
                  }
              }, "Scroller_State");
  
              if (this.s.dt.oLoadedState) {
                  this.s.topRowFloat = this.s.dt.oLoadedState.iScrollerTopRow || 0;
              }
  
              // Measure immediately. Scroller will have been added using preInit, so
              // we can reliably do this here. We could potentially also measure on
              // init complete, which would be useful for cases where the data is Ajax
              // loaded and longer than a single line.
              $(this.s.dt.nTable).one('init.dt', function () {
                  that.fnMeasure();
              });
  
              /* Destructor */
              this.s.dt.aoDestroyCallback.push({
                  "sName": "Scroller",
                  "fn": function () {
                      $(window).off('resize.DTS');
                      $(that.dom.scroller).off('touchstart.DTS scroll.DTS');
                      $(that.s.dt.nTableWrapper).removeClass('DTS');
                      $('div.DTS_Loading', that.dom.scroller.parentNode).remove();
                      $(that.s.dt.nTable).off('init.dt');
  
                      that.dom.table.style.position = "";
                      that.dom.table.style.top = "";
                      that.dom.table.style.left = "";
                  }
              });
          },
  
  
          /**
           * Scrolling function - fired whenever the scrolling position is changed.
           * This method needs to use the stored values to see if the table should be
           * redrawn as we are moving towards the end of the information that is
           * currently drawn or not. If needed, then it will redraw the table based on
           * the new position.
           *  @returns {void}
           *  @private
           */
          "_fnScroll": function () {
              var
                  that = this,
                  heights = this.s.heights,
                  iScrollTop = this.dom.scroller.scrollTop,
                  iTopRow;
  
              if (this.s.skip) {
                  return;
              }
  
              if (this.s.ingnoreScroll) {
                  return;
              }
  
              /* If the table has been sorted or filtered, then we use the redraw that
               * DataTables as done, rather than performing our own
               */
              if (this.s.dt.bFiltered || this.s.dt.bSorted) {
                  this.s.lastScrollTop = 0;
                  return;
              }
  
              /* Update the table's information display for what is now in the viewport */
              this._fnInfo();
  
              /* We don't want to state save on every scroll event - that's heavy
               * handed, so use a timeout to update the state saving only when the
               * scrolling has finished
               */
              clearTimeout(this.s.stateTO);
              this.s.stateTO = setTimeout(function () {
                  that.s.dt.oApi._fnSaveState(that.s.dt);
              }, 250);
  
              /* Check if the scroll point is outside the trigger boundary which would required
               * a DataTables redraw
               */
              if (iScrollTop < this.s.redrawTop || iScrollTop > this.s.redrawBottom) {
                  var preRows = Math.ceil(((this.s.displayBuffer - 1) / 2) * this.s.viewportRows);
  
                  if (Math.abs(iScrollTop - this.s.lastScrollTop) > heights.viewport || this.s.ani) {
                      iTopRow = parseInt(this._domain('physicalToVirtual', iScrollTop) / heights.row, 10) - preRows;
                      this.s.topRowFloat = this._domain('physicalToVirtual', iScrollTop) / heights.row;
                  } else {
                      iTopRow = this.fnPixelsToRow(iScrollTop) - preRows;
                      this.s.topRowFloat = this.fnPixelsToRow(iScrollTop, false);
                  }
  
                  if (iTopRow <= 0) {
                      /* At the start of the table */
                      iTopRow = 0;
                  } else if (iTopRow + this.s.dt._iDisplayLength > this.s.dt.fnRecordsDisplay()) {
                      /* At the end of the table */
                      iTopRow = this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength;
                      if (iTopRow < 0) {
                          iTopRow = 0;
                      }
                  } else if (iTopRow % 2 !== 0) {
                      // For the row-striping classes (odd/even) we want only to start
                      // on evens otherwise the stripes will change between draws and
                      // look rubbish
                      iTopRow++;
                  }
  
                  if (iTopRow != this.s.dt._iDisplayStart) {
                      /* Cache the new table position for quick lookups */
                      this.s.tableTop = $(this.s.dt.nTable).offset().top;
                      this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop;
  
                      var draw = function () {
                          if (that.s.scrollDrawReq === null) {
                              that.s.scrollDrawReq = iScrollTop;
                          }
  
                          that.s.dt._iDisplayStart = iTopRow;
                          that.s.dt.oApi._fnDraw(that.s.dt);
                      };
  
                      /* Do the DataTables redraw based on the calculated start point - note that when
                       * using server-side processing we introduce a small delay to not DoS the server...
                       */
                      if (this.s.dt.oFeatures.bServerSide) {
                          clearTimeout(this.s.drawTO);
                          this.s.drawTO = setTimeout(draw, this.s.serverWait);
                      } else {
                          draw();
                      }
  
                      if (this.dom.loader && !this.s.loaderVisible) {
                          this.dom.loader.css('display', 'block');
                          this.s.loaderVisible = true;
                      }
                  }
              } else {
                  this.s.topRowFloat = this._domain('physicalToVirtual', iScrollTop) / heights.row;
              }
  
              this.s.lastScrollTop = iScrollTop;
              this.s.stateSaveThrottle();
          },
  
  
          /**
           * Convert from one domain to another. The physical domain is the actual
           * pixel count on the screen, while the virtual is if we had browsers which
           * had scrolling containers of infinite height (i.e. the absolute value)
           *
           *  @param {string} dir Domain transform direction, `virtualToPhysical` or
           *    `physicalToVirtual`
           *  @returns {number} Calculated transform
           *  @private
           */
          _domain: function (dir, val) {
              var heights = this.s.heights;
              var coeff;
  
              // If the virtual and physical height match, then we use a linear
              // transform between the two, allowing the scrollbar to be linear
              if (heights.virtual === heights.scroll) {
                  return val;
              }
  
              // Otherwise, we want a non-linear scrollbar to take account of the
              // redrawing regions at the start and end of the table, otherwise these
              // can stutter badly - on large tables 30px (for example) scroll might
              // be hundreds of rows, so the table would be redrawing every few px at
              // the start and end. Use a simple quadratic to stop this. It does mean
              // the scrollbar is non-linear, but with such massive data sets, the
              // scrollbar is going to be a best guess anyway
              var xMax = (heights.scroll - heights.viewport) / 2;
              var yMax = (heights.virtual - heights.viewport) / 2;
  
              coeff = yMax / (xMax * xMax);
  
              if (dir === 'virtualToPhysical') {
                  if (val < yMax) {
                      return Math.pow(val / coeff, 0.5);
                  } else {
                      val = (yMax * 2) - val;
                      return val < 0 ?
                          heights.scroll :
                          (xMax * 2) - Math.pow(val / coeff, 0.5);
                  }
              } else if (dir === 'physicalToVirtual') {
                  if (val < xMax) {
                      return val * val * coeff;
                  } else {
                      val = (xMax * 2) - val;
                      return val < 0 ?
                          heights.virtual :
                          (yMax * 2) - (val * val * coeff);
                  }
              }
          },
  
  
          /**
           * Draw callback function which is fired when the DataTable is redrawn. The main function of
           * this method is to position the drawn table correctly the scrolling container for the rows
           * that is displays as a result of the scrolling position.
           *  @returns {void}
           *  @private
           */
          "_fnDrawCallback": function () {
              var
                  that = this,
                  heights = this.s.heights,
                  iScrollTop = this.dom.scroller.scrollTop,
                  iActualScrollTop = iScrollTop,
                  iScrollBottom = iScrollTop + heights.viewport,
                  iTableHeight = $(this.s.dt.nTable).height(),
                  displayStart = this.s.dt._iDisplayStart,
                  displayLen = this.s.dt._iDisplayLength,
                  displayEnd = this.s.dt.fnRecordsDisplay();
  
              // Disable the scroll event listener while we are updating the DOM
              this.s.skip = true;
  
              // Resize the scroll forcing element
              this._fnScrollForce();
  
              // Reposition the scrolling for the updated virtual position if needed
              if (displayStart === 0) {
                  // Linear calculation at the top of the table
                  iScrollTop = this.s.topRowFloat * heights.row;
              } else if (displayStart + displayLen >= displayEnd) {
                  // Linear calculation that the bottom as well
                  iScrollTop = heights.scroll - ((displayEnd - this.s.topRowFloat) * heights.row);
              } else {
                  // Domain scaled in the middle
                  iScrollTop = this._domain('virtualToPhysical', this.s.topRowFloat * heights.row);
              }
  
              this.dom.scroller.scrollTop = iScrollTop;
  
              // Store positional information so positional calculations can be based
              // upon the current table draw position
              this.s.baseScrollTop = iScrollTop;
              this.s.baseRowTop = this.s.topRowFloat;
  
              // Position the table in the virtual scroller
              var tableTop = iScrollTop - ((this.s.topRowFloat - displayStart) * heights.row);
              if (displayStart === 0) {
                  tableTop = 0;
              } else if (displayStart + displayLen >= displayEnd) {
                  tableTop = heights.scroll - iTableHeight;
              }
  
              this.dom.table.style.top = tableTop + 'px';
  
              /* Cache some information for the scroller */
              this.s.tableTop = tableTop;
              this.s.tableBottom = iTableHeight + this.s.tableTop;
  
              // Calculate the boundaries for where a redraw will be triggered by the
              // scroll event listener
              var boundaryPx = (iScrollTop - this.s.tableTop) * this.s.boundaryScale;
              this.s.redrawTop = iScrollTop - boundaryPx;
              this.s.redrawBottom = iScrollTop + boundaryPx;
  
              this.s.skip = false;
  
              // Restore the scrolling position that was saved by DataTable's state
              // saving Note that this is done on the second draw when data is Ajax
              // sourced, and the first draw when DOM soured
              if (this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null &&
                  typeof this.s.dt.oLoadedState.iScroller != 'undefined') {
                  // A quirk of DataTables is that the draw callback will occur on an
                  // empty set if Ajax sourced, but not if server-side processing.
                  var ajaxSourced = (this.s.dt.sAjaxSource || that.s.dt.ajax) && !this.s.dt.oFeatures.bServerSide ?
                      true :
                      false;
  
                  if ((ajaxSourced && this.s.dt.iDraw == 2) ||
                      (!ajaxSourced && this.s.dt.iDraw == 1)) {
                      setTimeout(function () {
                          $(that.dom.scroller).scrollTop(that.s.dt.oLoadedState.iScroller);
                          that.s.redrawTop = that.s.dt.oLoadedState.iScroller - (heights.viewport / 2);
  
                          // In order to prevent layout thrashing we need another
                          // small delay
                          setTimeout(function () {
                              that.s.ingnoreScroll = false;
                          }, 0);
                      }, 0);
                  }
              } else {
                  that.s.ingnoreScroll = false;
              }
  
              // Because of the order of the DT callbacks, the info update will
              // take precedence over the one we want here. So a 'thread' break is
              // needed.  Only add the thread break if bInfo is set
              if (this.s.dt.oFeatures.bInfo) {
                  setTimeout(function () {
                      that._fnInfo.call(that);
                  }, 0);
              }
  
              // Hide the loading indicator
              if (this.dom.loader && this.s.loaderVisible) {
                  this.dom.loader.css('display', 'none');
                  this.s.loaderVisible = false;
              }
          },
  
  
          /**
           * Force the scrolling container to have height beyond that of just the
           * table that has been drawn so the user can scroll the whole data set.
           *
           * Note that if the calculated required scrolling height exceeds a maximum
           * value (1 million pixels - hard-coded) the forcing element will be set
           * only to that maximum value and virtual / physical domain transforms will
           * be used to allow Scroller to display tables of any number of records.
           *  @returns {void}
           *  @private
           */
          _fnScrollForce: function () {
              var heights = this.s.heights;
              var max = 1000000;
  
              heights.virtual = heights.row * this.s.dt.fnRecordsDisplay();
              heights.scroll = heights.virtual;
  
              if (heights.scroll > max) {
                  heights.scroll = max;
              }
  
              // Minimum height so there is always a row visible (the 'no rows found'
              // if reduced to zero filtering)
              this.dom.force.style.height = heights.scroll > this.s.heights.row ?
                  heights.scroll + 'px' :
                  this.s.heights.row + 'px';
          },
  
  
          /**
           * Automatic calculation of table row height. This is just a little tricky here as using
           * initialisation DataTables has tale the table out of the document, so we need to create
           * a new table and insert it into the document, calculate the row height and then whip the
           * table out.
           *  @returns {void}
           *  @private
           */
          "_fnCalcRowHeight": function () {
              var dt = this.s.dt;
              var origTable = dt.nTable;
              var nTable = origTable.cloneNode(false);
              var tbody = $('<tbody/>').appendTo(nTable);
              var container = $(
                  '<div class="' + dt.oClasses.sWrapper + ' DTS">' +
                  '<div class="' + dt.oClasses.sScrollWrapper + '">' +
                  '<div class="' + dt.oClasses.sScrollBody + '"></div>' +
                  '</div>' +
                  '</div>'
              );
  
              // Want 3 rows in the sizing table so :first-child and :last-child
              // CSS styles don't come into play - take the size of the middle row
              $('tbody tr:lt(4)', origTable).clone().appendTo(tbody);
              while ($('tr', tbody).length < 3) {
                  tbody.append('<tr><td>&nbsp;</td></tr>');
              }
  
              $('div.' + dt.oClasses.sScrollBody, container).append(nTable);
  
              // If initialised using `dom`, use the holding element as the insert point
              var insertEl = this.s.dt.nHolding || origTable.parentNode;
  
              if (!$(insertEl).is(':visible')) {
                  insertEl = 'body';
              }
  
              container.appendTo(insertEl);
              this.s.heights.row = $('tr', tbody).eq(1).outerHeight();
  
              container.remove();
          },
  
  
          /**
           * Update any information elements that are controlled by the DataTable based on the scrolling
           * viewport and what rows are visible in it. This function basically acts in the same way as
           * _fnUpdateInfo in DataTables, and effectively replaces that function.
           *  @returns {void}
           *  @private
           */
          "_fnInfo": function () {
              if (!this.s.dt.oFeatures.bInfo) {
                  return;
              }
  
              var
                  dt = this.s.dt,
                  language = dt.oLanguage,
                  iScrollTop = this.dom.scroller.scrollTop,
                  iStart = Math.floor(this.fnPixelsToRow(iScrollTop, false, this.s.ani) + 1),
                  iMax = dt.fnRecordsTotal(),
                  iTotal = dt.fnRecordsDisplay(),
                  iPossibleEnd = Math.ceil(this.fnPixelsToRow(iScrollTop + this.s.heights.viewport, false, this.s.ani)),
                  iEnd = iTotal < iPossibleEnd ? iTotal : iPossibleEnd,
                  sStart = dt.fnFormatNumber(iStart),
                  sEnd = dt.fnFormatNumber(iEnd),
                  sMax = dt.fnFormatNumber(iMax),
                  sTotal = dt.fnFormatNumber(iTotal),
                  sOut;
  
              if (dt.fnRecordsDisplay() === 0 &&
                  dt.fnRecordsDisplay() == dt.fnRecordsTotal()) {
                  /* Empty record set */
                  sOut = language.sInfoEmpty + language.sInfoPostFix;
              } else if (dt.fnRecordsDisplay() === 0) {
                  /* Empty record set after filtering */
                  sOut = language.sInfoEmpty + ' ' +
                      language.sInfoFiltered.replace('_MAX_', sMax) +
                      language.sInfoPostFix;
              } else if (dt.fnRecordsDisplay() == dt.fnRecordsTotal()) {
                  /* Normal record set */
                  sOut = language.sInfo.replace('_START_', sStart).replace('_END_', sEnd).replace('_MAX_', sMax).replace('_TOTAL_', sTotal) +
                      language.sInfoPostFix;
              } else {
                  /* Record set after filtering */
                  sOut = language.sInfo.replace('_START_', sStart).replace('_END_', sEnd).replace('_MAX_', sMax).replace('_TOTAL_', sTotal) + ' ' +
                      language.sInfoFiltered.replace(
                          '_MAX_',
                          dt.fnFormatNumber(dt.fnRecordsTotal())
                      ) +
                      language.sInfoPostFix;
              }
  
              var callback = language.fnInfoCallback;
              if (callback) {
                  sOut = callback.call(dt.oInstance,
                      dt, iStart, iEnd, iMax, iTotal, sOut
                  );
              }
  
              var n = dt.aanFeatures.i;
              if (typeof n != 'undefined') {
                  for (var i = 0, iLen = n.length; i < iLen; i++) {
                      $(n[i]).html(sOut);
                  }
              }
  
              // DT doesn't actually (yet) trigger this event, but it will in future
              $(dt.nTable).triggerHandler('info.dt');
          }
      });
  
  
      /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       * Statics
       * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
  
      /**
       * Scroller default settings for initialisation
       *  @namespace
       *  @name Scroller.defaults
       *  @static
       */
      Scroller.defaults = /** @lends Scroller.defaults */{
          /**
           * Indicate if Scroller show show trace information on the console or not. This can be
           * useful when debugging Scroller or if just curious as to what it is doing, but should
           * be turned off for production.
           *  @type     bool
           *  @default  false
           *  @static
           *  @example
           *    var oTable = $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "oScroller": {
           *          "trace": true
           *        }
           *    } );
           */
          "trace": false,
  
          /**
           * Scroller will attempt to automatically calculate the height of rows for it's internal
           * calculations. However the height that is used can be overridden using this parameter.
           *  @type     int|string
           *  @default  auto
           *  @static
           *  @example
           *    var oTable = $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "oScroller": {
           *          "rowHeight": 30
           *        }
           *    } );
           */
          "rowHeight": "auto",
  
          /**
           * When using server-side processing, Scroller will wait a small amount of time to allow
           * the scrolling to finish before requesting more data from the server. This prevents
           * you from DoSing your own server! The wait time can be configured by this parameter.
           *  @type     int
           *  @default  200
           *  @static
           *  @example
           *    var oTable = $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "oScroller": {
           *          "serverWait": 100
           *        }
           *    } );
           */
          "serverWait": 200,
  
          /**
           * The display buffer is what Scroller uses to calculate how many rows it should pre-fetch
           * for scrolling. Scroller automatically adjusts DataTables' display length to pre-fetch
           * rows that will be shown in "near scrolling" (i.e. just beyond the current display area).
           * The value is based upon the number of rows that can be displayed in the viewport (i.e.
           * a value of 1), and will apply the display range to records before before and after the
           * current viewport - i.e. a factor of 3 will allow Scroller to pre-fetch 1 viewport's worth
           * of rows before the current viewport, the current viewport's rows and 1 viewport's worth
           * of rows after the current viewport. Adjusting this value can be useful for ensuring
           * smooth scrolling based on your data set.
           *  @type     int
           *  @default  7
           *  @static
           *  @example
           *    var oTable = $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "oScroller": {
           *          "displayBuffer": 10
           *        }
           *    } );
           */
          "displayBuffer": 9,
  
          /**
           * Scroller uses the boundary scaling factor to decide when to redraw the table - which it
           * typically does before you reach the end of the currently loaded data set (in order to
           * allow the data to look continuous to a user scrolling through the data). If given as 0
           * then the table will be redrawn whenever the viewport is scrolled, while 1 would not
           * redraw the table until the currently loaded data has all been shown. You will want
           * something in the middle - the default factor of 0.5 is usually suitable.
           *  @type     float
           *  @default  0.5
           *  @static
           *  @example
           *    var oTable = $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "oScroller": {
           *          "boundaryScale": 0.75
           *        }
           *    } );
           */
          "boundaryScale": 0.5,
  
          /**
           * Show (or not) the loading element in the background of the table. Note that you should
           * include the dataTables.scroller.css file for this to be displayed correctly.
           *  @type     boolean
           *  @default  false
           *  @static
           *  @example
           *    var oTable = $('#example').dataTable( {
           *        "sScrollY": "200px",
           *        "sDom": "frtiS",
           *        "bDeferRender": true,
           *        "oScroller": {
           *          "loadingIndicator": true
           *        }
           *    } );
           */
          "loadingIndicator": false
      };
  
      Scroller.oDefaults = Scroller.defaults;
  
  
      /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       * Constants
       * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
      /**
       * Scroller version
       *  @type      String
       *  @default   See code
       *  @name      Scroller.version
       *  @static
       */
      Scroller.version = "1.4.2";
  
  
      /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       * Initialisation
       * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
  // Legacy `dom` parameter initialisation support
      if (typeof $.fn.dataTable == "function" &&
          typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
          $.fn.dataTableExt.fnVersionCheck('1.10.0')) {
          $.fn.dataTableExt.aoFeatures.push({
              "fnInit": function (oDTSettings) {
                  var init = oDTSettings.oInit;
                  var opts = init.scroller || init.oScroller || {};
  
                  new Scroller(oDTSettings, opts);
              },
              "cFeature": "S",
              "sFeature": "Scroller"
          });
      } else {
          alert("Warning: Scroller requires DataTables 1.10.0 or greater - www.datatables.net/download");
      }
  
  // Attach a listener to the document which listens for DataTables initialisation
  // events so we can automatically initialise
      $(document).on('preInit.dt.dtscroller', function (e, settings) {
          if (e.namespace !== 'dt') {
              return;
          }
  
          var init = settings.oInit.scroller;
          var defaults = DataTable.defaults.scroller;
  
          if (init || defaults) {
              var opts = $.extend({}, init, defaults);
  
              if (init !== false) {
                  new Scroller(settings, opts);
              }
          }
      });
  
  
  // Attach Scroller to DataTables so it can be accessed as an 'extra'
      $.fn.dataTable.Scroller = Scroller;
      $.fn.DataTable.Scroller = Scroller;
  
  
  // DataTables 1.10 API method aliases
      var Api = $.fn.dataTable.Api;
  
      Api.register('scroller()', function () {
          return this;
      });
  
  // Undocumented and deprecated - is it actually useful at all?
      Api.register('scroller().rowToPixels()', function (rowIdx, intParse, virtual) {
          var ctx = this.context;
  
          if (ctx.length && ctx[0].oScroller) {
              return ctx[0].oScroller.fnRowToPixels(rowIdx, intParse, virtual);
          }
          // undefined
      });
  
  // Undocumented and deprecated - is it actually useful at all?
      Api.register('scroller().pixelsToRow()', function (pixels, intParse, virtual) {
          var ctx = this.context;
  
          if (ctx.length && ctx[0].oScroller) {
              return ctx[0].oScroller.fnPixelsToRow(pixels, intParse, virtual);
          }
          // undefined
      });
  
  // Undocumented and deprecated - use `row().scrollTo()` instead
      Api.register('scroller().scrollToRow()', function (row, ani) {
          this.iterator('table', function (ctx) {
              if (ctx.oScroller) {
                  ctx.oScroller.fnScrollToRow(row, ani);
              }
          });
  
          return this;
      });
  
      Api.register('row().scrollTo()', function (ani) {
          var that = this;
  
          this.iterator('row', function (ctx, rowIdx) {
              if (ctx.oScroller) {
                  var displayIdx = that
                      .rows({order: 'applied', search: 'applied'})
                      .indexes()
                      .indexOf(rowIdx);
  
                  ctx.oScroller.fnScrollToRow(displayIdx, ani);
              }
          });
  
          return this;
      });
  
      Api.register('scroller.measure()', function (redraw) {
          this.iterator('table', function (ctx) {
              if (ctx.oScroller) {
                  ctx.oScroller.fnMeasure(redraw);
              }
          });
  
          return this;
      });
  
      Api.register('scroller.page()', function () {
          var ctx = this.context;
  
          if (ctx.length && ctx[0].oScroller) {
              return ctx[0].oScroller.fnPageInfo();
          }
          // undefined
      });
  
      return Scroller;
  }));
  