Skip to main content

Paginate Button

Learn how to create buttons that display paginated lists of items using the PaginateButton class.

Overview

PaginateButton is an abstract class that simplifies the creation of buttons displaying paginated content. It automatically handles:

  • Slot distribution across multiple slots
  • Page calculation based on available slots
  • Navigation between pages with PREVIOUS/NEXT buttons

Basic Structure

import fr.maxlego08.menu.api.button.PaginateButton;
import fr.maxlego08.menu.api.engine.InventoryEngine;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;

public class MyPaginatedButton extends PaginateButton {

private final MyPlugin plugin;

public MyPaginatedButton(Plugin plugin) {
this.plugin = (MyPlugin) plugin;
}

@Override
public void onRender(Player player, InventoryEngine inventoryEngine) {
// Get your list of items
List<MyItem> items = getItems(player);

// Use the paginate helper method
paginate(items, inventoryEngine, (slot, item) -> {
// Create the ItemStack for display
ItemStack itemStack = createDisplayItem(item);

// Add item to the inventory with click handler
inventoryEngine.addItem(slot, itemStack)
.setClick(event -> handleClick(player, item, event));
});
}

@Override
public int getPaginationSize(Player player) {
// Return the total number of items to paginate
return getItems(player).size();
}

private List<MyItem> getItems(Player player) {
return plugin.getItemManager().getItems(player);
}

private ItemStack createDisplayItem(MyItem item) {
// Create and return the display ItemStack
return item.toItemStack();
}

private void handleClick(Player player, MyItem item, InventoryClickEvent event) {
// Handle the click event
player.sendMessage("You clicked on: " + item.getName());
}
}

Key Methods

onRender(Player, InventoryEngine)

Called when the inventory is rendered. Use this to populate items.

@Override
public void onRender(Player player, InventoryEngine inventoryEngine) {
List<MyItem> items = getMyItems(player);

paginate(items, inventoryEngine, (slot, item) -> {
ItemStack display = item.toItemStack();
inventoryEngine.addItem(slot, display)
.setClick(event -> processClick(player, item, event));
});
}

getPaginationSize(Player)

Returns the total number of items for pagination calculations.

@Override
public int getPaginationSize(Player player) {
return getMyItems(player).size();
}

paginate(List, InventoryEngine, BiConsumer)

Helper method that distributes items across available slots for the current page.

paginate(items, inventoryEngine, (slot, item) -> {
// slot: the inventory slot to use
// item: the current item from your list
});

getSlots()

Returns the list of slots configured for this button (from YAML configuration).

List<Integer> availableSlots = getSlots();

Registering the Button

Use NoneLoader to register a paginate button:

@Override
public void onEnable() {
MenuPlugin menuPlugin = (MenuPlugin) Bukkit.getPluginManager().getPlugin("zMenu");

if (menuPlugin != null) {
ButtonManager buttonManager = menuPlugin.getButtonManager();

// Register with NoneLoader for buttons with only a Plugin constructor
buttonManager.register(new NoneLoader(this, MyPaginatedButton.class, "MY_PAGINATED_BUTTON"));
}
}

YAML Configuration

name: "&6My Paginated Menu (%page%/%max-page%)"
size: 54

items:
myItems:
type: MY_PAGINATED_BUTTON
slots:
- 10-16
- 19-25
- 28-34
- 37-43
# Optional: Display when no items
else:
slots:
- 22
item:
material: BARRIER
name: "&cNo items found"
lore:
- "&7There are no items to display."

# Navigation buttons
previous:
type: PREVIOUS
is-permanent: true
slot: 48
item:
material: ARROW
name: "&ePrevious Page"
lore:
- "&7Go to page %page-1%"

next:
type: NEXT
is-permanent: true
slot: 50
item:
material: ARROW
name: "&eNext Page"
lore:
- "&7Go to page %page+1%"

Complete Example: Shop Items Button

ShopItemsButton.java

package com.example.shop.buttons;

import fr.maxlego08.menu.api.button.PaginateButton;
import fr.maxlego08.menu.api.engine.InventoryEngine;
import fr.maxlego08.menu.api.utils.Placeholders;
import com.example.shop.ShopPlugin;
import com.example.shop.ShopItem;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.Plugin;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class ShopItemsButton extends PaginateButton {

private final ShopPlugin plugin;

public ShopItemsButton(Plugin plugin) {
this.plugin = (ShopPlugin) plugin;
}

@Override
public void onRender(Player player, InventoryEngine inventoryEngine) {
List<ShopItem> items = plugin.getShopManager().getAvailableItems(player);

paginate(items, inventoryEngine, (slot, shopItem) -> {
ItemStack display = createShopDisplay(player, shopItem);
inventoryEngine.addItem(slot, display)
.setClick(createClickHandler(player, inventoryEngine, shopItem));
});
}

@Override
public int getPaginationSize(Player player) {
return plugin.getShopManager().getAvailableItems(player).size();
}

private ItemStack createShopDisplay(Player player, ShopItem shopItem) {
ItemStack item = shopItem.getItem().clone();
ItemMeta meta = item.getItemMeta();

if (meta != null) {
// Add price to lore
List<String> lore = meta.hasLore() ? new ArrayList<>(meta.getLore()) : new ArrayList<>();
lore.add("");
lore.add("§7Price: §e" + shopItem.getFormattedPrice());
lore.add("");
lore.add("§eLeft-click §7to buy x1");
lore.add("§eRight-click §7to buy x64");
lore.add("§eShift+click §7to sell");
meta.setLore(lore);
item.setItemMeta(meta);
}

return item;
}

private Consumer<InventoryClickEvent> createClickHandler(
Player player,
InventoryEngine inventoryEngine,
ShopItem shopItem) {

return event -> {
ClickType click = event.getClick();

if (click == ClickType.LEFT) {
// Buy 1
plugin.getShopManager().buyItem(player, shopItem, 1);
} else if (click == ClickType.RIGHT) {
// Buy 64
plugin.getShopManager().buyItem(player, shopItem, 64);
} else if (click.isShiftClick()) {
// Sell
plugin.getShopManager().sellItem(player, shopItem);
}

// Refresh inventory to update displays
inventoryEngine.updateInventory();
};
}
}

