This commit is contained in:
Tobe O 2019-02-01 09:39:10 -05:00
parent c2ba916f00
commit 9bfcd93a92
12 changed files with 69 additions and 246 deletions

View file

@ -0,0 +1 @@
2.1.0

Binary file not shown.

2
CHANGELOG.md Normal file
View file

@ -0,0 +1,2 @@
# 2.0.0
* Dart2 + Angel2 update.

View file

@ -1,7 +1,6 @@
# paginate
[![version 1.0.0+3](https://img.shields.io/badge/pub-v1.0.0+3-brightgreen.svg)](https://pub.dartlang.org/packages/angel_paginate)
[![Pub](https://img.shields.io/pub/v/angel_paginate.svg)](https://pub.dartlang.org/packages/angel_paginate)
[![build status](https://travis-ci.org/angel-dart/paginate.svg)](https://travis-ci.org/angel-dart/paginate)
![coverage: 100%](https://img.shields.io/badge/coverage-100%25-green.svg)
Platform-agnostic pagination library, with custom support for the
[Angel framework](https://github.com/angel-dart/angel).
@ -11,7 +10,7 @@ In your `pubspec.yaml` file:
```yaml
dependencies:
angel_paginate: ^1.0.0
angel_paginate: ^2.0.0
```
# Usage
@ -59,37 +58,3 @@ The entire Paginator API is documented, so check out the DartDocs.
Paginators by default cache paginations, to improve performance as you shift through pages.
This can be especially helpful in a client-side application where your UX involves a fast
response time, i.e. a search page.
## Use With Angel
Naturally, a library named `angel_paginate` has special provisions for the
[Angel framework](https://github.com/angel-dart/angel).
In `package:angel_paginate/server.dart`, a function called `paginate` generates
pagination service hooks for you. If the result of a hooked service event is an `Iterable`,
it will be paginated. This is convenient because it works with any data store, whether it
be MongoDB, RethinkDB, an in-memory store, or something else entirely.
```dart
configureServer(Angel app) {
var service = app.service('api/foo') as HookedService;
service.afterIndexed.listen(paginate(itemsPerPage: 10));
}
```
See `test/server_test.dart` for examples of usage with Angel.
The pagination hook also allows you to provide a `page` and/or `$limit` in the query.
If the user provides a `page` in the query, it will return a pagination of the given page.
Ex. `http://mysite.com/api/todos?page=4`
A `$limit` can be used to override the `itemsPerPage` set in the `paginate` hook. If you
would like to set a maximum on the number of items per page, you can set `maxItemsPerPage`
in the `paginate` call.
Ex. `http://mysite.com/api/todos?$limit=25`
You can use these pagination functions to provide powerful search experiences on your websites.
**NOTE**: If the paginated data is empty, expect `start_index` and `end_index`
to both be `-1`.

15
example/main.dart Normal file
View file

@ -0,0 +1,15 @@
import 'package:angel_paginate/angel_paginate.dart';
main() {
var iterable = [1, 2, 3, 4];
var p = new Paginator(iterable);
// Get the current page (default: page 1)
var page = p.current;
print(page.total);
print(page.startIndex);
print(page.data); // The actual items on this page.
p.next(); // Advance a page
p.back(); // Back one page
p.goToPage(10); // Go to page number (1-based, not a 0-based index)
}

View file

@ -57,8 +57,8 @@ class Paginator<T> {
nextPage: _page >= last - 1 ? -1 : _page + 2,
startIndex: it.isEmpty ? -1 : offset,
endIndex: offset + it.length - 1,
itemsPerPage: itemsPerPage < _items.length ? itemsPerPage : _items
.length,
itemsPerPage:
itemsPerPage < _items.length ? itemsPerPage : _items.length,
total: len);
}
@ -110,7 +110,7 @@ class Paginator<T> {
/// Stores the result of a pagination.
abstract class PaginationResult<T> {
factory PaginationResult.fromMap(Map<String, dynamic> map) =>
new _PaginationResultImpl(map['data'],
new _PaginationResultImpl((map['data'] as Iterable).cast<T>(),
currentPage: map['current_page'],
endIndex: map['end_index'],
itemsPerPage: map['items_per_page'],

View file

@ -1,49 +0,0 @@
import 'package:angel_framework/angel_framework.dart';
import 'angel_paginate.dart';
export 'angel_paginate.dart';
/// Paginates the results of service events.
///
/// Users can add a `page` to the query to display a certain page, i.e. `http://foo.com/api/todos?page=5`.
///
/// Users can also add a `$limit` to the query to display more or less items than specified in [itemsPerPage] (default: `5`).
/// If [maxItemsPerPage] is set, then even if the query contains a `$limit` parameter, it will be limited to the maximum.
HookedServiceEventListener paginate<T>(
{int itemsPerPage, int maxItemsPerPage}) {
return (HookedServiceEvent e) {
if (e.isBefore) throw new UnsupportedError(
'`package:angel_paginate` can only be run as an after hook.');
if (e.result is! Iterable) return;
int page = 1,
nItems = itemsPerPage;
if (e.params.containsKey('query') && e.params['query'] is Map) {
var query = e.params['query'] as Map;
if (query.containsKey('page')) {
try {
page = int.parse(query['page']?.toString());
} catch (e) {
// Fail silently...
}
}
if (query.containsKey(r'$limit')) {
try {
var lim = int.parse(query[r'$limit']?.toString());
if (lim > 0 && (maxItemsPerPage == null || lim <= maxItemsPerPage))
nItems = lim;
} catch (e) {
// Fail silently...
}
}
}
var paginator = new Paginator(
e.result, itemsPerPage: nItems)
..goToPage(page);
e.result = paginator.current;
};
}

View file

@ -1,13 +1,13 @@
name: angel_paginate
version: 1.0.0+3
version: 2.0.0
description: Platform-agnostic pagination library, with custom support for the Angel framework.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/paginate
environment:
sdk: ">=1.19.0"
sdk: ">=2.0.0-dev <3.0.0"
dependencies:
angel_framework: ^1.0.0
angel_framework: ^2.0.0-alpha
dev_dependencies:
angel_diagnostics: ^1.0.0
angel_test: ^1.0.0
test: ^0.12.15
angel_test: ^2.0.0
logging: ^0.11.0
test: ^1.0.0

View file

@ -1,10 +1,8 @@
import 'package:test/test.dart';
import 'bounds_test.dart' as bounds;
import 'paginate_test.dart' as paginate;
import 'server_test.dart' as server;
main() {
group('bounds', bounds.main);
group('paginate', paginate.main);
group('server', server.main);
}

View file

@ -1,11 +1,11 @@
import 'package:angel_client/angel_client.dart' as c;
import 'package:angel_diagnostics/angel_diagnostics.dart';
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_paginate/server.dart';
import 'package:angel_paginate/angel_paginate.dart';
import 'package:angel_test/angel_test.dart';
import 'package:logging/logging.dart';
import 'package:test/test.dart';
final List<Map<String, String>> DATA = [
final List<Map<String, String>> mjAlbums = [
{'billie': 'jean'},
{'off': 'the_wall'},
{'michael': 'jackson'}
@ -13,37 +13,45 @@ final List<Map<String, String>> DATA = [
main() {
TestClient client;
c.Service songService;
setUp(() async {
var app = new Angel()
..use('/api/songs', new AnonymousService(index: ([p]) async => DATA));
var service = app.service('api/songs') as HookedService;
service.afterIndexed.listen(paginate(itemsPerPage: 2));
await app.configure(logRequests());
var app = new Angel();
app.get('/api/songs', (req, res) {
var p = Paginator(mjAlbums, itemsPerPage: mjAlbums.length);
p.goToPage(int.parse(req.queryParameters['page'] ?? '1'));
return p.current;
});
client = await connectTo(app);
songService = client.service('api/songs');
app.logger = Logger('angel_paginate')
..onRecord.listen((rec) {
print(rec);
if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(rec.stackTrace);
});
});
tearDown(() => client.close());
test('limit exceeds size of collection', () async {
var response = await songService.index({
'query': {
r'$limit': DATA.length + 1
}
});
var response = await client.get(Uri(
path: '/api/songs',
queryParameters: {r'$limit': (mjAlbums.length + 1).toString()}));
var page = new PaginationResult<Map<String, dynamic>>.fromMap(
json.decode(response.body));
var page = new PaginationResult<Map<String, String>>.fromMap(response);
print('page: ${page.toJson()}');
expect(page.total, DATA.length);
expect(page.itemsPerPage, DATA.length);
expect(page.total, mjAlbums.length);
expect(page.itemsPerPage, mjAlbums.length);
expect(page.previousPage, -1);
expect(page.currentPage, 1);
expect(page.nextPage, -1);
expect(page.startIndex, 0);
expect(page.endIndex, DATA.length - 1);
expect(page.data, DATA);
expect(page.endIndex, mjAlbums.length - 1);
expect(page.data, mjAlbums);
});
}

View file

@ -2,8 +2,7 @@ import 'package:angel_paginate/angel_paginate.dart';
import 'package:test/test.dart';
// Count-down from 100, then 101 at the end...
final List<int> DATA = new List<int>.generate(100, (i) => 100 - i)
..add(101);
final List<int> DATA = new List<int>.generate(100, (i) => 100 - i)..add(101);
main() {
group('cache', () {
@ -68,8 +67,7 @@ main() {
});
test('third page', () {
var paginator = new Paginator<int>(DATA)
..goToPage(3);
var paginator = new Paginator<int>(DATA)..goToPage(3);
expect(paginator.pageNumber, 3);
var r = paginator.current;
print(r.toJson());
@ -108,7 +106,7 @@ main() {
do {
print(' * Page #${paginator.pageNumber}: ${paginator.current.data}');
paginator.next();
} while(paginator.canGoForward);
} while (paginator.canGoForward);
});
test('empty collection', () {

View file

@ -1,115 +0,0 @@
import 'package:angel_client/angel_client.dart' as c;
import 'package:angel_diagnostics/angel_diagnostics.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_paginate/server.dart';
import 'package:angel_test/angel_test.dart';
import 'package:test/test.dart';
final List DATA = new List.filled(50, {'foo': 'bar'}, growable: true)
..addAll(new List.filled(25, {'bar': 'baz'}));
main() {
group('no max', () {
Angel app;
TestClient client;
c.Service dataService;
setUp(() async {
app = new Angel()
..use('/data', new AnonymousService(index: ([p]) async => DATA));
await app.configure(logRequests());
var service = app.service('data') as HookedService;
service.afterIndexed.listen(paginate(itemsPerPage: 10));
client = await connectTo(app);
dataService = client.service('data');
});
tearDown(() => client.close());
test('first page', () async {
var response = await dataService
.index()
.then((m) => new PaginationResult.fromMap(m));
print(response.toJson());
expect(response.total, DATA.length);
expect(response.itemsPerPage, 10);
expect(response.startIndex, 0);
expect(response.endIndex, 9);
expect(response.previousPage, -1);
expect(response.currentPage, 1);
expect(response.nextPage, 2);
expect(response.data, DATA.take(10).toList());
});
test('third page', () async {
var response = await dataService.index({
'query': {'page': 3}
}).then((m) => new PaginationResult.fromMap(m));
print(response.toJson());
expect(response.total, DATA.length);
expect(response.itemsPerPage, 10);
expect(response.startIndex, 20);
expect(response.endIndex, 29);
expect(response.previousPage, 2);
expect(response.currentPage, 3);
expect(response.nextPage, 4);
expect(response.data, DATA.skip(20).take(10).toList());
});
test('custom limit', () async {
var response = await dataService.index({
'query': {'page': 4, r'$limit': 5}
}).then((m) => new PaginationResult.fromMap(m));
print(response.toJson());
expect(response.total, DATA.length);
expect(response.itemsPerPage, 5);
expect(response.startIndex, 15);
expect(response.endIndex, 19);
expect(response.previousPage, 3);
expect(response.currentPage, 4);
expect(response.nextPage, 5);
expect(response.data, DATA.skip(15).take(5).toList());
});
});
group('max 15', () {
Angel app;
TestClient client;
c.Service dataService;
setUp(() async {
app = new Angel()
..use('/data', new AnonymousService(index: ([p]) async => DATA));
await app.configure(logRequests());
var service = app.service('data') as HookedService;
service.afterIndexed.listen(
paginate(itemsPerPage: 10, maxItemsPerPage: 15));
client = await connectTo(app);
dataService = client.service('data');
});
tearDown(() => client.close());
test('exceed max', () async {
var response = await dataService.index({
'query': {'page': 4, r'$limit': 30}
}).then((m) => new PaginationResult.fromMap(m));
print(response.toJson());
// Should default to 10 items per page :)
expect(response.total, DATA.length);
expect(response.itemsPerPage, 10);
expect(response.startIndex, 30);
expect(response.endIndex, 39);
expect(response.previousPage, 3);
expect(response.currentPage, 4);
expect(response.nextPage, 5);
expect(response.data, DATA.skip(30).take(10).toList());
});
});
}