Class FieldBuilderCustomField<T>
- Type Parameters:
T
- field value type
- All Implemented Interfaces:
AttachNotifier
,BlurNotifier<CustomField<T>>
,DetachNotifier
,Focusable<CustomField<T>>
,FocusNotifier<CustomField<T>>
,HasElement
,HasEnabled
,HasHelper
,HasLabel
,HasSize
,HasStyle
,HasTheme
,HasValidation
,HasValue<AbstractField.ComponentValueChangeEvent<CustomField<T>,
,T>, T> HasValueAndElement<AbstractField.ComponentValueChangeEvent<CustomField<T>,
,T>, T> HasTooltip
,HasValidationProperties
,InputField<AbstractField.ComponentValueChangeEvent<CustomField<T>,
,T>, T> Serializable
,ValidatingField<AbstractField.ComponentValueChangeEvent<CustomField<T>,
T>, T>
CustomField
s for editing any model type
using sub-fields automatically generated from FieldBuilder
annotations to arbitrary recursion depth.
This class is just a BinderCustomField
that uses a FieldBuilder
to generate the sub-fields.
Example
Suppose model class Contract
has a "term" property of type DateInterval
which has a start and end date.
We want to use FieldBuilder
annotations to define the editor fields for the properties of Contract
,
including "term"
, but for that to work we will need to specify a custom field to handle the "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 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) { ... }
}
If you don't need to do any customization, you can use FieldBuilderCustomField
directly; the field's
value type will be inferred by the FieldBuilderCustomField(FieldBuilderContext)
constructor:
public class Contract {
@FieldBuilder.CheckBox(label = "Approved?")
public boolean isApproved() { ... }
public void setApproved(boolean approved) { ... }
// "DateInterval" value type is inferred from the method's return type
@FieldBuilder.CustomField(label = "Term", implementation = FieldBuilderCustomField.class)
public DateInterval getTerm() { ... }
public void setTerm(DateInterval term) { ... }
}
Edit in Dialog Window
For more complex value types, this class support an alternative display model in which the value's sub-fields
are edited in a Dialog
window instead of inline in the form. In lieu of the inline sub-fields, the form
displays an "Edit" button which, when clicked, opens a new Dialog
window into which the sub-fields
are laid out, plus "OK" and "Cancel" buttons.
To configure an edit dialog, add a @FieldBuilderCustomField.DialogForm
annotation to the method:
public class Contract {
@FieldBuilder.CheckBox(label = "Approved?")
public boolean isApproved() { ... }
public void setApproved(boolean approved) { ... }
@FieldBuilder.CustomField(label = "Term", implementation = FieldBuilderCustomField.class)
@FieldBuilderCustomField.DialogForm(windowTitle = "Contract Term")
public DateInterval getTerm() { ... }
public void setTerm(DateInterval term) { ... }
}
Nullable Values
When the field's value can be null, consider adding a
@FieldBuilder.NullifyCheckbox
annotation.
- See Also:
-
Nested Class Summary
Nested classes/interfaces inherited from class com.vaadin.flow.component.AbstractField
AbstractField.ComponentValueChangeEvent<C extends Component,
V extends Object> Nested classes/interfaces inherited from interface com.vaadin.flow.component.BlurNotifier
BlurNotifier.BlurEvent<C extends Component>
Nested classes/interfaces inherited from interface com.vaadin.flow.component.FocusNotifier
FocusNotifier.FocusEvent<C extends Component>
Nested classes/interfaces inherited from interface com.vaadin.flow.component.HasValue
HasValue.ValueChangeEvent<V extends Object>, HasValue.ValueChangeListener<E extends HasValue.ValueChangeEvent<?>>
-
Field Summary
Modifier and TypeFieldDescriptionprotected final FieldBuilderCustomField.DialogForm
protected AbstractFieldBuilder<?,
T> The field builder that builds this instance's sub-fields.Fields inherited from class org.dellroad.stuff.vaadin24.field.BinderCustomField
binder, modelType, subfieldValidationErrors
-
Constructor Summary
ConstructorDescriptionFieldBuilderCustomField
(Class<T> modelType) Constructor.FieldBuilderCustomField
(Class<T> modelType, FieldBuilderCustomField.DialogForm dialogForm) Constructor.Auto-configure Constructor. -
Method Summary
Modifier and TypeMethodDescriptionprotected void
Create this field's sub-fields and bind them toBinderCustomField.binder
.protected AbstractFieldBuilder<?,
T> Create a newAbstractFieldBuilder
for the given type.protected void
protected AbstractField<?,
?> Get theFieldComponent
sub-field corresponding to the given field name.protected FieldComponent<?>
getFieldComponent
(String name) Get theAbstractField
sub-field corresponding to the given field name.protected void
Layout the components required for this field.protected void
layoutEditDialogFields
(FormLayout formLayout) Layout components required for this field in a separate edit dialog window.protected void
Layout components required for this field when a separate edit dialog window is not being used.protected void
Layout components required for this field when a separate edit dialog window is being used.protected void
openEditDialog
(T bean) Open a dialog window containing a form for editing the given value.protected boolean
submitEditDialog
(T bean) Methods inherited from class org.dellroad.stuff.vaadin24.field.BinderCustomField
createBinder, createNewBean, generateModelValue, getModelType, initialize, setEnabled, setErrorMessage, setPresentationValue, validate
Methods inherited from class com.vaadin.flow.component.customfield.CustomField
add, addThemeVariants, getLabel, onAttach, remove, removeThemeVariants, setLabel, updateValue
Methods inherited from class com.vaadin.flow.component.AbstractField
addValueChangeListener, getEmptyValue, getValue, isEmpty, setModelValue, setValue, valueEquals
Methods inherited from class com.vaadin.flow.component.Component
addListener, findAncestor, fireEvent, from, get, getChildren, getElement, getEventBus, getId, getListeners, getLocale, getParent, getTranslation, getTranslation, getTranslation, getTranslation, getTranslation, getTranslation, getUI, hasListener, isAttached, isTemplateMapped, isVisible, onDetach, onEnabledStateChanged, removeFromParent, scrollIntoView, scrollIntoView, set, setElement, setId, setVisible
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
Methods inherited from interface com.vaadin.flow.component.AttachNotifier
addAttachListener
Methods inherited from interface com.vaadin.flow.component.BlurNotifier
addBlurListener
Methods inherited from interface com.vaadin.flow.component.DetachNotifier
addDetachListener
Methods inherited from interface com.vaadin.flow.component.Focusable
addFocusShortcut, blur, focus, getTabIndex, setTabIndex
Methods inherited from interface com.vaadin.flow.component.FocusNotifier
addFocusListener
Methods inherited from interface com.vaadin.flow.component.HasElement
getElement
Methods inherited from interface com.vaadin.flow.component.HasEnabled
isEnabled
Methods inherited from interface com.vaadin.flow.component.HasHelper
getHelperComponent, getHelperText, setHelperComponent, setHelperText
Methods inherited from interface com.vaadin.flow.component.HasSize
getHeight, getHeightUnit, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getWidth, getWidthUnit, setHeight, setHeight, setHeightFull, setMaxHeight, setMaxHeight, setMaxWidth, setMaxWidth, setMinHeight, setMinHeight, setMinWidth, setMinWidth, setSizeFull, setSizeUndefined, setWidth, setWidth, setWidthFull
Methods inherited from interface com.vaadin.flow.component.HasStyle
addClassName, addClassNames, getClassName, getClassNames, getStyle, hasClassName, removeClassName, removeClassNames, setClassName, setClassName
Methods inherited from interface com.vaadin.flow.component.HasTheme
addThemeName, addThemeNames, getThemeName, getThemeNames, hasThemeName, removeThemeName, removeThemeNames, setThemeName, setThemeName
Methods inherited from interface com.vaadin.flow.component.shared.HasTooltip
getTooltip, setTooltipText
Methods inherited from interface com.vaadin.flow.component.HasValidation
setManualValidation
Methods inherited from interface com.vaadin.flow.component.shared.HasValidationProperties
getErrorMessage, isInvalid, setInvalid
Methods inherited from interface com.vaadin.flow.component.HasValue
addValueChangeListener, clear, getEmptyValue, getOptionalValue, getValue, isEmpty, setValue
Methods inherited from interface com.vaadin.flow.component.HasValueAndElement
isReadOnly, isRequiredIndicatorVisible, setReadOnly, setRequiredIndicatorVisible
Methods inherited from interface org.dellroad.stuff.vaadin24.field.ValidatingField
addValidationTo
-
Field Details
-
dialogForm
-
fieldBuilder
The field builder that builds this instance's sub-fields.
-
-
Constructor Details
-
FieldBuilderCustomField
Auto-configure Constructor.This constructor will infer the target type and find any
@FieldBuilderCustomField.DialogForm
annotation by inspecting the annotated method.- Parameters:
ctx
- field builder context- Throws:
NullPointerException
- ifctx
is null
-
FieldBuilderCustomField
Constructor.- Parameters:
modelType
- model type to introspect forAbstractFieldBuilder
annotations- Throws:
IllegalArgumentException
- ifmodelType
is null
-
FieldBuilderCustomField
Constructor.- Parameters:
modelType
- model type to introspect forAbstractFieldBuilder
annotationsdialogForm
- configuration for using aDialog
window to edit the field value, or null for inline editing- Throws:
IllegalArgumentException
- ifmodelType
is null
-
-
Method Details
-
createAndBindFields
protected void createAndBindFields()Create this field's sub-fields and bind them toBinderCustomField.binder
.The implementation in
FieldBuilderCustomField
just invokesFieldBuilder.bindFields
(this.binder)
.- Specified by:
createAndBindFields
in classBinderCustomField<T>
-
createFieldBuilder
Create a newAbstractFieldBuilder
for the given type.The implementation in
FieldBuilderCustomField
returns a newFieldBuilder
each time.- Returns:
- field builder
-
layoutComponents
protected void layoutComponents()Layout the components required for this field.The implementation in
FieldBuilderCustomField
delegates tolayoutInlineComponents()
if#dialogForm
is null, otherwiselayoutNonInlineComponents()
.- Overrides:
layoutComponents
in classBinderCustomField<T>
-
layoutInlineComponents
protected void layoutInlineComponents()Layout components required for this field when a separate edit dialog window is not being used.The implementation in
FieldBuilderCustomField
iterates the bound fields infieldBuilder
into a newHorizontalLayout
which is then added to this instance. -
layoutNonInlineComponents
protected void layoutNonInlineComponents()Layout components required for this field when a separate edit dialog window is being used.The implementation in
FieldBuilderCustomField
-
editButtonPressed
protected void editButtonPressed() -
openEditDialog
Open a dialog window containing a form for editing the given value.- Parameters:
bean
- field value to edit- Throws:
IllegalArgumentException
- ifbean
is nullIllegalStateException
- ifthis.dialogForm
is null
-
submitEditDialog
-
layoutEditDialogFields
Layout components required for this field in a separate edit dialog window.The implementation in
FieldBuilderCustomField
delegtes toAbstractFieldBuilder.addFieldComponents(com.vaadin.flow.component.formlayout.FormLayout)
.- Parameters:
formLayout
- form
-
getField
Get theFieldComponent
sub-field corresponding to the given field name.- Parameters:
name
- field name- Returns:
- corresponding
FieldComponent
- Throws:
IllegalArgumentException
- ifname
is not foundIllegalArgumentException
- ifname
is null
-
getFieldComponent
Get theAbstractField
sub-field corresponding to the given field name.- Parameters:
name
- field name- Returns:
- corresponding
AbstractField
- Throws:
IllegalArgumentException
- ifname
is not foundIllegalArgumentException
- ifname
's field is not anAbstractField
IllegalArgumentException
- ifname
is null
-