All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.stuff.vaadin23.field.FieldBuilderCustomField Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2022 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.stuff.vaadin23.field;

import com.vaadin.flow.component.AbstractField;
import com.vaadin.flow.component.customfield.CustomField;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;

/**
 * Support superclass that mostly automates the creation of {@link CustomField}s for editing any model type
 * using sub-fields automatically generated from {@link FieldBuilder} annotations to arbitrary recursion depth.
 *
 * 
 * 
 * 
 *
 * 

* This class is just a {@link BinderCustomField} that uses a {@link FieldBuilder} to generate the sub-fields. * *

Example * *

* Suppose model class {@code Contract} has a "term" property of type {@code DateInterval} which has a start and end date. * We want to use {@link FieldBuilder} annotations to define the editor fields for the properties of {@code Contract}, * including {@code "term"}, but for that to work we will need to specify a custom field to handle the {@code "term"} property. * We also want this custom field to contain the logic for laying out the two date picker components as well as for * validating proper ordering. * *

* For example: * *


 * // This is complex type that we wish to edit with a single CustomField
 * public class DateInterval {
 *
 *     @FieldBuilder.DatePicker(label = "Start date")
 *     public LocalDate getStartDate() { ... }
 *     public void setStartDate(LocalDate date) { ... }
 *
 *     @FieldBuilder.DatePicker(label = "End date")
 *     public LocalDate getEndDate() { ... }
 *     public void setEndDate(LocalDate date) { ... }
 * }
 *
 * // This is the corresponding custom field
 * public class DateIntervalField extends FieldBuilderCustomField<DateInterval> {
 *
 *     public DateIntervalField() {
 *         super(DateInterval.class);
 *     }
 *
 *     // Customize how we want to layout the subfields
 *     @Override
 *     protected void layoutComponents() {
 *        this.add(new HorizontalLayout(
 *          new Text("From"), this.getField("startDate")), new Text("to"), this.getField("endDate"));
 *     }
 *
 *     // Bean level validation: ensure end date is after start date
 *     @Override
 *     public ValidationResult validate(DateInterval dates, ValueContext ctx) {
 *         if (dates.getStartDate().isAfter(dates.getEndDate())
 *             return ValidationResult.error("Dates out-of-order"));
 *         return super.validate(dates, ctx);           // always ensure superclass contraints apply also
 *     }
 * }
 * 
* *

* Once that's done, using {@link FieldBuilder} works recursively and automatically for this multi-level class: * *


 * public class Contract {
 *
 *     @FieldBuilder.CheckBox(label = "Approved?")
 *     public boolean isApproved() { ... }
 *     public void setApproved(boolean approved) { ... }
 *
 *     @FieldBuilder.CustomField(label = "Term", implementation = DateIntervalField.class)
 *     public DateInterval getTerm() { ... }
 *     public void setTerm(DateInterval term) { ... }
 * }
 * 
* * @param field value type */ @SuppressWarnings("serial") public class FieldBuilderCustomField extends BinderCustomField { /** * The field builder that builds this instance's sub-fields. */ protected AbstractFieldBuilder fieldBuilder; /** * Constructor. * * @param modelType model type to introspect for {@link AbstractFieldBuilder} annotations * @throws IllegalArgumentException if {@code modelType} is null */ public FieldBuilderCustomField(Class modelType) { super(modelType); } /** * {@inheritDoc} * *

* The implementation in {@link FieldBuilderCustomField} just invokes * {@link FieldBuilder#bindFields FieldBuilder.bindFields}{@code (this.binder)}. */ @Override protected void createAndBindFields() { this.fieldBuilder = this.createFieldBuilder(); this.fieldBuilder.bindFields(this.binder); } /** * Create a new {@link AbstractFieldBuilder} for the given type. * *

* The implementation in {@link FieldBuilderCustomField} returns a new {@link FieldBuilder} each time. * * @return field builder */ protected AbstractFieldBuilder createFieldBuilder() { return new FieldBuilder<>(this.modelType); } /** * Layout components required for this field. * *

* The implementation in {@link FieldBuilderCustomField} iterates the bound fields in {@link #fieldBuilder} * into a new {@link HorizontalLayout} which is then added to this instance. */ protected void layoutComponents() { final HorizontalLayout layout = new HorizontalLayout(); this.add(layout); this.fieldBuilder.getFieldComponents().values().stream() .map(FieldComponent::getComponent) .forEach(layout::add); } /** * Get the {@link FieldComponent} sub-field corresponding to the given field name. * * @param name field name * @return corresponding {@link FieldComponent} * @throws IllegalArgumentException if {@code name} is not found * @throws IllegalArgumentException if {@code name} is null */ protected AbstractField getField(String name) { final FieldComponent fieldComponent = this.getFieldComponent(name); try { return (AbstractField)fieldComponent.getField(); } catch (ClassCastException e) { throw new IllegalArgumentException("not an AbstractField: " + name); } } /** * Get the {@link AbstractField} sub-field corresponding to the given field name. * * @param name field name * @return corresponding {@link AbstractField} * @throws IllegalArgumentException if {@code name} is not found * @throws IllegalArgumentException if {@code name}'s {@linkplain FieldComponent#getField field} is not an {@link AbstractField} * @throws IllegalArgumentException if {@code name} is null */ protected FieldComponent getFieldComponent(String name) { if (name == null) throw new IllegalArgumentException("null name"); final FieldComponent fieldComponent = this.fieldBuilder.getFieldComponents().get(name); if (fieldComponent == null) throw new IllegalArgumentException("no such field: " + name); return fieldComponent; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy