Skip to content

Commit c6275a6

Browse files
dirPagination: allow expressions as pagiantionID
This changes allows you to use expressions in the `pagination-id` attribute, which in turn enables the use of multiple independent pagination instances generated by an ng-repeat. It also simplifies the API by not requiring the pagination id to be specified as a second parameter in the itemsPerPage filter.
1 parent 7c10e0a commit c6275a6

File tree

3 files changed

+139
-27
lines changed

3 files changed

+139
-27
lines changed

src/directives/pagination/README.md

+37-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ filtering of the collection.
2323
- [Writing A Custom Pagination-Controls Template](#writing-a-custom-pagination-controls-template)
2424
- [Special Repeat Start and End Points](#special-repeat-start-and-end-points)
2525
- [Multiple Pagination Instances on One Page](#multiple-pagination-instances-on-one-page)
26+
- [Multiple Instances With ngRepeat](#multiple-instances-with-ngrepeat)
2627
- [Demo](#demo-1)
2728
- [Working With Asynchronous Data](#working-with-asynchronous-data)
2829
- [Example Asynchronous Setup](#example-asynchronous-setup)
@@ -204,38 +205,68 @@ repeat a series of elements instead of just one parent element:
204205

205206
## Multiple Pagination Instances on One Page
206207

207-
Multiple instances of the directives may be included on a single page by specifying a `pagination-id`. This property **must** be specified in **3** places
208+
Multiple instances of the directives may be included on a single page by specifying a `pagination-id`. This property **must** be specified in **2** places
208209
for this to work:
209210

210211
1. Specify the `pagination-id` attribute on the `dir-paginate` directive.
211-
2. Specify the third parameter of the `itemsPerPage` filter.
212212
3. Specify the `pagination-id` attribute on the `dir-paginations-controls` directive.
213213

214+
**Note:** Prior to version 0.5.0, there was an additional requirement to add the ID as a second parameter of the `itemsPerPage` filter. This is now no longer required, as the
215+
directive will add this parameter automatically. Old code that *does* explicitly declare the ID in the filter will still work.
216+
214217
An example of two independent paginations on one page would look like this:
215218

216219
```HTML
217220
<!-- first pagination instance -->
218221
<ul>
219-
<li dir-paginate="customer in customers | itemsPerPage: 10: 'cust'" pagination-id="cust">{{ customer.name }}</li>
222+
<li dir-paginate="customer in customers | itemsPerPage: 10" pagination-id="cust">{{ customer.name }}</li>
220223
</ul>
221224

222225
<dir-pagination-controls pagination-id="cust"></dir-pagination-controls>
223226

224227
<!-- second pagination instance -->
225228
<ul>
226-
<li dir-paginate="branch in branches | itemsPerPage: 10: 'branch'" pagination-id="branch">{{ customer.name }}</li>
229+
<li dir-paginate="branch in branches | itemsPerPage: 10" pagination-id="branch">{{ customer.name }}</li>
227230
</ul>
228231

229232
<dir-pagination-controls pagination-id="branch"></dir-pagination-controls>
230233
```
231234

232235
The pagination-ids above are set to "cust" in the first instance and "branch" in the second. The pagination-ids can be anything you like,
233-
the important thing is to make sure the exact same id is used in all 3 places. If the 3 ids don't match, you should see a helpful
236+
the important thing is to make sure the exact same id is used on both the pagination and the controls directives. If the 2 ids don't match, you should see a helpful
234237
exception in the console.
235238

239+
### Multiple Instances With ngRepeat
240+
241+
You can use the pagination-id feature to dynamically create pagination instances, for example inside an `ng-repeat` block. Here is a bare-bones example to
242+
demonstrate how that would work:
243+
244+
```JavaScript
245+
// in the controller
246+
$scope.lists = [
247+
{
248+
id: 'list1',
249+
collection: [1, 2, 3, 4, 5]
250+
},
251+
{
252+
id: 'list2',
253+
collection: ['a', 'b', 'c', 'd', 'e']
254+
}];
255+
```
256+
257+
```HTML
258+
<!-- the view -->
259+
<div ng-repeat="list in lists">
260+
<ul>
261+
<li dir-paginate="item in list.collection | itemsPerPage: 3" pagination-id="list.id">ID: {{ list.id }}, item: {{ item }}</li>
262+
</ul>
263+
<dir-pagination-controls pagination-id="list.id"></dir-pagination-controls>
264+
</div>
265+
```
266+
236267
### Demo
237268

238-
Here is a working demo featuring two instances on one page: [http://plnkr.co/edit/Pm4L53UYAieF808v8wxL?p=preview](http://plnkr.co/edit/Pm4L53UYAieF808v8wxL?p=preview)
269+
Here is a working demo featuring two instances on one page: [http://plnkr.co/edit/xmjmIId0c9Glh5QH97xz?p=preview](http://plnkr.co/edit/xmjmIId0c9Glh5QH97xz?p=preview)
239270

240271

241272
## Working With Asynchronous Data

src/directives/pagination/dirPagination.js

+43-21
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* Config
2121
*/
2222
var moduleName = 'angularUtils.directives.dirPagination';
23+
var DEFAULT_ID = '__default';
2324

2425
/**
2526
* Module
@@ -40,15 +41,6 @@
4041
priority: 5000, // This setting is used in conjunction with the later call to $compile() to prevent infinite recursion of compilation
4142
compile: function dirPaginationCompileFn(tElement, tAttrs){
4243

43-
// Add ng-repeat to the dom element
44-
if (tElement[0].hasAttribute('dir-paginate-start') || tElement[0].hasAttribute('data-dir-paginate-start')) {
45-
// using multiElement mode (dir-paginate-start, dir-paginate-end)
46-
tAttrs.$set('ngRepeatStart', tAttrs.dirPaginate);
47-
tElement.eq(tElement.length - 1).attr('ng-repeat-end', true);
48-
} else {
49-
tAttrs.$set('ngRepeat', tAttrs.dirPaginate);
50-
}
51-
5244
var expression = tAttrs.dirPaginate;
5345
// regex taken directly from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211
5446
var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
@@ -60,10 +52,35 @@
6052
var itemsPerPageFilterRemoved = match[2].replace(filterPattern, '');
6153
var collectionGetter = $parse(itemsPerPageFilterRemoved);
6254

63-
var paginationId = tAttrs.paginationId || '__default';
64-
paginationService.registerInstance(paginationId);
55+
// If any value is specified for paginationId, we register the un-evaluated expression at this stage for the benefit of any
56+
// dir-pagination-controls directives that may be looking for this ID.
57+
var rawId = tAttrs.paginationId || DEFAULT_ID;
58+
paginationService.registerInstance(rawId);
6559

6660
return function dirPaginationLinkFn(scope, element, attrs){
61+
62+
// Now that we have access to the `scope` we can interpolate any expression given in the paginationId attribute and
63+
// potentially register a new ID if it evaluates to a different value than the rawId.
64+
var paginationId = $parse(attrs.paginationId)(scope) || attrs.paginationId || DEFAULT_ID;
65+
paginationService.registerInstance(paginationId);
66+
67+
var repeatExpression;
68+
var idDefinedInFilter = !!expression.match(/(\|\s*itemsPerPage\s*:[^|]*:[^|]*)/);
69+
if (paginationId !== DEFAULT_ID && !idDefinedInFilter) {
70+
repeatExpression = expression.replace(/(\|\s*itemsPerPage\s*:[^|]*)/, "$1 : '" + paginationId + "'");
71+
} else {
72+
repeatExpression = expression;
73+
}
74+
75+
// Add ng-repeat to the dom element
76+
if (element[0].hasAttribute('dir-paginate-start') || element[0].hasAttribute('data-dir-paginate-start')) {
77+
// using multiElement mode (dir-paginate-start, dir-paginate-end)
78+
attrs.$set('ngRepeatStart', repeatExpression);
79+
element.eq(element.length - 1).attr('ng-repeat-end', true);
80+
} else {
81+
attrs.$set('ngRepeat', repeatExpression);
82+
}
83+
6784
var compiled = $compile(element, false, 5000); // we manually compile the element again, as we have now added ng-repeat. Priority less than 5000 prevents infinite recursion of compiling dirPaginate
6885

6986
var currentPageGetter;
@@ -183,21 +200,26 @@
183200
},
184201
scope: {
185202
maxSize: '=?',
186-
onPageChange: '&?'
203+
onPageChange: '&?',
204+
paginationId: '=?'
187205
},
188-
link: function(scope, element, attrs) {
206+
link: function dirPaginationControlsLinkFn(scope, element, attrs) {
189207

190-
var paginationId;
191-
paginationId = attrs.paginationId || '__default';
192-
if (!scope.maxSize) { scope.maxSize = 9; }
193-
scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
194-
scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;
208+
// rawId is the un-interpolated value of the pagination-id attribute. This is only important when the corresponding dir-paginate directive has
209+
// not yet been linked (e.g. if it is inside an ng-if block), and in that case it prevents this controls directive from assuming that there is
210+
// no corresponding dir-paginate directive and wrongly throwing an exception.
211+
var rawId = attrs.paginationId || DEFAULT_ID;
212+
var paginationId = scope.paginationId || attrs.paginationId || DEFAULT_ID;
195213

196-
if (!paginationService.isRegistered(paginationId)) {
197-
var idMessage = (paginationId !== '__default') ? ' (id: ' + paginationId + ') ' : ' ';
214+
if (!paginationService.isRegistered(paginationId) && !paginationService.isRegistered(rawId)) {
215+
var idMessage = (paginationId !== DEFAULT_ID) ? ' (id: ' + paginationId + ') ' : ' ';
198216
throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.';
199217
}
200218

219+
if (!scope.maxSize) { scope.maxSize = 9; }
220+
scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
221+
scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;
222+
201223
var paginationRange = Math.max(scope.maxSize, 5);
202224
scope.pages = [];
203225
scope.pagination = {
@@ -291,7 +313,7 @@
291313

292314
return function(collection, itemsPerPage, paginationId) {
293315
if (typeof (paginationId) === 'undefined') {
294-
paginationId = '__default';
316+
paginationId = DEFAULT_ID;
295317
}
296318
if (!paginationService.isRegistered(paginationId)) {
297319
throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.';

src/directives/pagination/dirPagination.spec.js

+59
Original file line numberDiff line numberDiff line change
@@ -722,4 +722,63 @@ describe('dirPagination directive', function() {
722722
expect(containingElement.find('p').length).toEqual(3);
723723
});
724724
});
725+
726+
/**
727+
* THis suite tests out the ability to handle dynamically-generated pagination ids. The main use case is when
728+
* there are multiple dir-pagination instances being generated by an ng-repeat, and the pagination id is
729+
* only known at run-time.
730+
*/
731+
describe('dynamic pagination ids', function() {
732+
function compile() {
733+
var html = '<div ng-repeat="list in lists"><ul class="list">' +
734+
'<li dir-paginate="item in list.collection | itemsPerPage: 3" pagination-id="list.id">{{ item }}</li>' +
735+
'</ul>' +
736+
'<dir-pagination-controls pagination-id="list.id"></dir-pagination-controls>' +
737+
'</div>';
738+
739+
$scope.lists = [
740+
{
741+
id: 'list1',
742+
collection: [1, 2, 3, 4, 5]
743+
},
744+
{
745+
id: 'list2',
746+
collection: ['a', 'b', 'c', 'd', 'e']
747+
}
748+
];
749+
containingElement.append($compile(html)($scope));
750+
$scope.$apply();
751+
}
752+
753+
function getListItems($list) {
754+
return $list.find('li').map(function() {
755+
return $(this).text().trim();
756+
}).get();
757+
}
758+
759+
it('should not throw an exception', function() {
760+
expect(compile).not.toThrow();
761+
});
762+
763+
it('should allow independent pagination', function() {
764+
compile();
765+
766+
var $list1 = containingElement.find('ul.list').eq(0);
767+
var $list2 = containingElement.find('ul.list').eq(1);
768+
769+
expect(getListItems($list1)).toEqual([ '1', '2', '3' ]);
770+
expect(getListItems($list2)).toEqual([ 'a', 'b', 'c' ]);
771+
772+
// click the "page 2" link on the first set of pagination
773+
var pagination1 = $list1.parent().find('ul.pagination');
774+
pagination1.children().eq(2).find('a').triggerHandler('click');
775+
$scope.$apply();
776+
777+
// ensure only the first set of pagination changes
778+
expect(getListItems($list1)).toEqual([ '4', '5' ]);
779+
expect(getListItems($list2)).toEqual([ 'a', 'b', 'c' ]);
780+
});
781+
782+
783+
});
725784
});

0 commit comments

Comments
 (0)