Created
March 21, 2024 09:36
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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