2.0.0
This commit is contained in:
parent
c2ba916f00
commit
9bfcd93a92
12 changed files with 69 additions and 246 deletions
1
.dart_tool/pub/bin/sdk-version
Normal file
1
.dart_tool/pub/bin/sdk-version
Normal file
|
@ -0,0 +1 @@
|
|||
2.1.0
|
BIN
.dart_tool/pub/bin/test/test.dart.snapshot.dart2
Normal file
BIN
.dart_tool/pub/bin/test/test.dart.snapshot.dart2
Normal file
Binary file not shown.
2
CHANGELOG.md
Normal file
2
CHANGELOG.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# 2.0.0
|
||||
* Dart2 + Angel2 update.
|
41
README.md
41
README.md
|
@ -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
|
||||
|
@ -58,38 +57,4 @@ 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`.
|
||||
response time, i.e. a search page.
|
15
example/main.dart
Normal file
15
example/main.dart
Normal 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)
|
||||
}
|
|
@ -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'],
|
||||
|
@ -147,12 +147,12 @@ class _PaginationResultImpl<T> implements PaginationResult<T> {
|
|||
|
||||
_PaginationResultImpl(this._data,
|
||||
{this.currentPage,
|
||||
this.endIndex,
|
||||
this.itemsPerPage,
|
||||
this.nextPage,
|
||||
this.previousPage,
|
||||
this.startIndex,
|
||||
this.total});
|
||||
this.endIndex,
|
||||
this.itemsPerPage,
|
||||
this.nextPage,
|
||||
this.previousPage,
|
||||
this.startIndex,
|
||||
this.total});
|
||||
|
||||
@override
|
||||
List<T> get data => _cachedData ?? (_cachedData = new List<T>.from(_data));
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
12
pubspec.yaml
12
pubspec.yaml
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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', () {
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue