Add 'packages/paginate/' from commit 'a2daba88abb3c4c73b67922d3f93f2032b14e1da'

git-subtree-dir: packages/paginate
git-subtree-mainline: 12aa791a8e
git-subtree-split: a2daba88ab
This commit is contained in:
Tobe O 2020-02-15 18:28:46 -05:00
commit c6d7d5f416
16 changed files with 594 additions and 0 deletions

View file

@ -0,0 +1,2 @@
analyzer:
strong-mode: true

58
packages/paginate/.gitignore vendored Normal file
View file

@ -0,0 +1,58 @@
# See https://www.dartlang.org/tools/private-files.html
# Files and directories created by pub
.packages
.pub/
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
.dart_tool

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/paginate.iml" filepath="$PROJECT_DIR$/.idea/paginate.iml" />
</modules>
</component>
</project>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/packages" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>

View file

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Tests" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="checkedMode" value="false" />
<option name="filePath" value="$PROJECT_DIR$/test/all_test.dart" />
<method />
</configuration>
</component>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -0,0 +1 @@
language: dart

View file

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

21
packages/paginate/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 The Angel Framework
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,60 @@
# 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)
Platform-agnostic pagination library, with custom support for the
[Angel framework](https://github.com/angel-dart/angel).
# Installation
In your `pubspec.yaml` file:
```yaml
dependencies:
angel_paginate: ^2.0.0
```
# Usage
This library exports a `Paginator<T>`, which can be used to efficiently produce
instances of `PaginationResult<T>`. Pagination results, when serialized to JSON, look like
this:
```json
{
"total" : 75,
"items_per_page" : 10,
"previous_page" : 3,
"current_page" : 4,
"next_page" : 5,
"start_index" : 30,
"end_index" : 39,
"data" : ["<items...>"]
}
```
Results can be parsed from Maps using the `PaginationResult<T>.fromMap` constructor, and
serialized via their `toJson()` method.
To create a paginator:
```dart
import 'package:angel_paginate/angel_paginate.dart';
main() {
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)
}
```
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.

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

@ -0,0 +1,191 @@
/// Efficiently paginates collections of items in an object-oriented manner.
class Paginator<T> {
final Map<int, PaginationResult<T>> _cache = {};
PaginationResult<T> _current;
int _page = 0;
/// The collection of items to be paginated.
final Iterable<T> _items;
/// The maximum number of items to be shown per page.
final int itemsPerPage;
/// If `true` (default), then the results of paginations will be saved by page number.
///
/// For example, you would only have to paginate page 1 once. Future calls would return a cached version.
final bool useCache;
Paginator(this._items, {this.itemsPerPage: 5, this.useCache: true});
/// Returns `true` if there are more items at lesser page indices than the current one.
bool get canGoBack => _page > 0;
/// Returns `true` if there are more items at greater page indices than the current one.
bool get canGoForward => _page < _lastPage();
/// The current page index.
int get index => _page;
/// Returns the greatest possible page number for this collection, given the number of [itemsPerPage].
int get lastPageNumber => _lastPage();
/// The current page number. This is not the same as [index].
///
/// This getter will return user-friendly numbers. The lowest value it will ever return is `1`.
int get pageNumber => _page < 1 ? 1 : (_page + 1);
/// Fetches the current page. This will be cached until [back] or [next] is called.
///
/// If [useCache] is `true` (default), then computations will be cached even after the page changes.
PaginationResult<T> get current {
if (_current != null)
return _current;
else
return _current = _getPage();
}
PaginationResult<T> _computePage() {
var len = _items.length;
var it = _items.skip(_page * (itemsPerPage ?? 5));
var offset = len - it.length;
it = it.take(itemsPerPage);
var last = _lastPage();
// print('cur: $_page, last: $last');
return new _PaginationResultImpl(it,
currentPage: _page + 1,
previousPage: _page <= 0 ? -1 : _page,
nextPage: _page >= last - 1 ? -1 : _page + 2,
startIndex: it.isEmpty ? -1 : offset,
endIndex: offset + it.length - 1,
itemsPerPage:
itemsPerPage < _items.length ? itemsPerPage : _items.length,
total: len);
}
PaginationResult<T> _getPage() {
if (useCache != false)
return _cache.putIfAbsent(_page, () => _computePage());
else
return _computePage();
}
int _lastPage() {
var n = (_items.length / itemsPerPage).round();
// print('items: ${_items.length}');
// print('per page: $itemsPerPage');
// print('quotient: $n');
var remainder = _items.length - (n * itemsPerPage);
// print('remainder: $remainder');
return (remainder <= 0) ? n : n + 1;
}
/// Attempts to go the specified page. If it fails, then it will remain on the current page.
///
/// Keep in mind - this just not be a zero-based index, but a one-based page number. The lowest
/// allowed value is `1`.
void goToPage(int page) {
if (page > 0 && page <= _lastPage()) {
_page = page - 1;
_current = null;
}
}
/// Moves the paginator back one page, if possible.
void back() {
if (_page > 0) {
_page--;
_current = null;
}
}
/// Advances the paginator one page, if possible.
void next() {
if (_page < _lastPage()) {
_page++;
_current = null;
}
}
}
/// Stores the result of a pagination.
abstract class PaginationResult<T> {
factory PaginationResult.fromMap(Map<String, dynamic> map) =>
new _PaginationResultImpl((map['data'] as Iterable).cast<T>(),
currentPage: map['current_page'],
endIndex: map['end_index'],
itemsPerPage: map['items_per_page'],
nextPage: map['next_page'],
previousPage: map['previous_page'],
startIndex: map['start_index'],
total: map['total']);
List<T> get data;
int get currentPage;
int get previousPage;
int get nextPage;
int get itemsPerPage;
int get total;
int get startIndex;
int get endIndex;
Map<String, dynamic> toJson();
}
class _PaginationResultImpl<T> implements PaginationResult<T> {
final Iterable<T> _data;
Iterable<T> _cachedData;
@override
final int currentPage;
_PaginationResultImpl(this._data,
{this.currentPage,
this.endIndex,
this.itemsPerPage,
this.nextPage,
this.previousPage,
this.startIndex,
this.total});
@override
List<T> get data => _cachedData ?? (_cachedData = new List<T>.from(_data));
@override
final int endIndex;
@override
final int itemsPerPage;
@override
final int nextPage;
@override
final int previousPage;
@override
final int startIndex;
@override
final int total;
@override
Map<String, dynamic> toJson() {
return {
'total': total,
'items_per_page': itemsPerPage,
'previous_page': previousPage,
'current_page': currentPage,
'next_page': nextPage,
'start_index': startIndex,
'end_index': endIndex,
'data': data
};
}
}

View file

@ -0,0 +1,13 @@
name: angel_paginate
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: ">=2.0.0-dev <3.0.0"
dependencies:
angel_framework: ^2.0.0-alpha
dev_dependencies:
angel_test: ^2.0.0
logging: ^0.11.0
test: ^1.0.0

View file

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

View file

@ -0,0 +1,57 @@
import 'dart:convert';
import 'package:angel_framework/angel_framework.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>> mjAlbums = [
{'billie': 'jean'},
{'off': 'the_wall'},
{'michael': 'jackson'}
];
main() {
TestClient client;
setUp(() async {
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);
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 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));
print('page: ${page.toJson()}');
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, mjAlbums.length - 1);
expect(page.data, mjAlbums);
});
}

