Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vaadin 23.3.6 | DatePicker does not provide entered value as intended #4697

Open
mab-infokom opened this issue Feb 17, 2023 · 2 comments
Open
Labels
documentation Improvements or additions to documentation question Further information is requested

Comments

@mab-infokom
Copy link

mab-infokom commented Feb 17, 2023

Description

We are setting up a Vaadin Renderer for our business applications and are struggling with DatePicker currently.

When starting with empty DatePicker Fields, the user can enter a new value on the fly - let's say "17.02.2023".

Now the user has many options that need us to get the entered value.

  • STRG+S | user wants to save.
  • Mouse click in next field
  • Context menu
  • ...

In all those cases, he did not "finish" his input by typing ENTER, which would cause DatePicker to update its internal value.

Let's take STRG + S as an example:

When our save code runs, it needs current user input in the date field. But there is no way to get it, as DatePicker provides old null value.

We reached out to the Vaadin Chat and got first workaround idea, wich is not suitable for us, but makes this issue reproducable.

image

Expected outcome

We would expect, that we can access valid user input somehow when pressing STRG+S and our save code runs. If user input is not valid, we should be able to recognize this as well in order to abort saving process. As DatePicker has not processed the user input in our use case, we are stuck.

  • DatePicker.getValue() provides previous value
  • DatePicker.isInvalid() provides false
  • any event has not yet been fired()

Minimal reproducible example

package com.example.application.views.main.chat;

import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.KeyModifier;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.router.Route;


@Route("date-picker-instant-value-change")
public class DatePickerInstantValueChange extends HorizontalLayout {

    private DatePicker datePicker;

    private boolean datePickerDirty = false;

    private Button save;

    public DatePickerInstantValueChange() {
        datePicker = new DatePicker();
        datePicker.addValueChangeListener(event -> {
            System.out.println("Value changed even is from client? " + event.isFromClient());
            System.out.println("Value changed from event.getValue(): " + event.getValue());
            System.out.println("Value changed from datePicker.getValue(): " + datePicker.getValue());
            if (datePickerDirty) {
                System.out.println("Because date picker is dirty then we save it");
                System.out.println("Saving datePicker value: " + datePicker.getValue());
                datePickerDirty = false;
            }
        });
        // not needed:
//        datePicker.addClientValidatedEventListener(event -> {
//            System.out.println("Client validated: " + event.toString());
//        });
        datePicker.addOpenedChangeListener(event -> {
            System.out.println("Opened changed: " + event.isOpened());
            // value is still not good at that moment, so we cannot do this:
            if (!event.isFromClient()) {
                System.out.println("Opened triggered programmatically, and value: " + datePicker.getValue());
            }
        });

        save = new Button("Save");
        save.addClickListener(event -> {
            // still have the previous value:
            System.out.println("Saving: " + datePicker.getValue());
        });

        save.addFocusShortcut(Key.KEY_S, KeyModifier.CONTROL);
        UI.getCurrent().addShortcutListener(
                this::shortCutCtrlN, Key.KEY_N,
                KeyModifier.CONTROL);
        // addCtrlSaveShortcutListenerFirst();
        add(datePicker);
        add(save);
    }

    private void shortCutCtrlN() {
        datePicker.setOpened(false);
        datePickerDirty = true;
        System.out.println("Datepicker value, when shortcut CTR+N triggered: " + datePicker.getValue());
        // save click would still save the old value;
        // save.click();
    }

//    private void addCtrlSaveShortcutListenerFirst() {
//        Shortcuts.addShortcutListener(datePicker, () -> {
//            datePicker.setOpened(false);
//            save.focus();
//            save.click();
//            System.out.println("Ctrl+Enter pressed from datePicker" + datePicker.getValue());
//            System.out.println("Saving with CTR+Savefrom datePicker " + datePicker.getValue());
//        }, Key.KEY_S, KeyModifier.CONTROL);
//    }
}

Steps to reproduce

  1. Make given code runnable
  2. Enter a valid date
  3. type STRG+S

Environment

Vaadin Version 23.3.6
OS Linux and Windows

Browsers

Chrome, Firefox, Safari on iOS

@TatuLund TatuLund added documentation Improvements or additions to documentation question Further information is requested labels Feb 20, 2023
@TatuLund
Copy link
Contributor

You should use Binder and enable enforceFieldValidation=true in feature flags in src/main/resources/vaadin-featureflags.properties file.

com.vaadin.experimental.enforceFieldValidation=true
See more at: vaadin/platform#3066

That is a new feature we have developed exactly for this kind of use. I.e. to detect input of wrong format in DatePicker or some other fields.

Also it is possible to use DatePicker#addClientValidatedEventListener to detect changes when not using Binder.

@TatuLund
Copy link
Contributor

The question is actually about race condition when using key shortcuts. Here is a more simplified example of the case, which has been solved by using empty JavaScript call to post pone activity to the next roundtrip. This way get correct value.

package org.vaadin.tatu;

import java.time.LocalDate;

import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.KeyModifier;
import com.vaadin.flow.component.Shortcuts;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.router.Route;

@Route("form")
public class DatePickerView extends Div {

    public class Person {
        private LocalDate birthDate;
        private String name;

        public LocalDate getBirthDate() {
            return birthDate;
        }

        public void setBirthDate(LocalDate birthDate) {
            this.birthDate = birthDate;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    public DatePickerView() {
        DatePicker datePicker = new DatePicker("Birth date");
        TextField textField = new TextField("Name");
        Person person = new Person();
        Binder<Person> binder = new Binder<>();
        binder.forField(datePicker).asRequired().bind(Person::getBirthDate,
                Person::setBirthDate);
        binder.forField(textField).asRequired().bind(Person::getName,
                Person::setName);
        binder.readBean(person);
        Button save = new Button("Save");
        save.addClickListener(e -> {
            try {
                binder.writeBean(person);
                Notification.show("'" + person.getName() + " "
                        + person.getBirthDate().toString() + "' saved!");
            } catch (ValidationException e1) {
                Notification.show("Not valid");
            }
        });
        Shortcuts.addShortcutListener(save, e -> {
            datePicker.setOpened(false);
            datePicker.getElement().executeJs("return 0;").then(result -> {
                save.focus();
                save.click();
            });
        }, Key.KEY_S, KeyModifier.CONTROL).listenOn(this);
        add(textField, datePicker, save);
    }

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants