Java tutorial
/* * Copyright (c) 2012 Brown Bag Consulting. * This file is part of the ExpressUI project. * Author: Juan Osuna * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License Version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * Brown Bag Consulting, Brown Bag Consulting DISCLAIMS THE WARRANTY OF * NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the ExpressUI software without * disclosing the source code of your own applications. These activities * include: offering paid services to customers as an ASP, providing * services from a web application, shipping ExpressUI with a closed * source product. * * For more information, please contact Brown Bag Consulting at this * address: juan@brownbagconsulting.com. */ package com.expressui.core.view.field; import com.expressui.core.MainApplication; import com.expressui.core.dao.EntityDao; import com.expressui.core.dao.ReferenceEntityDao; import com.expressui.core.entity.ReferenceEntity; import com.expressui.core.util.*; import com.expressui.core.util.assertion.Assert; import com.expressui.core.validation.NumberConversionValidator; import com.expressui.core.view.field.format.EmptyPropertyFormatter; import com.expressui.core.view.form.EntityForm; import com.expressui.core.view.form.FormFieldSet; import com.vaadin.addon.beanvalidation.BeanValidationValidator; import com.vaadin.data.Property; import com.vaadin.data.Validator; import com.vaadin.data.util.BeanItemContainer; import com.vaadin.data.util.PropertyFormatter; import com.vaadin.terminal.CompositeErrorMessage; import com.vaadin.terminal.ErrorMessage; import com.vaadin.terminal.Sizeable; import com.vaadin.ui.*; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.beans.BeanUtils; import javax.annotation.Resource; import javax.persistence.Lob; import javax.validation.constraints.NotNull; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.*; /** * A field in a form that wraps a Vaadin field component, while providing other features and integration with ExpressUI: * <ul> * <li>Automatically and intelligently generates Vaadin field component based on data type of property this field * is bound to</li> * <li>Automatically and intelligently configures each Vaadin field with default settings</li> * <li>Automatically sets fields as required (with *) if bound property is @NotNull or @NotEmpty</li> * <li>Automatically adjusts width of fields based on property values/data</li> * <li>Keeps track of row and column positions in the form grid layout. A field can span multiple rows and columns.</li> * </ul> */ public class FormField extends DisplayField { private String tabName; private Field field; private Integer columnStart; private Integer rowStart; private Integer columnEnd; private Integer rowEnd; private boolean isRequired; private boolean isReadOnly; private com.vaadin.ui.Label label; private boolean isVisible; private AutoAdjustWidthMode autoAdjustWidthMode = AutoAdjustWidthMode.PARTIAL; private Integer defaultWidth; private boolean hasConversionError; @Resource private ReferenceEntityDao referenceEntityDao; /** * Constructs with reference to fieldSet this field belongs to and the property name this field is bound to, often * an entity object. * * @param formFieldSet fieldSet that contains this field * @param propertyId name of the property this field is bound to */ public FormField(FormFieldSet formFieldSet, String propertyId) { super(formFieldSet, propertyId); SpringApplicationContext.autowire(this); } /** * Gets the label used for this field. Generates one automatically, if not already set. * Generated one can be derived from the property name in the code, @Label annotation on the bound property * or looked up from resource bundle properties file, using the property name as the key. * <p/> * If I18n is required, then define labels in resource bundle properties files domainMessages/. * These messages have priority over annotations. Generating a label from the property name in code is done as a * last resort. * * @return display label */ public com.vaadin.ui.Label getFieldLabel() { getField(); // make sure field is initialized before label if (label == null) { String labelText = generateLabelText(); if (isOriginallyRequired()) { labelText = "<span class=\"e-required-field-indicator\">*</span>" + labelText; } label = new com.vaadin.ui.Label(labelText, com.vaadin.ui.Label.CONTENT_XHTML); label.setSizeUndefined(); setToolTip(generateTooltip()); } return label; } @Override protected String getLabelSectionDisplayName() { if (tabName.isEmpty()) { return getFieldSet().uiMessageSource.getMessage("formField.defaultLabelSectionDisplayName"); } else { return tabName; } } /** * Sets the field label, thus overriding default generated label. * * @param labelText display label */ public void setFieldLabel(String labelText) { getFieldLabel().setValue(labelText); } /** * Gets the name of the form tab this field resides in. * * @return name of form tab that contains this field */ public String getTabName() { return tabName; } /** * Sets the name of the form tab this field resides in. * * @param tabName name of form tab that contains this field */ public void setTabName(String tabName) { Assert.PROGRAMMING.isTrue(!(tabName.isEmpty() && getFormFieldSet().hasTabs()), "tabName arg must not be empty" + " if named tabs already exist for property " + getTypeAndPropertyId()); Set<String> tabNames = getFormFieldSet().getTabNames(); for (String name : tabNames) { Assert.PROGRAMMING.isTrue(tabName.isEmpty() || !name.isEmpty(), "tabName arg must be empty" + " if empty tabNames already exist for property " + getTypeAndPropertyId()); } this.tabName = tabName; } /** * Gets the column start coordinate of this field, starting with 1 not 0. * * @return column start coordinate */ public Integer getColumnStart() { return columnStart; } /** * Sets the column start coordinate of this field, starting with 1 not 0. * * @param columnStart column start coordinate */ public void setColumnStart(Integer columnStart) { this.columnStart = columnStart; } /** * Gets the row start coordinate of this field, starting with 1 not 0. * * @return row start coordinate */ public Integer getRowStart() { return rowStart; } /** * Sets the row start coordinate of this field, starting with 1 not 0. * * @param rowStart row start coordinate */ public void setRowStart(Integer rowStart) { this.rowStart = rowStart; } /** * Gets the column end coordinate of this field. * * @return column end coordinate */ public Integer getColumnEnd() { return columnEnd; } /** * Sets the column end coordinate of this field. * * @param columnEnd column end coordinate */ public void setColumnEnd(Integer columnEnd) { this.columnEnd = columnEnd; } /** * Gets the row end coordinate of this field. * * @return row end coordinate */ public Integer getRowEnd() { return rowEnd; } /** * Sets the row end coordinate of this field. * * @param rowEnd row end coordinate */ public void setRowEnd(Integer rowEnd) { this.rowEnd = rowEnd; } /** * Asserts that column start and row start are not null. */ public void assertValid() { Assert.PROGRAMMING.notNull(columnStart, "columnStart must not be null for property " + getTypeAndPropertyId()); Assert.PROGRAMMING.notNull(rowStart, "rowStart must not be null for property " + getTypeAndPropertyId()); } /** * Gets the underlying Vaadin field. The field is intelligently and automatically generated based on the property * type. * <p/> * In most cases, applications will not need to access Vaadin APIs directly. However, * the Vaadin field is exposed in case Vaadin features are needed that are not exposed by ExpressUI. * * @return Vaadin field */ public Field getField() { if (field == null) { field = generateField(); initializeFieldDefaults(); } return field; } /** * Sets the underlying Vaadin field. The field is intelligently and automatically generated based on the property type. * <p/> * In most cases, applications will not need to access Vaadin APIs directly. However, * the Vaadin field is exposed in case Vaadin features are needed that are not exposed by ExpressUI. */ public void setField(Field field) { setField(field, true); } /** * Sets the underlying Vaadin field, overriding the automatically generated one. * * @param field Vaadin field * @param initializeDefaults allow ExpressUI to initialize the default settings for Vaadin field */ public void setField(Field field, boolean initializeDefaults) { this.field = field; if (initializeDefaults) { initializeFieldDefaults(); } } private void initWidthAndMaxLengthDefaults(AbstractTextField abstractTextField) { Integer defaultTextWidth = MainApplication.getInstance().applicationProperties.getDefaultTextFieldWidth(); defaultWidth = MathUtil.maxIgnoreNull(defaultTextWidth, getBeanPropertyType().getMinimumLength()); abstractTextField.setWidth(defaultWidth, Sizeable.UNITS_EM); Integer maxWidth = getBeanPropertyType().getMaximumLength(); if (maxWidth != null) { abstractTextField.setMaxLength(maxWidth); } } /** * Gets auto-adjust-width mode. * * @return auto-adjust-width mode */ public AutoAdjustWidthMode getAutoAdjustWidthMode() { return autoAdjustWidthMode; } /** * Sets auto-adjust-width mode. * * @param autoAdjustWidthMode auto-adjust-width mode */ public void setAutoAdjustWidthMode(AutoAdjustWidthMode autoAdjustWidthMode) { this.autoAdjustWidthMode = autoAdjustWidthMode; } /** * Intelligently adjusts the width of fields to accommodate currently populated data. */ public void autoAdjustTextFieldWidth() { Assert.PROGRAMMING.instanceOf(getField(), AbstractTextField.class, "FormField.autoAdjustWidth can only be called on text fields for property " + getTypeAndPropertyId()); if (autoAdjustWidthMode == AutoAdjustWidthMode.NONE) return; Object value = getField().getPropertyDataSource().getValue(); if (value != null) { AbstractTextField textField = (AbstractTextField) getField(); int approximateWidth = StringUtil.approximateEmWidth(value.toString()); if (autoAdjustWidthMode == AutoAdjustWidthMode.FULL) { textField.setWidth(approximateWidth, Sizeable.UNITS_EM); } else if (autoAdjustWidthMode == AutoAdjustWidthMode.PARTIAL) { textField.setWidth(MathUtil.maxIgnoreNull(approximateWidth, defaultWidth), Sizeable.UNITS_EM); } } } /** * Gets width of the field. * * @return width of the field */ public float getWidth() { return getField().getWidth(); } /** * Manually sets width of the field and turns off auto width adjustment. * * @param width size of width * @param unit unit of measurement defined in Sizeable * @see Sizeable */ public void setWidth(float width, int unit) { setAutoAdjustWidthMode(FormField.AutoAdjustWidthMode.NONE); getField().setWidth(width, unit); } /** * Sets height of the field. * * @param height size of width * @param unit unit of measurement defined in Sizeable * @see Sizeable */ public void setHeight(float height, int unit) { getField().setHeight(height, unit); } /** * Intelligently adjusts the width of select fields to accommodate currently populated data. */ public void autoAdjustSelectWidth() { Assert.PROGRAMMING.instanceOf(getField(), AbstractSelect.class, "FormField.autoAdjustSelectWidth can only be called on select fields for property " + getTypeAndPropertyId()); if (autoAdjustWidthMode == AutoAdjustWidthMode.NONE) return; AbstractSelect selectField = (AbstractSelect) getField(); Collection itemsIds = selectField.getItemIds(); int maxWidth = 0; for (Object itemsId : itemsIds) { String caption = selectField.getItemCaption(itemsId); int approximateWidth = StringUtil.approximateEmWidth(caption); maxWidth = Math.max(maxWidth, approximateWidth); } if (autoAdjustWidthMode == AutoAdjustWidthMode.FULL) { selectField.setWidth(maxWidth, Sizeable.UNITS_EM); } else if (autoAdjustWidthMode == AutoAdjustWidthMode.PARTIAL) { Integer defaultSelectWidth = MainApplication.getInstance().applicationProperties .getDefaultSelectFieldWidth(); selectField.setWidth(MathUtil.maxIgnoreNull(maxWidth, defaultSelectWidth), Sizeable.UNITS_EM); } } /** * Sets the menu options in a select. * * @param items list of items * see com.expressui.core.entity.ReferenceEntity.DISPLAY_PROPERTY */ public void setSelectItems(List items) { // could be either collection or single item Object selectedItems = getSelectedItems(); Field field = getField(); Assert.PROGRAMMING.instanceOf(field, AbstractSelect.class, "property " + getTypeAndPropertyId() + " is not a AbstractSelect field"); boolean isReadOnly = false; AbstractSelect selectField = (AbstractSelect) field; try { if (selectField.isReadOnly()) { isReadOnly = true; selectField.setReadOnly(false); } if (selectField.getContainerDataSource() == null || !(selectField.getContainerDataSource() instanceof BeanItemContainer)) { BeanItemContainer container; if (getBeanPropertyType().isCollectionType()) { container = new BeanItemContainer(getBeanPropertyType().getCollectionValueType(), items); } else { container = new BeanItemContainer(getPropertyType(), items); } selectField.setContainerDataSource(container); } else { BeanItemContainer container = (BeanItemContainer) selectField.getContainerDataSource(); container.removeAllItems(); container.addAll(items); if (!getBeanPropertyType().isCollectionType() && !container.containsId(selectedItems)) { selectField.select(selectField.getNullSelectionItemId()); } } } finally { if (isReadOnly) { selectField.setReadOnly(true); } } autoAdjustSelectWidth(); } /** * Sets menu options in a select. * * @param items map of items where key is bound to entity and value is the display caption */ public void setSelectItems(Map<Object, String> items) { String nullCaption = getFieldSet().uiMessageSource.getMessage("formFieldSet.select.nullCaption"); setSelectItems(items, nullCaption); } /** * Sets menu options in a select. * * @param items map of items where key is bound to entity and value is the display caption * @param nullCaption caption displayed to represent null or no selection */ public void setSelectItems(Map<Object, String> items, String nullCaption) { Field field = getField(); Assert.PROGRAMMING.instanceOf(field, AbstractSelect.class, "property " + getTypeAndPropertyId() + " is not a AbstractSelect field"); boolean isReadOnly = false; AbstractSelect selectField = (AbstractSelect) field; try { if (selectField.isReadOnly()) { isReadOnly = true; selectField.setReadOnly(false); } Object previouslySelectedValue = selectField.getValue(); selectField.setItemCaptionMode(Select.ITEM_CAPTION_MODE_EXPLICIT); selectField.removeAllItems(); if (nullCaption != null) { selectField.addItem(nullCaption); selectField.setItemCaption(nullCaption, nullCaption); selectField.setNullSelectionItemId(nullCaption); } for (Object item : items.keySet()) { String caption = items.get(item); selectField.addItem(item); selectField.setItemCaption(item, caption); if (previouslySelectedValue != null && previouslySelectedValue.equals(item)) { selectField.setValue(item); } } } finally { if (isReadOnly) { selectField.setReadOnly(true); } } autoAdjustSelectWidth(); } /** * Gets selected items, which could be a single item or collection. * * @return single item or collection */ public Object getSelectedItems() { Field field = getField(); Assert.PROGRAMMING.instanceOf(field, AbstractSelect.class, "property " + getTypeAndPropertyId() + " is not a AbstractSelect field"); AbstractSelect selectField = (AbstractSelect) field; return selectField.getValue(); } /** * Sets the dimensions of a multi-select menu * * @param rows height * @param columns width */ public void setMultiSelectDimensions(int rows, int columns) { Field field = getField(); Assert.PROGRAMMING.instanceOf(field, ListSelect.class, "property " + getTypeAndPropertyId() + " is not a AbstractSelect field"); ListSelect selectField = (ListSelect) field; selectField.setRows(rows); selectField.setColumns(columns); } /** * Sets the property Id to be used as display caption in select menu. This property id must be defined * in the type of object that is bound to this select field. * * @param displayCaptionPropertyId bean property name */ public void setDisplayCaptionPropertyId(String displayCaptionPropertyId) { Assert.PROGRAMMING.instanceOf(field, AbstractSelect.class, "property " + getTypeAndPropertyId() + " is not a Select field"); ((AbstractSelect) field).setItemCaptionPropertyId(displayCaptionPropertyId); } /** * Adds listener for changes in this field's value. * * @param target target object to invoke * @param methodName name of method to invoke */ public void addValueChangeListener(Object target, String methodName) { AbstractComponent component = (AbstractComponent) getField(); component.addListener(Property.ValueChangeEvent.class, target, methodName); } /** * Gets the FormFieldSet that contains this field. * * @return FormFieldSet that contains this field */ public FormFieldSet getFormFieldSet() { return (FormFieldSet) getFieldSet(); } /** * Sets the visibility of this field and label * * @param isVisible true if visible */ public void setVisible(boolean isVisible) { this.isVisible = isVisible; getField().setVisible(isVisible); getFieldLabel().setVisible(isVisible); } /** * Allows the field to be visible from a security permissions standpoint, if it is configured to be visible */ public void allowView() { getField().setVisible(isVisible); getFieldLabel().setVisible(isVisible); } /** * Prevents the field from being visible from a security permissions standpoint. */ public void denyView() { getField().setVisible(false); getFieldLabel().setVisible(false); } /** * Asks if this field is originally required, which is automatically set based on the bound bean's validation * annotations, or can be set programmatically. * * @return true if required */ public boolean isOriginallyRequired() { return isRequired; } /** * Sets whether or not field is required. This value may also be set programmatically. * * @param isRequired true if read-only */ public void setOriginallyRequired(boolean isRequired) { setDynamicallyRequired(isRequired); this.isRequired = isRequired; } /** * Asks if this field is dynamically required. Note that this may be false while {@link #isOriginallyRequired()} * is true. * This may happen in the case where this field is bound to nested property id where the leaf is required but * one of its ancestors is not required and is currently null. For example, street may be required in Address but * contact.mailingAddress is not required and currently null. In this scenario, the street field may be currently * not required, since it's ancestor (contact.mailingAddress) is null. If contact.mailingAddress is set, * then the street field becomes required. * * @return true if required */ public boolean isDynamicallyRequired() { return getField().isRequired(); } /** * Sets whether or not this field is dynamically required. Note that this may be false while * {@link #isOriginallyRequired()} is true. * This may happen in the case where this field is bound to nested property id where the leaf is required but * one of its ancestors is not required and is dynamically null. For example, street may be required in Address but * contact.mailingAddress is not required and dynamically null. In this scenario, the street field may be currently * not required, since it's ancestor (contact.mailingAddress) is null. If contact.mailingAddress is set, * then the street field becomes required. * * @param isRequired true if required */ public void setDynamicallyRequired(boolean isRequired) { getField().setRequired(isRequired); } /** * Restore is-required setting to originally configured value. */ public void restoreIsRequired() { getField().setRequired(isRequired); } private String generateTooltip(Object... args) { String toolTipText = getToolTipTextFromMessageSource(false, args); if (toolTipText == null) { toolTipText = getToolTipTextFromAnnotation(); } return toolTipText; } private String getToolTipTextFromMessageSource(boolean useDefaultLocale, Object... args) { List<BeanPropertyType> ancestors = getBeanPropertyType().getAncestors(); String label = null; for (int i = 0; i < ancestors.size(); i++) { BeanPropertyType ancestor = ancestors.get(i); Class currentType = ancestor.getContainerType(); String currentPropertyId = ancestor.getId(); for (int y = i + 1; y < ancestors.size(); y++) { BeanPropertyType bpt = ancestors.get(y); currentPropertyId += "." + bpt.getId(); } while (label == null && currentType != null) { label = getToolTipTextFromMessageSource(useDefaultLocale, currentType, currentPropertyId, args); currentType = currentType.getSuperclass(); } if (label != null) break; Class[] interfaces = ancestor.getContainerType().getInterfaces(); for (Class anInterface : interfaces) { Class currentInterface = anInterface; while (label == null && currentInterface != null) { label = getToolTipTextFromMessageSource(useDefaultLocale, currentInterface, currentPropertyId, args); currentInterface = currentInterface.getSuperclass(); } if (label != null) break; } if (label != null) break; } if (label == null) { label = getToolTipTextFromMessageSource(useDefaultLocale, getBeanPropertyType().getType(), null, args); } if (label != null && label.contains("{0}") && args.length == 0) { return null; } else { if (label == null && !useDefaultLocale) { return getToolTipTextFromMessageSource(true, args); } else { return label; } } } private String getToolTipTextFromMessageSource(boolean useDefaultLocale, Class type, String propertyId, Object... args) { String fullPropertyPath = type.getName() + (propertyId == null ? "" : "." + propertyId) + ".toolTip"; if (useDefaultLocale) { return getFieldSet().domainMessageSourceNoFallback.getOptionalToolTipFromDefaultLocale(fullPropertyPath, args); } else { return getFieldSet().domainMessageSourceNoFallback.getOptionalToolTip(fullPropertyPath, args); } } private String getToolTipTextFromAnnotation() { Class propertyContainerType = getBeanPropertyType().getContainerType(); String propertyIdRelativeToContainerType = getBeanPropertyType().getId(); PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(propertyContainerType, propertyIdRelativeToContainerType); Method method = descriptor.getReadMethod(); ToolTip toolTipAnnotation = method.getAnnotation(ToolTip.class); if (toolTipAnnotation == null) { return null; } else { return toolTipAnnotation.value(); } } /** * Gets the description displayed during mouse-over/hovering. * * @return description displayed to user */ public String getToolTip() { return getField().getDescription(); } /** * Sets the description displayed during mouse-over/hovering. * * @param toolTip description displayed to user */ public void setToolTip(String toolTip) { if (toolTip != null) { getField().setDescription(toolTip); } } /** * Generates or re-generates tooltip, passing in arguments for interpolation using standard {0}, {1}, {2} * notation. This feature only works with resource bundle messages defined in domainMessages/. * * @param args */ public void setToolTipArgs(Object... args) { setToolTip(generateTooltip(args)); } /** * Sets whether or not field is enabled. * * @param isEnabled true if enabled */ public void setEnabled(boolean isEnabled) { getField().setEnabled(isEnabled); } /** * Asks if this field is originally read-only, irrespective of view-mode or security permissions. * * @return true if this field is read-only */ public boolean isOriginallyReadOnly() { return isReadOnly; } /** * Sets whether or not field is read-only, irrespective of view-mode or security permissions. * * @param isReadOnly true if read-only */ public void setOriginallyReadOnly(boolean isReadOnly) { setDynamicallyReadOnly(isReadOnly); this.isReadOnly = isReadOnly; } /** * Sets whether or not field is dynamically read-only, based on view-mode or security permissions. * * @param isReadOnly true if read-only */ public void setDynamicallyReadOnly(boolean isReadOnly) { if (getField() instanceof SelectField) { ((SelectField) getField()).setButtonVisible(!isReadOnly); } getField().setReadOnly(isReadOnly); } /** * Restores read-only setting to original value, if they were temporarily changed for view-only mode * or security permissions. */ public void restoreIsReadOnly() { if (getField() instanceof SelectField) { ((SelectField) getField()).setButtonVisible(!isOriginallyReadOnly()); } getField().setReadOnly(isOriginallyReadOnly()); } /** * Sets the value of the field. * * @param value value of field */ public void setValue(Object value) { getField().setValue(value); } /** * Asks if field dynamically has an error. * * @return true if field dynamically has an error */ public boolean hasError() { if (hasConversionError) { return true; } else if (getField() instanceof AbstractComponent) { AbstractComponent abstractComponent = (AbstractComponent) getField(); return abstractComponent.getComponentError() != null || hasIsRequiredError(); } else { return false; } } /** * Asks if field currently has error because field is empty but is required. * * @return true if field currently has error because field is empty but is required */ public boolean hasIsRequiredError() { return getField().isRequired() && StringUtil.isEmpty(getField().getValue()); } /** * Clears any errors on this field. * * @param clearConversionError true to clear data-type conversion error as well */ public void clearError(boolean clearConversionError) { if (clearConversionError) { hasConversionError = false; } if (getField() instanceof AbstractComponent) { AbstractComponent abstractComponent = (AbstractComponent) getField(); abstractComponent.setComponentError(null); } } /** * Asks if this field has a data-type conversion error. * * @return true if this field as a data-type conversion error */ public boolean hasConversionError() { return hasConversionError; } /** * Sets whether or not this field has a data-type conversion error. * * @param hasConversionError true if this field as a data-type conversion error */ public void setHasConversionError(boolean hasConversionError) { this.hasConversionError = hasConversionError; } /** * Add error message to this field. * * @param errorMessage error message, builds Vaadin composite error message */ public void addError(ErrorMessage errorMessage) { Assert.PROGRAMMING.instanceOf(getField(), AbstractComponent.class, "Error message cannot be added to field that is not an AbstractComponent for property " + getTypeAndPropertyId()); AbstractComponent abstractComponent = (AbstractComponent) getField(); ErrorMessage existingErrorMessage = abstractComponent.getComponentError(); if (existingErrorMessage == null) { abstractComponent.setComponentError(errorMessage); } else if (existingErrorMessage instanceof CompositeErrorMessage) { CompositeErrorMessage existingCompositeErrorMessage = (CompositeErrorMessage) existingErrorMessage; Iterator<ErrorMessage> iterator = existingCompositeErrorMessage.iterator(); Set<ErrorMessage> newErrorMessages = new LinkedHashSet<ErrorMessage>(); while (iterator.hasNext()) { ErrorMessage next = iterator.next(); newErrorMessages.add(next); } newErrorMessages.add(errorMessage); CompositeErrorMessage newCompositeErrorMessage = new CompositeErrorMessage(newErrorMessages); abstractComponent.setComponentError(newCompositeErrorMessage); } else { Set<ErrorMessage> newErrorMessages = new LinkedHashSet<ErrorMessage>(); newErrorMessages.add(existingErrorMessage); newErrorMessages.add(errorMessage); CompositeErrorMessage newCompositeErrorMessage = new CompositeErrorMessage(newErrorMessages); abstractComponent.setComponentError(newCompositeErrorMessage); } } @Override public PropertyFormatter getPropertyFormatter() { if (getField() instanceof AbstractTextField) { return super.getPropertyFormatter(); } else { return new EmptyPropertyFormatter(); } } private Field generateField() { Class propertyType = getPropertyType(); if (propertyType == null) { return null; } if (Date.class.isAssignableFrom(propertyType)) { return new DateField(); } if (boolean.class.isAssignableFrom(propertyType) || Boolean.class.isAssignableFrom(propertyType)) { return new CheckBox(); } if (ReferenceEntity.class.isAssignableFrom(propertyType)) { return new Select(); } if (Currency.class.isAssignableFrom(propertyType)) { return new Select(); } if (propertyType.isEnum()) { return new Select(); } if (Collection.class.isAssignableFrom(propertyType)) { return new ListSelect(); } if (getBeanPropertyType().hasAnnotation(Lob.class)) { return new RichTextArea(); } return new TextField(); } private void initializeFieldDefaults() { if (field == null) { return; } field.setInvalidAllowed(true); if (field instanceof AbstractField) { initAbstractFieldDefaults((AbstractField) field); } if (field instanceof AbstractTextField) { initTextFieldDefaults((AbstractTextField) field); initWidthAndMaxLengthDefaults((AbstractTextField) field); } if (field instanceof RichTextArea) { initRichTextFieldDefaults((RichTextArea) field); } if (field instanceof DateField) { initDateFieldDefaults((DateField) field); } if (field instanceof AbstractSelect) { initAbstractSelectDefaults((AbstractSelect) field); if (field instanceof Select) { initSelectDefaults((Select) field); } if (field instanceof ListSelect) { initListSelectDefaults((ListSelect) field); } Class valueType = getPropertyType(); if (getBeanPropertyType().isCollectionType()) { valueType = getBeanPropertyType().getCollectionValueType(); } List referenceEntities = null; if (Currency.class.isAssignableFrom(valueType)) { referenceEntities = CurrencyUtil.getAvailableCurrencies(); ((AbstractSelect) field).setItemCaptionPropertyId("currencyCode"); } else if (valueType.isEnum()) { Object[] enumConstants = valueType.getEnumConstants(); referenceEntities = Arrays.asList(enumConstants); } else if (ReferenceEntity.class.isAssignableFrom(valueType)) { EntityDao propertyDao = SpringApplicationContext .getBeanByTypeAndGenericArgumentType(EntityDao.class, valueType); if (propertyDao != null) { referenceEntities = propertyDao.findAll(); } else { referenceEntities = referenceEntityDao.findAll(valueType); } } if (referenceEntities != null) { setSelectItems(referenceEntities); } } if (getFormFieldSet().isEntityForm()) { if (getBeanPropertyType().isValidatable()) { initializeIsRequired(); initializeValidators(); } // Change listener causes erratic behavior for RichTextArea if (!(field instanceof RichTextArea)) { field.addListener(new FieldValueChangeListener()); } } setOriginallyReadOnly(field.isReadOnly()); isVisible = field.isVisible(); } private void initializeValidators() { if (field instanceof AbstractTextField) { if (getBeanPropertyType().getBusinessType() != null && getBeanPropertyType().getBusinessType().equals(BeanPropertyType.BusinessType.NUMBER)) { addValidator(new NumberConversionValidator(this)); } } } /** * Adds Vaadin validator to this field. * * @param validator Vaadin validator */ public void addValidator(Validator validator) { getField().addValidator(validator); } private void initializeIsRequired() { BeanValidationValidator validator = new BeanValidationValidator(getBeanPropertyType().getContainerType(), getBeanPropertyType().getId()); if (validator.isRequired()) { field.setRequired(true); field.setRequiredError(MainApplication.getInstance().validationMessageSource .getMessage("com.expressui.core.view.field.FormField.required.message")); } setOriginallyRequired(field.isRequired()); } /** * Initializes field to default settings. * * @param field Vaadin field to initialize */ public static void initAbstractFieldDefaults(AbstractField field) { field.setRequiredError(MainApplication.getInstance().validationMessageSource .getMessage("com.expressui.core.view.field.FormField.required.message")); field.setImmediate(true); field.setInvalidCommitted(false); field.setWriteThrough(true); } /** * Initializes field to default settings. * * @param field Vaadin field to initialize */ public static void initTextFieldDefaults(AbstractTextField field) { Integer defaultTextWidth = MainApplication.getInstance().applicationProperties.getDefaultTextFieldWidth(); field.setWidth(defaultTextWidth, Sizeable.UNITS_EM); field.setNullRepresentation(""); field.setNullSettingAllowed(true); } /** * Initializes field to default settings. * * @param field Vaadin field to initialize */ public static void initRichTextFieldDefaults(RichTextArea field) { field.setNullRepresentation(""); field.setNullSettingAllowed(false); } /** * Initializes field to default settings. * * @param field Vaadin field to initialize */ public static void initDateFieldDefaults(DateField field) { field.setResolution(DateField.RESOLUTION_DAY); field.setParseErrorMessage(MainApplication.getInstance().validationMessageSource .getMessage("com.expressui.core.view.field.FormField.dateParseError.message")); } /** * Initializes field to default settings. * * @param field Vaadin field to initialize */ public void initAbstractSelectDefaults(AbstractSelect field) { Integer defaultSelectWidth = MainApplication.getInstance().applicationProperties .getDefaultSelectFieldWidth(); field.setWidth(defaultSelectWidth, Sizeable.UNITS_EM); field.setItemCaptionMode(Select.ITEM_CAPTION_MODE_PROPERTY); if (getBeanPropertyType().hasAnnotation(NotNull.class) || getBeanPropertyType().hasAnnotation(NotEmpty.class) || getBeanPropertyType().hasAnnotation(NotBlank.class)) { field.setNullSelectionAllowed(false); } else { field.setNullSelectionAllowed(true); } field.setItemCaptionPropertyId(ReferenceEntity.DISPLAY_PROPERTY); } /** * Initializes field to default settings. * * @param field Vaadin field to initialize */ public static void initSelectDefaults(Select field) { field.setFilteringMode(Select.FILTERINGMODE_CONTAINS); } /** * Initializes field to default settings. * * @param field Vaadin field to initialize */ public static void initListSelectDefaults(ListSelect field) { field.setMultiSelect(true); } private class FieldValueChangeListener implements Property.ValueChangeListener { @Override public void valueChange(Property.ValueChangeEvent event) { EntityForm entityForm = (EntityForm) getFormFieldSet().getForm(); if (entityForm.isValidationEnabled()) { entityForm.validate(false); } } } /** * Mode for automatically adjusting field widths. */ public enum AutoAdjustWidthMode { /** * Fully automatic */ FULL, /** * Automatic but with minimum width specified in application.properties */ PARTIAL, /** * Turn off automatic width adjustment */ NONE } }