Skip to content

Commit

Permalink
feat: add i18n error message support for Checkbox, CheckboxGroup (#6475)
Browse files Browse the repository at this point in the history
  • Loading branch information
vursen authored Jul 30, 2024
1 parent c8d1afe commit 2834347
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
public class BasicValidationPage
extends AbstractValidationPage<CheckboxGroup<String>> {
public static final String REQUIRED_BUTTON = "required-button";
public static final String REQUIRED_ERROR_MESSAGE = "Field is required";

public BasicValidationPage() {
testField.setI18n(new CheckboxGroup.CheckboxGroupI18n()
.setRequiredErrorMessage(REQUIRED_ERROR_MESSAGE));

add(createButton(REQUIRED_BUTTON, "Enable required", event -> {
testField.setRequired(true);
}));
Expand All @@ -27,7 +31,6 @@ protected void validate() {
}
};
checkboxGroup.setItems(List.of("foo", "bar", "baz"));

return checkboxGroup;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
public class CheckboxBasicValidationPage
extends AbstractValidationPage<Checkbox> {
public static final String REQUIRED_BUTTON = "required-button";
public static final String REQUIRED_ERROR_MESSAGE = "Field is required";

public CheckboxBasicValidationPage() {
testField.setI18n(new Checkbox.CheckboxI18n()
.setRequiredErrorMessage(REQUIRED_ERROR_MESSAGE));

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

import static com.vaadin.flow.component.checkbox.tests.validation.BasicValidationPage.REQUIRED_BUTTON;
import static com.vaadin.flow.component.checkbox.tests.validation.BasicValidationPage.REQUIRED_ERROR_MESSAGE;

@TestPath("vaadin-checkbox-group/validation/basic")
public class BasicValidationIT
Expand All @@ -17,6 +18,7 @@ public class BasicValidationIT
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.deselectByText("foo");
assertValidationCount(1);
assertServerInvalid();
assertClientInvalid();
assertErrorMessage(REQUIRED_ERROR_MESSAGE);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import org.junit.Test;
import org.openqa.selenium.Keys;

import static com.vaadin.flow.component.checkbox.tests.validation.BasicValidationPage.REQUIRED_BUTTON;
import static com.vaadin.flow.component.checkbox.tests.validation.CheckboxBasicValidationPage.REQUIRED_BUTTON;
import static com.vaadin.flow.component.checkbox.tests.validation.CheckboxBasicValidationPage.REQUIRED_ERROR_MESSAGE;

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

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

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

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

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

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.junit.Test;
import org.openqa.selenium.Keys;

import static com.vaadin.flow.component.checkbox.tests.validation.BinderValidationPage.REQUIRED_ERROR_MESSAGE;
import static com.vaadin.flow.component.checkbox.tests.validation.CheckboxBinderValidationPage.REQUIRED_ERROR_MESSAGE;

@TestPath("vaadin-checkbox/validation/binder")
public class CheckboxBinderValidationIT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@
import com.vaadin.flow.dom.ElementConstants;
import com.vaadin.flow.dom.PropertyChangeListener;

import java.io.Serializable;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

/**
* Checkbox is an input field representing a binary choice.
Expand Down Expand Up @@ -68,8 +71,13 @@ public class Checkbox extends AbstractSinglePropertyField<Checkbox, Boolean>
private String ariaLabel;
private String ariaLabelledBy;

private CheckboxI18n i18n;

private boolean manualValidationEnabled = false;

private String customErrorMessage;
private String constraintErrorMessage;

/**
* Default constructor.
*/
Expand Down Expand Up @@ -193,6 +201,44 @@ protected void onAttach(AttachEvent attachEvent) {
ClientValidationUtil.preventWebComponentFromModifyingInvalidState(this);
}

/**
* 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);
}

/**
* Get the current label text.
*
Expand Down Expand Up @@ -328,27 +374,29 @@ public void setManualValidation(boolean enabled) {
}

/**
* Performs server-side validation of the current value. This is needed
* because it is possible to circumvent the client-side validation
* constraints using browser development tools.
* 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) {
setInvalid(isInvalid(getValue()));
if (this.manualValidationEnabled) {
return;
}
}

/**
* Performs a server-side validation of the given value. This is needed
* because it is possible to circumvent the client side validation
* constraints using browser development tools.
*/
private boolean isInvalid(Boolean value) {
ValidationResult requiredValidation = ValidationUtil
.validateRequiredConstraint("", isRequiredIndicatorVisible(),
value, getEmptyValue());

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

/**
Expand All @@ -370,4 +418,71 @@ boolean isDisabledBoolean() {
return getElement().getProperty("disabled", false);
}

/**
* 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(CheckboxI18n)}
*
* @return the i18n object or {@code null} if no i18n object has been set
*/
public CheckboxI18n getI18n() {
return i18n;
}

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

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

/**
* The internationalization properties for {@link Checkbox}.
*/
public static class CheckboxI18n implements Serializable {

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 Checkbox#isRequiredIndicatorVisible()
* @see Checkbox#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 Checkbox#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 Checkbox#isRequiredIndicatorVisible()
* @see Checkbox#setRequiredIndicatorVisible(boolean)
*/
public CheckboxI18n setRequiredErrorMessage(String errorMessage) {
requiredErrorMessage = errorMessage;
return this;
}
}

}
Loading

0 comments on commit 2834347

Please sign in to comment.