diff --git a/src/main/java/com/vaadin/flow/component/combobox/ComboBox.java b/src/main/java/com/vaadin/flow/component/combobox/ComboBox.java index 76829689..4dfcc745 100644 --- a/src/main/java/com/vaadin/flow/component/combobox/ComboBox.java +++ b/src/main/java/com/vaadin/flow/component/combobox/ComboBox.java @@ -49,6 +49,7 @@ import com.vaadin.flow.function.SerializableFunction; import com.vaadin.flow.internal.JsonUtils; import com.vaadin.flow.shared.Registration; + import elemental.json.Json; import elemental.json.JsonObject; import elemental.json.JsonValue; @@ -196,7 +197,8 @@ public interface ItemFilter extends SerializableBiPredicate { private boolean renderScheduled; // Filter set by the client when requesting data. It's sent back to client - // together with the response so client may know for what filter data is provided. + // together with the response so client may know for what filter data is + // provided. private String lastFilter; private DataCommunicator dataCommunicator; @@ -878,6 +880,13 @@ public Registration addCustomValueSetListener( return new CustomValueRegistration(registration); } + @Override + public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { + super.setRequiredIndicatorVisible(requiredIndicatorVisible); + getElement().callFunction("$connector.enableClientValidation", + !requiredIndicatorVisible); + } + CompositeDataGenerator getDataGenerator() { return dataGenerator; } diff --git a/src/main/resources/META-INF/resources/frontend/comboBoxConnector.js b/src/main/resources/META-INF/resources/frontend/comboBoxConnector.js index 1dd5d54d..a1857816 100644 --- a/src/main/resources/META-INF/resources/frontend/comboBoxConnector.js +++ b/src/main/resources/META-INF/resources/frontend/comboBoxConnector.js @@ -162,6 +162,61 @@ window.Vaadin.Flow.comboBoxConnector = { // Let server know we're done comboBox.$server.confirmUpdate(id); } + + comboBox.$connector.enableClientValidation = function( enable ){ + let input = comboBox.$["input"]; + if (input){ + if ( enable){ + enableClientValidation(comboBox); + enableTextFieldClientValidation(input); + } + else { + disableClientValidation(comboBox); + disableTextFieldClientValidation(input,comboBox ); + } + } + else { + setTimeout( function(){ + comboBox.$connector.enableClientValidation(enable); + }, 10); + } + } + + const disableClientValidation = function (combo){ + if ( typeof combo.$checkValidity == 'undefined'){ + combo.$checkValidity = combo.checkValidity; + combo.checkValidity = function() { return true; }; + } + if ( typeof combo.$validate == 'undefined'){ + combo.$validate = combo.validate; + combo.validate = function() { return true; }; + } + } + + const disableTextFieldClientValidation = function (field, comboBox){ + if ( typeof field.$checkValidity == 'undefined'){ + field.$checkValidity = field.checkValidity; + field.checkValidity = function() { return !comboBox.invalid; }; + } + } + + const enableTextFieldClientValidation = function (field){ + if ( field.$checkValidity ){ + field.checkValidity = field.$checkValidity; + delete field.$checkValidity; + } + } + + const enableClientValidation = function (combo){ + if ( combo.$checkValidity ){ + combo.checkValidity = combo.$checkValidity; + delete combo.$checkValidity; + } + if ( combo.$validate ){ + combo.validate = combo.$validate; + delete combo.$validate; + } + } const commitPage = function (page, callback) { let data = cache[page]; diff --git a/src/test/java/com/vaadin/flow/component/combobox/test/AbstractComboBoxIT.java b/src/test/java/com/vaadin/flow/component/combobox/test/AbstractComboBoxIT.java index f7b59d09..9dcd4577 100644 --- a/src/test/java/com/vaadin/flow/component/combobox/test/AbstractComboBoxIT.java +++ b/src/test/java/com/vaadin/flow/component/combobox/test/AbstractComboBoxIT.java @@ -21,13 +21,15 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.junit.Assert; +import org.junit.Ignore; +import org.openqa.selenium.WebElement; + import com.vaadin.flow.component.combobox.testbench.ComboBoxElement; import com.vaadin.flow.testutil.AbstractComponentIT; import com.vaadin.testbench.TestBenchElement; + import elemental.json.JsonObject; -import org.junit.Assert; -import org.junit.Ignore; -import org.openqa.selenium.WebElement; @Ignore public class AbstractComboBoxIT extends AbstractComponentIT { diff --git a/src/test/java/com/vaadin/flow/component/combobox/test/RequiredComboboxIT.java b/src/test/java/com/vaadin/flow/component/combobox/test/RequiredComboboxIT.java new file mode 100644 index 00000000..a0f7a37d --- /dev/null +++ b/src/test/java/com/vaadin/flow/component/combobox/test/RequiredComboboxIT.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2018 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.combobox.test; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.flow.component.combobox.testbench.ComboBoxElement; +import com.vaadin.flow.testutil.AbstractComponentIT; +import com.vaadin.flow.testutil.TestPath; +import com.vaadin.testbench.TestBenchElement; + +@TestPath("required-combobox") +public class RequiredComboboxIT extends AbstractComponentIT { + + @Test + public void serverSideValidation_persistsOnBlur() { + open(); + + ComboBoxElement comboBox = $(ComboBoxElement.class).first(); + + // Select an invalid item + comboBox.openPopup(); + executeScript( + "arguments[0].selectedItem = arguments[0].filteredItems[0]", + comboBox); + + // The validation shows errors + assertValidationError(comboBox); + + TestBenchElement msg = $(TestBenchElement.class).id("message"); + Assert.assertEquals("Value changed from 'null' to 'foo'", + msg.getText()); + + // blur + msg.click(); + + // validation error is still shown + assertValidationError(comboBox); + + // change the item to a valid one + comboBox.openPopup(); + executeScript( + "arguments[0].selectedItem = arguments[0].filteredItems[1]", + comboBox); + + // no invalid attribute + Assert.assertEquals(Boolean.FALSE.toString(), + comboBox.getAttribute("invalid")); + // the error message is not visible + TestBenchElement error = comboBox.$("vaadin-text-field").first() + .$(TestBenchElement.class).id("vaadin-text-field-error-0"); + waitUntil(driver -> error.getSize().getHeight() == 0); + } + + private void assertValidationError(ComboBoxElement comboBox) { + Assert.assertEquals(Boolean.TRUE.toString(), + comboBox.getAttribute("invalid")); + + TestBenchElement error = comboBox.$("vaadin-text-field").first() + .$(TestBenchElement.class).id("vaadin-text-field-error-0"); + Assert.assertTrue(error.getSize().getHeight() > 0); + Assert.assertEquals("'foo' is invalid value", error.getText()); + } +} diff --git a/src/test/java/com/vaadin/flow/component/combobox/test/RequiredComboboxPage.java b/src/test/java/com/vaadin/flow/component/combobox/test/RequiredComboboxPage.java new file mode 100644 index 00000000..f99726a2 --- /dev/null +++ b/src/test/java/com/vaadin/flow/component/combobox/test/RequiredComboboxPage.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2018 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.combobox.test; + +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.combobox.bean.TestItem; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.router.Route; + +@Route("required-combobox") +public class RequiredComboboxPage extends Div { + + public RequiredComboboxPage() { + Div message = new Div(); + message.setId("message"); + + Binder binder = new Binder<>(); + + ComboBox comboBox = new ComboBox<>(); + comboBox.setItems("foo", "bar"); + comboBox.addValueChangeListener(event -> message + .setText(String.format("Value changed from '%s' to '%s'", + event.getOldValue(), event.getValue()))); + + binder.forField(comboBox).asRequired() + .withValidator(value -> !"foo".equals(value), + "'foo' is invalid value") + .bind(TestItem::getName, TestItem::setName); + TestItem item = new TestItem(0); + binder.setBean(item); + + add(comboBox, message); + } +}