Wednesday, October 1, 2014

Infinite scrolling with JavaFX TableView

Today I want to show how to realise infinite scrolling with JavaFX TableView.

The idea behind infinite scrolling is to retrieve more data once the user has scrolled to the bottom of the currently loaded data.

To realise this, we need to get the vertical ScrollBar of the TableView like this:
    private ScrollBar getVerticalScrollbar(TableView<?> table) {
        ScrollBar result = null;
        for (Node n : table.lookupAll(".scroll-bar")) {
            if (n instanceof ScrollBar) {
                ScrollBar bar = (ScrollBar) n;
                if (bar.getOrientation().equals(Orientation.VERTICAL)) {
                    result = bar;
                }
            }
        }       
        return result;
    }


Note that this method can only be executed after the table view has been rendered.


With this method we can add a listener to the scrollbars value property:
        ScrollBar bar = getVerticalScrollbar(table);
        bar.valueProperty().addListener(this::scrolled);


The listener can now check whether we are at the bottom and add new data (Persons in our case) to the item list of the table view:
    void scrolled(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
        double value = newValue.doubleValue();
        System.out.println("Scrolled to " + value);
        ScrollBar bar = getVerticalScrollbar(table);
        if (value == bar.getMax()) {
            System.out.println("Adding new persons.");
            double targetValue = value * items.size();
            addPersons();
            bar.setValue(targetValue / items.size());
        }
    }

Note the call to bar.setValue() after data has been added. This is neccessary to keep the view at the elements viewed when reaching the bottom.

Here is the complete example:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;

import org.apache.commons.lang.RandomStringUtils;

public class TableViewInfiniteScrolling extends Application {
   
    ObservableList<Person> items = FXCollections.observableArrayList();
    TableView<Person> table = new TableView<Person>();

    public TableViewInfiniteScrolling() {
        addPersons();
    }

    private void addPersons() {
        for (int i = 0; i < 50; i++) {
            Person p = new Person();
            p.setFirstName(items.size() + " " + RandomStringUtils.random(5, "ABEGHILOUZ"));
            p.setLastName(RandomStringUtils.random(10, "QWRTOUPL"));
            items.add(p);
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        table.setItems(items);
        TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
        firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
        TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
        lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));       
        table.getColumns().setAll(firstNameCol, lastNameCol);       
       
        Scene scene = new Scene(table, 400, 400);
        primaryStage.setScene(scene);       
        primaryStage.show();
        ScrollBar bar = getVerticalScrollbar(table);
        bar.valueProperty().addListener(this::scrolled);
    }
   
    private ScrollBar getVerticalScrollbar(TableView<?> table) {
        ScrollBar result = null;
        for (Node n : table.lookupAll(".scroll-bar")) {
            if (n instanceof ScrollBar) {
                ScrollBar bar = (ScrollBar) n;
                if (bar.getOrientation().equals(Orientation.VERTICAL)) {
                    result = bar;
                }
            }
        }       
        return result;
    }
       
    void scrolled(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
        double value = newValue.doubleValue();
        System.out.println("Scrolled to " + value);
        ScrollBar bar = getVerticalScrollbar(table);
        if (value == bar.getMax()) {
            System.out.println("Adding new persons.");
            double targetValue = value * items.size();
            addPersons();
            bar.setValue(targetValue / items.size());
        }
    }
   
    public static void main(String[] args) {
        launch(args);
    }
   
    public class Person {
        private StringProperty firstName;
        public void setFirstName(String value) { firstNameProperty().set(value); }
        public String getFirstName() { return firstNameProperty().get(); }
        public StringProperty firstNameProperty() {
            if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
            return firstName;
        }
   
        private StringProperty lastName;
        public void setLastName(String value) { lastNameProperty().set(value); }
        public String getLastName() { return lastNameProperty().get(); }
        public StringProperty lastNameProperty() {
            if (lastName == null) lastName = new SimpleStringProperty(this, "lastName");
            return lastName;
        }
    }   
   
}

2 comments: