Util ListPaginator - An advanced chat paginating utility

Discussion in 'Resources' started by Reflxction, Jul 3, 2019.

  1. Offline


    Hello! I'm sure we all have been attracted to all those nice help menus, or interactive friend list messages, and so on. Such things can be tedious to make and implement, aside from the not-fun math behind it. In hopes of making these beautiful things easier to implement and integrate, I created this utility to simplify creation of advanced chat pages.

    • Easy and simple to use
    • Filled with clarifying documentations
    • Ability to add custom actions when the list is empty or when a requested page is invalid easily
    • Does not use a fixed messaging method, instead, you can add a custom method on how to send messages (e.g instead of standard #sendMessage(message), you could make it send clickable text messages, using another utility/framework).
    • Ability to add a header and a footer to pages, where it is also possible to specify how headers and footers are sent. (Similar to the point above)
    • Ability to add a medium which converts list elements to messages. This will allow directly inputting lists like Bukkit.getOnlinePlayers() without needing to copy the list to be player names and blablabla.
    A live screenshot of this being used in a complex way:

    Source: A friends plugin I've been working on

    In order to allow as much convenience in creating pages as possible, there are a lot of abstract interfaces which you can add your own implementation to them, such as how the list element is converted to a message, or how the header or footer is sent, etc.

    There are 4 total interfaces nested inside the ListPaginator class:

    1- MessagePlatform<M(essage)>:
    This represents the implementation of how the message is sent. An example implementation would be invoking CommandSender#sendMessage(), another implementation could be sending a clickable message, or an action bar, another would be a packet, etc. It can be anything

    For example, if we only need to send the CommandSender a string message using CommandSender#sendMessage(), implementation would be:
    MessagePlatform<String> platform = (sender, message) -> sender.sendMessage(message)
    (we can use a method reference, however for the sake of clarity it will be like that)
    ListPaginator will use the platform to send any message. The generic of MessagePlatform must match the second generic in ListPaginator, otherwise compilers will complain.

    2- MessageConverter<E(lement), M(essage)>:
    This will convert the element in the list to be a valid message. For example, if we want to convert a Player to a message where it would be"{playername} has a UUID of {uuid}", the implementation would be:
    MessageConverter<Player, String> converter = p -> p.getName() + " has a UUID of " + p.getUniqueId().toString();
    3- Header & Footer:
    These 2 interfaces are very similar to each other. They represent the implementation of how the header/footer is sent to the player. For example, if all we want is send the player "----------------" as a header, implementation would be:
    Header header = (sender, pageIndex, pageCount) -> sender.sendMessage("-------------");
    sender represents the command sender (for reference)
    pageIndex represents the currently opened page (for reference
    pageCount represents the total pages count

    Implementation of Footer is the same exact process of implementing Header.

    Yes, I know, it gets quite complicated, however, this all was made so you would refrain from changing the source code and messing your hands.

    4 and most importantly- ListPaginator<Element, Message>:
    This is the class that reconciles all the above interfaces with each other.
    To figure out what are the appropriate generics:
    1- The first generic should be the same as the generic your List holds
    2- The second generic should be how the message is sent. If only #sendMessage(), this can be String. Otherwise, it can be anything, although implementation would be required in MessagePlatform and MessageConverter.

    It can be created by invoking the following constructor:
    public ListPaginator(int linesPerPage, MessagePlatform<M> platform, MessageConverter<E, M> converter) 
    linesPerPage (int): Self-explanatory. This represents the lines each page should contain. This only represents the elements being listed, and does not contain the header or footer.
    platform (MessagePlatform<Message>): The implementation of how the message is sent. See above for more details on usage/implementation.
    converter (MessageConverter<Element, Message>: The element converter that converts list elements to messages. See above for more details on usage/implementation.

    Further customization of ListPaginator can be made from its builder-styled methods:
    setHeader(Header header): Sets the Header. See above for details on Header/Footer
    setFooter(Footer footer): Sets the Footer. See above for details on Header/Footer
    ifEmpty(Consumer<CommandSender> task): Adds a callback action when the specified list is empty. CommandSender can be used as a reference to the target
    ifPageIsInvalid(BiConsumer<CommandSender, Integer>): Adds a callback action when the requested page is invalid. CommandSender can be used as a reference to the target, and Integer refers to the invalid page number.

    And most importantly, ListPaginator#sendPage(List<E> list, CommandSender target, int pageNumber). This method sends the page to the player using all the implementations provided.

    Example of using ListPaginator to send the alphabet to a player, 7 alphabets per page:
         * The list of all alphabets
        private static final ImmutableList<String> ALPHABET = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z");
         * How the message is sent
        private static final MessagePlatform<String> PLATFORM = CommandSender::sendMessage;
         * How an alphabet from {@link #ALPHABET} is converted to a message (string in this case)
        private static final MessageConverter<String, String> MESSAGE_CONVERTER = letter -> "Small: " + letter + " | Capital: " + letter.toUpperCase();
         * Our paginator
        private static final ListPaginator<String, String> PAGINATOR = new ListPaginator<>(7, PLATFORM, MESSAGE_CONVERTER)
                .setHeader((sender, pageIndex, pageCount) -> sender.sendMessage(ChatColor.GREEN + "----Page (" + pageIndex + "/" + pageCount + ")----"))
                .setFooter((sender, pageIndex, pageCount) -> sender.sendMessage(ChatColor.GREEN + "----FOOTER----"))
                .ifEmpty((sender) -> sender.sendMessage("No alphabets to teach! " + ChatColor.RED + ":("))
                .ifPageIsInvalid((sender, page) -> sender.sendMessage(ChatColor.RED + "Invalid page: " + ChatColor.YELLOW + page));
        public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
            int pageIndex;
            if (args.length == 0) {
                pageIndex = 1;
            } else {
                pageIndex = Integer.parseInt(args[0]);
            PAGINATOR.sendPage(ALPHABET, sender, pageIndex);
            return true;
    Which, when executed, will display this:

    ListPaginator: https://gist.github.com/ReflxctionDev/a0e30936ad5f8b7d15f79881c427e1b0
    All other interfaces are nested classes.

    Tell me what you think :)
    Last edited: Jul 3, 2019
    zThana and MCMastery like this.
  2. Offline


    This is awesome, well done
    Reflxction likes this.

Share This Page