diff --git a/src/pagination/pagination.js b/src/pagination/pagination.js
new file mode 100644
index 0000000000..3924102ec5
--- /dev/null
+++ b/src/pagination/pagination.js
@@ -0,0 +1,52 @@
+angular.module('ui.bootstrap.pagination', [])
+
+.directive('pagination', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ numPages: '=',
+ currentPage: '=',
+ onSelectPage: '&'
+ },
+ templateUrl: 'template/pagination/pagination.html',
+ replace: true,
+ link: function(scope) {
+ scope.$watch('numPages', function(value) {
+ scope.pages = [];
+ for(var i=1;i<=value;i++) {
+ scope.pages.push(i);
+ }
+ if ( scope.currentPage > value ) {
+ scope.selectPage(value);
+ }
+ });
+ 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) ) {
+ scope.currentPage = page;
+ scope.onSelectPage({ page: page });
+ }
+ };
+
+ scope.selectPrevious = function() {
+ if ( !scope.noPrevious() ) {
+ scope.selectPage(scope.currentPage-1);
+ }
+ };
+ scope.selectNext = function() {
+ if ( !scope.noNext() ) {
+ scope.selectPage(scope.currentPage+1);
+ }
+ };
+ }
+ };
+});
\ No newline at end of file
diff --git a/src/pagination/test/pagination.spec.js b/src/pagination/test/pagination.spec.js
new file mode 100644
index 0000000000..1150a72baa
--- /dev/null
+++ b/src/pagination/test/pagination.spec.js
@@ -0,0 +1,118 @@
+describe('pagination directive', function () {
+ var $rootScope, element;
+ beforeEach(module('ui.bootstrap.pagination'));
+ beforeEach(module('template/pagination/pagination.html'));
+ beforeEach(inject(function(_$compile_, _$rootScope_) {
+ $compile = _$compile_;
+ $rootScope = _$rootScope_;
+ $rootScope.numPages = 5;
+ $rootScope.currentPage = 3;
+ element = $compile('')($rootScope);
+ $rootScope.$digest();
+ }));
+
+ it('has a "pagination" css class', function() {
+ expect(element.hasClass('pagination')).toBe(true);
+ });
+
+ it('contains one ul and num-pages + 2 li elements', function() {
+ expect(element.find('ul').length).toBe(1);
+ expect(element.find('li').length).toBe(7);
+ expect(element.find('li').eq(0).text()).toBe('Previous');
+ expect(element.find('li').eq(-1).text()).toBe('Next');
+ });
+
+ it('has the number of the page as text in each page item', function() {
+ var lis = element.find('li');
+ for(var i=1; i<=$rootScope.numPages;i++) {
+ expect(lis.eq(i).text()).toEqual(''+i);
+ }
+ });
+
+ it('sets the current-page to be active', function() {
+ var currentPageItem = element.find('li').eq($rootScope.currentPage);
+ expect(currentPageItem.hasClass('active')).toBe(true);
+ });
+
+ it('disables the "previous" link if current-page is 1', function() {
+ $rootScope.currentPage = 1;
+ $rootScope.$digest();
+ var previousPageItem = element.find('li').eq(0);
+ expect(previousPageItem.hasClass('disabled')).toBe(true);
+ });
+
+ it('disables the "next" link if current-page is num-pages', function() {
+ $rootScope.currentPage = 5;
+ $rootScope.$digest();
+ var nextPageItem = element.find('li').eq(-1);
+ expect(nextPageItem.hasClass('disabled')).toBe(true);
+ });
+
+ it('changes currentPage if a page link is clicked', function() {
+ var page2 = element.find('li').eq(2).find('a');
+ page2.click();
+ $rootScope.$digest();
+ expect($rootScope.currentPage).toBe(2);
+ });
+
+ 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 page2 = element.find('li').eq(2).find('a').eq(0);
+ page2.click();
+ $rootScope.$digest();
+ expect($rootScope.selectPageHandler).toHaveBeenCalledWith(2);
+ });
+
+ it('changes the number of items when numPages changes', function() {
+ $rootScope.numPages = 8;
+ $rootScope.$digest();
+ expect(element.find('li').length).toBe(10);
+ 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(4);
+ expect($rootScope.currentPage).toBe(2);
+ expect($rootScope.selectPageHandler).toHaveBeenCalledWith(2);
+ });
+});
\ No newline at end of file
diff --git a/template/pagination/pagination.html b/template/pagination/pagination.html
new file mode 100644
index 0000000000..f12ac959dd
--- /dev/null
+++ b/template/pagination/pagination.html
@@ -0,0 +1,6 @@
+