On Github wadetandy / angular-presentation-ude
Chaz Chandler
Wade Tandy
angular.module('ui.bootstrap.pagination', [])
.constant('paginationConfig', {
itemsPerPage: 10,
boundaryLinks: false,
directionLinks: true,
firstText: 'First',
previousText: 'Previous',
nextText: 'Next',
lastText: 'Last',
rotate: true
})
.controller('PaginationController', [
'$scope', '$attrs', '$parse', '$interpolate',
function ($scope, $attrs, $parse, $interpolate) {
var self = this,
setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
this.init = function(defaultItemsPerPage) {
if ($attrs.itemsPerPage) {
$scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
self.itemsPerPage = parseInt(value, 10);
$scope.totalPages = self.calculateTotalPages();
});
} else {
this.itemsPerPage = defaultItemsPerPage;
}
};
this.noPrevious = function() {
return this.page === 1;
};
this.noNext = function() {
return this.page === $scope.totalPages;
};
this.isActive = function(page) {
return this.page === page;
};
this.calculateTotalPages = function() {
var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
return Math.max(totalPages || 0, 1);
};
this.getAttributeValue = function(attribute, defaultValue, interpolate) {
return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
};
this.render = function() {
this.page = parseInt($scope.page, 10) || 1;
if (this.page > 0 && this.page <= $scope.totalPages) {
$scope.pages = this.getPages(this.page, $scope.totalPages);
}
};
$scope.selectPage = function(page) {
if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
$scope.page = page;
$scope.onSelectPage({ page: page });
}
};
$scope.$watch('page', function() {
self.render();
});
$scope.$watch('totalItems', function() {
$scope.totalPages = self.calculateTotalPages();
});
$scope.$watch('totalPages', function(value) {
setNumPages($scope.$parent, value); // Readonly variable
if ( self.page > value ) {
$scope.selectPage(value);
} else {
self.render();
}
});
}])
.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
return {
restrict: 'EA',
scope: {
page: '=',
totalItems: '=',
onSelectPage:' &'
},
controller: 'PaginationController',
templateUrl: 'template/pagination/pagination.html',
replace: true,
link: function(scope, element, attrs, paginationCtrl) {
// Setup configuration parameters
var maxSize,
boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
paginationCtrl.init(config.itemsPerPage);
if (attrs.maxSize) {
scope.$parent.$watch($parse(attrs.maxSize), function(value) {
maxSize = parseInt(value, 10);
paginationCtrl.render();
});
}
// Create page object used in template
function makePage(number, text, isActive, isDisabled) {
return {
number: number,
text: text,
active: isActive,
disabled: isDisabled
};
}
paginationCtrl.getPages = function(currentPage, totalPages) {
var pages = [];
// Default page limits
var startPage = 1, endPage = totalPages;
var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
// recompute if maxSize
if ( isMaxSized ) {
if ( rotate ) {
// Current page is displayed in the middle of the visible ones
startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
endPage = startPage + maxSize - 1;
// Adjust if limit is exceeded
if (endPage > totalPages) {
endPage = totalPages;
startPage = endPage - maxSize + 1;
}
} else {
// Visible pages are paginated with maxSize
startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
// Adjust last page if limit is exceeded
endPage = Math.min(startPage + maxSize - 1, totalPages);
}
}
// Add page number links
for (var number = startPage; number <= endPage; number++) {
var page = makePage(number, number, paginationCtrl.isActive(number), false);
pages.push(page);
}
// Add links to move between page sets
if ( isMaxSized && ! rotate ) {
if ( startPage > 1 ) {
var previousPageSet = makePage(startPage - 1, '...', false, false);
pages.unshift(previousPageSet);
}
if ( endPage < totalPages ) {
var nextPageSet = makePage(endPage + 1, '...', false, false);
pages.push(nextPageSet);
}
}
// Add previous & next links
if (directionLinks) {
var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
pages.unshift(previousPage);
var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
pages.push(nextPage);
}
// Add first & last links
if (boundaryLinks) {
var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
pages.unshift(firstPage);
var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
pages.push(lastPage);
}
return pages;
};
}
};
}])
.constant('pagerConfig', {
itemsPerPage: 10,
previousText: '« Previous',
nextText: 'Next »',
align: true
})
.directive('pager', ['pagerConfig', function(config) {
return {
restrict: 'EA',
scope: {
page: '=',
totalItems: '=',
onSelectPage:' &'
},
controller: 'PaginationController',
templateUrl: 'template/pagination/pager.html',
replace: true,
link: function(scope, element, attrs, paginationCtrl) {
// Setup configuration parameters
var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
align = paginationCtrl.getAttributeValue(attrs.align, config.align);
paginationCtrl.init(config.itemsPerPage);
// 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 )
};
}
paginationCtrl.getPages = function(currentPage) {
return [
makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
];
};
}
};
}]);
app.directive 'bbPaginate', ['Paginator', (Paginator) ->
template: """
<ul>
<li ng-repeat="page in paginator.pages" ng-class="page.listItemClasses()">
<a href="" ng-click="selectPage(page)">{{page.text}}</a>
</li>
</ul>
"""
scope:
pagination: "=bbPaginate"
link: ($scope, $element, $attrs) ->
$scope.paginator = new Paginator($scope.pagination)
$scope.$watch 'pagination', $scope.paginator.redraw, true
]
app.factory 'StandardPaginator', ['Page', 'PrevPage', 'NextPage', (Page, PrevPage, NextPage) ->
class StandardPaginator
# do stuff
app.factory 'CustomPaginator', ['StandardPaginator', 'SpecialPage', (StandardPaginator, SpecialPage) ->
class CustomPaginator extends StandardPaginator
# do stuff
app.directive 'bbPaginate', ['$injector', ($injector) ->
# ... skip some stuff
link: ($scope, $element, $attrs) ->
paginator = $attrs.paginator || 'StandardPaginator'
klass = $injector.get(paginator)
$scope.paginator = new klass($scope.pagination)
# ... more stuff
]
<div bb-paginate paginator="CustomPaginator">
describe 'Paginator', ->
described_class = null
beforeEach ->
module('bbPaginate')
inject (Paginator) ->
described_class = Paginator
describe '#new', ->
it 'should create a paginator instance if required attributes are provided', ->
subject = new described_class
current_page: 1
per_page: 20
total_entries: 100
expect(subject.isValid()).toBeTruthy()
class PaginationController
constructor: (@scope, @attrs) ->
@_pageSelectHandlers = []
onPageSelect: (fn) =>
@_pageSelectHandlers.push(fn)
# ...
app.directive 'bbPaginate', ['Paginator', (Paginator) ->
controller: ['$scope', '$attrs', PaginationController]
# ...
app.directive 'bbPaginateScrollTo', ->
require: 'bbPaginate'
link: ($scope, $element, $attrs, bbPaginateCtrl) ->
bbPaginateCtrl.onPageSelect (page) ->
$.scrollTo($attrs.bbPaginateScrollTo, 500)