text: ﻿/************************************************************************
  * PAGING extension for jTable                                           *
  *************************************************************************/
  (function ($) {
  
      //Reference to base object members
      var base = {
          load: $.hik.jtable.prototype.load,
          _create: $.hik.jtable.prototype._create,
          _setOption: $.hik.jtable.prototype._setOption,
          _createRecordLoadUrl: $.hik.jtable.prototype._createRecordLoadUrl,
          _createJtParamsForLoading: $.hik.jtable.prototype._createJtParamsForLoading,
          _addRowToTable: $.hik.jtable.prototype._addRowToTable,
          _addRow: $.hik.jtable.prototype._addRow,
          _removeRowsFromTable: $.hik.jtable.prototype._removeRowsFromTable,
          _onRecordsLoaded: $.hik.jtable.prototype._onRecordsLoaded
      };
  
      //extension members
      $.extend(true, $.hik.jtable.prototype, {
  
          /************************************************************************
          * DEFAULT OPTIONS / EVENTS                                              *
          *************************************************************************/
          options: {
              paging: false,
              pageList: 'normal', //possible values: 'minimal', 'normal'
              pageSize: 10,
              pageSizes: [10, 25, 50, 100, 250, 500],
              pageSizeChangeArea: true,
              gotoPageArea: 'combobox', //possible values: 'textbox', 'combobox', 'none'
  
              messages: {
                  pagingInfo: 'Showing {0}-{1} of {2}',
                  pageSizeChangeLabel: 'Row count',
                  gotoPageLabel: 'Go to page'
              }
          },
  
          /************************************************************************
          * PRIVATE FIELDS                                                        *
          *************************************************************************/
  
          _$bottomPanel: null, //Reference to the panel at the bottom of the table (jQuery object)
          _$pagingListArea: null, //Reference to the page list area in to bottom panel (jQuery object)
          _$pageSizeChangeArea: null, //Reference to the page size change area in to bottom panel (jQuery object)
          _$pageInfoSpan: null, //Reference to the paging info area in to bottom panel (jQuery object)
          _$gotoPageArea: null, //Reference to 'Go to page' input area in to bottom panel (jQuery object)
          _$gotoPageInput: null, //Reference to 'Go to page' input in to bottom panel (jQuery object)
          _totalRecordCount: 0, //Total count of records on all pages
          _currentPageNo: 1, //Current page number
  
          /************************************************************************
          * CONSTRUCTOR AND INITIALIZING METHODS                                  *
          *************************************************************************/
  
          /* Overrides base method to do paging-specific constructions.
          *************************************************************************/
          _create: function() {
              base._create.apply(this, arguments);
              if (this.options.paging) {
                  this._loadPagingSettings();
                  this._createBottomPanel();
                  this._createPageListArea();
                  this._createGotoPageInput();
                  this._createPageSizeSelection();
              }
          },
  
          /* Loads user preferences for paging.
          *************************************************************************/
          _loadPagingSettings: function() {
              if (!this.options.saveUserPreferences) {
                  return;
              }
  
              var pageSize = this._getCookie('page-size');
              if (pageSize) {
                  this.options.pageSize = this._normalizeNumber(pageSize, 1, 1000000, this.options.pageSize);
              }
          },
  
          /* Creates bottom panel and adds to the page.
          *************************************************************************/
          _createBottomPanel: function() {
              this._$bottomPanel = $('<div />')
                  .addClass('jtable-bottom-panel')
                  .insertAfter(this._$table);
  
              this._jqueryuiThemeAddClass(this._$bottomPanel, 'ui-state-default');
  
              $('<div />').addClass('jtable-left-area').appendTo(this._$bottomPanel);
              $('<div />').addClass('jtable-right-area').appendTo(this._$bottomPanel);
          },
  
          /* Creates page list area.
          *************************************************************************/
          _createPageListArea: function() {
              this._$pagingListArea = $('<span></span>')
                  .addClass('jtable-page-list')
                  .appendTo(this._$bottomPanel.find('.jtable-left-area'));
  
              this._$pageInfoSpan = $('<span></span>')
                  .addClass('jtable-page-info')
                  .appendTo(this._$bottomPanel.find('.jtable-right-area'));
          },
  
          /* Creates page list change area.
          *************************************************************************/
          _createPageSizeSelection: function() {
              var self = this;
  
              if (!self.options.pageSizeChangeArea) {
                  return;
              }
  
              //Add current page size to page sizes list if not contains it
              if (self._findIndexInArray(self.options.pageSize, self.options.pageSizes) < 0) {
                  self.options.pageSizes.push(parseInt(self.options.pageSize));
                  self.options.pageSizes.sort(function(a, b) { return a - b; });
              }
  
              //Add a span to contain page size change items
              self._$pageSizeChangeArea = $('<span></span>')
                  .addClass('jtable-page-size-change')
                  .appendTo(self._$bottomPanel.find('.jtable-left-area'));
  
              //Page size label
              self._$pageSizeChangeArea.append('<span>' + self.options.messages.pageSizeChangeLabel + ': </span>');
  
              //Page size change combobox
              var $pageSizeChangeCombobox = $('<select></select>').appendTo(self._$pageSizeChangeArea);
  
              //Add page sizes to the combobox
              for (var i = 0; i < self.options.pageSizes.length; i++) {
                  $pageSizeChangeCombobox.append('<option value="' + self.options.pageSizes[i] + '">' + self.options.pageSizes[i] + '</option>');
              }
  
              //Select current page size
              $pageSizeChangeCombobox.val(self.options.pageSize);
  
              //Change page size on combobox change
              $pageSizeChangeCombobox.change(function() {
                  self._changePageSize(parseInt($(this).val()));
              });
          },
  
          /* Creates go to page area.
          *************************************************************************/
          _createGotoPageInput: function() {
              var self = this;
  
              if (!self.options.gotoPageArea || self.options.gotoPageArea == 'none') {
                  return;
              }
  
              //Add a span to contain goto page items
              this._$gotoPageArea = $('<span></span>')
                  .addClass('jtable-goto-page')
                  .appendTo(self._$bottomPanel.find('.jtable-left-area'));
  
              //Goto page label
              this._$gotoPageArea.append('<span>' + self.options.messages.gotoPageLabel + ': </span>');
  
              //Goto page input
              if (self.options.gotoPageArea == 'combobox') {
  
                  self._$gotoPageInput = $('<select></select>')
                      .appendTo(this._$gotoPageArea)
                      .data('pageCount', 1)
                      .change(function() {
                          self._changePage(parseInt($(this).val()));
                      });
                  self._$gotoPageInput.append('<option value="1">1</option>');
  
              } else { //textbox
  
                  self._$gotoPageInput = $('<input type="text" maxlength="10" value="' + self._currentPageNo + '" />')
                      .appendTo(this._$gotoPageArea)
                      .keypress(function(event) {
                          if (event.which == 13) { //enter
                              event.preventDefault();
                              self._changePage(parseInt(self._$gotoPageInput.val()));
                          } else if (event.which == 43) { // +
                              event.preventDefault();
                              self._changePage(parseInt(self._$gotoPageInput.val()) + 1);
                          } else if (event.which == 45) { // -
                              event.preventDefault();
                              self._changePage(parseInt(self._$gotoPageInput.val()) - 1);
                          } else {
                              //Allow only digits
                              var isValid = (
                                  (47 < event.keyCode && event.keyCode < 58 && event.shiftKey == false && event.altKey == false)
                                      || (event.keyCode == 8)
                                      || (event.keyCode == 9)
                              );
  
                              if (!isValid) {
                                  event.preventDefault();
                              }
                          }
                      });
  
              }
          },
  
          /* Refreshes the 'go to page' input.
          *************************************************************************/
          _refreshGotoPageInput: function() {
              if (!this.options.gotoPageArea || this.options.gotoPageArea == 'none') {
                  return;
              }
  
              if (this._totalRecordCount <= 0) {
                  this._$gotoPageArea.hide();
              } else {
                  this._$gotoPageArea.show();
              }
  
              if (this.options.gotoPageArea == 'combobox') {
                  var oldPageCount = this._$gotoPageInput.data('pageCount');
                  var currentPageCount = this._calculatePageCount();
                  if (oldPageCount != currentPageCount) {
                      this._$gotoPageInput.empty();
  
                      //Skip some pages is there are too many pages
                      var pageStep = 1;
                      if (currentPageCount > 10000) {
                          pageStep = 100;
                      } else if (currentPageCount > 5000) {
                          pageStep = 10;
                      } else if (currentPageCount > 2000) {
                          pageStep = 5;
                      } else if (currentPageCount > 1000) {
                          pageStep = 2;
                      }
  
                      for (var i = pageStep; i <= currentPageCount; i += pageStep) {
                          this._$gotoPageInput.append('<option value="' + i + '">' + i + '</option>');
                      }
  
                      this._$gotoPageInput.data('pageCount', currentPageCount);
                  }
              }
  
              //same for 'textbox' and 'combobox'
              this._$gotoPageInput.val(this._currentPageNo);
          },
  
          /************************************************************************
          * OVERRIDED METHODS                                                     *
          *************************************************************************/
  
          /* Overrides load method to set current page to 1.
          *************************************************************************/
          load: function() {
              this._currentPageNo = 1;
  
              base.load.apply(this, arguments);
          },
  
          /* Used to change options dynamically after initialization.
          *************************************************************************/
          _setOption: function(key, value) {
              base._setOption.apply(this, arguments);
  
              if (key == 'pageSize') {
                  this._changePageSize(parseInt(value));
              }
          },
  
          /* Changes current page size with given value.
          *************************************************************************/
          _changePageSize: function(pageSize) {
              if (pageSize == this.options.pageSize) {
                  return;
              }
  
              this.options.pageSize = pageSize;
  
              //Normalize current page
              var pageCount = this._calculatePageCount();
              if (this._currentPageNo > pageCount) {
                  this._currentPageNo = pageCount;
              }
              if (this._currentPageNo <= 0) {
                  this._currentPageNo = 1;
              }
  
              //if user sets one of the options on the combobox, then select it.
              var $pageSizeChangeCombobox = this._$bottomPanel.find('.jtable-page-size-change select');
              if ($pageSizeChangeCombobox.length > 0) {
                  if (parseInt($pageSizeChangeCombobox.val()) != pageSize) {
                      var selectedOption = $pageSizeChangeCombobox.find('option[value=' + pageSize + ']');
                      if (selectedOption.length > 0) {
                          $pageSizeChangeCombobox.val(pageSize);
                      }
                  }
              }
  
              this._savePagingSettings();
              this._reloadTable();
          },
  
          /* Saves user preferences for paging
          *************************************************************************/
          _savePagingSettings: function() {
              if (!this.options.saveUserPreferences) {
                  return;
              }
  
              this._setCookie('page-size', this.options.pageSize);
          },
  
          /* Overrides _createRecordLoadUrl method to add paging info to URL.
          *************************************************************************/
          _createRecordLoadUrl: function() {
              var loadUrl = base._createRecordLoadUrl.apply(this, arguments);
              loadUrl = this._addPagingInfoToUrl(loadUrl, this._currentPageNo);
              return loadUrl;
          },
  
          /* Overrides _createJtParamsForLoading method to add paging parameters to jtParams object.
          *************************************************************************/
          _createJtParamsForLoading: function () {
              var jtParams = base._createJtParamsForLoading.apply(this, arguments);
              
              if (this.options.paging) {
                  jtParams.jtStartIndex = (this._currentPageNo - 1) * this.options.pageSize;
                  jtParams.jtPageSize = this.options.pageSize;
              }
  
              return jtParams;
          },
  
          /* Overrides _addRowToTable method to re-load table when a new row is created.
          * NOTE: THIS METHOD IS DEPRECATED AND WILL BE REMOVED FROM FEATURE RELEASES.
          * USE _addRow METHOD.
          *************************************************************************/
          _addRowToTable: function ($tableRow, index, isNewRow) {
              if (isNewRow && this.options.paging) {
                  this._reloadTable();
                  return;
              }
  
              base._addRowToTable.apply(this, arguments);
          },
  
          /* Overrides _addRow method to re-load table when a new row is created.
          *************************************************************************/
          _addRow: function ($row, options) {
              if (options && options.isNewRow && this.options.paging) {
                  this._reloadTable();
                  return;
              }
  
              base._addRow.apply(this, arguments);
          },
  
          /* Overrides _removeRowsFromTable method to re-load table when a row is removed from table.
          *************************************************************************/
          _removeRowsFromTable: function ($rows, reason) {
              base._removeRowsFromTable.apply(this, arguments);
  
              if (this.options.paging) {
                  if (this._$tableRows.length <= 0 && this._currentPageNo > 1) {
                      --this._currentPageNo;
                  }
  
                  this._reloadTable();
              }
          },
  
          /* Overrides _onRecordsLoaded method to to do paging specific tasks.
          *************************************************************************/
          _onRecordsLoaded: function (data) {
              if (this.options.paging) {
                  this._totalRecordCount = data.TotalRecordCount;
                  this._createPagingList();
                  this._createPagingInfo();
                  this._refreshGotoPageInput();
              }
  
              base._onRecordsLoaded.apply(this, arguments);
          },
  
          /************************************************************************
          * PRIVATE METHODS                                                       *
          *************************************************************************/
  
          /* Adds jtStartIndex and jtPageSize parameters to a URL as query string.
          *************************************************************************/
          _addPagingInfoToUrl: function (url, pageNumber) {
              if (!this.options.paging) {
                  return url;
              }
  
              var jtStartIndex = (pageNumber - 1) * this.options.pageSize;
              var jtPageSize = this.options.pageSize;
  
              return (url + (url.indexOf('?') < 0 ? '?' : '&') + 'jtStartIndex=' + jtStartIndex + '&jtPageSize=' + jtPageSize);
          },
  
          /* Creates and shows the page list.
          *************************************************************************/
          _createPagingList: function () {
              if (this.options.pageSize <= 0) {
                  return;
              }
  
              this._$pagingListArea.empty();
              if (this._totalRecordCount <= 0) {
                  return;
              }
  
              var pageCount = this._calculatePageCount();
  
              this._createFirstAndPreviousPageButtons();
              if (this.options.pageList == 'normal') {
                  this._createPageNumberButtons(this._calculatePageNumbers(pageCount));
              }
              this._createLastAndNextPageButtons(pageCount);
              this._bindClickEventsToPageNumberButtons();
          },
  
          /* Creates and shows previous and first page links.
          *************************************************************************/
          _createFirstAndPreviousPageButtons: function () {
              var $first = $('<span></span>')
                  .addClass('jtable-page-number-first')
                  .html('&lt&lt')
                  .data('pageNumber', 1)
                  .appendTo(this._$pagingListArea);
  
              var $previous = $('<span></span>')
                  .addClass('jtable-page-number-previous')
                  .html('&lt')
                  .data('pageNumber', this._currentPageNo - 1)
                  .appendTo(this._$pagingListArea);
  
              this._jqueryuiThemeAddClass($first, 'ui-button ui-state-default', 'ui-state-hover');
              this._jqueryuiThemeAddClass($previous, 'ui-button ui-state-default', 'ui-state-hover');
  
              if (this._currentPageNo <= 1) {
                  $first.addClass('jtable-page-number-disabled');
                  $previous.addClass('jtable-page-number-disabled');
                  this._jqueryuiThemeAddClass($first, 'ui-state-disabled');
                  this._jqueryuiThemeAddClass($previous, 'ui-state-disabled');
              }
          },
  
          /* Creates and shows next and last page links.
          *************************************************************************/
          _createLastAndNextPageButtons: function (pageCount) {
              var $next = $('<span></span>')
                  .addClass('jtable-page-number-next')
                  .html('&gt')
                  .data('pageNumber', this._currentPageNo + 1)
                  .appendTo(this._$pagingListArea);
              var $last = $('<span></span>')
                  .addClass('jtable-page-number-last')
                  .html('&gt&gt')
                  .data('pageNumber', pageCount)
                  .appendTo(this._$pagingListArea);
  
              this._jqueryuiThemeAddClass($next, 'ui-button ui-state-default', 'ui-state-hover');
              this._jqueryuiThemeAddClass($last, 'ui-button ui-state-default', 'ui-state-hover');
  
              if (this._currentPageNo >= pageCount) {
                  $next.addClass('jtable-page-number-disabled');
                  $last.addClass('jtable-page-number-disabled');
                  this._jqueryuiThemeAddClass($next, 'ui-state-disabled');
                  this._jqueryuiThemeAddClass($last, 'ui-state-disabled');
              }
          },
  
          /* Creates and shows page number links for given number array.
          *************************************************************************/
          _createPageNumberButtons: function (pageNumbers) {
              var previousNumber = 0;
              for (var i = 0; i < pageNumbers.length; i++) {
                  //Create "..." between page numbers if needed
                  if ((pageNumbers[i] - previousNumber) > 1) {
                      $('<span></span>')
                          .addClass('jtable-page-number-space')
                          .html('...')
                          .appendTo(this._$pagingListArea);
                  }
  
                  this._createPageNumberButton(pageNumbers[i]);
                  previousNumber = pageNumbers[i];
              }
          },
  
          /* Creates a page number link and adds to paging area.
          *************************************************************************/
          _createPageNumberButton: function (pageNumber) {
              var $pageNumber = $('<span></span>')
                  .addClass('jtable-page-number')
                  .html(pageNumber)
                  .data('pageNumber', pageNumber)
                  .appendTo(this._$pagingListArea);
  
              this._jqueryuiThemeAddClass($pageNumber, 'ui-button ui-state-default', 'ui-state-hover');
              
              if (this._currentPageNo == pageNumber) {
                  $pageNumber.addClass('jtable-page-number-active jtable-page-number-disabled');
                  this._jqueryuiThemeAddClass($pageNumber, 'ui-state-active');
              }
          },
  
          /* Calculates total page count according to page size and total record count.
          *************************************************************************/
          _calculatePageCount: function () {
              var pageCount = Math.floor(this._totalRecordCount / this.options.pageSize);
              if (this._totalRecordCount % this.options.pageSize != 0) {
                  ++pageCount;
              }
  
              return pageCount;
          },
  
          /* Calculates page numbers and returns an array of these numbers.
          *************************************************************************/
          _calculatePageNumbers: function (pageCount) {
              if (pageCount <= 4) {
                  //Show all pages
                  var pageNumbers = [];
                  for (var i = 1; i <= pageCount; ++i) {
                      pageNumbers.push(i);
                  }
  
                  return pageNumbers;
              } else {
                  //show first three, last three, current, previous and next page numbers
                  var shownPageNumbers = [1, 2, pageCount - 1, pageCount];
                  var previousPageNo = this._normalizeNumber(this._currentPageNo - 1, 1, pageCount, 1);
                  var nextPageNo = this._normalizeNumber(this._currentPageNo + 1, 1, pageCount, 1);
  
                  this._insertToArrayIfDoesNotExists(shownPageNumbers, previousPageNo);
                  this._insertToArrayIfDoesNotExists(shownPageNumbers, this._currentPageNo);
                  this._insertToArrayIfDoesNotExists(shownPageNumbers, nextPageNo);
  
                  shownPageNumbers.sort(function (a, b) { return a - b; });
                  return shownPageNumbers;
              }
          },
  
          /* Creates and shows paging informations.
          *************************************************************************/
          _createPagingInfo: function () {
              if (this._totalRecordCount <= 0) {
                  this._$pageInfoSpan.empty();
                  return;
              }
  
              var startNo = (this._currentPageNo - 1) * this.options.pageSize + 1;
              var endNo = this._currentPageNo * this.options.pageSize;
              endNo = this._normalizeNumber(endNo, startNo, this._totalRecordCount, 0);
  
              if (endNo >= startNo) {
                  var pagingInfoMessage = this._formatString(this.options.messages.pagingInfo, startNo, endNo, this._totalRecordCount);
                  this._$pageInfoSpan.html(pagingInfoMessage);
              }
          },
  
          /* Binds click events of all page links to change the page.
          *************************************************************************/
          _bindClickEventsToPageNumberButtons: function () {
              var self = this;
              self._$pagingListArea
                  .find('.jtable-page-number,.jtable-page-number-previous,.jtable-page-number-next,.jtable-page-number-first,.jtable-page-number-last')
                  .not('.jtable-page-number-disabled')
                  .click(function (e) {
                      e.preventDefault();
                      self._changePage($(this).data('pageNumber'));
                  });
          },
  
          /* Changes current page to given value.
          *************************************************************************/
          _changePage: function (pageNo) {
              pageNo = this._normalizeNumber(pageNo, 1, this._calculatePageCount(), 1);
              if (pageNo == this._currentPageNo) {
                  this._refreshGotoPageInput();
                  return;
              }
  
              this._currentPageNo = pageNo;
              this._reloadTable();
          }
  
      });
  
  })(jQuery);
  