diff --git a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/main/java/com/vaadin/flow/component/radiobutton/tests/RequiredValidationPage.java b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/main/java/com/vaadin/flow/component/radiobutton/tests/RequiredValidationPage.java index 4b1aa54a60e..26270468248 100644 --- a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/main/java/com/vaadin/flow/component/radiobutton/tests/RequiredValidationPage.java +++ b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/main/java/com/vaadin/flow/component/radiobutton/tests/RequiredValidationPage.java @@ -19,19 +19,20 @@ import com.vaadin.flow.component.html.NativeButton; import com.vaadin.flow.component.radiobutton.RadioButtonGroup; import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.data.binder.Binder.Binding; import com.vaadin.flow.router.Route; @Route("vaadin-radio-button/radio-button-group-required-binder") public class RequiredValidationPage extends Div { public RequiredValidationPage() { - RadioButtonGroup group = new RadioButtonGroup<>(); + final RadioButtonGroup group = new RadioButtonGroup<>(); group.setItems("male", "female", "unknown"); group.setLabel("Gender"); Entity entity = new Entity(); Binder binder = new Binder<>(Entity.class); - binder.forField(group).bind("gender"); + Binding nonRequiredBinding = binder.forField(group).bind("gender"); group.setId("gender"); @@ -40,11 +41,34 @@ public RequiredValidationPage() { add(group); NativeButton off = new NativeButton( - "Make required indicator invisible and set requied", event -> { - group.setRequiredIndicatorVisible(false); - group.setRequired(true); + "Make required and validate", event -> { + nonRequiredBinding.unbind(); + binder.forField(group).asRequired("required").bind("gender"); + binder.validate(); }); off.setId("hide"); add(off); + + RadioButtonGroup radioGroupWithInvalidOption = new RadioButtonGroup<>(); + radioGroupWithInvalidOption.setId("radio-button-with-invalid-option"); + radioGroupWithInvalidOption.setItems("valid 1", "valid 2", "invalid"); + Binder binderForInvalidOption = new Binder<>(Entity.class); + binderForInvalidOption.forField(radioGroupWithInvalidOption) + .withValidator(value->!"invalid".equals(value), "Value is invalid") + .bind("gender"); + add(radioGroupWithInvalidOption); + + RadioButtonGroup radioGroupInvalidOnAttach = new RadioButtonGroup<>(); + radioGroupInvalidOnAttach.setId("radio-button-invalid-on-attach"); + radioGroupInvalidOnAttach.setItems("valid 1", "valid 2", "invalid"); + Binder binderForInvalidOnAttach = new Binder<>(Entity.class); + binderForInvalidOnAttach.forField(radioGroupInvalidOnAttach) + .withValidator(value->!"invalid".equals(value), "Value is invalid") + .bind("gender"); + Entity invalidBean = new Entity(); + invalidBean.setGender("invalid"); + binderForInvalidOnAttach.setBean(invalidBean); + binderForInvalidOnAttach.validate(); + add(radioGroupInvalidOnAttach); } } diff --git a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/test/java/com/vaadin/flow/component/radiobutton/tests/RadioButtonGroupIT.java b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/test/java/com/vaadin/flow/component/radiobutton/tests/RadioButtonGroupIT.java index 838b80a36a6..7fa9c27242d 100644 --- a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/test/java/com/vaadin/flow/component/radiobutton/tests/RadioButtonGroupIT.java +++ b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/test/java/com/vaadin/flow/component/radiobutton/tests/RadioButtonGroupIT.java @@ -313,30 +313,6 @@ public void assertThemeVariant() { verifyThemeVariantsBeingToggled(); } - @Test - public void groupHasLabelAndErrorMessage_setInvalidShowEM_setValueRemoveEM() { - TestBenchElement group = $(TestBenchElement.class) - .id("group-with-label-and-error-message"); - - Assert.assertEquals("Label Attribute should present with correct text", - group.getAttribute("label"), "Group label"); - - TestBenchElement errorMessage = group.$(TestBenchElement.class) - .attribute("part", "error-message").first(); - verifyGroupValid(group, errorMessage); - - layout.findElement(By.id("group-with-label-button")).click(); - verifyGroupInvalid(group, errorMessage); - - Assert.assertEquals( - "Correct error message should be shown after the button clicks", - "Field has been set to invalid from server side", - errorMessage.getText()); - - executeScript("arguments[0].value=2;", group); - verifyGroupValid(group, errorMessage); - } - @Test public void verifyHelper() { RadioButtonGroupElement groupWithHelperText = $(RadioButtonGroupElement.class) diff --git a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/test/java/com/vaadin/flow/component/radiobutton/tests/RequiredValidationIT.java b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/test/java/com/vaadin/flow/component/radiobutton/tests/RequiredValidationIT.java index 4a591a07f94..21fa832cfd2 100644 --- a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/test/java/com/vaadin/flow/component/radiobutton/tests/RequiredValidationIT.java +++ b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow-integration-tests/src/test/java/com/vaadin/flow/component/radiobutton/tests/RequiredValidationIT.java @@ -38,10 +38,39 @@ public void requiredValidation_disabledWithBinder_enabledViaExpicitCall() Boolean.parseBoolean(group.getAttribute("invalid"))); findElement(By.id("hide")).click(); - $("vaadin-radio-button").first().sendKeys(Keys.TAB); Assert.assertTrue("Radio button group should be invalid", Boolean.parseBoolean(group.getAttribute("invalid"))); } + + @Test + public void groupWithInvalidOption() { + open(); + + WebElement group = findElement(By.id("radio-button-with-invalid-option")); + WebElement radioButton = group.findElements(By.tagName("vaadin-radio-button")).get(2); + + Assert.assertFalse("Radio button group should be valid.", + Boolean.parseBoolean(group.getAttribute("invalid"))); + radioButton.click(); + + Assert.assertTrue("Radio button group should be invalid.", + Boolean.parseBoolean(group.getAttribute("invalid"))); + + radioButton.sendKeys(Keys.TAB); + Assert.assertTrue("Radio button group should keep invalid.", + Boolean.parseBoolean(group.getAttribute("invalid"))); + } + + @Test + public void groupInvalidOnAttach() { + open(); + + WebElement group = findElement(By.id("radio-button-invalid-on-attach")); + + Assert.assertTrue("Radio button group should be invalid.", + Boolean.parseBoolean(group.getAttribute("invalid"))); + } + } diff --git a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/FieldValidationUtil.java b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/FieldValidationUtil.java new file mode 100644 index 00000000000..4228db66d30 --- /dev/null +++ b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/FieldValidationUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.flow.component.radiobutton; + + +class FieldValidationUtil { + private FieldValidationUtil() { + + } + + static void disableClientValidation(RadioButtonGroup component) { + // if the component was already attached override the validate() + + component.addAttachListener(e -> overrideClientValidation(component)); + } + + private static void overrideClientValidation(RadioButtonGroup component) { + component.getElement() + .executeJs("this.validate = function () {" + + "return this.checkValidity();};"); + + component.getUI().ifPresent(ui -> ui.beforeClientResponse(component, (e) -> { + if (component.isInvalid()) { + // By default, the invalid flag is always false when a component is created. + // However, if the component is populated and validated in the same HTTP request, + // the server side state may have changed before the JavaScript disabling client + // side validation was properly executed. This can sometimes lead to a situation + // where the client side thinks the value is valid (before client side validation + // was disabled) and the server side thinks the value is invalid. This will lead to + // strange behavior until the two states are synchronized again. To avoid this, we will + // explicitly change the client side value if the server side is invalid. + component.getElement().executeJs("this.invalid = true"); + } + })); + } +} \ No newline at end of file diff --git a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/GeneratedVaadinRadioGroup.java b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/GeneratedVaadinRadioGroup.java index bfaf4dcc92a..11bd64f3239 100644 --- a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/GeneratedVaadinRadioGroup.java +++ b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/GeneratedVaadinRadioGroup.java @@ -221,14 +221,9 @@ protected void setReadonly(boolean readonly) { *

*

* This property is set to true when the value is invalid. - *

- * This property is synchronized automatically from client side when a - * 'invalid-changed' event happens. - *

* * @return the {@code invalid} property from the webcomponent */ - @Synchronize(property = "invalid", value = "invalid-changed") protected boolean isInvalidBoolean() { return getElement().getProperty("invalid", false); } diff --git a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/RadioButtonGroup.java b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/RadioButtonGroup.java index 75c3c677810..1e131111fd4 100755 --- a/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/RadioButtonGroup.java +++ b/vaadin-radio-button-flow-parent/vaadin-radio-button-flow/src/main/java/com/vaadin/flow/component/radiobutton/RadioButtonGroup.java @@ -116,6 +116,7 @@ public RadioButtonGroup() { RadioButtonGroup::modelToPresentation, true); registerValidation(); + FieldValidationUtil.disableClientValidation(this); } @Override