From d952647565e0fc32f14d22589e1144da045c4cf1 Mon Sep 17 00:00:00 2001 From: tbekos Date: Mon, 17 Jun 2013 12:52:07 +0300 Subject: [PATCH] feat(pagination): add pager directive --- src/pagination/docs/demo.html | 3 + src/pagination/docs/readme.md | 18 +++- src/pagination/pagination.js | 89 +++++++++++++--- src/pagination/test/pager.spec.js | 171 ++++++++++++++++++++++++++++++ template/pagination/pager.html | 5 + 5 files changed, 270 insertions(+), 16 deletions(-) create mode 100644 src/pagination/test/pager.spec.js create mode 100644 template/pagination/pager.html diff --git a/src/pagination/docs/demo.html b/src/pagination/docs/demo.html index 6eb217081c..13c4d83527 100644 --- a/src/pagination/docs/demo.html +++ b/src/pagination/docs/demo.html @@ -10,7 +10,10 @@

Default

The selected page no: {{currentPage}}
+

Pager

+ +

Limit the maximimum visible page-buttons

diff --git a/src/pagination/docs/readme.md b/src/pagination/docs/readme.md index 4a3d62964f..8394e5bb8d 100644 --- a/src/pagination/docs/readme.md +++ b/src/pagination/docs/readme.md @@ -1,7 +1,7 @@ A lightweight pagination directive that is focused on ... providing pagination & will take care of visualising a pagination bar and enable / disable buttons correctly! -### Settings ### +### Pagination Settings ### Settings can be provided as attributes in the `` or globally configured through the `paginationConfig`. @@ -49,3 +49,19 @@ Settings can be provided as attributes in the `` or globally configu _(Default: 'Last')_ : Text for Last button. +### Pager Settings ### + +Settings can be provided as attributes in the `` or globally configured through the `pagerConfig`. +For `num-pages`, `current-page` and `on-select-page (page)` see pagination settings. Other settings are: + + * `align` + _(Default: true)_ : + Whether to align each link to the sides. + + * `previous-text` + _(Default: '« Previous')_ : + Text for Previous button. + + * `next-text` + _(Default: 'Next »')_ : + Text for Next button. diff --git a/src/pagination/pagination.js b/src/pagination/pagination.js index 6abc75aba0..69aec65c52 100644 --- a/src/pagination/pagination.js +++ b/src/pagination/pagination.js @@ -1,5 +1,26 @@ angular.module('ui.bootstrap.pagination', []) +.controller('PaginationController', ['$scope', function (scope) { + + scope.noPrevious = function() { + return scope.currentPage === 1; + }; + scope.noNext = function() { + return scope.currentPage === scope.numPages; + }; + + scope.isActive = function(page) { + return scope.currentPage === page; + }; + + scope.selectPage = function(page) { + if ( ! scope.isActive(page) && page > 0 && page <= scope.numPages) { + scope.currentPage = page; + scope.onSelectPage({ page: page }); + } + }; +}]) + .constant('paginationConfig', { boundaryLinks: false, directionLinks: true, @@ -19,6 +40,7 @@ angular.module('ui.bootstrap.pagination', []) maxSize: '=', onSelectPage: '&' }, + controller: 'PaginationController', templateUrl: 'template/pagination/pagination.html', replace: true, link: function(scope, element, attrs) { @@ -111,22 +133,59 @@ angular.module('ui.bootstrap.pagination', []) scope.selectPage(scope.numPages); } }); - scope.noPrevious = function() { - return scope.currentPage === 1; - }; - scope.noNext = function() { - return scope.currentPage === scope.numPages; - }; - scope.isActive = function(page) { - return scope.currentPage === page; - }; - - scope.selectPage = function(page) { - if ( ! scope.isActive(page) && page > 0 && page <= scope.numPages) { - scope.currentPage = page; - scope.onSelectPage({ page: page }); + } + }; +}]) + +.constant('pagerConfig', { + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(config) { + return { + restrict: 'EA', + scope: { + numPages: '=', + currentPage: '=', + onSelectPage: '&' + }, + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, paginationCtrl) { + + // Setup configuration parameters + var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : config.previousText; + var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : config.nextText; + var align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : config.align; + + // Create page object used in template + function makePage(number, text, isDisabled, isPrevious, isNext) { + return { + number: number, + text: text, + disabled: isDisabled, + previous: ( align && isPrevious ), + next: ( align && isNext ) + }; + } + + scope.$watch('numPages + currentPage', function() { + scope.pages = []; + + // Add previous & next links + var previousPage = makePage(scope.currentPage - 1, previousText, scope.noPrevious(), true, false); + scope.pages.unshift(previousPage); + + var nextPage = makePage(scope.currentPage + 1, nextText, scope.noNext(), false, true); + scope.pages.push(nextPage); + + if ( scope.currentPage > scope.numPages ) { + scope.selectPage(scope.numPages); } - }; + }); } }; }]); diff --git a/src/pagination/test/pager.spec.js b/src/pagination/test/pager.spec.js new file mode 100644 index 0000000000..1dbec62ba4 --- /dev/null +++ b/src/pagination/test/pager.spec.js @@ -0,0 +1,171 @@ +describe('pager directive with default configuration', function () { + var $rootScope, element; + beforeEach(module('ui.bootstrap.pagination')); + beforeEach(module('template/pagination/pager.html')); + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.numPages = 5; + $rootScope.currentPage = 3; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + it('has a "pager" css class', function() { + expect(element.hasClass('pager')).toBe(true); + }); + + it('contains 2 li elements', function() { + expect(element.find('li').length).toBe(2); + expect(element.find('li').eq(0).text()).toBe('« Previous'); + expect(element.find('li').eq(-1).text()).toBe('Next »'); + }); + + it('aligns previous & next page', function() { + expect(element.find('li').eq(0).hasClass('previous')).toBe(true); + expect(element.find('li').eq(0).hasClass('next')).toBe(false); + + expect(element.find('li').eq(-1).hasClass('previous')).toBe(false); + expect(element.find('li').eq(-1).hasClass('next')).toBe(true); + }); + + it('disables the "previous" link if current-page is 1', function() { + $rootScope.currentPage = 1; + $rootScope.$digest(); + expect(element.find('li').eq(0).hasClass('disabled')).toBe(true); + }); + + it('disables the "next" link if current-page is num-pages', function() { + $rootScope.currentPage = 5; + $rootScope.$digest(); + expect(element.find('li').eq(-1).hasClass('disabled')).toBe(true); + }); + + it('changes currentPage if the "previous" link is clicked', function() { + var previous = element.find('li').eq(0).find('a').eq(0); + previous.click(); + $rootScope.$digest(); + expect($rootScope.currentPage).toBe(2); + }); + + it('changes currentPage if the "next" link is clicked', function() { + var next = element.find('li').eq(-1).find('a').eq(0); + next.click(); + $rootScope.$digest(); + expect($rootScope.currentPage).toBe(4); + }); + + it('does not change the current page on "previous" click if already at first page', function() { + var previous = element.find('li').eq(0).find('a').eq(0); + $rootScope.currentPage = 1; + $rootScope.$digest(); + previous.click(); + $rootScope.$digest(); + expect($rootScope.currentPage).toBe(1); + }); + + it('does not change the current page on "next" click if already at last page', function() { + var next = element.find('li').eq(-1).find('a').eq(0); + $rootScope.currentPage = 5; + $rootScope.$digest(); + next.click(); + $rootScope.$digest(); + expect($rootScope.currentPage).toBe(5); + }); + + it('executes the onSelectPage expression when the current page changes', function() { + $rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler'); + element = $compile('')($rootScope); + $rootScope.$digest(); + var next = element.find('li').eq(-1).find('a').eq(0); + next.click(); + $rootScope.$digest(); + expect($rootScope.selectPageHandler).toHaveBeenCalledWith(4); + }); + + it('does not changes the number of items when numPages changes', function() { + $rootScope.numPages = 8; + $rootScope.$digest(); + expect(element.find('li').length).toBe(2); + expect(element.find('li').eq(0).text()).toBe('« Previous'); + expect(element.find('li').eq(-1).text()).toBe('Next »'); + }); + + it('sets the current page to the last page if the numPages is changed to less than the current page', function() { + $rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler'); + element = $compile('')($rootScope); + $rootScope.$digest(); + $rootScope.numPages = 2; + $rootScope.$digest(); + expect(element.find('li').length).toBe(2); + expect($rootScope.currentPage).toBe(2); + expect($rootScope.selectPageHandler).toHaveBeenCalledWith(2); + }); +}); + +describe('setting pagerConfig', function() { + var $rootScope, element; + var originalConfig = {}; + beforeEach(module('ui.bootstrap.pagination')); + beforeEach(module('template/pagination/pager.html')); + beforeEach(inject(function(_$compile_, _$rootScope_, pagerConfig) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.numPages = 5; + $rootScope.currentPage = 3; + angular.extend(originalConfig, pagerConfig); + pagerConfig.previousText = 'PR'; + pagerConfig.nextText = 'NE'; + pagerConfig.align = false; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + afterEach(inject(function(pagerConfig) { + // return it to the original state + angular.extend(pagerConfig, originalConfig); + })); + + it('contains 2 li elements', function() { + expect(element.find('li').length).toBe(2); + }); + + it('should change paging text', function () { + expect(element.find('li').eq(0).text()).toBe('PR'); + expect(element.find('li').eq(-1).text()).toBe('NE'); + }); + + it('should not align previous & next page link', function () { + expect(element.find('li').eq(0).hasClass('previous')).toBe(false); + expect(element.find('li').eq(-1).hasClass('next')).toBe(false); + }); + +}); + +describe('pagination bypass configuration from attributes', function () { + var $rootScope, element; + beforeEach(module('ui.bootstrap.pagination')); + beforeEach(module('template/pagination/pager.html')); + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.numPages = 5; + $rootScope.currentPage = 3; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + it('contains 2 li elements', function() { + expect(element.find('li').length).toBe(2); + }); + + it('should change paging text from attributes', function () { + expect(element.find('li').eq(0).text()).toBe('<'); + expect(element.find('li').eq(-1).text()).toBe('>'); + }); + + it('should not align previous & next page link', function () { + expect(element.find('li').eq(0).hasClass('previous')).toBe(false); + expect(element.find('li').eq(-1).hasClass('next')).toBe(false); + }); + +}); \ No newline at end of file diff --git a/template/pagination/pager.html b/template/pagination/pager.html new file mode 100644 index 0000000000..74b77f2d8b --- /dev/null +++ b/template/pagination/pager.html @@ -0,0 +1,5 @@ +
+ +