View file

@ -0,0 +1,127 @@
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);
main() {
group('cache', () {
var cached = new Paginator<int>(DATA),
uncached = new Paginator<int>(DATA, useCache: false);
test('always cache current', () {
expect(cached.current, cached.current);
expect(uncached.current, uncached.current);
});
test('only cache prev/next if useCache != false', () {
var cached1 = cached.current;
cached.goToPage(4);
var cached4 = cached.current;
cached.goToPage(1);
expect(cached.current, cached1);
cached.goToPage(4);
expect(cached.current, cached4);
var uncached1 = uncached.current;
uncached.goToPage(4);
var uncached4 = uncached.current;
uncached.goToPage(1);
expect(uncached.current, isNot(uncached1));
uncached.goToPage(4);
expect(uncached.current, isNot(uncached4));
});
});
test('default state', () {
var paginator = new Paginator<int>(DATA);
expect(paginator.index, 0);
expect(paginator.pageNumber, 1);
expect(paginator.itemsPerPage, 5);
expect(paginator.useCache, true);
expect(paginator.canGoBack, false);
expect(paginator.canGoForward, true);
expect(paginator.lastPageNumber, 21);
// Going back should do nothing
var p = paginator.pageNumber;
paginator.back();
expect(paginator.pageNumber, p);
});
group('paginate', () {
test('first page', () {
var paginator = new Paginator<int>(DATA);
expect(paginator.pageNumber, 1);
var r = paginator.current;
print(r.toJson());
expect(r.total, DATA.length);
expect(r.itemsPerPage, 5);
expect(r.previousPage, -1);
expect(r.currentPage, 1);
expect(r.nextPage, 2);
expect(r.startIndex, 0);
expect(r.endIndex, 4);
expect(r.data, DATA.skip(r.startIndex).take(r.itemsPerPage).toList());
});
});
test('third page', () {
var paginator = new Paginator<int>(DATA)..goToPage(3);
expect(paginator.pageNumber, 3);
var r = paginator.current;
print(r.toJson());
expect(r.total, DATA.length);
expect(r.itemsPerPage, 5);
expect(r.previousPage, 2);
expect(r.currentPage, 3);
expect(r.nextPage, 4);
expect(r.startIndex, 10);
expect(r.endIndex, 14);
expect(r.data, DATA.skip(r.startIndex).take(r.itemsPerPage).toList());
paginator.back();
expect(paginator.pageNumber, 2);
});
test('last page', () {
var paginator = new Paginator<int>(DATA);
paginator.goToPage(paginator.lastPageNumber);
var r = paginator.current;
expect(r.total, DATA.length);
expect(r.itemsPerPage, 5);
expect(r.previousPage, paginator.lastPageNumber - 1);
expect(r.currentPage, paginator.lastPageNumber);
expect(r.nextPage, -1);
expect(r.startIndex, (paginator.lastPageNumber - 1) * 5);
expect(r.endIndex, r.startIndex);
expect(r.data, [DATA.last]);
expect(r.data, DATA.skip(r.startIndex).take(r.itemsPerPage).toList());
});
test('dump pages', () {
var paginator = new Paginator<int>(DATA);
print('${paginator.lastPageNumber} page(s) of data:');
do {
print(' * Page #${paginator.pageNumber}: ${paginator.current.data}');
paginator.next();
} while (paginator.canGoForward);
});
test('empty collection', () {
var paginator = new Paginator([]);
var page = paginator.current;
print(page.toJson());
expect(page.total, 0);
expect(page.previousPage, -1);
expect(page.nextPage, -1);
expect(page.currentPage, 1);
expect(page.startIndex, -1);
expect(page.endIndex, -1);
expect(page.data, isEmpty);
expect(paginator.canGoBack, isFalse);
expect(paginator.canGoForward, isFalse);
});
}