Management Screen Pagination

Introduction

Pagination for screens is the process of dynamically breaking the list of elements the screen is designed to load and displaying it in successive pages separated by a page number. This can be a very useful feature to build into a management screen when the number of elements in a given list you are loading into the screen is large, outgrows a single manageable page length, and detracts from the screen's usability.

This tutorial outlines a general approach you can use to address this sort of use case for your own management screens.

Note: This tutorial assumes some knowledge of dynamic forms and we strongly recommend that you read the Dynamic Forms API as well as take a look at the Dynamic Forms Tutorial before starting. As well as the other tutorials for how to build game management screens in this section, you can also review the tutorials in the Live Game Operations section.

Important! If you're working on a new game that was created after the Game Data Service was launched in January 2018, you won't be able to create new Mongo Runtime collections and this tutorial will not work.

Imposing Pagination - The Example

The simple example we'll use to illustrate how to build automatic pagination into a management screen requires the following under Manage>Admin Screens:

Secondly, we follow the recommended design principle that the Screen acts as a placeholder for Snippets, and the Snippets then contain the logic that controls how the screen will behave - in our example, a single Snippet:

Starting Point - A Page that Displays All Items

To begin with, we'll open the operator_list Snippet to configure it in the editor and we'll start with a page that is currently displaying all items.

As you can see from the code below, the getOperators function is intended to return a cursor of "operators" in this example based on a given query. This Operator Collection Query imposes an arbitrary limit of 20 on the loading of data items into the screen. The particulars here are not important, this is only to provide a starting point for our example of how to build a pagination feature into a screen that is displaying all of its items.

Code Samples? At the top of each code example we'll indicate in which part of the Admin Screens>Snippet editor the code is written. We'll also include the Response.JSON for each step to help you follow the steps and carry out any debugging.

// JavaScript
Spark.setScriptData("form", SnippetProcessor(Spark.getData().scriptData));
function SnippetProcessor(data) {

    var form = {};
    form.operators = getOperators({});
    return form;

    function getOperators(query) {
        // Operator Collection Query, limited to 20 documents.
        return Spark.runtimeCollection("operator").find(query, {"name": -1}).limit(20);
    }
}

// Response.JSON
{
    "form": {
        "operators": [
            {
                "_id": {
                    "$oid": "5a28077646e0fb0001a2119e"
                },
                "name": "operator 1"
            },
            {
                "_id": {
                    "$oid": "5a28077646e0fb0001a2119e"
                },
                "name": "operator 2"
            },
            ...
        ]
    }
}

Setting Initial Data

So far, the screen design won't do, because someone opening the screen will only see the first 20 items, which was the arbitrary limit we imposed on data loading. Our aim with the finished screen design is to present the loaded data items - say 20 in total - as a series of 4 separate pages, which the user can click through in turn and view 5 data items on each successive page.

Next therefore, we'll need a starting point for our pagination mechanism to start to break the total available data into separate pages of defined and equal length. We set this up by inspecting the incoming data using the setDefaultData function and checking if pageNumber exists. If pageNumber does exist we can leave it alone. If pageNumber does not exist, we'll need to initialize this variable.

As you can see from the code below, we'll need to modify our getOperators function to accept a second parameter, namely pageNumber. This parameter represents the page number we are currently looking for:

// Javascript
Spark.setScriptData("form", SnippetProcessor(Spark.getData().scriptData));
function SnippetProcessor(data) {

    var form = {};
    data = setDefaultData(data);
    form.operators = getOperators({}, data.pageNumber);
    return form;

    function setDefaultData(data) {
        if (data.pageNumber === undefined || data.pageNumber === "") data.pageNumber = 0;
        return data;
    }

    function getOperators(query, pageNumber) {

        var docPerPage = 5; // Defines the amount of documents to display per page

        var queryResult = Spark.runtimeCollection("operator")
            .find(query, {"name": -1})
            .skip(pageNumber * docPerPage)
            .limit(docPerPage);

        return {
            "documents": queryResult,
            "pageCount": Math.round(Number(queryResult.count()/docPerPage))
        };
    }
}
// Response.JSON
{
    "form": {
        "operators": {
            "documents": [
                {
                    "_id": {
                        "$oid": "5a28077646e0fb0001a2119e"
                    },
                    "name": "operator 1"
                },
                {
                    "_id": {
                        "$oid": "5a28077646e0fb0001a2119e"
                    },
                    "name": "operator 2"
                },
                ...
            ],
            "pageCount": 4
        }
    }
}

