Skip to content

Commit

Permalink
Refactor paging controls to support various display modes
Browse files Browse the repository at this point in the history
Added FirstLastPageDisplayMode enum to control visibility and type of first/last page navigation. Updated PagingControlsSkin to handle new modes and refactored methods for clearer separation of button creation. Modified demo and stylesheet accordingly.
  • Loading branch information
dlemmermann committed Sep 24, 2024
1 parent a4e220d commit 4562afa
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ public class PagingControlsApp extends Application {

@Override
public void start(Stage stage) {
VBox vBox1 = createSection(10, 221, false, MessageLabelStrategy.SHOW_WHEN_NEEDED);
VBox vBox2 = createSection(15, 45, false, MessageLabelStrategy.SHOW_WHEN_NEEDED);
VBox vBox3 = createSection(20, 1000, true, MessageLabelStrategy.SHOW_WHEN_NEEDED);
VBox vBox4 = createSection(5, 5, false, MessageLabelStrategy.ALWAYS_SHOW);
VBox vBox5 = createSection(5, 0, false, MessageLabelStrategy.ALWAYS_SHOW);
VBox vBox1 = createSection(10, 221, MessageLabelStrategy.SHOW_WHEN_NEEDED, PagingControls.FirstLastPageDisplayMode.HIDE);
VBox vBox2 = createSection(15, 45, MessageLabelStrategy.SHOW_WHEN_NEEDED, PagingControls.FirstLastPageDisplayMode.SHOW_ARROW_BUTTONS);
VBox vBox3 = createSection(20, 1000, MessageLabelStrategy.SHOW_WHEN_NEEDED, PagingControls.FirstLastPageDisplayMode.SHOW_PAGE_BUTTONS);
VBox vBox4 = createSection(5, 5, MessageLabelStrategy.ALWAYS_SHOW, PagingControls.FirstLastPageDisplayMode.HIDE);
VBox vBox5 = createSection(5, 0, MessageLabelStrategy.ALWAYS_SHOW, PagingControls.FirstLastPageDisplayMode.HIDE);

ChoiceBox<HPos> alignmentChoiceBox = new ChoiceBox<>();
alignmentChoiceBox.getItems().setAll(HPos.values());
Expand All @@ -49,21 +49,22 @@ public void start(Stage stage) {

CSSFX.start(stackPane);

scene.focusOwnerProperty().addListener(it -> System.out.println(scene.getFocusOwner()));

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

private VBox createSection(int pageSize, int itemCount, boolean showFirstLastButtons, MessageLabelStrategy messageLabelStrategy) {
private VBox createSection(int pageSize, int itemCount, MessageLabelStrategy messageLabelStrategy, PagingControls.FirstLastPageDisplayMode displayMode) {
PagingControls pagingControls = new PagingControls();
pagingControls.alignmentProperty().bind(alignmentProperty);
pagingControls.setMessageLabelStrategy(messageLabelStrategy);
pagingControls.setTotalItemCount(itemCount);
pagingControls.setPageSize(pageSize);

pagingControls.setShowFirstLastPageButton(showFirstLastButtons);
pagingControls.setFirstLastPageDisplayMode(displayMode);

pagingControls.setStyle("-fx-border-color: black; -fx-padding: 20px");
pagingControls.setPrefWidth(800);
Expand All @@ -74,11 +75,9 @@ private VBox createSection(int pageSize, int itemCount, boolean showFirstLastBut
Label pageCountLabel = new Label();
pageCountLabel.textProperty().bind(Bindings.createStringBinding(() -> "Page count: " + pagingControls.getPageCount(), pagingControls.pageCountProperty()));

CheckBox showFirstLastPageButton = new CheckBox("Show first / last page buttons");
showFirstLastPageButton.selectedProperty().bindBidirectional(pagingControls.showFirstLastPageButtonProperty());

CheckBox showMaxPage = new CheckBox("Show max page");
showMaxPage.selectedProperty().bindBidirectional(pagingControls.showMaxPageProperty());
ChoiceBox<PagingControls.FirstLastPageDisplayMode> displayModeChoiceBox = new ChoiceBox<>();
displayModeChoiceBox.getItems().setAll(PagingControls.FirstLastPageDisplayMode.values());
displayModeChoiceBox.valueProperty().bindBidirectional(pagingControls.firstLastPageDisplayModeProperty());

CheckBox showPreviousNextButton = new CheckBox("Show prev / next buttons");
showPreviousNextButton.selectedProperty().bindBidirectional(pagingControls.showPreviousNextPageButtonProperty());
Expand All @@ -91,17 +90,20 @@ private VBox createSection(int pageSize, int itemCount, boolean showFirstLastBut
maxPageIndicatorsBox.getItems().setAll(List.of(1, 2, 5, 10));
maxPageIndicatorsBox.valueProperty().bindBidirectional(pagingControls.maxPageIndicatorsCountProperty().asObject());

HBox displayModeBox = new HBox(5, new Label("Display mode: "), displayModeChoiceBox);
displayModeBox.setAlignment(Pos.CENTER_LEFT);

HBox strategyBox = new HBox(5, new Label("Label strategy: "), strategyChoiceBox);
strategyBox.setAlignment(Pos.CENTER_LEFT);

HBox indicatorBox = new HBox(5, new Label("# Indicators: "), maxPageIndicatorsBox);
indicatorBox.setAlignment(Pos.CENTER_LEFT);

FlowPane flowPane = new FlowPane(pageLabel, pageCountLabel, new Spacer(), showFirstLastPageButton, showPreviousNextButton, showMaxPage, strategyBox, indicatorBox);
FlowPane flowPane = new FlowPane(pageLabel, pageCountLabel, new Spacer(), showPreviousNextButton, displayModeBox, strategyBox, indicatorBox);
flowPane.setVgap(10);
flowPane.setHgap(20);

VBox vBox = new VBox(10, pagingControls, flowPane);
VBox vBox = new VBox(10, pagingControls); //, flowPane);
vBox.setMaxHeight(Region.USE_PREF_SIZE);

return vBox;
Expand Down
110 changes: 66 additions & 44 deletions gemsfx/src/main/java/com/dlsc/gemsfx/PagingControls.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import javafx.scene.control.Skin;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.util.Callback;

import java.util.Objects;
Expand All @@ -34,6 +35,7 @@ public class PagingControls extends Control {
public PagingControls() {
getStyleClass().add(DEFAULT_STYLE_CLASS);

addEventFilter(MouseEvent.MOUSE_PRESSED, evt -> requestFocus());
setMessageLabelProvider(view -> {
if (getPageCount() == 0) {
return "No items";
Expand All @@ -46,7 +48,7 @@ public PagingControls() {
int startIndex = (view.getPage() * getPageSize()) + 1;
int endIndex = startIndex + getPageSize() - 1;

return "Showing items " + startIndex + " to " + endIndex + " of " + getTotalItemCount();
return "Showing items " + startIndex + " to " + endIndex + " of " + getTotalItemCount();
});

addEventHandler(KeyEvent.KEY_PRESSED, evt -> {
Expand All @@ -69,9 +71,13 @@ public PagingControls() {
return count;
}, totalItemCountProperty(), pageSizeProperty()));

Label separator = new Label("...");
separator.getStyleClass().add("max-page-separator");
setMaxPageDivider(separator);
Label firstPageDivider = new Label("...");
firstPageDivider.getStyleClass().addAll("page-divider", "first-page-divider");
setFirstPageDivider(firstPageDivider);

Label lastPageDivider = new Label("...");
lastPageDivider.getStyleClass().addAll("page-divider", "first-page-divider");
setLastPageDivider(lastPageDivider);
}

@Override
Expand Down Expand Up @@ -168,44 +174,81 @@ public final void setMessageLabelStrategy(MessageLabelStrategy messageLabelStrat
this.messageLabelStrategy.set(messageLabelStrategy);
}

private final BooleanProperty showMaxPage = new SimpleBooleanProperty(this, "showMaxButton");
/**
* An enum listing the different ways the control will display or
* not display controls to quickly go to the first or the last page.
*/
public enum FirstLastPageDisplayMode {

/**
* Do not show controls for jumping to the first or last page.
*/
HIDE,

/**
* Show separate controls in front and after the page buttons to
* perform the jump.
*/
SHOW_ARROW_BUTTONS,

/**
* Show extra page buttons to perform the jump (1 ... 5 6 7 8 ... 20).
*/
SHOW_PAGE_BUTTONS
}

private final ObjectProperty<FirstLastPageDisplayMode> firstLastPageDisplayMode = new SimpleObjectProperty<>(this, "firstLastPageStrategy");

public final FirstLastPageDisplayMode getFirstLastPageDisplayMode() {
return firstLastPageDisplayMode.get();
}

public final ObjectProperty<FirstLastPageDisplayMode> firstLastPageDisplayModeProperty() {
return firstLastPageDisplayMode;
}

public final boolean isShowMaxPage() {
return showMaxPage.get();
public final void setFirstLastPageDisplayMode(FirstLastPageDisplayMode firstLastPageDisplayMode) {
this.firstLastPageDisplayMode.set(firstLastPageDisplayMode);
}

private final ObjectProperty<Node> firstPageDivider = new SimpleObjectProperty<>(this, "firstPageDivider");

public final Node getFirstPageDivider() {
return firstPageDivider.get();
}

/**
* A boolean property used to control whether the view will display a separate / special button
* for the "last" page (for quick access / quick jumping to the end).
* Stores the node that will be placed between the regular page buttons and the page button
* that represents the "first" page. This is usually a label showing "...".
*
* @return a flag used for showing / hiding the max page button
* @return a node for separating the "first page" button, usually a label showing "..."
*/
public final BooleanProperty showMaxPageProperty() {
return showMaxPage;
public final ObjectProperty<Node> firstPageDividerProperty() {
return firstPageDivider;
}

public final void setShowMaxPage(boolean showMaxPage) {
this.showMaxPage.set(showMaxPage);
public final void setFirstPageDivider(Node firstPageDivider) {
this.firstPageDivider.set(firstPageDivider);
}

private final ObjectProperty<Node> maxPageDivider = new SimpleObjectProperty<>(this, "maxPageDivider");
private final ObjectProperty<Node> lastPageDivider = new SimpleObjectProperty<>(this, "firstPageDivider");

public final Node getMaxPageDivider() {
return maxPageDivider.get();
public final Node getLastPageDivider() {
return lastPageDivider.get();
}

/**
* Stores the node that will be placed between the regular page buttons and the page button
* that represents the "last" page. This is usually a label showing "...".
*
* @return a node for separating the "max page" button, usually a label showing "..."
* @return a node for separating the "last page" button, usually a label showing "..."
*/
public final ObjectProperty<Node> maxPageDividerProperty() {
return maxPageDivider;
public final ObjectProperty<Node> lastPageDividerProperty() {
return lastPageDivider;
}

public final void setMaxPageDivider(Node maxPageDivider) {
this.maxPageDivider.set(maxPageDivider);
public final void setLastPageDivider(Node lastPageDivider) {
this.lastPageDivider.set(lastPageDivider);
}

private final ObjectProperty<Callback<PagingControls, String>> messageLabelProvider = new SimpleObjectProperty<>(this, "messageLabelProvider");
Expand Down Expand Up @@ -293,10 +336,9 @@ public final int getPageCount() {
* A read-only property that stores the number of pages that are required for the given
* number of items and the given page size.
*
* @return a read-only integer property storing the number of required pages
* @see #totalItemCountProperty()
* @see #pageSizeProperty()
*
* @return a read-only integer property storing the number of required pages
*/
public final ReadOnlyIntegerProperty pageCountProperty() {
return pageCount.getReadOnlyProperty();
Expand Down Expand Up @@ -360,24 +402,4 @@ public final IntegerProperty pageSizeProperty() {
public final void setPageSize(int pageSize) {
this.pageSize.set(pageSize);
}

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

public final void setShowFirstLastPageButton(boolean showFirstLastPageButton) {
this.showFirstLastPageButton.set(showFirstLastPageButton);
}

public final boolean isShowFirstLastPageButton() {
return showFirstLastPageButton.get();
}

/**
* A flag used to determine whether the control will display arrow buttons to
* go directly to the first or the last page.
*
* @return a boolean property to control the visibility of the first / last buttons
*/
public final BooleanProperty showFirstLastPageButtonProperty() {
return showFirstLastPageButton;
}
}
52 changes: 40 additions & 12 deletions gemsfx/src/main/java/com/dlsc/gemsfx/skins/PagingControlsSkin.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ public PagingControlsSkin(PagingControls view) {
view.pageProperty().addListener(buildViewListener);
view.pageCountProperty().addListener(buildViewListener);
view.maxPageIndicatorsCountProperty().addListener(buildViewListener);
view.showMaxPageProperty().addListener(buildViewListener);
view.firstLastPageDisplayModeProperty().addListener(buildViewListener);
view.alignmentProperty().addListener(buildViewListener);
view.firstPageDividerProperty().addListener(buildViewListener);
startPage.addListener(buildViewListener);

view.pageProperty().addListener((obs, oldPage, newPage) -> {
int startPage = this.startPage.get();
int totalPages = view.getPageCount();
int maxPageIndicatorCount = view.getMaxPageIndicatorsCount();

if (newPage.intValue() < startPage) {
Expand Down Expand Up @@ -83,17 +83,19 @@ private void createButtons() {
messageLabel.managedProperty().bind(messageLabel.visibleProperty());

firstPageButton = createFirstPageButton();
firstPageButton.setFocusTraversable(false);
firstPageButton.setGraphic(new FontIcon(MaterialDesign.MDI_PAGE_FIRST));
firstPageButton.getStyleClass().addAll("navigation-button", "first-page-button");
firstPageButton.managedProperty().bind(firstPageButton.visibleProperty());
firstPageButton.disableProperty().bind(startPage.greaterThan(0).not());
firstPageButton.visibleProperty().bind(view.showFirstLastPageButtonProperty().and(view.pageCountProperty().greaterThan(1)));
firstPageButton.visibleProperty().bind(view.firstLastPageDisplayModeProperty().isEqualTo(PagingControls.FirstLastPageDisplayMode.SHOW_ARROW_BUTTONS).and(view.pageCountProperty().greaterThan(1)));
firstPageButton.setOnAction(evt -> {
view.setPage(0);
startPage.set(0);
});

previousButton = createPreviousPageButton();
previousButton.setFocusTraversable(false);
previousButton.setGraphic(new FontIcon(MaterialDesign.MDI_CHEVRON_LEFT));
previousButton.getStyleClass().addAll("navigation-button", "previous-page-button");
previousButton.setOnAction(evt -> view.setPage(Math.max(0, view.getPage() - 1)));
Expand All @@ -103,6 +105,7 @@ private void createButtons() {
previousButton.disableProperty().bind(view.pageProperty().greaterThan(0).not());

nextButton = createNextPageButton();
nextButton.setFocusTraversable(false);
nextButton.setGraphic(new FontIcon(MaterialDesign.MDI_CHEVRON_RIGHT));
nextButton.getStyleClass().addAll("navigation-button", "next-page-button");
nextButton.setOnAction(evt -> view.setPage(Math.min(view.getPageCount() - 1, view.getPage() + 1)));
Expand All @@ -112,11 +115,12 @@ private void createButtons() {
nextButton.disableProperty().bind(view.pageProperty().lessThan(view.pageCountProperty().subtract(1)).not());

lastPageButton = createLastPageButton();
lastPageButton.setFocusTraversable(false);
lastPageButton.setGraphic(new FontIcon(MaterialDesign.MDI_PAGE_LAST));
lastPageButton.getStyleClass().addAll("navigation-button", "last-page-button");
lastPageButton.managedProperty().bind(lastPageButton.visibleProperty());
lastPageButton.disableProperty().bind(startPage.add(view.getMaxPageIndicatorsCount()).lessThan(view.getPageCount()).not());
lastPageButton.visibleProperty().bind(view.showFirstLastPageButtonProperty().and(view.pageCountProperty().greaterThan(1)));
lastPageButton.visibleProperty().bind(view.firstLastPageDisplayModeProperty().isEqualTo(PagingControls.FirstLastPageDisplayMode.SHOW_ARROW_BUTTONS).and(view.pageCountProperty().greaterThan(1)));
lastPageButton.setOnAction(evt -> view.setPage(view.getPageCount() - 1));
}

Expand Down Expand Up @@ -147,39 +151,63 @@ private void updateView() {
pageButtonsBox.getStyleClass().add("page-buttons-container");
pageButtonsBox.setMaxWidth(Region.USE_PREF_SIZE);
pageButtonsBox.managedProperty().bind(pageButtonsBox.visibleProperty());

pageButtonsBox.getChildren().setAll(firstPageButton, previousButton);

int pageIndex;
int startIndex = startPage.get();
int endIndex = Math.min(view.getPageCount(), startIndex + view.getMaxPageIndicatorsCount());

if (endIndex - startIndex < view.getMaxPageIndicatorsCount()) {
startIndex = Math.max(0, endIndex - view.getMaxPageIndicatorsCount());
}

addFirstPageButton(view, startIndex);
addPageButtons(startIndex, endIndex, view);
addLastPageButton(view, endIndex);

pageButtonsBox.getChildren().addAll(nextButton, lastPageButton);

// might have been updated above
startPage.set(startIndex);
}

private void addPageButtons(int startIndex, int endIndex, PagingControls view) {
int pageIndex;
for (pageIndex = startIndex; pageIndex < endIndex; pageIndex++) {
Button pageButton = createPageButton(pageIndex);
pageButton.setFocusTraversable(false);
pageButton.visibleProperty().bind(view.pageCountProperty().greaterThan(1));
if (pageIndex == view.getPage()) {
pageButton.getStyleClass().add("current");
}
pageButtonsBox.getChildren().add(pageButton);
}
}

if (view.isShowMaxPage() && endIndex < view.getPageCount()) {
private void addLastPageButton(PagingControls view, int endIndex) {
if (view.getFirstLastPageDisplayMode().equals(PagingControls.FirstLastPageDisplayMode.SHOW_PAGE_BUTTONS) && endIndex < view.getPageCount()) {
// we need to show the "max page" button
Button pageButton = createPageButton(view.getPageCount() - 1);
pageButton.setFocusTraversable(false);
pageButton.visibleProperty().bind(view.pageCountProperty().greaterThan(1));
Node dividerNode = view.getMaxPageDivider();
Node dividerNode = view.getLastPageDivider();
dividerNode.visibleProperty().bind(view.pageCountProperty().greaterThan(1));
dividerNode.setFocusTraversable(false);
pageButtonsBox.getChildren().addAll(dividerNode, pageButton);
}
}


pageButtonsBox.getChildren().addAll(nextButton, lastPageButton);

// might have been updated above
startPage.set(startIndex);
private void addFirstPageButton(PagingControls view, int startIndex) {
if (view.getFirstLastPageDisplayMode().equals(PagingControls.FirstLastPageDisplayMode.SHOW_PAGE_BUTTONS) && startIndex > 1) {
// we need to show the "max page" button
Button pageButton = createPageButton(0);
pageButton.setFocusTraversable(false);
pageButton.visibleProperty().bind(view.pageCountProperty().greaterThan(1));
Node dividerNode = view.getFirstPageDivider();
dividerNode.visibleProperty().bind(view.pageCountProperty().greaterThan(1));
dividerNode.setFocusTraversable(false);
pageButtonsBox.getChildren().addAll(pageButton, dividerNode);
}
}

protected Button createFirstPageButton() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
}

.paging-controls > .pane.horizontal {
-fx-padding: 0px 0px 0px 5px;
-fx-spacing: 10px;
-fx-alignment: center-left;
}
Expand Down

0 comments on commit 4562afa

Please sign in to comment.