javajfxtras

How to make an analog of LocalDateTextField from JFXtras that supports regular text?


I want to provide users with the ability to search in the database. They should be able to search by text on one column and by dates on another column. But I don't want to make two separate fields in the UI, I want to combine them in one TextField. LocalDateTextField in JFXtras is useful for this, as it allows to pick dates with convenient picker, but also allows to enter text. However, it automatically parses the input, and if it can't be parsed into LocalDate, the field clears (as it should by definition).

My question is: can I make it keep the text, so that I get LocalDate if there is one, or if the LocalDate field is null, then I get just the text? I've tried experimenting with setParseErrorCallback (tried to set the text property there), but it seems that deletion happens somewhere down the line. I could just put a button next to the regular TextField that calls the PopOver with LocalDatePicker, but I'm not sure how to replicate the way it looks. How do I style it so that the popover is colored with gradient just like Button? I've tried doing picker.getStyleClass().add("button") but that carries over on-hover glow and padding.

Here's how LocalDateTextField picker looks:

LocalDateTextField

Note the padding, the gradient and how it's directly below the text field. And here's LocalDatePicker in the PopOver that is called when Button is pressed:

LocalDatePicker in PopOver

In summary: can I change the behaviour of LocalDateTextField, if yes then how, if not then how do I style my own implementation to look the same? I've tried looking at the source files, but I can't understand where the magic is happening there for the life of me.


Solution

  • Ended up making my own SearchTextField. Got a HBox, put TextField and Button there, Button calls PopOver with LocalDatePicker, the picker is styled with the following css from JFXtras:

    .LocalDatePicker {
        -fx-background-color: -fx-body-color;
        -fx-background-insets: 0 0 -1 0,0,1,2;
        -fx-background-radius: 5,5,4,3;
        -fx-padding: 0.466667em 0.333333em 0.35em 0.333333em;
        -fx-text-fill: -fx-text-base-color;
    }
    

    Then added some listeners to provide the needed logic and all done.

    Here's the code (sorry)

    package view.elements;
    
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.beans.value.ChangeListener;
    import javafx.beans.value.ObservableValue;
    import javafx.collections.ListChangeListener;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.scene.control.Button;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.HBox;
    import javafx.util.Duration;
    import jfxtras.scene.control.LocalDatePicker;
    import org.controlsfx.control.PopOver;
    import org.controlsfx.glyphfont.FontAwesome;
    
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    
    public class SearchTextField extends HBox {
        private final TextField field = new TextField();
        private final LocalDatePicker picker = new LocalDatePicker();
        private final PopOver over = new PopOver(picker);
        private final Button searchButton = new Button("Искать");
        private final StringProperty promptTextProperty = new SimpleStringProperty();
        private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
    
        public SearchTextField() {
            super();
            field.promptTextProperty().bind(promptTextProperty);
            field.setPrefColumnCount(20);
            over.setArrowSize(0);
            over.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER);
            picker.getStylesheets().add(SearchTextField.class.getResource("datefield.css").toExternalForm());
            picker.getStyleClass().add("LocalDatePicker");
            picker.prefWidthProperty().bind(this.widthProperty());
            picker.setMode(LocalDatePicker.Mode.SINGLE);
            picker.setAllowNull(true);
            final Button showPicker = new Button();
            showPicker.setGraphic(MethodFX.getFontAwesome().create(FontAwesome.Glyph.CALENDAR_ALT));
    
            showPicker.setOnAction(event -> {
                if (over.isShowing())
                    over.hide();
                else
                    over.show(field, -2);
            });
            field.textProperty().addListener((obs, oldText, newText) -> {
                if(newText.matches("(^(((0[1-9]|1[0-9]|2[0-8])[.](0[1-9]|1[012]))|((29|30|31)[.](0[13578]|1[02]))|((29|30)[.](0[4,6,9]|11)))[.](19|[2-9][0-9])\\d\\d$)|(^29[.]02[.](19|[2-9][0-9])(00|04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96)$)"))
                    picker.localDateProperty().setValue(LocalDate.parse(newText, dateFormatter));
                else
                    picker.localDateProperty().setValue(null);
            });
            picker.localDates().addListener((ListChangeListener<LocalDate>) c -> {
                while(c.next() && c.wasAdded()) {
                    field.setText(dateFormatter.format(c.getAddedSubList().get(0)));
                }
                over.hide();
            });
            field.focusedProperty().addListener((observable, oldValue, newValue) -> {
                if (newValue && over.isShowing())
                    over.hide();
            });
    
            this.setSpacing(4);
            this.getChildren().addAll(field, showPicker, searchButton);
        }
    
        public String getText() { return field.getText(); }
    
        public LocalDate getLocalDate() { return picker.getLocalDate(); }
    
        public void hidePicker(Duration duration) { if (over.isShowing()) over.hide(duration); }
    
        public void setOnSearch(EventHandler<ActionEvent> eventHandler) { searchButton.setOnAction(eventHandler); }
    
        public StringProperty promptTextProperty() {
            return promptTextProperty;
        }
    
        public void setPromptText(String value) { promptTextProperty.setValue(value); }
    
        public String getPromptText() { return promptTextProperty.getValue(); }
    }