-
Notifications
You must be signed in to change notification settings - Fork 243
/
pagination-controls-cmp.ts
214 lines (190 loc) · 6.38 KB
/
pagination-controls-cmp.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import {Component, ViewChild, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef} from '@angular/core'
import {Subscription} from 'rxjs';
import {PaginationService, IPaginationInstance} from "./pagination-service";
import {DEFAULT_TEMPLATE, DEFAULT_STYLES} from './template';
export interface IPage {
label: string;
value: any;
}
@Component({
selector: 'pagination-controls',
template: DEFAULT_TEMPLATE,
styles: [DEFAULT_STYLES],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PaginationControlsCmp {
@Input() id: string;
@Input() maxSize: number = 7;
@Input()
get directionLinks(): boolean {
return this._directionLinks;
}
set directionLinks(value: boolean) {
this._directionLinks = !!value && <any>value !== 'false';
}
@Input()
get autoHide(): boolean {
return this._autoHide;
}
set autoHide(value: boolean) {
this._autoHide = !!value && <any>value !== 'false';
}
@Output() pageChange: EventEmitter<number> = new EventEmitter<number>();
@ViewChild('template') template;
pages: IPage[] = [];
private hasTemplate: boolean = false;
private changeSub: Subscription;
private _directionLinks: boolean = true;
private _autoHide: boolean = false;
constructor(
private service: PaginationService,
private changeDetectorRef: ChangeDetectorRef
) {
this.changeSub = this.service.change
.subscribe(id => {
if (this.id === id) {
this.updatePageLinks();
this.changeDetectorRef.markForCheck();
}
});
}
ngOnInit() {
if (this.id === undefined) {
this.id = this.service.defaultId;
}
}
ngOnChanges() {
this.updatePageLinks();
}
ngAfterViewInit() {
if (this.template && 0 < this.template.nativeElement.children.length) {
this.hasTemplate = true;
}
}
ngOnDestroy() {
this.changeSub.unsubscribe();
}
/**
* Go to the previous page
*/
previous() {
this.setCurrent(this.getCurrent() - 1);
}
/**
* Go to the next page
*/
next() {
this.setCurrent(this.getCurrent() + 1);
}
/**
* Returns true if current page is first page
*/
isFirstPage(): boolean {
return this.getCurrent() === 1;
}
/**
* Returns true if current page is last page
*/
isLastPage(): boolean {
return this.getLastPage() === this.getCurrent();
}
/**
* Set the current page number.
*/
setCurrent(page: number) {
this.pageChange.emit(page);
}
/**
* Get the current page number.
*/
getCurrent(): number {
return this.service.getCurrentPage(this.id);
}
/**
* Returns the last page number
*/
getLastPage(): number {
let inst = this.service.getInstance(this.id);
return Math.ceil(inst.totalItems / inst.itemsPerPage);
}
/**
* Updates the page links and checks that the current page is valid. Should run whenever the
* PaginationService.change stream emits a value matching the current ID, or when any of the
* input values changes.
*/
private updatePageLinks() {
let inst = this.service.getInstance(this.id);
this.pages = this.createPageArray(inst.currentPage, inst.itemsPerPage, inst.totalItems, this.maxSize);
const correctedCurrentPage = this.outOfBoundCorrection(inst);
if (correctedCurrentPage !== inst.currentPage) {
this.setCurrent(correctedCurrentPage);
}
}
/**
* Checks that the instance.currentPage property is within bounds for the current page range.
* If not, return a correct value for currentPage, or the current value if OK.
*/
private outOfBoundCorrection(instance: IPaginationInstance): number {
const totalPages = Math.ceil(instance.totalItems / instance.itemsPerPage);
if (totalPages < instance.currentPage && 0 < totalPages) {
return totalPages;
} else if (instance.currentPage < 1) {
return 1;
}
return instance.currentPage;
}
/**
* Returns an array of IPage objects to use in the pagination controls.
*/
private createPageArray(currentPage: number, itemsPerPage: number, totalItems: number, paginationRange: number): IPage[] {
// paginationRange could be a string if passed from attribute, so cast to number.
paginationRange = +paginationRange;
let pages = [];
const totalPages = Math.ceil(totalItems / itemsPerPage);
const halfWay = Math.ceil(paginationRange / 2);
const isStart = currentPage <= halfWay;
const isEnd = totalPages - halfWay < currentPage;
const isMiddle = !isStart && !isEnd;
let ellipsesNeeded = paginationRange < totalPages;
let i = 1;
while (i <= totalPages && i <= paginationRange) {
let label;
let pageNumber = this.calculatePageNumber(i, currentPage, paginationRange, totalPages);
let openingEllipsesNeeded = (i === 2 && (isMiddle || isEnd));
let closingEllipsesNeeded = (i === paginationRange - 1 && (isMiddle || isStart));
if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
label = '...';
} else {
label = pageNumber;
}
pages.push({
label: label,
value: pageNumber
});
i ++;
}
return pages;
}
/**
* Given the position in the sequence of pagination links [i],
* figure out what page number corresponds to that position.
*/
private calculatePageNumber(i: number, currentPage: number, paginationRange: number, totalPages: number) {
let halfWay = Math.ceil(paginationRange / 2);
if (i === paginationRange) {
return totalPages;
} else if (i === 1) {
return i;
} else if (paginationRange < totalPages) {
if (totalPages - halfWay < currentPage) {
return totalPages - paginationRange + i;
} else if (halfWay < currentPage) {
return currentPage - halfWay + i;
} else {
return i;
}
} else {
return i;
}
}
}