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

feat: add i18n error message support for ComboBox, MultiSelectComboBox #6474

Merged
merged 8 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
public class ComboBoxBasicValidationPage
extends AbstractValidationPage<ComboBox<String>> {
public static final String REQUIRED_BUTTON = "required-button";
public static final String REQUIRED_ERROR_MESSAGE = "Field is required";
public static final String ENABLE_CUSTOM_VALUE_BUTTON = "enable-custom-value-button";

public ComboBoxBasicValidationPage() {
testField.setI18n(new ComboBox.ComboBoxI18n()
.setRequiredErrorMessage(REQUIRED_ERROR_MESSAGE));

add(createButton(REQUIRED_BUTTON, "Enable required", event -> {
testField.setRequired(true);
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.vaadin.flow.component.combobox.test.validation;

import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.combobox.MultiSelectComboBoxI18n;
import com.vaadin.flow.router.Route;
import com.vaadin.tests.validation.AbstractValidationPage;

Expand All @@ -10,9 +11,13 @@
public class MultiSelectComboBoxBasicValidationPage
extends AbstractValidationPage<MultiSelectComboBox<String>> {
public static final String REQUIRED_BUTTON = "required-button";
public static final String REQUIRED_ERROR_MESSAGE = "Field is required";
public static final String ENABLE_CUSTOM_VALUE_BUTTON = "enable-custom-value-button";

public MultiSelectComboBoxBasicValidationPage() {
testField.setI18n(new MultiSelectComboBoxI18n()
.setRequiredErrorMessage(REQUIRED_ERROR_MESSAGE));

add(createButton(REQUIRED_BUTTON, "Enable required", event -> {
testField.setRequired(true);
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import static com.vaadin.flow.component.combobox.test.validation.ComboBoxBasicValidationPage.ENABLE_CUSTOM_VALUE_BUTTON;
import static com.vaadin.flow.component.combobox.test.validation.ComboBoxBasicValidationPage.REQUIRED_BUTTON;
import static com.vaadin.flow.component.combobox.test.validation.ComboBoxBasicValidationPage.REQUIRED_ERROR_MESSAGE;

@TestPath("vaadin-combo-box/validation/basic")
public class ComboBoxBasicValidationIT
Expand All @@ -17,6 +18,7 @@ public class ComboBoxBasicValidationIT
public void fieldIsInitiallyValid() {
assertClientValid();
assertServerValid();
assertErrorMessage(null);
}

@Test
Expand All @@ -25,6 +27,7 @@ public void triggerBlur_assertValidity() {
assertValidationCount(0);
assertServerValid();
assertClientValid();
assertErrorMessage(null);
}

@Test
Expand All @@ -35,6 +38,7 @@ public void required_triggerBlur_assertValidity() {
assertValidationCount(0);
assertServerValid();
assertClientValid();
assertErrorMessage(null);
}

@Test
Expand All @@ -45,11 +49,13 @@ public void required_changeValue_assertValidity() {
assertValidationCount(1);
assertServerValid();
assertClientValid();
assertErrorMessage("");

testField.clear();
assertValidationCount(1);
assertServerInvalid();
assertClientInvalid();
assertErrorMessage(REQUIRED_ERROR_MESSAGE);
}

@Test
Expand All @@ -60,6 +66,7 @@ public void required_enterCustomValue_assertValidity() {
assertValidationCount(0);
assertServerValid();
assertClientValid();
assertErrorMessage(null);
}

@Test
Expand All @@ -71,6 +78,7 @@ public void required_customValuesAllowed_enterCustomValue_assertValidity() {
assertValidationCount(1);
assertServerValid();
assertClientValid();
assertErrorMessage("");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import static com.vaadin.flow.component.combobox.test.validation.MultiSelectComboBoxBasicValidationPage.ENABLE_CUSTOM_VALUE_BUTTON;
import static com.vaadin.flow.component.combobox.test.validation.MultiSelectComboBoxBasicValidationPage.REQUIRED_BUTTON;
import static com.vaadin.flow.component.combobox.test.validation.MultiSelectComboBoxBasicValidationPage.REQUIRED_ERROR_MESSAGE;

@TestPath("vaadin-multi-select-combo-box/validation/basic")
public class MultiSelectComboBoxBasicValidationIT
Expand All @@ -17,6 +18,7 @@ public class MultiSelectComboBoxBasicValidationIT
public void fieldIsInitiallyValid() {
assertClientValid();
assertServerValid();
assertErrorMessage(null);
}

@Test
Expand All @@ -25,6 +27,7 @@ public void triggerBlur_assertValidity() {
assertValidationCount(0);
assertServerValid();
assertClientValid();
assertErrorMessage(null);
}

@Test
Expand All @@ -35,6 +38,7 @@ public void required_triggerBlur_assertValidity() {
assertValidationCount(0);
assertServerValid();
assertClientValid();
assertErrorMessage(null);
}

@Test
Expand All @@ -45,11 +49,13 @@ public void required_changeValue_assertValidity() {
assertValidationCount(1);
assertServerValid();
assertClientValid();
assertErrorMessage("");

testField.deselectAll();
assertValidationCount(1);
assertServerInvalid();
assertClientInvalid();
assertErrorMessage(REQUIRED_ERROR_MESSAGE);
}

@Test
Expand All @@ -60,6 +66,7 @@ public void required_enterCustomValue_assertValidity() {
assertValidationCount(0);
assertServerValid();
assertClientValid();
assertErrorMessage(null);
}

@Test
Expand All @@ -71,11 +78,13 @@ public void required_customValuesAllowed_enterCustomValue_assertValidity() {
assertValidationCount(1);
assertServerValid();
assertClientValid();
assertErrorMessage("");

testField.deselectAll();
assertValidationCount(1);
assertServerInvalid();
assertClientInvalid();
assertErrorMessage(REQUIRED_ERROR_MESSAGE);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import java.io.Serializable;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

import com.vaadin.flow.component.HasSize;
Expand Down Expand Up @@ -353,4 +355,66 @@ public void setOverlayWidth(float width, Unit unit) {
Objects.requireNonNull(unit, "Unit can not be null");
setOverlayWidth(HasSize.getCssSize(width, unit));
}

/**
* Gets the internationalization object previously set for this component.
* <p>
* NOTE: Updating the instance that is returned from this method will not
* update the component if not set again using
* {@link #setI18n(ComboBoxI18n)}
*
* @return the i18n object or {@code null} if no i18n object has been set
*/
public ComboBoxI18n getI18n() {
return (ComboBoxI18n) super.getI18n();
}

/**
* Sets the internationalization object for this component.
*
* @param i18n
* the i18n object, not {@code null}
*/
public void setI18n(ComboBoxI18n i18n) {
super.setI18n(i18n);
}

/**
* The internationalization properties for {@link ComboBox}.
*/
public static class ComboBoxI18n implements ComboBoxBaseI18n {

private String requiredErrorMessage;

/**
* Gets the error message displayed when the field is required but
* empty.
*
* @return the error message or {@code null} if not set
* @see ComboBox#isRequiredIndicatorVisible()
* @see ComboBox#setRequiredIndicatorVisible(boolean)
*/
public String getRequiredErrorMessage() {
return requiredErrorMessage;
}

/**
* Sets the error message to display when the field is required but
* empty.
* <p>
* Note, custom error messages set with
* {@link ComboBox#setErrorMessage(String)} take priority over i18n
* error messages.
*
* @param errorMessage
* the error message or {@code null} to clear it
* @return this instance for method chaining
* @see ComboBox#isRequiredIndicatorVisible()
* @see ComboBox#setRequiredIndicatorVisible(boolean)
*/
public ComboBoxI18n setRequiredErrorMessage(String errorMessage) {
requiredErrorMessage = errorMessage;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import com.vaadin.flow.component.shared.InputField;
import com.vaadin.flow.component.shared.ValidationUtil;
import com.vaadin.flow.data.binder.HasValidator;
import com.vaadin.flow.data.binder.ValidationResult;
import com.vaadin.flow.data.provider.BackEndDataProvider;
import com.vaadin.flow.data.provider.CallbackDataProvider;
import com.vaadin.flow.data.provider.CompositeDataGenerator;
Expand All @@ -69,6 +70,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

/**
* Provides base functionality for combo box related components, such as
Expand Down Expand Up @@ -123,8 +125,13 @@ public void remove() {
private final ComboBoxDataController<TItem> dataController;
private int customValueListenersCount;

private ComboBoxBaseI18n i18n;

private boolean manualValidationEnabled = false;

private String customErrorMessage;
private String constraintErrorMessage;

/**
* Constructs a new ComboBoxBase instance
*
Expand Down Expand Up @@ -184,6 +191,45 @@ public Locale get() {
addValueChangeListener(e -> validate());
}

/**
* Sets an error message to display for all constraint violations.
* <p>
* This error message takes priority over i18n error messages when both are
* set.
*
* @param errorMessage
* the error message to set, or {@code null} to clear
*
*/
@Override
public void setErrorMessage(String errorMessage) {
customErrorMessage = errorMessage;
updateErrorMessage();
}

/**
* Gets the error message displayed for all constraint violations.
*
* @return the error message
*/
@Override
public String getErrorMessage() {
return customErrorMessage;
}

private void setConstraintErrorMessage(String errorMessage) {
constraintErrorMessage = errorMessage;
updateErrorMessage();
}

private void updateErrorMessage() {
String errorMessage = constraintErrorMessage;
if (customErrorMessage != null && !customErrorMessage.isEmpty()) {
errorMessage = customErrorMessage;
}
getElement().setProperty("errorMessage", errorMessage);
}

/**
* Whether the component should automatically receive focus when the page
* loads.
Expand Down Expand Up @@ -1190,13 +1236,29 @@ public void setManualValidation(boolean enabled) {
this.manualValidationEnabled = enabled;
}

/**
* Validates the current value against the constraints and sets the
* {@code invalid} property and the {@code errorMessage} property based on
* the result. If a custom error message is provided with
* {@link #setErrorMessage(String)}, it is used. Otherwise, the error
* message defined in the i18n object is used.
* <p>
* The method does nothing if the manual validation mode is enabled.
*/
protected void validate() {
if (!this.manualValidationEnabled) {
boolean isInvalid = ValidationUtil.validateRequiredConstraint("",
isRequiredIndicatorVisible(), getValue(), getEmptyValue())
.isError();
if (this.manualValidationEnabled) {
return;
}

setInvalid(isInvalid);
ValidationResult result = ValidationUtil.validateRequiredConstraint(
getI18nErrorMessage(ComboBoxBaseI18n::getRequiredErrorMessage),
isRequiredIndicatorVisible(), getValue(), getEmptyValue());
if (result.isError()) {
setInvalid(true);
setConstraintErrorMessage(result.getErrorMessage());
} else {
setInvalid(false);
setConstraintErrorMessage("");
}
}

Expand All @@ -1223,4 +1285,33 @@ public String getDetail() {
return detail;
}
}

/**
* Gets the internationalization object previously set for this component.
* <p>
* NOTE: Updating the instance that is returned from this method will not
* update the component if not set again using
* {@link #setI18n(ComboBoxBaseI18n)}
*
* @return the i18n object or {@code null} if no i18n object has been set
*/
protected ComboBoxBaseI18n getI18n() {
return i18n;
}

/**
* Sets the internationalization object for this component.
*
* @param i18n
* the i18n object, not {@code null}
*/
protected void setI18n(ComboBoxBaseI18n i18n) {
this.i18n = Objects.requireNonNull(i18n,
"The i18n properties object should not be null");
}

private String getI18nErrorMessage(
Function<ComboBoxBaseI18n, String> getter) {
return Optional.ofNullable(i18n).map(getter).orElse("");
}
}
Loading