5.8.6.2. Example of Integrating a Vaadin Component into the Generic UI

Section 4.5.10.2, “Integration with Generic UI” covers the basics of native components integration into the Generic UI, which allows them to be declared in XML-descriptors and associated with data.

In the previous section, we have included the third-party Stepper component in the project. Integration of the IntStepper component, which implements incremental change of int numerical values, is described below.

  • Definition of component interface in the gui module:

    package com.company.myproject.gui.components;
    
    import com.haulmont.cuba.gui.components.Field;
    
    public interface IntStepper extends Field {
    
        String NAME = "intStepper";
    
        boolean isManualInputAllowed();
        void setManualInputAllowed(boolean value);
    
        boolean isMouseWheelEnabled();
        void setMouseWheelEnabled(boolean value);
    
        int getStepAmount();
        void setStepAmount(int amount);
    
        int getMaxValue();
        void setMaxValue(int maxValue);
    
        int getMinValue();
        void setMinValue(int minValue);
    }

    The chosen base interface for the component is Field, which allows data binding, i.e. the ability to view and edit an entity attribute.

  • Component implementation in the web module:

    package com.company.myproject.web.components;
    
    import com.company.myproject.gui.components.IntStepper;
    import com.haulmont.cuba.web.gui.components.WebAbstractField;
    
    public class WebIntStepper 
            extends WebAbstractField<org.vaadin.risto.stepper.IntStepper> 
            implements IntStepper {
    
        public WebIntStepper() {
            component = new org.vaadin.risto.stepper.IntStepper();
        }
    
        @Override
        public boolean isManualInputAllowed() {
            return component.isManualInputAllowed();
        }
        @Override
        public void setManualInputAllowed(boolean value) {
            component.setManualInputAllowed(value);
        }
    
        @Override
        public boolean isMouseWheelEnabled() {
            return component.isMouseWheelEnabled();
        }
        @Override
        public void setMouseWheelEnabled(boolean value) {
            component.setMouseWheelEnabled(value);
        }
    
        @Override
        public int getStepAmount() {
            return component.getStepAmount();
        }
        @Override
        public void setStepAmount(int amount) {
            component.setStepAmount(amount);
        }
    
        @Override
        public int getMaxValue() {
            return component.getMaxValue();
        }
        @Override
        public void setMaxValue(int maxValue) {
            component.setMaxValue(maxValue);
        }
    
        @Override
        public int getMinValue() {
            return component.getMinValue();
        }
        @Override
        public void setMinValue(int minValue) {
            component.setMinValue(minValue);
        }
    }

    The chosen base class is WebAbstractField. The base class implements the datasource binding Field logic and other methods of the Field interface.

  • Implementation of XML loader in the gui module:

    package com.company.myproject.gui.loaders;
    
    import com.company.myproject.gui.components.IntStepper;
    import com.haulmont.cuba.gui.components.Component;
    import com.haulmont.cuba.gui.xml.layout.*;
    import com.haulmont.cuba.gui.xml.layout.loaders.AbstractFieldLoader;
    import org.dom4j.Element;
    
    public class IntStepperLoader extends AbstractFieldLoader {
    
        public IntStepperLoader(Context context, LayoutLoaderConfig config, ComponentsFactory factory) {
            super(context, config, factory);
        }
    
        @Override
        public Component loadComponent(ComponentsFactory factory, Element element, Component parent) {
            IntStepper component = (IntStepper) super.loadComponent(factory, element, parent);
    
            String manualInput = element.attributeValue("manualInput");
            if (manualInput != null) {
                component.setManualInputAllowed(Boolean.valueOf(manualInput));
            }
            String mouseWheel = element.attributeValue("mouseWheel");
            if (mouseWheel != null) {
                component.setMouseWheelEnabled(Boolean.valueOf(mouseWheel));
            }
            String stepAmount = element.attributeValue("stepAmount");
            if (stepAmount != null) {
                component.setStepAmount(Integer.valueOf(stepAmount));
            }
            String maxValue = element.attributeValue("maxValue");
            if (maxValue != null) {
                component.setMaxValue(Integer.valueOf(maxValue));
            }
            String minValue = element.attributeValue("minValue");
            if (minValue != null) {
                component.setMinValue(Integer.valueOf(minValue));
            }
            return component;
        }
    }

    The AbstractFieldLoader class contains the loading logic for the basic properties of the Field component. It is sufficient to load the specific properties of the IntStepper component only.

  • Implementation of component palette in the web module:

    package com.company.myproject.web;
    
    import com.company.myproject.gui.components.IntStepper;
    import com.company.myproject.gui.loaders.IntStepperLoader;
    import com.company.myproject.web.components.WebIntStepper;
    import com.haulmont.cuba.gui.ComponentPalette;
    import com.haulmont.cuba.gui.components.Component;
    import com.haulmont.cuba.gui.xml.layout.ComponentLoader;
    import java.util.HashMap;
    import java.util.Map;
    
    public class AppComponentPalette implements ComponentPalette {
    
        @Override
        public Map<String, Class<? extends ComponentLoader>> getLoaders() {
            Map<String, Class<? extends ComponentLoader>> loaders = new HashMap<>();
            loaders.put(IntStepper.NAME, IntStepperLoader.class);
            return loaders;
        }
    
        @Override
        public Map<String, Class<? extends Component>> getComponents() {
            Map<String, Class<? extends Component>> components = new HashMap<>();
            components.put(IntStepper.NAME, WebIntStepper.class);
            return components;
        }
    }
  • Registration of the component palette in the App class of the web module:

    package com.company.myproject.web;
    
    import com.haulmont.cuba.web.DefaultApp;
    import com.haulmont.cuba.web.gui.WebUIPaletteManager;
    
    public class App extends DefaultApp {
    
        static {
            WebUIPaletteManager.registerPalettes(new AppComponentPalette());
        }
    }
  • Definition of the component XSD in the gui module:

    <xs:schema targetNamespace="http://schemas.company.com/app/0.1/app-components.xsd"
               xmlns:xs="http://www.w3.org/2001/XMLSchema"
               xmlns="http://schemas.company.com/app/0.1/app-components.xsd"
               elementFormDefault="qualified"
               attributeFormDefault="unqualified">
    
        <xs:element name="intStepper">
            <xs:complexType>
                <xs:attribute name="id" type="xs:string"/>
                <xs:attribute name="caption" type="xs:string"/>
                <xs:attribute name="width" type="xs:string"/>
                <xs:attribute name="height" type="xs:string"/>
                <xs:attribute name="datasource" type="xs:string"/>
                <xs:attribute name="property" type="xs:string"/>
                <xs:attribute name="manualInput" type="xs:boolean"/>
                <xs:attribute name="mouseWheel" type="xs:boolean"/>
                <xs:attribute name="stepAmount" type="xs:int"/>
                <xs:attribute name="maxValue" type="xs:int"/>
                <xs:attribute name="minValue" type="xs:int"/>
            </xs:complexType>
        </xs:element>
    
    </xs:schema>
  • Example of using the component inside a container:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
            xmlns:app="http://schemas.company.com/app/0.1/app-components.xsd"
            caption="msg://editCaption"
            class="com.company.myproject.web.customer.CustomerEdit"
            datasource="customerDs"
            focusComponent="fieldGroup"
            messagesPack="com.company.myproject.web.customer">
        <dsContext>
            <datasource id="customerDs"
                        class="com.company.myproject.entity.Customer"
                        view="_local"/>
        </dsContext>
        <layout expand="windowActions"
                spacing="true">
            <app:intStepper id="stepper" datasource="customerDs" property="score" caption="Score"
                            minValue="1" maxValue="20"/>
            <iframe id="windowActions"
                    screen="editWindowActions"/>
        </layout>
    </window>

    In the example above, the intStepper component is associated with the score attribute of the Customer entity, an instance of which is contained within the customerDs data source.

  • Example of using the component in a FieldGroup:

    <dsContext>
        <datasource id="customerDs"
                    class="com.company.myproject.entity.Customer"
                    view="_local"/>
    </dsContext>
    <layout expand="windowActions"
            spacing="true">
        <fieldGroup id="fieldGroup"
                    datasource="customerDs">
            <column width="250px">
                <field id="name"/>
                <field id="score" custom="true" caption="Score"/>
            </column>
        </fieldGroup>
    ...
    @Inject
    private ComponentsFactory componentsFactory;
    @Inject
    private FieldGroup fieldGroup;
    
    @Override
    public void init(Map<String, Object> params) {
        fieldGroup.addCustomField("score", new FieldGroup.CustomFieldGenerator() {
            @Override
            public Component generateField(final Datasource datasource, final String propertyId) {
                IntStepper stepper = componentsFactory.createComponent(IntStepper.NAME);
                stepper.setDatasource(datasource, propertyId);
                stepper.setWidth("100%");
                return stepper;
            }
        });
    }