vaadin / framework

Vaadin 6, 7, 8 is a Java framework for modern Java web applications.
http://vaadin.com/
Other
1.78k stars 730 forks source link

Vaadin 8.24+ feature to validate all values through binder breaks the ability to refresh the fields when not all fields are valid yet. #12623

Closed arbitorius closed 2 months ago

arbitorius commented 3 months ago

Actual behavior

The new feature in Vaadin 8.24+ to validate all values when changing one value in a form which is bound to a binder causes a problem where it is not possible to bind an empty bean and fill in values if more than one field is validated (e.g. required) and thus initially invalid if the fields need to be refreshed to show nested values. This worked in Vaadin 8.23 where only the changed value was validated but breaks in Vaadin 8.24 when all values are validated and the bean is not written back by the binder if any value is invalid.

The refreshFields() call shows this immediately, because the fields are not written back to the binder and thus all emptied again. But in our applications we make use of this quite often to show the values nested in a picked value.

Expected behavior

Being able to bind an empty bean and filling in the values even if the others are initially invalid, like it was possible before.

Minimal reproducible example

Create a maven-project and use the given files.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example.demo</groupId>
    <artifactId>vaadin-binding-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>vaadin-binding-demo</name>
    <description>Demo</description>
    <properties>
        <java.version>17</java.version>
        <vaadin.version>8.25.2</vaadin.version>
        <!-- <vaadin.version>8.23.0</vaadin.version> -->
        <vaadin-maven-plugin.version>${vaadin.version}</vaadin-maven-plugin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>   

        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-themes</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-bom</artifactId>
                <version>${vaadin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-maven-plugin</artifactId>
                <version>${vaadin-maven-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>update-theme</goal>
                            <goal>update-widgetset</goal>
                            <goal>compile</goal>
                            <!-- Comment out compile-theme goal to use on-the-fly theme compilation -->
                            <goal>compile-theme</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
public class Person {

    private String description;

    private Name name;

    public Name getName() {
        return this.name;
    }

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

    public String getDescription() {
        return this.description;
    }

    public void setDescription(final String description) {
        this.description = description;
    }
}
public class Name {

    private String name;

    private String lastName;

    public Name(final String name, final String lastName) {
        this.name = name;
        this.lastName = lastName;
    }

    public String getName() {
        return this.name;
    }

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

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(final String lastName) {
        this.lastName = lastName;
    }
}
@SpringUI
public class MyUI extends UI {

    private Person person = new Person();

    final Binder<Person> binder = new Binder<>(Person.class);

    @Override
    protected void init(final VaadinRequest request) {

        final Name maxName = new Name("Max", "Mustermann");
        final Name marieName = new Name("Marie", "Musterfrau");
        final Collection<Name> names = List.of(maxName, marieName);

        final ComboBox<Name> name = new ComboBox<>("name");
        name.setItems(names);
        name.setItemCaptionGenerator(Name::getName);

        final TextField lastName = new TextField("lastName");
        lastName.setEnabled(false);

        final TextField description = new TextField("description");

        this.binder.forField(name).asRequired().bind(Person::getName, Person::setName);
        this.binder.forField(lastName).bind(person -> person.getName() != null ? person.getName().getLastName() : "",
            null);
        this.binder.forField(description).asRequired().bind(Person::getDescription, Person::setDescription);

        this.binder.setBean(this.person);
        this.binder.addValueChangeListener(event -> this.binder.refreshFields());

        setContent(new VerticalLayout(name, lastName, description));
    }
}

Run it as Spring-Boot and open the page in a browser.

You can either enter the description or the name, either won't write back to the bean, because the other field is invalid. The call to refreshFields() on the binder should show the lastName as additional info from the picked name. If you change back to Vaadin 8.23 it works as expected.

I have not found a workaround for this and it prevents us from updating further than 8.23.

It would be great if this feature could be disabled on the binder, so it could be used strict (like in 8.24) for editing and lenient (like in 8.23) for creating a bean from scratch.

Versions

Vaadin: 8.24+ Java: Eclipse Adoptium 17.0.10.7 OS: amd64 Windows 10 Browser: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Iron Safari/537.36

thevaadinman commented 3 months ago

We're investigating this issue.