Offset pagination for Vapor π
This package currently offers support for offset pagination on Array
and QueryBuilder
.
Add Paginator
to the package dependencies (in your Package.swift
file):
dependencies: [
...,
.package(url: "https://github.com/nodes-vapor/paginator.git", from: "4.0.0")
]
as well as to your target (e.g. βAppβ):
targets: [
...
.target(
name: "App",
dependencies: [... "Paginator" ...]
),
...
]
Next, copy/paste the Resources/Views/Paginator
folder into your project in order to be able to use the provided Leaf tags. These files can be changed as explained in the Leaf Tags section, however itβs recommended to copy this folder to your project anyway. This makes it easier for you to keep track of updates and your project will work if you decide later on to not use your own customized leaf files.
First make sure that youβve imported Paginator everywhere itβs needed:
import Paginator
In order to do pagination in Leaf, please make sure to add the Leaf tag:
public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
services.register { _ -> LeafTagConfig in
var tags = LeafTagConfig.default()
tags.use([
"offsetPaginator": OffsetPaginatorTag(templatePath: "Paginator/offsetpaginator")
])
return tags
}
}
If you want to fully customize the way the pagination control are being generated, you are free to override the template path.
QueryBuilder
To return a paginated result from QueryBuilder
, you can do the following:
router.get("galaxies") { (req: Request) -> Future<OffsetPaginator<Galaxy>> in
return Galaxy.query(on: req).paginate(on: req)
}
Array
For convenience, Paginator also comes with support for paginating Array
:
router.get("galaxies") { (req: Request) -> Future<OffsetPaginator<Galaxy>> in
let galaxies = [Galaxy(), Galaxy(), Galaxy()]
return galaxies.paginate(on: req)
}
RawSQL
Paginator also comes with support for paginating raw SQL queries for complex expressions not compatible with Fluent.
Simple example using PostgreSQL:
router.get("galaxies") { (req: Request) -> Future<OffsetPaginator<Galaxy>> in
return req.withPooledConnection(to: .psql) { conn -> Future<OffsetPaginator<Galaxy>> in
let rawBuilder = RawSQLBuilder<PostgreSQLDatabase, Galaxy>(
query: """
SELECT *
FROM public."Galaxy"
""", countQuery: """
SELECT COUNT(*) as "count"
FROM public."Galaxy"
""", connection: conn)
return try rawBuilder.paginate(on: req)
}
}
Note: the count query is expected to have a result with one column named count
and with the total columns value in the first row
To use Paginator together with Leaf, you can do the following:
struct GalaxyList: Codable {
let galaxies: [Galaxy]
}
router.get("galaxies") { (req: Request) -> Response in
let paginator: Future<OffsetPaginator<Galaxy>> = Galaxy.query(on: req).paginate(on: req)
return paginator.flatMap(to: Response.self) { paginator in
return try req.view().render(
"MyLeafFile",
GalaxyList(galaxies: paginator.data ?? []),
userInfo: try paginator.userInfo(),
on: req
)
.encode(for: req)
}
}
Please note how the Paginator data is being passed in using
userInfo
on therender
call. Forgetting to pass this in will result in an error being thrown.
Then in your MyLeafFile.leaf
you could do something like:
<ul>
#for(galaxy in galaxies) {
<li>#(galaxy.name)</li>
}
</ul>
#offsetPaginator()
Calling the Leaf tag for OffsetPaginator
will automatically generate the Bootstrap 4 HTML for showing the pagination controls:
<nav class="paginator">
<ul class="pagination justify-content-center table-responsive">
<li class="page-item">
<a href="/admin/users?page=16" class="page-link" rel="prev" aria-label="Previous">
<span aria-hidden="true">Β«</span>
<span class="sr-only">Previous</span>
</a>
</li>
<li class="page-item "><a href="/admin/users?page=1" class="page-link">1</a></li>
<li class="disabled page-item"><a href="#" class="page-link">...</a></li>
<li class="page-item "><a href="" class="page-link">12</a></li>
<li class="page-item "><a href="" class="page-link">13</a></li>
<li class="page-item "><a href="" class="page-link">14</a></li>
<li class="page-item "><a href="" class="page-link">15</a></li>
<li class="page-item "><a href="" class="page-link">16</a></li>
<li class="page-item active "><a href="" class="page-link">17</a></li>
<li class="page-item "><a href="/admin/users?page=18" class="page-link">18</a></li>
<li class="page-item">
<a href="/admin/users?page=18" class="page-link" rel="next" aria-label="Next">
<span aria-hidden="true">Β»</span>
<span class="sr-only">Next</span>
</a>
</li>
</ul>
</nav>
The data in an OffsetPaginator can be transformed by mapping over the paginator and transforming each element at a time:
Galaxy.query(on: req).paginate(on: req).map { paginator in
paginator.map { (galaxy: Galaxy) -> GalaxyViewModel in
GalaxyViewModel(galaxy: galaxy)
}
}
You can also transform a whole page of results at once:
Galaxy.query(on: req).paginate(on: req).map { paginator in
paginator.map { (galaxies: [Galaxy]) -> [GalaxyViewModel] in
galaxies.map(GalaxyViewModel.init)
}
}
In case the transformation requires async work you can do:
Galaxy.query(on: req).paginate(on: req).map { paginator in
paginator.flatMap { (galaxies: [Galaxy]) -> Future<[GalaxyViewModel]> in
galaxies.someAsyncMethod()
}
}
The OffsetPaginator
has a configuration file (OffsetPaginatorConfig
) that can be overwritten if needed. This can be done in configure.swift
:
public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
// ..
services.register(OffsetPaginatorConfig(
perPage: 1,
defaultPage: 1
))
}
This package is developed and maintained by the Vapor team at Nodes.
This package is open-sourced software licensed under the MIT license