4.7.4.2. Подключение аддона Vaadin с интеграцией в Generic UI

В предыдущем разделе мы подключили в проект сторонний компонент Stepper. В данном разделе мы интегрируем его в универсальный пользовательский интерфейс CUBA, что даст разработчикам возможность использовать компонент декларативно в XML-дескрипторах экранов и связывать его с сущностями через источники данных.

Создадим новый проект в CUBA Studio и назовем его addon-gui-demo.

Создадим модуль web-toolkit, нажав на кнопку Create web toolkit module секции Project properties навигатора Studio.

Далее нажимаем на кнопку New UI component. Откроется окно создания визуального компонента UI component generation. В секции Component type выбираем значение Vaadin add-on.

studio vaadin addon wizard gui

Заполним поля Add-on Maven dependency и Inherited widgetset как описано в предыдущем разделе.

Далее заполним поля в нижней секции:

  • Integrate into Generic UI указывает на необходимости интеграции компонента в универсальный пользовательский интерфейс платформы.

  • Component XML element - имя элемента компонента в XML-дескрипторе экрана. Введите значение stepper.

  • Component interface name - имя интерфейса компонента для универсального UI платформы. Введите Stepper.

  • FQN of Vaadin component from add-on - полное имя класса компонента Vaadin из аддона. В нашем случае это org.vaadin.risto.stepper.IntStepper.

После нажатия кнопки OK Studio сделает следующее:

  • Добавит аддон Vaadin в зависимости модуля web в файле build.gradle.

  • Подключит виджетсет аддона в файле AppWidgetSet.gwt.xml модуля web-toolkit.

  • Сгенерирует заготовки для следующих файлов:

    • Stepper - интерфейс компонента в подкаталоге gui модуля web.

    • WebStepper - реализация компонента в подкаталоге gui модуля web.

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

    • ui-component.xsd - описатель схемы XML для нового компонента. Если файл уже существовал на момент генерации компонента, то информация о новом компоненте будет добавлена в существующий файл.

    • cuba-ui-component.xml - файл регистрации загрузчика нового компонента в модуле web. Если файл существовал, то информация о новом компоненте будет добавлена в существующий файл.

Откройте проект в IDE.

