Skip to content

Commit

Permalink
Support for placeholder node in PagingListView. Various fixes and imp…
Browse files Browse the repository at this point in the history
…rovements.
  • Loading branch information
dlemmermann committed Dec 9, 2024
1 parent 02738c8 commit 58b1136
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public PagingControlsSettingsView(PagingControlBase pagingControls) {
setSpacing(10);

Label pageLabel = new Label();
pageLabel.textProperty().bind(Bindings.createStringBinding(() -> "Page Index: " + pagingControls.getPage(), pagingControls.pageProperty()));
pageLabel.textProperty().bind(Bindings.createStringBinding(() -> "Page index: " + pagingControls.getPage(), pagingControls.pageProperty()));

Label pageCountLabel = new Label();
pageCountLabel.textProperty().bind(Bindings.createStringBinding(() -> "Page count: " + pagingControls.getPageCount(), pagingControls.pageCountProperty()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package com.dlsc.gemsfx.demo;

import com.dlsc.gemsfx.PagingListView;
import com.dlsc.gemsfx.util.StageManager;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
Expand All @@ -20,11 +31,15 @@ public class PagingListViewApp extends Application {

private final BooleanProperty simulateDelayProperty = new SimpleBooleanProperty(false);

private final BooleanProperty simulateNoData = new SimpleBooleanProperty(false);

private final IntegerProperty count = new SimpleIntegerProperty(205);

@Override
public void start(Stage stage) {
PagingListView<String> pagingListView = new PagingListView<>();
pagingListView.setPrefWidth(400);
pagingListView.setTotalItemCount(205);
pagingListView.setPrefWidth(600);
pagingListView.totalItemCountProperty().bind(Bindings.createIntegerBinding(() -> simulateNoData.get() ? 0 : count.get(), simulateNoData, count));
pagingListView.setPageSize(10);
pagingListView.setLoader(loadRequest -> {
if (simulateDelayProperty.get()) {
Expand All @@ -47,6 +62,8 @@ public void start(Stage stage) {
return data;
});

simulateNoData.addListener(it -> pagingListView.reload());

Button scenicView = new Button("Scenic View");
scenicView.setOnAction(evt -> ScenicView.show(scenicView.getScene()));

Expand All @@ -56,7 +73,21 @@ public void start(Stage stage) {
CheckBox simulateDelay = new CheckBox("Simulate delay");
simulateDelay.selectedProperty().bindBidirectional(simulateDelayProperty);

HBox settingsBox = new HBox(10, fillBox, simulateDelay);
ComboBox<Side> location = new ComboBox<>();
location.getItems().addAll(Side.TOP, Side.BOTTOM);
location.valueProperty().bindBidirectional(pagingListView.pagingControlsLocationProperty());

Button clearSetData = new Button("Clear Set Data");
clearSetData.setOnAction(evt -> simulateNoData.set(!simulateNoData.get()));

Button reduceItemCount = new Button("Reduce Count");
reduceItemCount.setOnAction(evt -> count.set(count.get() - 1));

Button increaseItemCount = new Button("Increase Count");
increaseItemCount.setOnAction(evt -> count.set(count.get() + 1));

HBox settingsBox = new HBox(10, fillBox, simulateDelay, new Label("Location"), location, clearSetData, reduceItemCount, increaseItemCount);
settingsBox.setAlignment(Pos.CENTER_LEFT);

VBox box = new VBox(20, pagingListView, settingsBox, new PagingControlsSettingsView(pagingListView), scenicView);
box.setPadding(new Insets(20));
Expand All @@ -67,9 +98,12 @@ public void start(Stage stage) {

stage.setTitle("Paging List View");
stage.setScene(scene);
stage.sizeToScene();
stage.centerOnScreen();

StageManager.install(stage, "product.list.view");
stage.show();

Platform.runLater(stage::sizeToScene);
}

public static void main(String[] args) {
Expand Down
20 changes: 16 additions & 4 deletions gemsfx/src/main/java/com/dlsc/gemsfx/PagingControlBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import javafx.scene.layout.Region;
import javafx.util.Callback;

import java.text.MessageFormat;
import java.util.Objects;

public abstract class PagingControlBase extends Control {
Expand All @@ -30,14 +31,19 @@ public PagingControlBase() {
}

if (getPageCount() == 1) {
return "Showing all items";
int total = getTotalItemCount();
if (total == 1) {
return "Showing the only item.";
}

return MessageFormat.format("Showing all {0} items.", getTotalItemCount());
}

int startIndex = (view.getPage() * getPageSize()) + 1;
int endIndex = startIndex + getPageSize() - 1;

endIndex = Math.min(endIndex, getTotalItemCount());
return "Showing items " + startIndex + " to " + endIndex + " of " + getTotalItemCount();
return "Showing items " + startIndex + " to " + endIndex + " of " + getTotalItemCount() + ".";
});

pageCount.bind(Bindings.createIntegerBinding(() -> {
Expand All @@ -48,6 +54,12 @@ public PagingControlBase() {
return count;
}, totalItemCountProperty(), pageSizeProperty()));

pageCount.addListener(it -> {
if (getPageCount() <= getPage()) {
setPage(Math.max(0, getPageCount() - 1));
}
});

Region firstPageDivider = new Region();
firstPageDivider.getStyleClass().addAll("page-divider", "first-page-divider");
setFirstPageDivider(firstPageDivider);
Expand Down Expand Up @@ -262,7 +274,7 @@ public void lastPage() {
* @see #pageProperty()
*/
public void nextPage() {
setPage(Math.min(getPageCount() - 1, getPage() + 1));
setPage(Math.max(0, Math.min(getPageCount() - 1, getPage() + 1)));
}

/**
Expand All @@ -271,7 +283,7 @@ public void nextPage() {
* @see #pageProperty()
*/
public void previousPage() {
setPage(Math.max(0, getPage() - 1));
setPage(Math.min(getTotalItemCount() / getPageSize(), Math.max(0, getPage() - 1)));
}

private final IntegerProperty totalItemCount = new SimpleIntegerProperty(this, "totalItemCount");
Expand Down
63 changes: 55 additions & 8 deletions gemsfx/src/main/java/com/dlsc/gemsfx/PagingListView.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.geometry.Side;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Cell;
import javafx.scene.control.ListCell;
Expand Down Expand Up @@ -94,6 +96,14 @@ protected void updateItem(T item, boolean empty) {
pageProperty().addListener(weakUpdateListener);
cellFactoryProperty().addListener(weakUpdateListener);
fillLastPageProperty().addListener(weakUpdateListener);
totalItemCountProperty().addListener(weakUpdateListener);

pagingControlsLocation.addListener((it, oldLocation, newLocation) -> {
if (newLocation.equals(Side.LEFT) || newLocation.equals(Side.RIGHT)) {
setPagingControlsLocation(Side.BOTTOM);
throw new IllegalArgumentException("unsupported location for the paging controls: " + newLocation);
}
});
}

@Override
Expand All @@ -115,6 +125,45 @@ public final ListView<T> getListView() {
return listView;
}

private final ObjectProperty<Side> pagingControlsLocation = new SimpleObjectProperty<>(this, "pagingControlsLocation", Side.BOTTOM);

public final Side getPagingControlsLocation() {
return pagingControlsLocation.get();
}

/**
* Controls on which side the paging controls should be located. Currently only {@link Side#TOP} and
* {@link Side#BOTTOM} are supported.
*
* @return the location where the paging controls will be shown
*/
public final ObjectProperty<Side> pagingControlsLocationProperty() {
return pagingControlsLocation;
}

public final void setPagingControlsLocation(Side pagingControlsLocation) {
this.pagingControlsLocation.set(pagingControlsLocation);
}

private final BooleanProperty showPagingControls = new SimpleBooleanProperty(this, "showPagingControls", true);

public final boolean isShowPagingControls() {
return showPagingControls.get();
}

/**
* A flag used to control the visibility of the paging controls (page buttons, previous, next, etc...).
*
* @return a property that is true if the paging controls should be visible
*/
public final BooleanProperty showPagingControlsProperty() {
return showPagingControls;
}

public final void setShowPagingControls(boolean showPagingControls) {
this.showPagingControls.set(showPagingControls);
}

private final BooleanProperty fillLastPage = new SimpleBooleanProperty(this, "fillLastPage", false);

public final boolean isFillLastPage() {
Expand Down Expand Up @@ -245,27 +294,25 @@ public int getPageSize() {

private final ObjectProperty<Callback<LoadRequest, List<T>>> loader = new SimpleObjectProperty<>(this, "loader");

public Callback<LoadRequest, List<T>> getLoader() {
public final Callback<LoadRequest, List<T>> getLoader() {
return loader.get();
}

public ObjectProperty<Callback<LoadRequest, List<T>>> loaderProperty() {
public final ObjectProperty<Callback<LoadRequest, List<T>>> loaderProperty() {
return loader;
}

public void setLoader(Callback<LoadRequest, List<T>> loader) {
public final void setLoader(Callback<LoadRequest, List<T>> loader) {
this.loader.set(loader);
}

private ObjectProperty<Node> placeholder;

/**
* The {@code Node} to show to the user when the {@code ListView} has no content to show.
* The {@code Node} to show to the user when the {@code PagingListView} has no content to show.
* This happens when the list model has no data or when a filter has been applied to the list model, resulting in
* there being nothing to show the user.
*
* @since JavaFX 8.0
*/
private ObjectProperty<Node> placeholder;

public final ObjectProperty<Node> placeholderProperty() {
if (placeholder == null) {
placeholder = new SimpleObjectProperty<>(this, "placeholder");
Expand Down
26 changes: 26 additions & 0 deletions gemsfx/src/main/java/com/dlsc/gemsfx/skins/InnerListViewSkin.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
import javafx.beans.Observable;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.skin.ListViewSkin;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;

Expand Down Expand Up @@ -53,9 +57,31 @@ protected void layoutChildren(double x, double y, double w, double h) {
loadingPane.resizeRelocate(x, y, w, h);
}

private Label placeholderLabel;

public void refresh() {
content.getChildren().clear();

if (pagingListView.getTotalItemCount() == 0) {
Node placeholder = pagingListView.getPlaceholder();
if (placeholder == null) {
if (placeholderLabel == null) {
placeholderLabel = new Label("No items");
placeholderLabel.getStyleClass().add("placeholder");
VBox.setVgrow(placeholderLabel, Priority.ALWAYS);
}
placeholder = placeholderLabel;
}

content.getChildren().add(placeholder);
content.setAlignment(Pos.CENTER);
} else {
buildItems();
content.setAlignment(Pos.TOP_LEFT);
}
}

private void buildItems() {
Callback<ListView<T>, ListCell<T>> cellFactory = pagingListView.getCellFactory();
if (cellFactory != null) {

Expand Down
Loading

0 comments on commit 58b1136

Please sign in to comment.