Pagination - DevNatan/inventory-framework GitHub Wiki

Pagination is a state-component that allows you to display a large amount of data in an view by separating it into pages.

Creating Pagination

To create a pagination you have two essential parameters:

  1. sourceProvider to define the pagination data e.g.: a list of names.
  2. elementFactory to create the items that will be displayed to the player in the view for each paginated element e.g.: a head of a player.

Use paginationState to create a new static pagination.
(There are other types of pagination that will be explained later)

final State<Pagination> paginationState = paginationState(sourceProvider, elementFactory);

In the example below, our pagination data 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 player head item is provided by the elementFactory parameter.

Preview
final State<Pagination> paginationState = paginationState(
    Arrays.asList("Notch", "Natan", "Michael"),
    [...]
);

In the elementFactory parameter you have access to everything you has: the current context to get access like ctx.getPlayer(); a builder to construct the item instance; the current pagination index of the element being paginated; the value which is the element being paginated, in the case below it is the player's name.

Builder allows you to use everything we learned earlier in Basic Usage page.

Preview
final State<Pagination> paginationState = paginationState(
    Arrays.asList("Notch", "Natan", "Michael"),
    (ctx, builder, index, value) -> [...]
);

Static, Lazy and Computed Pagination

Pagination is a state-component that has different types of nature and way of working: statically, lazy or computed. As well as types of state (explained in State Management) that work in exactly the same way.

paginationState

Pagination is static in nature, the data is provided during initialization and does not have access to the context during its creation, that is, it is not possible to access the context to conditionally create the data to be paginated.

public final <T> State<Pagination> paginationState(
    List<? super T>! sourceProvider,
    PaginationValueConsumer<[...], T>! elementFactory
);
Notes
  • Data is loaded when the context is created, before rendering phase
  • update() has no effect

lazyPaginationState

<T> State<Pagination> lazyPaginationState(
    Function<[...], List<? super T>!>! sourceProvider,
    PaginationValueConsumer<[...], T>! elementFactory
);
Notes
  • Data is loaded when it is needed. If the pagination component is hidden during the context rendering phase, the data is not loaded, only when the component is shown
  • update() has no effect

computedPaginationState

<T> State<Pagination> computedPaginationState(
    Function<[...], List<? super T>!>! sourceProvider,
    PaginationValueConsumer<[...], T>! elementFactory
);
Notes
  • Data is loaded when the component is rendered and re-loaded when the component is updated
  • update() updates the component thus reloading pagination data

Pagination state to State

Asynchronous Pagination

Asynchronous Pagination is experimental and is not subject to the general compatibility guarantees given for the standard library: the behavior of such API may be changed or the API may be removed completely in any further release.

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;
}

Watching for loading state of pagination

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))
        .displayIf(pagination::isLoading)
        .updateOnStateChange(paginationState);
}

Pagination + Layouts

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)

Preview

@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.

Preview

@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!

Diving into Pagination

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

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))
        .updateOnStateChange(paginationState)
        .onClick(pagination::back);

    // To advance
    render.lastSlot(new ItemStack(Material.ARROW))
        .updateOnStateChange(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))
        .updateOnStateChange(paginationState)
        .displayIf(pagination::canBack)
        .onClick(pagination::back);

    // To advance
    render.lastSlot(new ItemStack(Material.ARROW))
       .updateOnStateChange(paginationState)
       .displayIf(pagination::canAdvance)
       .onClick(pagination::advance);
}

Handling Page Switch

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();

Multiple paginations

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

Preview
@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(...).updateOnStateChange(leftPaginationState).onClick(leftPagination::back)
    render.slot(...).updateOnStateChange(leftPaginationState).onClick(leftPagination::advance)

    // Manipulate "R" pagination navigation
    render.slot(...).updateOnStateChange(rightPaginationState).onClick(rightPagination::back)
    render.slot(...).updateOnStateChange(rightPaginationState).onClick(rightPagination::advance)
}
⚠️ **GitHub.com Fallback** ⚠️