-
Notifications
You must be signed in to change notification settings - Fork 21
Pagination
Pagination is a component that allows you to display a large amount of data in an view by separating it into pages.
- Creating a Pagination
- Data Sources
- Asynchronous Pagination
- Pagination + Layouts
- Diving into Pagination
- Navigation
- Handling Page Switch
- Multiple Paginations
To create a pagination state you have two essential parameters:
-
sourceProvider
will be used to define the pagination data, e.g.: a list of things. -
itemFactory
will be used to create the items that will be displayed in the view for each paginated element, e.g.: a head of a player.
Use paginationState
to create a new Pagination state.
private final State<Pagination> paginationState = paginationState(sourceProvider, itemFactory);
In the example below, our data to be paginated is a list of player names: Notch, Natan and Michael.
The list of players name is provided by the sourceProvider
parameter and for each name we will display a head provided by the itemFactory
parameter.
private final State<Pagination> paginationState = paginationState(
Arrays.asList("Notch", "Natan", "Michael"),
(builder, name) -> builder.withItem(new ItemStack(...))
);
The second parameter, itemFactory
, is a BiConsumer<T, V>
being T what you will use to define build your item, it has a set of functions like onClick
, withItem
, among others. We learned about it in the Basic Usage. V is the type of the element that's being paginated, in our case it's String
that's the player name.
private final State<Pagination> paginationState = paginationState(
Arrays.asList("Notch", "Natan", "Michael"),
(builder, name) -> builder.withItem(createHead(name))
);
private ItemStack createHead(String name) { ... }
Layouts are a feature that we provide to your to organize the items based on a set of characters and they can work together with pagination items! Take a look on how the code of the previous topic behavis with and without a layout.
No layout provided (default behavior)
@Override
public void onInit(ViewConfigBuilder config) {
config.size(3);
}
With user provided layout in the configuration
Respects the position specified by "O" character in the layout.
@Override
public void onInit(ViewConfigBuilder config) {
config.layout(
" ",
" OOOOO ",
" "
);
}
Layouts are not used only in pagination so learn more about layouts on Wiki to do not get thing messy elsewhere as well!
By default the paging state allows providing multiple types of data source, a static data source can be defined for paging externally provided data that does not need a context, e.g. the server's warps.
final class WarpsListView extends View {
private final State<Pagination> paginationState;
private WarpsListView(WarpsManager warpsManager) {
this.paginationState = paginationState(
warpsManager.getWarps(),
(itemBuilder, warp) -> itemBuilder.withItem(...)
);
}
}
Now when more context data is needed like for example: the userid, name, or anything else from the context, we start working with a per-player dynamic data source (it's per context but we'll use the term "per-player" to make it easier to understand).
The paging state provides an overload containing Function<IFContext, T>
which can be used as a way to assign the paging data source using the current context as a parameter.
In the example below we will create an inventory whose pagination source will be the player's inventory items.
private final State<Pagination> paginationState = paginationState(
(context) -> Arrays.asList(context.getPlayer().getInventory().getContents()),
(itemBuilder, value) -> ...
);
Asynchronous Pagination is experimental and is not subject to the general compatibility guarantees such functionality may be changed or may be removed completely in any further release, its behavior may be inconsistent and is subject to change.
Asynchronous pagination allows the developer to use a CompletableFuture
as data source. It works like dynamic pagination but it waits for the future to complete each time the page switches.
Use asyncPaginationState
/buildAsyncPaginationState
to create asynchronous pagination.
private final State<Pagination> paginationState = paginationState(
(context) -> fetchClansFromDatabase(),
(itemBuilder, value) -> ...
);
private CompletableFuture<List<Clan>> fetchClansFromDatabase() {
CompletableFuture<List<Clan>> task = /* ... some hard work goin on here ... */
...
return task;
}
There is a property called isLoading
that changes every time the task to load the pagination data is begins or completes load, for you to know if the data is being loaded or not.
You can display an item like "Data is being loaded..." based on this condition. In the example below we show a clock while the data is loading.
private final State<Pagination> paginationState = paginationState(
(context) -> /* CompletableFuture */,
(itemBuilder, value) -> ...
);
@Override
public void onFirstRender(RenderContext render) {
final Pagination pagination = paginationState.get(render);
render.lastSlot(new ItemStack(Material.CLOCK))
.watch(paginationState)
.displayIf(pagination::isLoading);
}
Pagination exposes a specific type of state value called Pagination
which is where the pagination data of a context is stored.
It contains several functions such as the number and index of the current page, next and previous pages, pagination data, navigation, etc. Dive into this to discover the possibilities of what to do with pagination for a context.
State<Pagination> pagination = paginationState.get(context);
Navigation is no secret and there are some very explicitly named functions for it.
-
back()
Backs to the previous page if available; -
advance()
Advances to the next page if any; -
switchTo(n)
Jumps to a specific page index.
In the example below we have "ARROW" for the "back to previous page" in the first slot and "forward to next page" in the last slot items.
@Override
public void onFirstRender(RenderContext render) {
Pagination pagination = paginationState.get(render);
// To back
render.firstSlot(new ItemStack(Material.ARROW))
.watch(paginationState)
.onClick(pagination::back);
// To advance
render.lastSlot(new ItemStack(Material.ARROW))
.watch(paginationState)
.onClick(pagination::advance);
}
"What if I don't want to display the navigation items if there are no pages to navigate?"
For this you can use displayIf(BooleanSupplier)
which was learned in the Basic Usage on Wiki.
@Override
public void onFirstRender(RenderContext render) {
Pagination pagination = paginationState.get(render);
// To back
render.firstSlot(new ItemStack(Material.ARROW))
.watch(paginationState)
.displayIf(pagination::canBack)
.onClick(pagination::back);
// To advance
render.lastSlot(new ItemStack(Material.ARROW))
.watch(paginationState)
.displayIf(pagination::canAdvance)
.onClick(pagination::advance);
}
Handling of page switching is something little used so it is not accessible directly in the quick function paginationState
, it will be necessary to use the advanced pagination builder for that buildPaginationState
.
private final State<Pagination> paginationState = buildPaginationState(...)
.onPageSwitch((context, pagination) -> {
// ... do something on page switch ...
})
.build();
As stated in the Introduction, pagination is a component and as it is possible to have multiple components in the view you can have multiple paginations as well. You just need to define the target layout character for each pagination.
In the example of this topic, we will render two paginations side by side, the one on the left side as "L" and the one on the right side as "R".
private final State<Pagination> leftPaginationState = buildPaginationState(...)
.layoutTarget('L')
.itemFactory((item, value) -> item.withItem(new ItemStack(Material.GOLD_INGOT)))
.build();
private final State<Pagination> rightPaginationState = buildPaginationState(...)
.layoutTarget('R')
.itemFactory((item, value) -> item.withItem(new ItemStack(Material.IRON_INGOT)))
.build();
And then just create a layout that contains both "L" and "R" characters
@Override
public void onInit(ViewConfigBuilder config) {
config.layout(
" ",
" LLL RRR ",
" LLL RRR ",
" LLL RRR ",
" "
);
}
Navigation is done in the same way as normal pagination, add an item to it and navigate as usual.
@Override
public void onFirstRender(RenderContext render) {
Pagination leftPagination = leftPaginationState.get(render);
Pagination rightPagination = rightPaginationState.get(render);
// Manipulate "L" pagination navigation
render.slot(...).watch(leftPagination).onClick(leftPagination::back)
render.slot(...).watch(leftPagination).onClick(leftPagination::advance)
// Manipulate "R" pagination navigation
render.slot(...).watch(rightPaginationState).onClick(rightPagination::back)
render.slot(...).watch(rightPaginationState).onClick(rightPagination::advance)
}