Skip to content

Commit b35fca4

Browse files
authored
Merge pull request angular-ui#5174 from chatcher/custom-pagination
feat(pagination): Add custom pagination with variable page sizes
2 parents a3dc3b7 + 0d03099 commit b35fca4

File tree

4 files changed

+280
-21
lines changed

4 files changed

+280
-21
lines changed
+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
@ngdoc overview
2+
@name Tutorial: 406 Custom Pagination
3+
@description
4+
5+
When pagination is enabled, the data is displayed in pages that can be browsed using the built in
6+
pagination selector. However, you don't always have pages with the same number of rows.
7+
8+
For custom pagination, set the `pageSizes` option and the `useCustomPagination`.
9+
~~ and implement the `gridApi.pagination.on.paginationChanged` callback function. The callback
10+
may contain code to update any pagination state variables your application may utilize, e.g. variables containing
11+
the `pageNumber`, `pageSize`, and `pageSizeList`. The REST call used to fetch the data from the server should be
12+
called from within this callback. The URL of the call should contain query parameters that will allow the server-side
13+
code to have sufficient information to be able to retrieve the specific subset of data that the client requires from
14+
the entire set.~~
15+
16+
It should also update the `$scope.gridOptions.totalItems` variable with the total count of rows that exist (but
17+
were not all fetched in the REST call mentioned above since they exist in other pages of data).
18+
19+
This will allow ui-grid to calculate the correct number of pages on the client-side.
20+
21+
@example
22+
This shows custom pagination.
23+
<example module="app">
24+
<file name="app.js">
25+
var app = angular.module('app', ['ngTouch', 'ui.grid', 'ui.grid.pagination']);
26+
27+
app.controller('MainCtrl', [
28+
'$scope', '$http', 'uiGridConstants', function($scope, $http, uiGridConstants) {
29+
30+
$scope.gridOptions1 = {
31+
paginationPageSizes: null,
32+
useCustomPagination: true,
33+
columnDefs: [
34+
{ name: 'name', enableSorting: false },
35+
{ name: 'gender', enableSorting: false },
36+
{ name: 'company', enableSorting: false }
37+
]
38+
};
39+
40+
$scope.gridOptions2 = {
41+
paginationPageSizes: null,
42+
useCustomPagination: true,
43+
useExternalPagination : true,
44+
columnDefs: [
45+
{ name: 'name', enableSorting: false },
46+
{ name: 'gender', enableSorting: false },
47+
{ name: 'company', enableSorting: false }
48+
],
49+
onRegisterApi: function(gridApi) {
50+
gridApi.pagination.on.paginationChanged($scope, function (pageNumber, pageSize) {
51+
$scope.gridOptions2.data = getPage($scope.grid2data, pageNumber);
52+
});
53+
}
54+
};
55+
56+
$http.get('/data/100_ASC.json')
57+
.success(function (data) {
58+
$scope.gridOptions1.data = data;
59+
$scope.gridOptions1.paginationPageSizes = calculatePageSizes(data);
60+
});
61+
62+
$http.get('/data/100.json')
63+
.success(function (data) {
64+
$scope.grid2data = data;
65+
$scope.gridOptions2.totalItems = 0;//data.length;
66+
$scope.gridOptions2.paginationPageSizes = calculatePageSizes(data);
67+
$scope.gridOptions2.data = getPage($scope.grid2data, 1);
68+
});
69+
70+
71+
function calculatePageSizes(data) {
72+
var initials = [];
73+
return data.reduce(function(pageSizes, row) {
74+
var initial = row.name.charAt(0);
75+
var index = initials.indexOf(initial);
76+
if(index < 0)
77+
{
78+
index = initials.length;
79+
initials.push(initial);
80+
}
81+
pageSizes[index] = (pageSizes[index] || 0) + 1;
82+
return pageSizes;
83+
}, []);
84+
}
85+
86+
function getPage(data, pageNumber)
87+
{
88+
var initials = [];
89+
return data.reduce(function(pages, row) {
90+
var initial = row.name.charAt(0);
91+
92+
if(!pages[initial]) pages[initial] = [];
93+
pages[initial].push(row);
94+
95+
if(initials.indexOf(initial) < 0)
96+
{
97+
initials.push(initial);
98+
initials.sort();
99+
}
100+
101+
return pages;
102+
103+
}, {})[initials[pageNumber - 1]] || [];
104+
}
105+
106+
}
107+
]);
108+
</file>
109+
<file name="index.html">
110+
<div ng-controller="MainCtrl">
111+
<div ui-grid="gridOptions1" ui-grid-pagination class="grid"></div>
112+
<div ui-grid="gridOptions2" ui-grid-pagination class="grid"></div>
113+
</div>
114+
</file>
115+
<file name="main.css">
116+
.grid {
117+
width: 600px;
118+
}
119+
</file>
120+
</example>

