Skip to content

Instantly share code, notes, and snippets.

@TatuLund
Created March 21, 2024 09:36
Show Gist options
  • Save TatuLund/9ede667ae2934ad7e86b24937d160077 to your computer and use it in GitHub Desktop.
Save TatuLund/9ede667ae2934ad7e86b24937d160077 to your computer and use it in GitHub Desktop.
In Vaadin 24 there is a feature of client side validation support for Binder. This is particularly important for some fields like DatePicker. However you can extend the functionality to many other fields if you so wish. For example if you want the ComboBox to show validation error when the input is not matching the suggestions, here is an example.
package com.example.application.views;
import java.util.concurrent.CopyOnWriteArrayList;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.Notification.Position;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.binder.ValidationStatusChangeEvent;
import com.vaadin.flow.data.binder.ValidationStatusChangeListener;
import com.vaadin.flow.data.binder.Validator;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.shared.Registration;
@SuppressWarnings("serial")
@Route(value = "validating-combo", layout = MainLayout.class)
public class ValidatingComboView extends VerticalLayout {
public ValidatingComboView() {
var binder = new Binder<Bean>();
var field = new ValidatingComboBox<String>("Name");
field.setItems("John", "Matt", "Roger", "Alice", "Ella", "Rebecca");
field.setValidationErrorMessage("Does not match one of the items");
binder.forField(field).bind(Bean::getName, Bean::setName);
var bean = new Bean();
var label = new Span();
binder.setBean(bean);
binder.addValueChangeListener(e -> {
Notification.show("New value: " + bean.getName())
.setPosition(Position.TOP_START);
label.setText(bean.getName());
});
add(field, label);
}
public static class ValidatingComboBox<T> extends ComboBox<T> {
private Boolean valid = null;
private final CopyOnWriteArrayList<ValidationStatusChangeListener<T>> validationStatusChangeListeners = new CopyOnWriteArrayList<>();
private String error = "Not matching items";
public ValidatingComboBox(String label) {
super(label);
addClientValidatedEventListener(e -> {
if (valid == null || e.isValid() != valid) {
valid = e.isValid();
fireValidationStatusChangeEvent(e.isValid());
} else {
valid = e.isValid();
}
});
addValueChangeListener(e -> {
if (e.isFromClient()) {
valid = true;
fireValidationStatusChangeEvent(true);
}
});
getElement().executeJs(
"""
$0.inputElement.addEventListener('keydown', (e) => {
console.warn($0.inputElement.value);
const event = new CustomEvent('validated', {
detail: {
valid: false
},
composed: true,
cancelable: true,
bubbles: true
});
const filtered = $0.filteredItems.map(item => item.label);
console.warn(filtered);
if (filtered.includes($0.inputElement.value)) {
event.detail.valid = true;
}
console.log(event);
$0.dispatchEvent(event);
});
$0.inputElement.addEventListener('change', (e) => {
console.warn($0.value);
const event = new CustomEvent('validated', {
detail: {
valid: true
},
composed: true,
cancelable: true,
bubbles: true
});
$0.dispatchEvent(event);
});
""",
getElement());
}
@Override
public Registration addValidationStatusChangeListener(
ValidationStatusChangeListener<T> listener) {
return Registration.addAndRemove(validationStatusChangeListeners,
listener);
}
private void fireValidationStatusChangeEvent(boolean newStatus) {
ValidationStatusChangeEvent<T> event = new ValidationStatusChangeEvent<>(
this, newStatus);
validationStatusChangeListeners.forEach(
listener -> listener.validationStatusChanged(event));
}
@Override
public Validator<T> getDefaultValidator() {
return Validator.from((value) -> {
return valid == null || valid;
}, error);
}
public void setValidationErrorMessage(String errorMessage) {
this.error = errorMessage;
}
}
public static class Bean {
private String name = "Name";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment