Search code examples
vaadinvaadin-flowvaadin23

How to propagate validation to CustomField sub Components


I have a simple CustomComponent with Person being an simple data class with two string TextFields: name and sname.

This is the class:

data class Person(var name:String, var birth:LocalDate)

class PersonField : CustomField<Person>(){

    private val nameField:TextField
    private val birthField:DatePicker

    init {
        nameField = TextField().apply {
            placeholder = "Name"

            addValueChangeListener{updateValue()}
        }

        birthField = DatePicker().apply {
            placeholder = "Birth"

            addValueChangeListener{updateValue()}
        }

        add(nameField, birthField)
        setWidthFull()
    }

    override fun setPresentationValue(person: Person?) {
        if (person != null) {
            nameField?.value = person.name
            birthField?.value = person.birth
        }
    }

    override fun generateModelValue(): Person {
        return Person(nameField.value, birthField.value)
    }
}

And this is the binder where I use two validators:

binder.forField(personField)
            .withValidator({ value ->
                value?.name?.isNotEmpty() ?: false || value?.name?.isNotEmpty() ?: false
            }, "Name is Required")
            .withValidator({ value ->
                value.birth.isBefore(LocalDate.now())
            }, "Birth must be before today")
            .bind({person->person}, {person,field-> person.name=field.name;person.birth=field.birth})

Validation works correctly but I'd like to highligh the specific field that generated the error. Instead it only gives me the error message:

enter image description here

How could I make the "Name" subfield to turn red-ish whe the proper validator fires?


Solution

  • You need to move the binder inside your CustomField and bind to the internal TextField data DateField. Additionally when you do so, you can implement DefaultValidator inside your custom field. Something like:

    @Override
    public Validator<LocalDate> getDefaultValidator() {
        return (value, context) -> { 
            if (binder.isValid()) {
                return ValidationResult.ok();
            } else {
                return ValidationResult.error("Person is not valid");
            }
        }
    }
    

    This will chain inner and outer binders, if you bind PersonField in a form using another binder there.