APIs

Show:
/**
  @module ember-flexberry
 */

import Mixin from '@ember/object/mixin';
import { computed } from '@ember/object';

/**
  Mixin for controller, that pagination support.

  @example
    ```javascript
    // app/controllers/employees.js
    import Controller from '@ember/controller';
    import PaginatedController from 'ember-flexberry/mixins/paginated-controller'
    export default Controller.extend(PaginatedController, {
    });
    ```

    ```javascript
    // app/routes/employees.js
    import Route from '@ember/routing/route';
    import PaginatedRoute from 'ember-flexberry/mixins/paginated-route'
    export default Route.extend(PaginatedRoute, {
    });
    ```

    ```handlebars
    <!-- app/templates/employees.hbs -->
    ...
    {{flexberry-objectlistview
      ...
      pages=pages
      recordsTotalCount=recordsTotalCount
      perPageValue=perPageValue
      perPageValues=perPageValues
      hasPreviousPage=hasPreviousPage
      hasNextPage=hasNextPage
      previousPage=(action 'previousPage')
      gotoPage=(action 'gotoPage')
      nextPage=(action 'nextPage')
      ...
    }}
    ...
    ```

  @class PaginatedController
  @uses <a href="https://www.emberjs.com/api/ember/release/classes/Mixin">Mixin</a>
 */
