Simple Paging in DukeScript

I’ve recently been asked about techniques for improving List performance in HTML-based views. One of the cheapest tricks to do that is paging. Most modern List implementations do apply it in one way or the another. Often it is hidden behind scrolling. Here’s a simple way to do classical paging with buttons to navigate between pages using DukeScript and Knockout. First let’s create a simple model with 1000 Strings:

@Model(className = "Data", targetId = "", properties = {
    @Property(name = "list", type = String.class, array = true),
    @Property(name = "currentPageIndex", type = int.class),
    @Property(name = "pageLength", type = int.class)
})
final class DataModel {

    private static Data ui;

    @Function
    public static void gotoPage(Data model, int data){
        model.setCurrentPageIndex(data);
    }

    static void onPageLoad() throws Exception {
        ui = new Data();
        ui.setPageLength(20);
        ui.setCurrentPageIndex(0);
        Models.toRaw(ui);
        KOLazyLoad.init();
        for (int i = 0; i < 1000; i++) {
            ui.getList().add("Item " + i);
        }
        ui.applyBindings();
    }
}

We have a method for switching to a certain page if somebody clicks a link. The trick to support dynamic paging is to add two ComputedProperties:

@ComputedProperty
    public static int pageCount(int pageLength, List<String> list) {
        return list.size() / pageLength;
    }

    @ComputedProperty
    public static List<String> currentPage(int currentPageIndex, int pageLength, List<String> list) {
        int start = (currentPageIndex * pageLength) + 1;
        int end = start + pageLength;
        if (list.size() > start) {
            return list.subList(start, list.size() >= end ? end : list.size());
        }
        return Collections.EMPTY_LIST;
    }

The first one dynamically updates the page count when elements are removed or added. The second one gives you a sublist corresponding to the currently selected page. In HTML you can use it like this:

<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <ul data-bind="foreach: currentPage">
            <li><div data-bind="text: $data"></div></li>
        </ul>
        <!-- ko foreach: new Array(pageCount())  -->
        <a href="#" data-bind="text: ''+$index(), click: $parent.gotoPage.bind($parent,$index())"></a>
        <!-- /ko --></body>
</html>

The tricky part here is to create the links. Knockout only has a foreach binding by default. The solution to this is to simply create an array with pageCount number of entries. Now we can iterate over this array. In order to pass the index to our function we use the click binding. And we extend it by binding the index to the call. This way we’ll receive the index in our gotoPage method.

That’s it, you can now run the code and you will see nice and simple dynamic paging. In order to prove that it really is dynamic you can add a little Timer that adds new entries:

import java.util.Timer;
import java.util.TimerTask;
import net.java.html.BrwsrCtx;

/**
 *
 * @author antonepple
 */
public final class Periodicaly extends TimerTask {
    private final BrwsrCtx ctx;
    private final Data data;
    private int idx = 0;
    private Periodicaly(BrwsrCtx ctx, Data data) {
        // remember the browser context and use it later
        this.ctx = ctx;
        this.data = data;
        
    }

    public void run() {
        // arrives on wrong thread, needs to be re-scheduled
        ctx.execute(new Runnable() {
            public void run() {
                data.getList().add(0,"Item "+(idx++));
            }
        });
    }

    public static void onPageLoad(Data data) throws Exception {
        // the context at the time of page initialization
        BrwsrCtx initialCtx = BrwsrCtx.findDefault(Periodicaly.class);
        // the task that is associated with context 
        Periodicaly task = new Periodicaly(initialCtx, data);
        // creates a timer
        Timer t = new Timer("Add items");
        // run the task ever 1000ms
        t.scheduleAtFixedRate(task, 0, 1000);
    }
}

Start the timer after you’ve initialized the view model, and you’ll see how new links appear as the new items roll in, and how the page automatically updates.

static void onPageLoad() throws Exception {
        ui = new Data();
        ui.setPageLength(20);
        ui.setCurrentPageIndex(0);
        Models.toRaw(ui);
        KOLazyLoad.init();
        for (int i = 0; i < 1000; i++) {
            ui.getList().add("Item " + i);
        }
        ui.applyBindings();
        Periodicaly.onPageLoad(ui);
    }

Ever thought paging can be so simple?