src/features/pagination/js/pagination.js

+48-18
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@
6565
getPage: function () {
6666
return grid.options.enablePagination ? grid.options.paginationCurrentPage : null;
6767
},
68+
/**
69+
* @ngdoc method
70+
* @name getFirstRowIndex
71+
* @methodOf ui.grid.pagination.api:PublicAPI
72+
* @description Returns the index of the first row of the current page.
73+
*/
74+
getFirstRowIndex: function () {
75+
if (grid.options.useCustomPagination) {
76+
return grid.options.paginationPageSizes.reduce(function(result, size, index) {
77+
return index < grid.options.paginationCurrentPage - 1 ? result + size : result;
78+
}, 0);
79+
}
80+
return ((grid.options.paginationCurrentPage - 1) * grid.options.paginationPageSize);
81+
},
82+
/**
83+
* @ngdoc method
84+
* @name getLastRowIndex
85+
* @methodOf ui.grid.pagination.api:PublicAPI
86+
* @description Returns the index of the last row of the current page.
87+
*/
88+
getLastRowIndex: function () {
89+
if (grid.options.useCustomPagination) {
90+
return publicApi.methods.pagination.getFirstRowIndex() + grid.options.paginationPageSizes[grid.options.paginationCurrentPage - 1];
91+
}
92+
return Math.min(grid.options.paginationCurrentPage * grid.options.paginationPageSize, grid.options.totalItems);
93+
},
6894
/**
6995
* @ngdoc method
7096
* @name getTotalPages
@@ -76,6 +102,10 @@
76102
return null;
77103
}
78104

105+
if (grid.options.useCustomPagination) {
106+
return grid.options.paginationPageSizes.length;
107+
}
108+
79109
return (grid.options.totalItems === 0) ? 1 : Math.ceil(grid.options.totalItems / grid.options.paginationPageSize);
80110
},
81111
/**
@@ -146,12 +176,14 @@
146176
var visibleRows = renderableRows.filter(function (row) { return row.visible; });
147177
grid.options.totalItems = visibleRows.length;
148178

149-
var firstRow = (currentPage - 1) * pageSize;
179+
var firstRow = publicApi.methods.pagination.getFirstRowIndex();
180+
var lastRow = publicApi.methods.pagination.getLastRowIndex();
181+
150182
if (firstRow > visibleRows.length) {
151183
currentPage = grid.options.paginationCurrentPage = 1;
152184
firstRow = (currentPage - 1) * pageSize;
153185
}
154-
return visibleRows.slice(firstRow, firstRow + pageSize);
186+
return visibleRows.slice(firstRow, lastRow);
155187
};
156188

157189
grid.registerRowsProcessor(processPagination, 900 );
@@ -189,6 +221,16 @@
189221
* and totalItems. Defaults to `false`
190222
*/
191223
gridOptions.useExternalPagination = gridOptions.useExternalPagination === true;
224+
225+
/**
226+
* @ngdoc property
227+
* @name useCustomPagination
228+
* @propertyOf ui.grid.pagination.api:GridOptions
229+
* @description Disables client-side pagination. When true, handle the `paginationChanged` event and set `data`,
230+
* `firstRowIndex`, `lastRowIndex`, and `totalItems`. Defaults to `false`.
231+
*/
232+
gridOptions.useCustomPagination = gridOptions.useCustomPagination === true;
233+
192234
/**
193235
* @ngdoc property
194236
* @name totalItems
@@ -367,13 +409,6 @@
367409

368410
$scope.$on('$destroy', dataChangeDereg);
369411

370-
var setShowing = function () {
371-
$scope.showingLow = ((options.paginationCurrentPage - 1) * options.paginationPageSize) + 1;
372-
$scope.showingHigh = Math.min(options.paginationCurrentPage * options.paginationPageSize, options.totalItems);
373-
};
374-
375-
var deregT = $scope.$watch('grid.options.totalItems + grid.options.paginationPageSize', setShowing);
376-
377412
var deregP = $scope.$watch('grid.options.paginationCurrentPage + grid.options.paginationPageSize', function (newValues, oldValues) {
378413
if (newValues === oldValues || oldValues === undefined) {
379414
return;
@@ -389,30 +424,25 @@
389424
return;
390425
}
391426

392-
setShowing();
393427
uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
394428
}
395429
);
396430

397431
$scope.$on('$destroy', function() {
398-
deregT();
399432
deregP();
400433
});
401434

402435
$scope.cantPageForward = function () {
403-
if (options.totalItems > 0) {
404-
return options.paginationCurrentPage >= $scope.paginationApi.getTotalPages();
436+
if ($scope.paginationApi.getTotalPages()) {
437+
return $scope.cantPageToLast();
405438
} else {
406439
return options.data.length < 1;
407440
}
408441
};
409442

410443
$scope.cantPageToLast = function () {
411-
if (options.totalItems > 0) {
412-
return $scope.cantPageForward();
413-
} else {
414-
return true;
415-
}
444+
var totalPages = $scope.paginationApi.getTotalPages();
445+
return !totalPages || options.paginationCurrentPage >= totalPages;
416446
};
417447

418448
$scope.cantPageBackward = function () {

src/features/pagination/templates/pagination.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
</div>
7979
<div
8080
class="ui-grid-pager-row-count-picker"
81-
ng-if="grid.options.paginationPageSizes.length > 1">
81+
ng-if="grid.options.paginationPageSizes.length > 1 && !grid.options.useCustomPagination">
8282
<select
8383
ui-grid-one-bind-aria-labelledby-grid="'items-per-page-label'"
8484
ng-model="grid.options.paginationPageSize"
@@ -101,12 +101,12 @@
101101
class="ui-grid-pager-count">
102102
<span
103103
ng-show="grid.options.totalItems > 0">
104-
{{showingLow}}
104+
{{ 1 + paginationApi.getFirstRowIndex() }}
105105
<abbr
106106
ui-grid-one-bind-title="paginationThrough">
107107
-
108108
</abbr>
109-
{{showingHigh}} {{paginationOf}} {{grid.options.totalItems}} {{totalItemsLabel}}
109+
{{ 1 + paginationApi.getLastRowIndex() }} {{paginationOf}} {{grid.options.totalItems}} {{totalItemsLabel}}
110110
</span>
111111
</div>
112112
</div>

src/features/pagination/test/pagination.spec.js

+109
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ describe('ui.grid.pagination uiGridPaginationService', function () {
6464
describe('initialisation', function () {
6565
it('registers the API and methods', function () {
6666
expect(gridApi.pagination.getPage).toEqual(jasmine.any(Function));
67+
expect(gridApi.pagination.getFirstRowIndex).toEqual(jasmine.any(Function));
68+
expect(gridApi.pagination.getLastRowIndex).toEqual(jasmine.any(Function));
6769
expect(gridApi.pagination.getTotalPages).toEqual(jasmine.any(Function));
6870
expect(gridApi.pagination.nextPage).toEqual(jasmine.any(Function));
6971
expect(gridApi.pagination.previousPage).toEqual(jasmine.any(Function));
@@ -174,4 +176,111 @@ describe('ui.grid.pagination uiGridPaginationService', function () {
174176
});
175177
});
176178
});
179+
180+
describe('custom pagination', function () {
181+
182+
var pages = ['COSU', 'DJLPQTVX', 'ABFGHIKNRY', 'EMWZ'];
183+
184+
function getPage(data, pageNumber) {
185+
return data.filter(function(datum) {
186+
return pages[pageNumber-1].indexOf(datum.col2) >= 0;
187+
});
188+
}
189+
190+
beforeEach(inject(function (_$rootScope_, _$timeout_, $compile) {
191+
$rootScope = _$rootScope_;
192+
$timeout = _$timeout_;
193+
194+
$rootScope.gridOptions.useCustomPagination = true;
195+
$rootScope.gridOptions.useExternalPagination = true;
196+
$rootScope.gridOptions.paginationPageSizes = [4,8,10,4];
197+
198+
var data = $rootScope.gridOptions.data;
199+
$rootScope.gridOptions.data = getPage(data, 1);
200+
gridApi.pagination.on.paginationChanged($rootScope, function (pageNumber) {
201+
$rootScope.gridOptions.data = getPage(data, pageNumber);
202+
});
203+
204+
$rootScope.$digest();
205+
}));
206+
207+
it('starts at page 1 with 4 records', function () {
208+
var gridRows = gridElement.find('div.ui-grid-row');
209+
210+
expect(gridApi.pagination.getPage()).toBe(1);
211+
expect(gridRows.length).toBe(4);
212+
213+
var firstCell = gridRows.eq(0).find('div.ui-grid-cell:first-child');
214+
expect(firstCell.text()).toBe('6_1');
215+
216+
var lastCell = gridRows.eq(3).find('div.ui-grid-cell:last-child');
217+
expect(lastCell.text()).toBe('25_4');
218+
});
219+
220+
it('calculates the total number of pages correctly', function () {
221+
expect(gridApi.pagination.getTotalPages()).toBe(4);
222+
});
223+
224+
it('displays page 2 if I call nextPage()', function () {
225+
gridApi.pagination.nextPage();
226+
$rootScope.$digest();
227+
$timeout.flush();
228+
229+
var gridRows = gridElement.find('div.ui-grid-row');
230+
231+
expect(gridApi.pagination.getPage()).toBe(2);
232+
expect(gridRows.length).toBe(8);
233+
234+
var firstCell = gridRows.eq(0).find('div.ui-grid-cell:first-child');
235+
expect(firstCell.text()).toBe('4_1');
236+
237+
var lastCell = gridRows.eq(7).find('div.ui-grid-cell:last-child');
238+
expect(lastCell.text()).toBe('21_4');
239+
});
240+
241+
it('displays 10 rows on page 3', function () {
242+
gridApi.pagination.seek(3);
243+
$rootScope.$digest();
244+
$timeout.flush();
245+
246+
var gridRows = gridElement.find('div.ui-grid-row');
247+
248+
expect(gridApi.pagination.getPage()).toBe(3);
249+
expect(gridRows.length).toBe(10);
250+
251+
var firstCell = gridRows.eq(0).find('div.ui-grid-cell:first-child');
252+
expect(firstCell.text()).toBe('1_1');
253+
254+
var lastCell = gridRows.eq(9).find('div.ui-grid-cell:last-child');
255+
expect(lastCell.text()).toBe('26_4');
256+
});
257+
258+
it('paginates correctly on a sorted grid', function() {
259+
gridApi.grid.sortColumn(gridApi.grid.columns[1]).then(function () {
260+
gridApi.pagination.seek(3);
261+
$rootScope.$digest();
262+
$timeout.flush();
263+
264+
var gridRows = gridElement.find('div.ui-grid-row');
265+
expect(gridApi.pagination.getPage()).toBe(1);
266+
expect(gridRows.eq(0).find('div.ui-grid-cell').eq(1).text()).toBe('A');
267+
expect(gridRows.eq(1).find('div.ui-grid-cell').eq(1).text()).toBe('B');
268+
expect(gridRows.eq(2).find('div.ui-grid-cell').eq(1).text()).toBe('F');
269+
expect(gridRows.eq(3).find('div.ui-grid-cell').eq(1).text()).toBe('G');
270+
expect(gridRows.eq(4).find('div.ui-grid-cell').eq(1).text()).toBe('H');
271+
expect(gridRows.eq(5).find('div.ui-grid-cell').eq(1).text()).toBe('I');
272+
expect(gridRows.eq(6).find('div.ui-grid-cell').eq(1).text()).toBe('K');
273+
expect(gridRows.eq(7).find('div.ui-grid-cell').eq(1).text()).toBe('N');
274+
expect(gridRows.eq(8).find('div.ui-grid-cell').eq(1).text()).toBe('R');
275+
expect(gridRows.eq(9).find('div.ui-grid-cell').eq(1).text()).toBe('Y');
276+
277+
gridApi.pagination.nextPage();
278+
$rootScope.$digest();
279+
280+
gridRows = gridElement.find('div.ui-grid-row');
281+
expect(gridApi.pagination.getPage()).toBe(2);
282+
expect(gridRows.eq(0).find('div.ui-grid-cell').eq(1).text()).toBe('E');
283+
});
284+
});
285+
});
177286
});

0 commit comments

Comments
 (0)