export default Mixin.create({
  /**
    Start page.

    @property page
    @type Number
    @default 1
  */
  page: 1,

  /**
    Count records on page.

    @property perPage
    @type Number
    @default 5
  */
  perPage: 5,

  /**
    User selectable options values for `perPage`.

    @property perPageValues
    @type Array
    @default [5, 10, 20, 50]
  */
  perPageValues: [5, 10, 20, 50],

  /**
    Get or set `perPage` value.

    @property perPageValue
    @type Number
  */
  perPageValue: computed('perPage', {
    /* eslint-disable no-unused-vars */
    get(key) {
      let perPage = this.get('perPage');
      /*let perPageValues = this.get('perPageValues');
      if (perPageValues.indexOf(perPage) === -1) {
        // Если perPage не будет в perPageValues,
        // то в select-е будет выбрано undefined,
        // => perPage изменится undefined, т.к. на нем биндинг.
        perPageValues.push(perPage);
        perPageValues.sort((a, b) => a - b);
      }*/

      return perPage;
    },

    set(key, value) {
      let perPage = parseInt(value, 10);

      // Check that the current page number does not exceed the last page number.
      let currentPage = this.get('page');
      let newLastPage = this._getLastPage(perPage);
      if (currentPage > newLastPage) {
        // Changing page or perPage value reloads route automatically.
        this.setProperties({
          page: newLastPage,
          perPage: perPage
        });
      } else {
        // Changing perPage value reloads route automatically.
        this.set('perPage', perPage);
      }

      return perPage;
    }
    /* eslint-enable no-unused-vars */
  }),

  /**
    Total count records.

    @property recordsTotalCount
    @type Number
    @readOnly
  */
  recordsTotalCount: computed('model', function() {
    return this.get('model.meta.count');
  }),

  /**
    If `true` next page exists.

    @property hasNextPage
    @type Boolean
    @readOnly
  */
  hasNextPage: computed('page', 'perPage', 'recordsTotalCount', function() {
    let page = this.get('page');
    let lastPage = this._getLastPage();
    return page < lastPage;
  }),

  /**
    If `true` previous page exists.

    @property hasPreviousPage
    @type Boolean
    @readOnly
  */
  hasPreviousPage: computed('page', function() {
    return this.get('page') > 1;
  }),

  /**
    Array of objects corresponding to list of pages.

    Each page is presented as object with following properties:
    - **number** - Number of page.
    - **isCurrent** - Page is current.
    - **isEllipsis** - If `true` this page not showing in list.

    @property pages
    @type Array
    @readOnly
  */
  pages: computed('page', 'perPage', 'recordsTotalCount', function() {
    let page = this.get('page');
    let lastPage = this._getLastPage();

    // Pages are shown via list like [1] … [10] {11} [12] … [19], initial and final pages are shown always,
    // and nearest neighbors are and displayed for the current page. In case, when the current page is located in close to the beginning or end
    // list of page is shown accordingly [1] [2] {3} [4] … [19] or [1] … [16] {17} [18] [19].
    const visiblePageCount = 5;
    const visibleEndPageCount = 1;
    let arr = [];

    if (visiblePageCount >= lastPage) {
      // If the total page number do not exceed the number of visible pages.
      for (let i = 1; i <= lastPage; i++) {
        this._addPageNumberIntoArray(arr, i, false);
      }
    } else {
      // Number of visible pages near current page.
      let visibleMidlePageHalfCount =  Math.floor((visiblePageCount - visibleEndPageCount * 2) / 2);

      // Number of visible pages to the left end.
      let leftEndPageCount = page < visibleEndPageCount + visibleMidlePageHalfCount + 1 ?
            visiblePageCount - visibleEndPageCount
            : visibleEndPageCount;

      // Number of visible pages to the right end.
      let rightEndPageCount = page > lastPage - (visibleEndPageCount + visibleMidlePageHalfCount) ?
            visiblePageCount - visibleEndPageCount
            : visibleEndPageCount;

      // Add pages to the left edge.
      for (let i = 1; i <= Math.min(leftEndPageCount, lastPage); i++) {
        this._addPageNumberIntoArray(arr, i, false);
      }

      // Add left ellipsis if needed.
      if (page > visibleEndPageCount + visibleMidlePageHalfCount + 1) {
        this._addPageNumberIntoArray(arr, 0, true);
      }

      // Add middle pades including current page.
      let middleBlockStartPage = Math.max(leftEndPageCount + 1, page - visibleMidlePageHalfCount);
      let middleBlockEndPage = Math.min(page + visibleMidlePageHalfCount, lastPage - rightEndPageCount);
      for (let i = middleBlockStartPage; i <= middleBlockEndPage; i++) {
        this._addPageNumberIntoArray(arr, i, false);
      }

      // Add right ellipsis if needed.
      if (page < lastPage - (visibleEndPageCount + visibleMidlePageHalfCount)) {
        this._addPageNumberIntoArray(arr, 0, true);
      }

      // Add pages to the right edge.
      for (let i = lastPage - rightEndPageCount + 1; i <= lastPage; i++) {
        this._addPageNumberIntoArray(arr, i, false);
      }
    }

    return arr;
  }),

  /**
    Defines which query parameters the controller accepts. [More info.](https://emberjs.com/api/ember/release/classes/Controller#property_queryParams).

    @property queryParams
    @type Array
    @default ['page', 'perPage']
  */
  queryParams: ['page', 'perPage'],

  actions: {
    /**
      Transition to page with number.

      @method actions.gotoPage
      @param {Number} pageNum Number of page.
    */
    gotoPage(pageNum) {
      let num = this._checkPageNumber(pageNum);
      this.set('page', num);
    },

    /**
      Transition to next page.

      @method actions.nextPage
    */
    nextPage() {
      let page = this.get('page');
      let nextPage = this._checkPageNumber(page + 1);
      this.set('page', nextPage);
    },

    /**
      Transition to previous page.

      @method actions.previousPage
    */
    previousPage() {
      let page = this.get('page');
      let prevPage = this._checkPageNumber(page - 1);
      this.set('page', prevPage);
    },

    /**
      Transition to last page.

      @method actions.lastPage
    */
    lastPage() {
      let lastPage = this._getLastPage();
      this.set('page', lastPage);
    },

    /**
      Transition to first page.

      @method actions.firstPage
    */
    firstPage() {
      this.set('page', 1);
    },
  },

  /**
    Add page number into array.

    @method _addPageNumberIntoArray
    @param {Array} arr Array pages.
    @param {Number} pageNumber Number of page.
    @param {Boolean} isEllipsis If `true` this page not showing in list.
    @private
  */
  _addPageNumberIntoArray(arr, pageNumber, isEllipsis) {
    let page = this.get('page');
    arr.push({
      number: pageNumber,
      isCurrent: (pageNumber === page),
      isEllipsis: isEllipsis
    });
  },

  /**
    Get number last page.

    @method _getLastPage
    @param {Number} perPage Count records on page.
    @param {Number} count Total count records.
    @return {Number} Number last page.
    @private
  */
  _getLastPage(perPage = this.get('perPage'), count = this.get('recordsTotalCount')) {
    let lastPage = Math.ceil(count / perPage);
    return lastPage > 0 ? lastPage : 1;
  },

  /**
    Check there is a page with this number.

    @method _checkPageNumber
    @param {Number} pageNum Number of page.
    @return {Boolean} If page exists, return `pageNum`, else, return `lastPage`.
    @private
  */
  _checkPageNumber(pageNum) {
    const firstPage = 1;
    let lastPage = this._getLastPage();
    return Math.max(firstPage, Math.min(pageNum, lastPage));
  }
});