Последовательно пройдемся по сгенерированным Studio заготовкам файлов и внесем в них необходимые изменения.

  • Перейдите к интерфейсу Stepper в подкаталоге gui модуля web. Замените его содержимое на следующий код:

    package com.company.addonguidemo.web.gui.components;
    
    import com.haulmont.cuba.gui.components.Field;
    
    // note that Stepper should extend Field
    public interface Stepper extends Field {
    
        String NAME = "stepper";
    
        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), то есть отображать и редактировать значение некоторого атрибута сущности.

  • Далее перейдите к классу WebStepper - реализации компонента в подкаталоге gui модуля web. Замените содержимое класса следующим кодом:

    package com.company.addonguidemo.web.gui.components;
    
    import com.company.addonguidemo.web.gui.components.Stepper;
    import com.haulmont.cuba.web.gui.components.WebAbstractField;
    import org.vaadin.risto.stepper.IntStepper;
    
    // note that WebStepper should extend WebAbstractField
    public class WebStepper extends WebAbstractField<IntStepper> implements Stepper {
        public WebStepper() {
            this.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.

  • StepperLoader в модуле web загружает компонент из его представления в XML.

    package com.company.addonguidemo.web.gui.xml.layout.loaders;
    
    import com.company.addonguidemo.web.gui.components.Stepper;
    import com.haulmont.cuba.gui.xml.layout.loaders.AbstractFieldLoader;
    
    public class StepperLoader extends AbstractFieldLoader<Stepper> {
        @Override
        public void createComponent() {
            resultComponent = factory.createComponent(Stepper.class);
            loadId(resultComponent, element);
        }
    
        @Override
        public void loadComponent() {
    
            super.loadComponent();
    
            String manualInput = element.attributeValue("manualInput");
            if (manualInput != null) {
                resultComponent.setManualInputAllowed(Boolean.parseBoolean(manualInput));
            }
            String mouseWheel = element.attributeValue("mouseWheel");
            if (mouseWheel != null) {
                resultComponent.setMouseWheelEnabled(Boolean.parseBoolean(mouseWheel));
            }
            String stepAmount = element.attributeValue("stepAmount");
            if (stepAmount != null) {
                resultComponent.setStepAmount(Integer.parseInt(stepAmount));
            }
            String maxValue = element.attributeValue("maxValue");
            if (maxValue != null) {
                resultComponent.setMaxValue(Integer.parseInt(maxValue));
            }
            String minValue = element.attributeValue("minValue");
            if (minValue != null) {
                resultComponent.setMinValue(Integer.parseInt(minValue));
            }
        }
    }

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

  • В файле cuba-ui-component.xml, расположенном в корне модуля web, регистрируется новый компонент и его загрузчик. Оставляем файл без изменений.

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <components xmlns="http://schemas.haulmont.com/cuba/components.xsd">
        <component>
            <name>stepper</name>
            <componentLoader>com.company.addonguidemo.web.gui.xml.layout.loaders.StepperLoader</componentLoader>
            <class>com.company.addonguidemo.web.gui.components.WebStepper</class>
        </component>
    </components>
  • Файл ui-component.xsd, расположенный в корне модуля web, это описатель XML схемы новых компонентов проекта. Добавим к элементу stepper описание его атрибутов.

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <xs:schema xmlns="http://schemas.company.com/agd/0.1/ui-component.xsd"
               attributeFormDefault="unqualified"
               elementFormDefault="qualified"
               targetNamespace="http://schemas.company.com/agd/0.1/ui-component.xsd"
               xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <xs:element name="stepper">
            <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>

Далее рассмотрим, как добавить новый компонент на экран.

  • Создадим новую сущность Customer с двумя полями:

    • name типа String

    • score типа Integer

  • Сгенерируем для новой сущности стандартные экраны.

  • Далее добавим компонент stepper на экран. Вы можете поместить его как в FieldGroup, так и в отдельный контейнер. Рассмотрим оба способа.

    1. Использование компонента в экране внутри произвольного контейнера.

      • Откройте файл customer-edit.xml.

      • Объявите новое пространство имен xmlns:app="http://schemas.company.com/agd/0.1/ui-component.xsd"

      • Удалите поле score из fieldGroup.

      • Добавьте компонент stepper на экран.

      В результате XML-дескриптор редактора должен выглядеть так:

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

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

    2. Использование компонента в поле FieldGroup:

      <?xml version="1.0" encoding="UTF-8" standalone="no"?>
      <window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
              caption="msg://editCaption"
              class="com.company.addonguidemo.web.customer.CustomerEdit"
              datasource="customerDs"
              focusComponent="fieldGroup"
              messagesPack="com.company.addonguidemo.web.customer">
          <dsContext>
              <datasource id="customerDs"
                          class="com.company.addonguidemo.entity.Customer"
                          view="_local"/>
          </dsContext>
          <layout expand="windowActions" spacing="true">
              <fieldGroup id="fieldGroup" datasource="customerDs">
                  <column width="250px">
                      <field property="name"/>
                      <field property="score" custom="true"/>
                  </column>
              </fieldGroup>
              <frame id="windowActions" screen="editWindowActions"/>
          </layout>
      </window>
      package com.company.addonguidemo.web.customer;
      
      import com.company.addonguidemo.gui.components.Stepper;
      import com.haulmont.cuba.gui.components.AbstractEditor;
      import com.company.addonguidemo.entity.Customer;
      import com.haulmont.cuba.gui.components.FieldGroup;
      import com.haulmont.cuba.gui.data.Datasource;
      import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;
      
      import javax.inject.Inject;
      import java.util.Map;
      
      public class CustomerEdit extends AbstractEditor<Customer> {
      
          @Inject
          private ComponentsFactory componentsFactory;
          @Inject
          private FieldGroup fieldGroup;
          @Inject
          private Datasource<Customer> customerDs;
      
          @Override
          public void init(Map<String, Object> params) {
              Stepper stepper = componentsFactory.createComponent(Stepper.class);
              stepper.setDatasource(customerDs, "score");
              stepper.setWidth("100%");
              fieldGroup.getFieldNN("score").setComponent(stepper);
          }
      }
  • Для адаптации внешнего вида компонента создадим в проекте расширение темы. Для этого в Studio выполним команду Create theme extension секции Project properties навигатора. В списке тем для расширения выберем halo и нажмем кнопку Create. Затем откроем файл themes/halo/com.company.application/halo-ext.scss модуля web, и добавим в него следующий код:

    /* Define your theme modifications inside next mixin */
    @mixin com_company_application-halo-ext {
        /* Basic styles for stepper inner text box */
        .stepper input[type="text"] {
           @include box-defaults;
           @include valo-textfield-style;
           &:focus {
             @include valo-textfield-focus-style;
           }
        }
    }
  • Запускаем сервер приложения. Экран редактирования должен выглядеть следующим образом:

customer edit result