SOURCE CODE: Uize.Widget.Pagination
VIEW REFERENCE

/*______________
|       ______  |   U I Z E    J A V A S C R I P T    F R A M E W O R K
|     /      /  |   ---------------------------------------------------
|    /    O /   |    MODULE : Uize.Widget.Pagination Class
|   /    / /    |
|  /    / /  /| |    ONLINE : http://www.uize.com
| /____/ /__/_| | COPYRIGHT : (c)2007-2012 UIZE
|          /___ |   LICENSE : Available under MIT License or GNU General Public License
|_______________|             http://www.uize.com/license.html
*/

/* Module Meta Data
  type: Class
  importance: 5
  codeCompleteness: 0
  testCompleteness: 0
  docCompleteness: 0
*/

/*?
  Introduction
    The =Uize.Widget.Pagination= class implements support for client-side pagination

    *DEVELOPERS:* `Ben Ilegbodu`

    The =Uize.Widget.Pagination= module defines the =Uize.Widget.Pagination= widget class, a subclass of =Uize.Widget=.
*/

Uize.module ({
  name:'Uize.Widget.Pagination',
  required:[
    'Uize.Widget.Button',
    'Uize.Node',
    'Uize.Node.Classes'
  ],
  builder:function (_superclass) {
    /*** Variables for Scruncher Optimization ***/

    /*** Class Constructor ***/
      var
        _class = _superclass.subclass (
          null,
          function() {
            var _this = this;

            _this._addChildButton('prev', function() { _this._gotoPage(_this._value - 1) } );
            _this._addChildButton('next', function() { _this._gotoPage(_this._value + 1) } );
          }
        ),
        _classPrototype = _class.prototype
      ;

    /*** Private Methods ***/
      _classPrototype._addChildButton = Uize.Widget.Button.addChildButton;

      _classPrototype._calculateMaxPages = function() { return Math.ceil(this._numResults / this._pageSize) };

      _classPrototype._calculatePagesStart = function() {
        var
          _this = this,
          _value = _this._value,
          _numPagesToShow = _this._numPagesToShow,
          _maxPages = _this._calculateMaxPages(),
          _minPagesStart = 1 + (_this._showFirstPage && _value > 1), // i.e. if we're shhowing the first page we want to start at 2
          _maxPagesEnd = _maxPages - (_this._showLastPage && _value < _maxPages),  // i.e. if we're showing the last page we want to end at the page before
          _deltaFromCurrentPage = Math.ceil(_numPagesToShow / 2),
          _deltaLeft = _value - _deltaFromCurrentPage + 1,
          _deltaRight = _value + (_numPagesToShow - _deltaFromCurrentPage)
        ;

        if (_deltaLeft >= _minPagesStart && _deltaRight <= _maxPagesEnd)
          return _deltaLeft;
        else if (_deltaLeft >= _minPagesStart)
          return Math.max(_minPagesStart, _maxPagesEnd - _numPagesToShow + 1);
        else
          return _minPagesStart;
      };

      _classPrototype._gotoPage = function(_pageNumber) { this.set({_value:_pageNumber}) };

      _classPrototype._updatePages = function() {
        var
          _this = this,
          _children = _this.children,
          _value = _this._value,
          _maxPages = _this._calculateMaxPages(),
          _hasMultiplePages = _maxPages > 1,
          _numResults = _this._numResults
        ;

        if (_this.isWired) {
          _this.displayNode('', _numResults > 0);
          _this.displayNode('paginationShell', _hasMultiplePages);

          _this.setNodeInnerHtml(
            'displayShell',
            _this.localize(
              'displayInfo',
              {
                number:(_value - 1) * _this._pageSize + 1,
                toNumber:Math.min(_numResults, _value * _this._pageSize),
                total:_this.localize('numResultsDisplay', {numResults:_this._numResults}) || _numResults
              }
            )
          );

          if (_hasMultiplePages) {
            function _enable(_pageButtonName, _mustEnable) {
              _children[_pageButtonName].set({enabled:_mustEnable ? 'inherit' : _mustEnable})
            }
            function _display(_pageButtonName, _mustDisplay) {
              _children[_pageButtonName].displayNode('', _mustDisplay)
            }
            function _setText(_pageButtonName, _text) {
              _children[_pageButtonName].set({text:_text})
            }

            _enable('prev', _value > 1);
            _enable('next', _value < _maxPages);

            _display('first', _value > 1);
            _display('last', _value < _maxPages);
            _setText('last', _maxPages);

            var _pagesStart = _this._calculatePagesStart();

            _this.displayNode('less', _pagesStart > (1 + _this._showFirstPage));
            _this.displayNode('more', (_pagesStart + _this._numPagesToShow) < (_maxPages - _this._showLastPage));

            for (var _pageNo = -1; ++_pageNo < _this._numPagesToShow;) {
              var
                _pageName = 'page' + _pageNo,
                _pageLinkNode = _children[_pageName].getNode(),
                _pageNumber = _pagesStart + _pageNo,
                _isCurrentPage = _pageNumber == _value
              ;

              _setText(
                _pageName,
                _isCurrentPage
                  ? _this.localize('selectedPage', {page:_pageNumber}) || _pageNumber
                  : _pageNumber
              );
              //_enable(_pageName, _pageNumber != _value);
              _display(_pageName, _pageNumber == _value || _pageNumber <= (_maxPages - _this._showLastPage));
              Uize.Node.Classes.setState(_pageLinkNode, _this._classSelected, _isCurrentPage);
            }
          }
        }
      };

    /*** Public Methods ***/
      _classPrototype.updateUi = function() {
        var _this = this;

        if (_this.isWired) {
          _this._updatePages();
          _superclass.prototype.updateUi.call(_this);
        }
      };

      _classPrototype.wireUi = function() {
        var
          _this = this
        ;

        if (!_this.isWired) {
          /*** Determine which page links exist ***/
            function _childExists(_childName) { return !!Uize.Node.getById(_this.get('idPrefix') + '_' + _childName) }

            _this._showFirstPage = _childExists('first');
            _this._showLastPage = _childExists('last');

            /** Calculate how many inner page linkss there are to show ***/
              _this._numPagesToShow = -1;
              while (_childExists('page' + ++_this._numPagesToShow));

          _this._showFirstPage
            && _this._addChildButton('first', function() { _this._gotoPage(1) } );
          _this._showLastPage
            && _this._addChildButton('last', function() { _this._gotoPage(_this._calculateMaxPages()) } );

          function _addPageButton (_pageNo) {
            _this._addChildButton(
              'page' + _pageNo,
              function() { _this._gotoPage(_this._calculatePagesStart() + _pageNo) }
            )
          }

          for (var _pageNo = -1; ++_pageNo < _this._numPagesToShow;)
            _addPageButton(_pageNo)
          ;

          _superclass.prototype.wireUi.call(_this);
        }
      };

    /*** Register Properties ***/
      _class.registerProperties ({
        _classSelected:{
          name:'classSelected',
          value:'selected'
        },
        _numResults:{
          name:'numResults',
          onChange:[
            _classPrototype._calculateMaxPages,
            _classPrototype._updatePages
          ]
        },
        _pageSize:{
          name:'pageSize',
          conformer:function(_newPageSize) { return _newPageSize ? _newPageSize : 30 },
          onChange:[
            _classPrototype._calculateMaxPages,
            _classPrototype._updatePages
          ],
          value:30
        },
        _value:{
          name:'value',
          conformer:function(_newValue) {
            var _maxPages = this._calculateMaxPages();

            return _newValue ? (!_maxPages || _newValue < _maxPages ? _newValue : _maxPages) : 1
          },
          onChange:_classPrototype._updatePages,
          value:1
        }
      });

    return _class;
  }
});