3.5.13. Pluggable Component Factories

The pluggable component factories mechanism extends the standard component creation procedure and allows you to create different edit fields in FieldGroup, Table and DataGrid. It means that application components or your project itself can provide custom strategies that will create non-standard components and/or support custom data types.

An entry point to the mechanism is the UiComponentsGenerator.generate(ComponentGenerationContext) method. It works as follows:

  • Tries to find ComponentGenerationStrategy implementations. If at least one strategy exists, then:

    • Iterates over strategies according to the org.springframework.core.Ordered interface.

    • Returns the first created not null component.

ComponentGenerationStrategy implementations are used to create UI components. A project can contain any number of such strategies.

ComponentGenerationContext is a class which stores the following information that can be used when creating a component:

  • metaClass - defines the entity for which the component is created.

  • property - defines the entity attribute for which the component is created.

  • datasource - a datasource.

  • optionsDatasource - a datasource that can be used to show options.

  • valueSource - a value source that can be used to create the component.

  • options - an options object that can be used to show options.

  • xmlDescriptor - an XML descriptor which contains additional information, in case the component is declared in an XML descriptor.

  • componentClass - a component class for which a component is created, e.g. FieldGroup, Table, DataGrid.

There are two built-in component strategies:

  • DefaultComponentGenerationStrategy - used to create a component according to the given ComponentGenerationContext object. Has the order value ComponentGenerationStrategy.LOWEST_PLATFORM_PRECEDENCE (1000).

  • DataGridEditorComponentGenerationStrategy - used to create a component according to the given ComponentGenerationContext object for a DataGrid Editor. Has the order value ComponentGenerationStrategy.HIGHEST_PLATFORM_PRECEDENCE + 30 (130).

The sample below shows how to replace the default FieldGroup field generation for a certain attribute of a specific entity.

import com.company.sales.entity.Order;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.cuba.core.global.Metadata;
import com.haulmont.cuba.gui.UiComponents;
import com.haulmont.cuba.gui.components.*;
import com.haulmont.cuba.gui.components.data.ValueSource;
import org.springframework.core.Ordered;

import javax.annotation.Nullable;
import javax.inject.Inject;
import java.sql.Date;

@org.springframework.stereotype.Component(SalesComponentGenerationStrategy.NAME)
public class SalesComponentGenerationStrategy implements ComponentGenerationStrategy, Ordered {

    public static final String NAME = "sales_SalesComponentGenerationStrategy";

    @Inject
    private UiComponents uiComponents;

    @Inject
    private Metadata metadata;

    @Nullable
    @Override
    public Component createComponent(ComponentGenerationContext context) {
        String property = context.getProperty();
        MetaClass orderMetaClass = metadata.getClassNN(Order.class);

        // Check the specific field of the Order entity
        // and that the component is created for the FieldGroup component
        if (orderMetaClass.equals(context.getMetaClass())
                && "date".equals(property)
                && context.getComponentClass() != null
                && FieldGroup.class.isAssignableFrom(context.getComponentClass())) {
            DatePicker<Date> datePicker = uiComponents.create(DatePicker.TYPE_DATE);

            ValueSource valueSource = context.getValueSource();
            if (valueSource != null) {
                //noinspection unchecked
                datePicker.setValueSource(valueSource);
            }

            return datePicker;
        }

        return null;
    }

    @Override
    public int getOrder() {
        return 50;
    }
}

Pay attention that overriding existing generation strategies may produce errors in case of changing returned component type, because some screen controllers may have code that expects a certain component type.

For example, in case of using the strategy above, the following injection will produce the exception:

@Named("fieldGroup.date")
private DateField<Date> dateField;

If you try to open such screen you will get the following exception:

IllegalArgumentException: Can not set com.haulmont.cuba.gui.components.DateField field com.company.sales.web.order.OrderEdit.dateField to com.haulmont.cuba.web.gui.components.WebDatePicker

The sample below shows how to define a ComponentGenerationStrategy for a specific datatype.

import com.company.colordatatype.datatypes.ColorDatatype;
import com.haulmont.chile.core.datatypes.Datatype;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.chile.core.model.Range;
import com.haulmont.cuba.core.app.dynamicattributes.DynamicAttributesUtils;
import com.haulmont.cuba.gui.UiComponents;
import com.haulmont.cuba.gui.components.ColorPicker;
import com.haulmont.cuba.gui.components.Component;
import com.haulmont.cuba.gui.components.ComponentGenerationContext;
import com.haulmont.cuba.gui.components.ComponentGenerationStrategy;
import com.haulmont.cuba.gui.components.data.ValueSource;
import org.springframework.core.annotation.Order;

import javax.annotation.Nullable;
import javax.inject.Inject;

@Order(100)
@org.springframework.stereotype.Component(ColorComponentGenerationStrategy.NAME)
public class ColorComponentGenerationStrategy implements ComponentGenerationStrategy {

    public static final String NAME = "colordatatype_ColorComponentGenerationStrategy";

    @Inject
    private UiComponents uiComponents;

    @Nullable
    @Override
    public Component createComponent(ComponentGenerationContext context) {
        String property = context.getProperty();
        MetaPropertyPath mpp = resolveMetaPropertyPath(context.getMetaClass(), property);

        if (mpp != null) {
            Range mppRange = mpp.getRange();
            if (mppRange.isDatatype()
                    && ((Datatype) mppRange.asDatatype()) instanceof ColorDatatype) {
                ColorPicker colorPicker = uiComponents.create(ColorPicker.class);
                colorPicker.setDefaultCaptionEnabled(true);

                ValueSource valueSource = context.getValueSource();
                if (valueSource != null) {
                    //noinspection unchecked
                    colorPicker.setValueSource(valueSource);
                }

                return colorPicker;
            }
        }

        return null;
    }

    protected MetaPropertyPath resolveMetaPropertyPath(MetaClass metaClass, String property) {
        MetaPropertyPath mpp = metaClass.getPropertyPath(property);

        if (mpp == null && DynamicAttributesUtils.isDynamicAttribute(property)) {
            mpp = DynamicAttributesUtils.getMetaPropertyPath(metaClass, property);
        }

        return mpp;
    }
}