Complete Example: Player List Button

OnlinePlayersButton.java

package com.example.admin.buttons;

import fr.maxlego08.menu.api.button.PaginateButton;
import fr.maxlego08.menu.api.engine.InventoryEngine;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.plugin.Plugin;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class OnlinePlayersButton extends PaginateButton {

private final Plugin plugin;

public OnlinePlayersButton(Plugin plugin) {
this.plugin = plugin;
}

@Override
public void onRender(Player player, InventoryEngine inventoryEngine) {
List<Player> onlinePlayers = new ArrayList<>(Bukkit.getOnlinePlayers());

// Remove the viewer from the list
onlinePlayers.remove(player);

paginate(onlinePlayers, inventoryEngine, (slot, targetPlayer) -> {
ItemStack head = createPlayerHead(targetPlayer);
inventoryEngine.addItem(slot, head)
.setClick(createClickHandler(player, targetPlayer));
});
}

@Override
public int getPaginationSize(Player player) {
// Subtract 1 because we remove the viewing player
return Math.max(0, Bukkit.getOnlinePlayers().size() - 1);
}

private ItemStack createPlayerHead(Player targetPlayer) {
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) head.getItemMeta();

if (meta != null) {
meta.setOwningPlayer(targetPlayer);
meta.setDisplayName("§e" + targetPlayer.getName());
meta.setLore(Arrays.asList(
"§7World: §f" + targetPlayer.getWorld().getName(),
"§7Health: §c" + (int) targetPlayer.getHealth() + "§7/§c20",
"",
"§aLeft-click §7to teleport",
"§cRight-click §7to kick"
));
head.setItemMeta(meta);
}

return head;
}

private Consumer<InventoryClickEvent> createClickHandler(Player admin, Player target) {
return event -> {
if (event.isLeftClick()) {
admin.closeInventory();
admin.teleport(target.getLocation());
admin.sendMessage("§aTeleported to " + target.getName());
} else if (event.isRightClick()) {
if (admin.hasPermission("admin.kick")) {
target.kickPlayer("Kicked by " + admin.getName());
admin.sendMessage("§cKicked " + target.getName());
}
}
};
}
}

Advanced: Real-time Updates

Update pagination items in real-time when data changes:

public class LiveItemsButton extends PaginateButton {

private final MyPlugin plugin;

public LiveItemsButton(Plugin plugin) {
this.plugin = (MyPlugin) plugin;
}

@Override
public void onRender(Player player, InventoryEngine inventoryEngine) {
List<MyItem> items = plugin.getItemManager().getItems(player);

paginate(items, inventoryEngine, (slot, item) -> {
ItemStack display = item.toItemStack();
inventoryEngine.addItem(slot, display)
.setClick(createClick(player, inventoryEngine, slot, item, display));
});
}

@Override
public int getPaginationSize(Player player) {
return plugin.getItemManager().getItems(player).size();
}

private Consumer<InventoryClickEvent> createClick(
Player player,
InventoryEngine inventoryEngine,
int slot,
MyItem item,
ItemStack itemStack) {

return event -> {
// Process the click
boolean success = processItemClick(player, item, event);

if (success) {
// Update this specific slot
updateSlot(player, inventoryEngine, slot, item);
}
};
}

/**
* Update a specific slot in the inventory without full refresh
*/
private void updateSlot(Player player, InventoryEngine inventoryEngine, int slot, MyItem item) {
ItemStack newDisplay = item.toItemStack();
inventoryEngine.getSpigotInventory().setItem(slot, newDisplay);
}

/**
* Called from external events to update all open inventories
*/
public void updateAllInventories(MyItem changedItem, boolean wasAdded) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
var holder = onlinePlayer.getOpenInventory().getTopInventory().getHolder();

if (holder instanceof InventoryEngine inventoryEngine) {
var buttons = inventoryEngine.getMenuInventory()
.getButtons(LiveItemsButton.class);

if (!buttons.isEmpty()) {
// Trigger a refresh
inventoryEngine.updateInventory();
}
}
}
}
}

Handling Empty States

Display a fallback item when no items exist:

items:
myItems:
type: MY_PAGINATED_BUTTON
slots:
- 10-16
- 19-25
else:
slots:
- 13 # Center slot
item:
material: STRUCTURE_VOID
name: "&c&lNo Items"
lore:
- "&7You don't have any items yet."
- ""
- "&eClick here to get started!"
actions:
- type: message
messages:
- "&aVisit the shop to purchase items!"

Best Practices

  1. Return accurate size - getPaginationSize() must return the exact count
  2. Handle empty lists - Use the else configuration for empty states
  3. Use click consumers - Set click handlers via setClick() for proper event handling
  4. Cache expensive calculations - Don't recalculate lists in every render
  5. Update efficiently - Use updateInventory() instead of reopening
  6. Test pagination - Verify navigation works with various item counts

Next Steps

Copyright © 2026 GroupeZ|Build #loading...|-