0

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.

graynk
  • 131
  • 1
  • 2
  • 17
  • No, I will not change the behavior of LocalDateTextField. :-) But how about using LocalDatePicker directly (because that is what LocalDateTextField uses as a popup), the skin code in JFXtras CalendarTextField(!) shows you how to do that. You can just leave out the whole parsing on the textfield then. – tbeernot Feb 06 '18 at 09:36
  • No-no, the behaviour of LocalDateTextField shouldn't change in general, of course. I was talking about overriding or extending it just for my case. I want to use Picker directly, but it's the skin code is where I'm stuck. Can you point me to the right section? – graynk Feb 06 '18 at 10:34
  • I sort of get lost between all the createDefaultSkin, getUserAgentStylesheet (that pulls some css) and other methods. [This](https://github.com/JFXtras/jfxtras/blob/8.0/jfxtras-controls/src/main/java/jfxtras/scene/control/CalendarTextField.java) is where I should be looking, right? Would appreciate some guidance – graynk Feb 06 '18 at 11:04

1 Answers1

0

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(); }
}
graynk
  • 131
  • 1
  • 2
  • 17
  • Yup. That is how I would do it in an application; no need to write a complete control for it. The picker per default has no styling indeed, so it can more easily adapt. Maybe I should create a LocalDatePickerPopup :-/ – tbeernot Feb 07 '18 at 16:44