Skip to content

Commit

Permalink
Add DayOfWeekPicker component
Browse files Browse the repository at this point in the history
Introduce a new DayOfWeekPicker control for selecting days of the week with single and multiple selection modes. Related changes include demo application updates and minor text adjustments for better user experience.
  • Loading branch information
leewyatt committed Oct 18, 2024
1 parent f063a35 commit 8b1534a
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.dlsc.gemsfx.demo;

import com.dlsc.gemsfx.DayOfWeekPicker;
import com.dlsc.gemsfx.demo.fake.SimpleControlPane;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class DayOfWeekPickerApp extends Application {

private final DayOfWeekPicker dayOfWeekPicker = new DayOfWeekPicker();

@Override
public void start(Stage primaryStage) throws Exception {

dayOfWeekPicker.prefWidthProperty().bind(dayOfWeekPicker.getSelectionModel().selectionModeProperty().map(sm -> sm == SelectionMode.SINGLE ? 135 : 160));
StackPane wrapper = new StackPane(dayOfWeekPicker);
wrapper.setStyle("-fx-padding: 30px; -fx-background-color: white;");

SplitPane splitPane = new SplitPane(wrapper,getControlPanel());
splitPane.setDividerPositions(0.68);

primaryStage.setScene(new Scene(splitPane, 800, 600));
primaryStage.setTitle("Hello DayOfWeekPicker");
primaryStage.show();
}

public Node getControlPanel() {
ComboBox<SelectionMode> selectionModeComboBox = new ComboBox<>();
selectionModeComboBox.getItems().addAll(SelectionMode.values());
selectionModeComboBox.valueProperty().bindBidirectional(dayOfWeekPicker.getSelectionModel().selectionModeProperty());

CheckBox autoHideOnSelection = new CheckBox("Hide On Selection");
autoHideOnSelection.selectedProperty().bindBidirectional(dayOfWeekPicker.autoHideOnSelectionProperty());

Button selectAll = new Button("Select All");
selectAll.setMaxWidth(Double.MAX_VALUE);
selectAll.managedProperty().bind(selectAll.visibleProperty());
selectAll.visibleProperty().bind(dayOfWeekPicker.getSelectionModel().selectionModeProperty().isEqualTo(SelectionMode.MULTIPLE));
selectAll.setOnAction(evt -> dayOfWeekPicker.getSelectionModel().selectAll());

Button clearSelection = new Button("Clear Selection");
clearSelection.setMaxWidth(Double.MAX_VALUE);
clearSelection.setOnAction(evt -> dayOfWeekPicker.getSelectionModel().clearSelection());

VBox selectionButtons = new VBox(10, selectAll, clearSelection);
selectionButtons.setAlignment(Pos.CENTER);
selectionButtons.setFillWidth(true);

return new SimpleControlPane(
"DayOfWeekPicker",
new SimpleControlPane.ControlItem("SelectionMode", selectionModeComboBox),
new SimpleControlPane.ControlItem("Auto Hide", autoHideOnSelection),
new SimpleControlPane.ControlItem("Test Select", selectionButtons)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloTimeRangePicker extends Application {
public class TimeRangePickerApp extends Application {

private TimeRangePicker timeRangePicker;

Expand Down
210 changes: 210 additions & 0 deletions gemsfx/src/main/java/com/dlsc/gemsfx/DayOfWeekPicker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package com.dlsc.gemsfx;

import com.dlsc.gemsfx.util.SimpleStringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.SelectionMode;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.TextStyle;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
* A custom control that allows users to select days of the week.
* It provides support for two {@link SelectionMode}: single and multiple .
* <ul>
* <li>{@link SelectionMode#SINGLE} mode allows selection of only one day at a time.</li>
* <li>{@link SelectionMode#MULTIPLE} mode allows selection of multiple days.</li>
* </ul>
*
* @see SelectionBox
*/
public class DayOfWeekPicker extends SelectionBox<DayOfWeek> {

private static final String DEFAULT_STYLE_CLASS = "day-of-week-picker";

public DayOfWeekPicker() {
getStyleClass().add(DEFAULT_STYLE_CLASS);

// Set Items
getItems().setAll(getLocalizedDayOrder());

// Set Extra Buttons Provider
setExtraButtonsProvider(model -> switch (model.getSelectionMode()) {
case SINGLE -> {
// Button tomorrowButton = createExtraButton("Tomorrow", () -> model.clearAndSelect(getItems().indexOf(LocalDate.now().getDayOfWeek().plus(1))));
// Button yesterdayButton = createExtraButton("Yesterday", () -> model.clearAndSelect(getItems().indexOf(LocalDate.now().getDayOfWeek().minus(1))));
Button todayButton = createExtraButton("Today", () -> model.clearAndSelect(getItems().indexOf(LocalDate.now().getDayOfWeek())));
Button clearButton = createExtraButton("Clear", model::clearSelection);
yield List.of(clearButton, todayButton);
}
case MULTIPLE -> {
// When clicking on the button, it will clear the current selection and select all weekdays
Button weekdaysButton = createExtraButton("Weekdays", () -> {
getSelectionModel().clearSelection();
for (DayOfWeek day : getWeekdays()) {
getSelectionModel().select(day);
}
});
// When clicking on the button, it will clear the current selection and select all weekend days
Button weekendsButton = createExtraButton("Weekends", () -> {
getSelectionModel().clearSelection();
for (DayOfWeek day : getWeekendDays()) {
getSelectionModel().select(day);
}
});
Button clearButton = createExtraButton("Clear", model::clearSelection);
Button anyDayButton = createExtraButton("Any Day", model::selectAll);
yield List.of(clearButton, anyDayButton, weekdaysButton, weekendsButton);
}
});

// set item converter
setItemConverter(new SimpleStringConverter<>(dayOfWeek -> dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault())));

// Set selected items converter
setSelectedItemsConverter(new SimpleStringConverter<>(selectedDays -> {
if (selectedDays == null || selectedDays.isEmpty()) {
return "";
} else if (selectedDays.size() == 1) {
return selectedDays.get(0).getDisplayName(TextStyle.FULL, Locale.getDefault());
} else if (isSelectedAll()) {
return "All Days";
} else if (isOnlyWeekdaysSelected()) {
return "Weekdays";
} else if (isOnlyWeekendsSelected()) {
return "Weekends";
} else {
// Group selected days into consecutive ranges
List<List<DayOfWeek>> ranges = mergeConsecutiveItem(selectedDays);

// Build the display text
List<String> rangeStrings = new ArrayList<>();
for (List<DayOfWeek> range : ranges) {
if (range.size() > 1) {
String startDay = range.get(0).getDisplayName(TextStyle.SHORT, Locale.getDefault());
String endDay = range.get(range.size() - 1).getDisplayName(TextStyle.SHORT, Locale.getDefault());
rangeStrings.add(startDay + " ~ " + endDay);
} else {
String dayName = range.get(0).getDisplayName(TextStyle.SHORT, Locale.getDefault());
rangeStrings.add(dayName);
}
}
return String.join(", ", rangeStrings);
}
}));
}

private List<List<DayOfWeek>> mergeConsecutiveItem(List<DayOfWeek> selectedDays) {
List<DayOfWeek> days = new ArrayList<>(selectedDays);
if (days.isEmpty()) {
return Collections.emptyList();
}

// Get localized day order
List<DayOfWeek> dayOrder = getLocalizedDayOrder();

// Sort days according to localized order
days.sort(Comparator.comparingInt(dayOrder::indexOf));

List<List<DayOfWeek>> ranges = new ArrayList<>();
List<DayOfWeek> currentRange = new ArrayList<>();
currentRange.add(days.get(0));

for (int i = 1; i < days.size(); i++) {
DayOfWeek previousDay = days.get(i - 1);
DayOfWeek currentDay = days.get(i);

int prevIndex = dayOrder.indexOf(previousDay);
int currIndex = dayOrder.indexOf(currentDay);

if ((prevIndex + 1) % 7 == currIndex % 7) {
currentRange.add(currentDay);
} else {
ranges.add(new ArrayList<>(currentRange));
currentRange.clear();
currentRange.add(currentDay);
}
}
ranges.add(currentRange);
return ranges;
}

/**
* Get the localized order of DayOfWeek.
*/
public List<DayOfWeek> getLocalizedDayOrder() {
WeekFields weekFields = WeekFields.of(Locale.getDefault());
DayOfWeek firstDayOfWeek = weekFields.getFirstDayOfWeek();
List<DayOfWeek> dayOrderList = new ArrayList<>();
DayOfWeek day = firstDayOfWeek;
for (int i = 0; i < 7; i++) {
dayOrderList.add(day);
day = day.plus(1);
}
return dayOrderList;
}

/**
* Checks if only weekdays are selected in the current selection model.
*
* @return true if the selected days match exactly with the weekdays, otherwise false
*/
public boolean isOnlyWeekdaysSelected() {
Set<DayOfWeek> selectedDays = new HashSet<>(getSelectionModel().getSelectedItems());
Set<DayOfWeek> weekdays = new HashSet<>(getWeekdays());
return selectedDays.equals(weekdays);
}

/**
* Checks if only weekend days are selected in the current selection model.
*
* @return true if the selected days match exactly with the weekend days, otherwise false
*/
public boolean isOnlyWeekendsSelected() {
Set<DayOfWeek> selectedDays = new HashSet<>(getSelectionModel().getSelectedItems());
Set<DayOfWeek> weekends = new HashSet<>(getWeekendDays());
return selectedDays.equals(weekends);
}

/**
* Checks if all days are selected in the current selection model.
*
* @return true if all days are selected, otherwise false
*/
public boolean isSelectedAll() {
return getSelectionModel().getSelectedItems().size() == getItems().size();
}

/**
* Retrieves a list of the weekend days based on the current locale.
*
* @return a list of DayOfWeek objects representing the weekend days.
*/
public List<DayOfWeek> getWeekendDays() {
WeekFields weekFields = WeekFields.of(Locale.getDefault());
DayOfWeek weekendStart = weekFields.getFirstDayOfWeek().plus(5 % 7);
DayOfWeek weekendEnd = weekendStart.plus(1);

return List.of(weekendStart, weekendEnd);
}

/**
* Retrieves a list of the weekdays by excluding weekend days.
*
* @return a list of DayOfWeek objects representing the weekdays.
*/
public List<DayOfWeek> getWeekdays() {
List<DayOfWeek> weekdays = new ArrayList<>(List.of(DayOfWeek.values()));
weekdays.removeAll(getWeekendDays());
return weekdays;
}

}
2 changes: 1 addition & 1 deletion gemsfx/src/main/java/com/dlsc/gemsfx/TimeRangePicker.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public TimeRangePicker(TimeRange... ranges) {
int selectedCount = selectedRanges.size();

if (selectedCount == 0) {
return "No Data";
return "";
} else if (selectedCount == 1) {
return convertRangeToText(selectedRanges.get(0));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ private String getDefaultDisplayText(List<T> selectedItems) {
int selectedCount = selectedItems.size();

if (selectedCount == 0) {
return "No Data";
return "";
} else if (selectedCount == 1) {
return convertItemToText(selectedItems.get(0));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,6 @@ public void select(T obj) {
int index = items.indexOf(obj);
if (index >= 0) {
select(index);
} else {
// Object not in the list, clear selection
clearSelection();
}
}

Expand Down

0 comments on commit 8b1534a

Please sign in to comment.