Pagination State and HTML

Now that we've laid out the basics of our pagination system, we can begin to look at the HTML display. This will require us to define a getPagination function, which we can use to help work out the state of our pagination. You'll also notice that instead of nesting the operator documents as we did in the previous step, we can now separate these out as shown by the following:

// Javascript
Spark.setScriptData("form", SnippetProcessor(Spark.getData().scriptData));
function SnippetProcessor(data) {

    data = setDefaultData(data);
    var r = getOperators({}, data.pageNumber);

    var form = {};
    form.operators = r.documents;
    form.pagination = getPagination(data.pageNumber, r.pageCount);
    return form;

    function setDefaultData(data) {
        if (data.pageNumber === undefined || data.pageNumber === "") data.pageNumber = 0;
        return data;
    }

    function getPagination(currentPageNumber, pageCount) {
        var doc = {};
        currentPageNumber = parseInt(currentPageNumber);

        if (currentPageNumber < pageCount - 1 && pageCount > 1) {
            doc.nextPage = currentPageNumber + 1;
            doc.hasNextPage = true;
        }
        if (currentPageNumber > 0) {
            doc.previousPage = currentPageNumber - 1;
            doc.hasPreviousPage = true;
        }

        if (pageCount === 0) pageCount = 1;
        doc.pageCount = pageCount;
        doc.currentPage = currentPageNumber;
        doc.displayCurrentPageNumber = currentPageNumber + 1;
        return doc;
    }

    function getOperators(query, pageNumber) {

        var docPerPage = 5; // Defines the amount of documents to display per page

        var queryResult = Spark.runtimeCollection("operator")
            .find(query, {"name": -1})
            .skip(pageNumber * docPerPage)
            .limit(docPerPage);

        return {
            "documents": queryResult,
            "pageCount": Math.round(Number(queryResult.count()/docPerPage))
        };
    }
}
// Response.JSON
{
    "form": {
        "operators": [
            {
                "_id": {
                    "$oid": "5a28077646e0fb0001a2119e"
                },
                "name": "operator 1"
            },
            {
                "_id": {
                    "$oid": "5a28077646e0fb0001a2119e"
                },
                "name": "operator 2"
            },
            ...
        ],
        "pagination": {
            "nextPage": 1,
            "hasNextPage": true,
            "pageCount": 4,
            "currentPage": 0,
            "displayCurrentPageNumber": 1
        }
    }
}
<!-- Handlebars -->
<style>
    th {background-color: #457ad1;}
    .tab-header {height:42px; line-height:42px;}
    .tab-header p {font-size: 15px; line-height: 42px}
</style>

<div class="black-box">

    <!-- Pagination -->
    <div class="tab-header">
        <p>
            {{#if form.pagination.hasPreviousPage}}
                <gs-link snippet="operator_list?pageNumber={{form.pagination.previousPage}}" target="operatorManager">
                    <span style="font-size:24px">&#8666;</span>
                </gs-link>
            {{/if}}

            {{form.pagination.displayCurrentPageNumber}} of {{form.pagination.pageCount}}

            {{#if form.pagination.hasNextPage}}
                <gs-link snippet="operator_list?pageNumber={{form.pagination.nextPage}}" target="operatorManager">
                    <span style="font-size:24px">&#8667;</span>
                </gs-link>
            {{/if}}
        </p>
    </div>

    <!-- List Operators -->
    <div class="tab-content" style="padding:20px">
        <table class="table js-table-sorter" border="5" cellpadding="10">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                </tr>
            </thead>
            <tbody>
                {{#each form.operators}}
                    <tr>
                        <td>{{_id.$oid}}</td>
                        <td>{{name}}</td>
                    </tr>
                {{/each}}
            </tbody>
        </table>
    </div>
</div>

Here's our finished screen, which users can click to page through the loaded data items:

Summary

In this tutorial, we looked at a very simple example of how to implement a pagination system on a collection of documents. You can use this approach throughout the Management Screen section of the Portal for your own screens, both to provide better performance and to enhance user experience. You can easily expand this example to accommodate different kinds of requirements, such as retaining a particular query search and paginating the result set of the query. So, use this example as a template to customize and adapt to your own game, live ops, or back office needs. Let us know on the forums if you need any help adapting this example to fit your own requirements.

Did this page help you? Please enter your feedback below. For questions about using this part of the platform, please contact support here