5.8.6.2. Пример интеграции компонента Vaadin в Generic UI

В Раздел 4.5.10.2, «Интеграция компонентов в Generic UI» были рассмотрены принципы интеграции "нативных" компонентов в универсальный UI для того, чтобы их можно было объявлять в XML-дескрипторах экранов и связывать с данными.

В предыдущем разделе мы подключили в проект сторонний компонент Stepper. Рассмотрим процесс интеграции в универсальный UI класса IntStepper, реализующего пошаговое изменение числового значения типа int.

  • Интерфейс компонента в модуле gui:

    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);
    }

    В качестве базового для нашего компонента выбран интерфейс Field. Это позволяет осуществить связь с данными (data binding), то есть отображать и редактировать значение некоторого атрибута сущности.

  • Реализация компонента в модуле web:

    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);
        }
    }

    В квчестве базового класса выбран WebAbstractField, который реализует логику интерфейса Field по связыванию с источником данных и другие его методы.

  • XML-загрузчик компонента в модуле gui:

    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;
        }
    }

    Логика загрузки базовых свойств компонента Field сосредоточена в классе AbstractFieldLoader. Нам достаточно загрузить только специфические свойства IntStepper.

  • Палитра компонентов проекта в модуле web:

    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;
        }
    }
  • Регистрация палитры компонентов в классе App модуля web:

    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());
        }
    }
  • XSD компонентов проекта в модуле gui:

    <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>
  • Использование компонента в экране внутри произвольного контейнера:

    <?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>

    В данном примере компонент intStepper подсоединен к атрибуту score сущности Customer, экземпляр которой находится в источнике данных customerDs.

  • Использование компонента в кастомном поле 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;
            }
        });
    }