3.5.17.4.5. Поддержка собственных визуальных компонентов и фасетов в CUBA Studio

Дизайнер экранов в CUBA Studio позволяет разработчикам встроить собственный UI компонент (или фасет), реализованный в аддоне или в проекте, добавляя особые аннотации метаданных к определению компонента.

Собственные UI компоненты и фасеты поддерживаются в CUBA Studio начиная с версии Studio 14.0 и версии платформы 7.2.5.

Поддержка UI компонента в дизайнере экранов включает в себя следующие возможности:

  • Отображение компонента в панели Palette.

  • Генерация XML кода для компонента, когда он добавляется из палитры (Palette). Автоматическое добавление импорта пространства имен XSD, если это указано в объявлении компонента.

  • Отображение иконки, соответствующей добавленному компоненту, в панели Hierarchy.

  • Отображение и редактирование свойств компонента в панели Inspector. Генерация атрибутов XML тегов при изменении свойств компонента.

  • Показ подсказок для значений свойств.

  • Валидация значений свойств.

  • Инжекция компонента в контроллер экрана.

  • Генерация обработчиков событий и делегирующих методов, объявленных компонентом.

  • Отображение заглушки компонента в панели Layout Preview.

  • Переход к web-документации, если ссылка предоставлена разработчиком.



Предварительные условия

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

  • Создан класс интерфейса компонента или фасета.

  • Для компонентов - реализован загрузчик компонента. Для фасетов - создан провайдер фасета, это Spring бин, реализующий интерфейс com.haulmont.cuba.gui.xml.FacetProvider, параметризованный классом фасета.

  • Компонент и его загрузчик зарегистрированы в файле cuba-ui-component.xml.

  • Необязательно: определена XML схема, описывающая структуру и ограничения на описание компонента (фасета) в дескрипторе экрана.

Демо-проект Stepper

Полностью реализованный пример собственного UI компонента с аннотациями метаданных можно найти в проекте-примере интеграции Vaadin аддона Stepper. Его исходный код находится здесь: https://github.com/cuba-labs/vaadin-stepper-addon-integration.

Файлы, на которые следует обратить внимание:

  • Аннотации UI метаданных добавлены к интерфейсу компонента: com.company.demo.web.gui.components.Stepper.

  • Иконки компонента для палитры (stepper.svg и stepper_dark.svg) расположены в каталоге modules/web/src/com/company/demo/web/gui/components/icons.

  • Дескриптор экрана customer-edit.xml использует компонент stepper в вёрстке.

import com.haulmont.cuba.gui.meta.*;

@StudioComponent(category = "Samples",
        unsupportedProperties = {"description", "icon", "responsive"},
        xmlns = "http://schemas.company.com/demo/0.1/ui-component.xsd",
        xmlnsAlias = "app",
        icon = "com/company/demo/web/gui/components/icons/stepper.svg",
        canvasBehaviour = CanvasBehaviour.INPUT_FIELD)
@StudioProperties(properties = {
        @StudioProperty(name = "dataContainer", type = PropertyType.DATACONTAINER_REF),
        @StudioProperty(name = "property", type = PropertyType.PROPERTY_PATH_REF, options = "int"),
}, groups = @PropertiesGroup(
        properties = {"dataContainer", "property"}, constraint = PropertiesConstraint.ALL_OR_NOTHING
))
public interface Stepper extends Field<Integer> {

    @StudioProperty(name = "manualInput", type = PropertyType.BOOLEAN, defaultValue = "true")
    void setManualInputAllowed(boolean value);

    boolean isManualInputAllowed();

// ...

}

Открыв файл customer-edit.xml в дизайнере экранов, вы увидите, как компонент встроен в панели дизайнера.

Панель Component Palette содержит элемент Stepper:

palette

Панель Component Hierarchy отображает компонент рядом с другими компонентами в дереве:

hierarchy

Панель Component Inspector отображает и позволяет редактировать свойства компонента:

inspector

Наконец, панель предпросмотра вёрстки отображает компонент в виде текстового поля:

preview

Теперь давайте перейдем к аннотациям и атрибутам, которые нужно добавить к интерфейсу компонента, чтобы добиться такой интеграции.

Список аннотаций метаданных

Все аннотации UI метаданных и связанные классы расположены в пакете com.haulmont.cuba.gui.meta. Аннотации UI метаданных поддерживаются для следующих типов UI элементов:

Доступны следующие аннотации:

  • @StudioComponent - указывает, что UI компонент, чей интерфейс проаннотирован, должен быть доступен в дизайнере экранов. Предоставляет атрибуты, необходимые для панелей дизайнера экранов. Проаннотированный интерфейс должен быть потомком интерфейса com.haulmont.cuba.gui.components.Component.

  • @StudioFacet - указывает, что фасет, чей интерфейс проаннотирован, должен быть доступен в дизайнере экранов. Предоставляет необходимые атрибуты для панелей дизайнера экранов. Проаннотированный интерфейс должен быть потомком интерфейса com.haulmont.cuba.gui.components.Facet. Фасет должен иметь связанный бин FacetProvider, определенный в проекте.

  • @StudioProperty - указывает, что проаннотированный метод-setter нужно показать в панели Inspector как свойство UI компонента или фасета.

  • @StudioProperties - определяет дополнительные свойства и группы свойств для UI компонента или фасета. Можно использовать, чтобы определить дополнительные свойства, не относящиеся к setter-методам свойств компонента, а также чтобы переопределить унаследованные свойства или чтобы провалидировать связанные по смыслу пары свойств.

  • @PropertiesGroup - определяет группу свойств: список связанных свойств, которые должны быть определены только одновременно, или наоборот - взаимно исключают друг друга.

  • @StudioElementsGroup - указывает, что проаннотированный метод-setter нужно показать в дизайнере экранов как вложенную группу под-элементов UI компонента или фасета, например колонки, действия или список динамических свойств.

  • @StudioElement - указывает, что проаннотированный класс или интерфейс должен быть доступен в дизайнере экранов как часть UI компонента или фасета, например колонка, действие или динамическое свойство.

  • @StudioEmbedded - используется для случаев, когда набор свойств компонента выделен в отдельный POJO.

  • @StudioCollection - объявляет метаданные для вложенной группы под-элементов, которые должны быть поддержаны в дизайнере экранов, например колонки, действия, поля.

Объявление UI компонента

Чтобы объявить, что UI компонент должен появиться в дизайнере экранов, нужно пометить его интерфейс аннотацией com.haulmont.cuba.gui.meta.StudioComponent.

@StudioComponent(caption = "GridLayout", xmlElement = "bgrid", category = "Containers")
private interface BGridLayout extends BLayout {
    // ...
}

Аннотация @StudioComponent имеет следующие атрибуты:

  • caption - подпись компонента, отображаемая в панели Palette.

  • description - описание компонента, отображаемое в панели Palette как всплывающая подсказка при наведении мыши.

  • category - категория в панели Palette (Containers, Components и т.д.), куда компонент будет помещён.

  • icon - путь к файлу иконки компонента, используемой в панелях Palette и Hierarchy, в формате SVG или PNG, относительно корня модуля, в котором расположен компонент. Иконка компонента может иметь два варианта: для светлой и тёмной тем IDE. Имя файла тёмного варианта иконки определяется добавлением постфикса _dark к имени файла иконки, например: stepper.svg и stepper_dark.svg.

  • xmlElement - название XML тега, которое будет вставляться в XML дескриптор экрана, когда компонент добавляется в экран.

  • xmlns - пространство имён XML, требуемое для компонента. Когда компонент добавляется в экран, Studio автоматически добавляет импорт пространства имён к корневому элементу дескриптора экрана.

  • xmlnsAlias - алиас пространства имён XML, требуемый для компонента. Например если алиас пространства имён - track, а название XML тега - это googleTracker, то компонент будет добавлен в экран как тег <track:googleTracker/>.

  • defaultProperty - имя свойства компонента по умолчанию, оно будет автоматически выбрано в панели Inspector, когда компонент выбирается в вёрстке.

  • unsupportedProperties - список свойств, которые унаследованы от родительских интерфейсов компонента, но на самом деле не поддерживаются компонентом. Эти свойства будут скрыты в панели Inspector.

  • canvasBehavior - определяет то, как UI компонент будет выглядеть на панели layout preview. Возможные варианты:

    • COMPONENT - компонент отображается на предпросмотре как простой прямоугольник с иконкой.

    • INPUT_FIELD - компонент отображается на предпросмотре как текстовое поле.

    • CONTAINER - компонент отображается на предпросмотре как контейнер.

  • canvasIcon - путь к файлу иконки, отображаемой на предпросмотре как заполнитель, если атрибут canvasBehaviour имеет значение COMPONENT. Файл иконки должен быть в формате SVG или PNG. Если это значение не указано, то будет использоваться атрибут icon.

  • canvasIconSize - размер иконки, отображаемой на предпросмотре как заполнитель. Возможные значения:

    • SMALL - небольшая иконка.

    • LARGE - большая иконка, и под ней отображается id компонента.

  • containerType - вид расположения контейнера (вертикальный, горизонтальный или flow), если атрибут canvasBehaviour имеет значение CONTAINER.

  • documentationURL - URL, указывающий на страницу документации для UI компонента. Используется действием CUBA Documentation в дизайнере экранов. Если путь к документации зависит от версии библиотеки, то используйте %VERSION% как заместитель. Он будет заменён минорной версией (например 1.2) библиотеки, содержащей UI компонент.

Определение фасета

Чтобы указать, что собственный фасет должен быть доступен в дизайнере экранов, нужно пометить его интерфейс аннотацией com.haulmont.cuba.gui.meta.StudioFacet.

Чтобы фасет появился в дизайнере экранов, в проекте нужно создать связанную с ним реализацию FacetProvider.

FacetProvider - это Spring бин, реализующий интерфейс com.haulmont.cuba.gui.xml.FacetProvider, и параметризованный классом интерфейса фасета. Используйте класс платформы com.haulmont.cuba.web.gui.facets.ClipboardTriggerFacetProvider как пример.

Атрибуты аннотации @StudioFacet совпадают с атрибутами аннотации @StudioComponent, описанными в предыдущей секции.

Пример:

@StudioFacet(
        xmlElement = "clipboardTrigger",
        category = "Facets",
        icon = "icon/clipboardTrigger.svg",
        documentationURL = "https://doc.cuba-platform.com/manual-%VERSION%/gui_ClipboardTrigger.html"
)
public interface ClipboardTrigger extends Facet {

    @StudioProperty(type = PropertyType.COMPONENT_REF, options = "com.haulmont.cuba.gui.components.TextInputField")
    void setInput(TextInputField<?> input);

    @StudioProperty(type = PropertyType.COMPONENT_REF, options = "com.haulmont.cuba.gui.components.Button")
    void setButton(Button button);

    // ...
}
Стандартные свойства компонентов

Свойства компонентов объявляются с помощью двух аннотаций:

  • @StudioProperty - указывает, что проаннотированный метод (setter, setXxx) должен быть показан в панели Inspector как свойство компонента.

  • @StudioProperties - объявляет на интерфейсе компонента дополнительные свойства и группы свойств компонента, не связанные с setter-методами.

Пример:

@StudioComponent(caption = "RichTextArea")
@StudioProperties(properties = {
        @StudioProperty(name = "css", type = PropertyType.CSS_BLOCK)
})
interface RichTextArea extends Component {
    @StudioProperty(type = PropertyType.CSS_CLASSNAME_LIST)
    void setStylename(String stylename);

    @StudioProperty(type = PropertyType.SIZE, defaultValue = "auto")
    void setWidth(String width);

    @StudioProperty(type = PropertyType.SIZE, defaultValue = "auto")
    void setHeight(String height);

    @StudioProperty(type = PropertyType.LOCALIZED_STRING)
    void setContextHelpText(String contextHelpText);

    @StudioProperty(type = PropertyType.LOCALIZED_STRING)
    void setDescription(String description);

    @StudioProperty
    @Min(-1)
    void setTabIndex(int tabIndex);
}

Метод-setter может быть проаннотирован как @StudioProperty без указания каких-либо атрибутов. В этом случае:

  • имя и подпись свойства будут выведены из имени метода.

  • тип свойства будет определен по типу параметра setter-метода.

Аннотация @StudioProperty имеет следующие атрибуты:

  • name - название свойства.

  • type - определяет вид содержимого, которое хранится в свойстве, например это может быть строка, имя сущности или ссылка на другой компонент в том же экране. Поддерживаемые типы свойств описаны ниже.

  • caption - подпись свойства, отображаемая в панели Inspector.

  • description - дополнительное описание свойства, которое будет отображаться в панели Inspector как всплывающая подсказка при наведении мыши.

  • category - категория свойств в панели Inspector (в данный момент ещё не используется дизайнером экранов Studio)

  • required - свойство является обязательным, дизайнер экранов не позволит ввести пустое значение данного свойства.

  • defaultValue - указывает значение свойства, которое используется компонентом неявно в том случае, если соответствующий XML атрибут не указан. Данное значение, если его ввести, будет удалено из XML кода.

  • options - контекстно зависимый список вариантов для свойства компонента:

    • Для типа ENUMERATION - список вариантов перечисления.

    • Для типа BEAN_REF - список разрешенных базовых классов для Spring бина.

    • Для типа COMPONENT_REF - список разрешенных базовых классов (интерфейсов) для компонента.

    • Для типа PROPERTY_PATH_REF - список разрешенных типов атрибута сущности. Используйте зарегистрированные имена встроенных типов данных для примитивных атрибутов или to_one и to_many для атрибутов-ассоциаций.

  • xmlAttribute - название XML-атрибута, если не указано, то будет использоваться значение из name.

  • xmlElement - название XML-элемента. Если использовать этот атрибут, то вы можете продекларировать свойство компонента, которое будет записываться в под-тег основного XML-тега компонента, см. ниже.

  • typeParameter - указывает имя генерик-параметра типа компонента, который предоставляется этим свойством компонента. См. описание ниже.

Типы свойств компонента

Поддерживаются следующие типы свойств (com.haulmont.cuba.gui.meta.PropertyType):

  • INTEGER, LONG, FLOAT, DOUBLE, STRING, BOOLEAN, CHARACTER - примитивные типы.

  • DATE - дата в формате YYYY-MM-DD.

  • DATE_TIME - дата и время в формате YYYY-MM-DD hh:mm:ss.

  • TIME - время в формате hh:mm:ss.

  • ENUMERATION - перечислимый тип. Список вариантов перечисления предоставляется атрибутом аннотации options.

  • COMPONENT_ID - идентификатор компонента, под-элемента или действия. Значение должно быть корректным идентификатором Java.

  • ICON_ID - путь к файлу иконки или ID иконки из иконок, предопределенных в CUBA или определенных в проекте.

  • SIZE - значение размера, например ширина или высота.

  • LOCALIZED_STRING - локализованное сообщение, представленное строковым значением или ключом сообщения с префиксом msg:// или mainMsg://.

  • JPA_QUERY - строка запроса на языке JPA QL.

  • ENTITY_NAME - имя сущности (указанное в атрибуте аннотации javax.persistence.Entity#name), определенной в проекте.

  • ENTITY_CLASS - полное имя класса сущности, определенной в проекте.

  • JAVA_CLASS_NAME - полное имя произвольного Java-класса.

  • CSS_CLASSNAME_LIST - список CSS-классов, разделенных пробелом.

  • CSS_BLOCK - строка CSS свойств.

  • BEAN_REF - ID бина Spring, определенного в проекте. Список разрешенных базовых классов, от которых Spring бин должен быть унаследован, можно указать в атрибуте options данной аннотации.

  • COMPONENT_REF - ID компонента, определенного в данном экране. Список разрешенных базовых классов, от которых компонент должен быть унаследован, можно указать в атрибуте options данной аннотации.

  • DATASOURCE_REF - ID источника данных, определенного в экране (устаревшее API).

  • COLLECTION_DATASOURCE_REF - ID источника данных-коллекции, определенного в экране (устаревшее API).

  • DATALOADER_REF - ID загрузчика данных, определенного в экране.

  • DATACONTAINER_REF - ID контейнера данных, определенного в экране.

  • COLLECTION_DATACONTAINER_REF - ID контейнера коллекции данных, определенного в экране.

  • PROPERTY_REF - имя атрибута сущности. Список типов данных атрибута можно ограничить, указав атрибут options данной аннотации. Чтобы для данного свойства отображались подсказки, свойство компонента должно быть связано с другим свойством компонента, определяющим контейнер данных или источник данных, с помощью группы свойств.

  • PROPERTY_PATH_REF - имя атрибута сущности или список вложенных атрибутов, разделенных точкой, например user.group.name. Список типов данных атрибута можно ограничить, указав атрибут options данной аннотации. Чтобы для данного свойства отображались подсказки, свойство компонента должно быть связано с другим свойством компонента, определяющим контейнер данных или источник данных, с помощью группы свойств.

  • DATATYPE_ID - ID типа данных CUBA, например string или decimal.

  • SHORTCUT - комбинация клавиш, например CTRL-SHIFT-U.

  • SCREEN_CLASS - полное имя класса контроллера экрана, определенного в проекте.

  • SCREEN_ID - ID экрана, определенного в проекте.

  • SCREEN_OPEN_MODE - режим открытия экрана.

Валидация свойств компонента

Панель Inspector поддерживает валидацию свойств компонента с помощью ограниченного набора аннотаций BeanValidation:

  • @Min, @Max, @DecimalMin, @DecimalMax.

  • @Negative, @Positive, @PosizitiveOrZero, @NegativeOrZero.

  • @NotBlank, @NotEmpty.

  • @Digits.

  • @Pattern.

  • @Size, @Length.

  • @URL.

Пример:

@StudioProperty(type = PropertyType.INTEGER)
@Positive
void setStepAmount(int amount);
int getStepAmount();

Если пользователь пытается ввести некорректное значение, то отображается следующее сообщение об ошибке:

bean validation
Аннотация @StudioProperties и группы свойств

Метаданные, определенные аннотацией @StudioProperty, могут быть переопределены аннотацией @StudioProperties, определенной на интерфейсе компонента.

Аннотация @StudioProperties может иметь несколько объявлений групп в атрибуте groups - с помощью аннотации @PropertiesGroup. Каждая группа определяет группу свойств, тип этой группы определяется атрибутом @PropertiesGroup#constraint:

  • ONE_OF - свойства взаимно исключают друг друга, не могут быть указаны одновременно.

  • ALL_OR_NOTHING - список семантически связанных свойств, которые должны использоваться только вместе, одновременно.

Важное применение группы свойств - это указание пары атрибутов dataContainer и property для компонентов, которые могут быть привязаны к контейнеру данных. Эти свойства обязаны быть включены в группу вида ALL_OR_NOTHING. Вот пример компонента с подобной группой свойств:

@StudioComponent(
        caption = "RichTextField",
        category = "Fields",
        canvasBehaviour = CanvasBehaviour.INPUT_FIELD)
@StudioProperties(properties = {
        @StudioProperty(name = "dataContainer", type = PropertyType.DATACONTAINER_REF),
        @StudioProperty(name = "property", type = PropertyType.PROPERTY_PATH_REF, options = "string")
}, groups = @PropertiesGroup(
        properties = {"dataContainer", "property"}, constraint = PropertiesConstraint.ALL_OR_NOTHING
))
interface RichTextField extends Component {
    // ...
}
Объявление метаданных под-элемента с помощью @StudioCollection

Составные компоненты, такие как table, pickerField или графики - определяются в дескрипторе экрана как несколько вложенных XML тегов. Под-теги представляют собой части компонента, и они загружаются в экран загрузчиком ComponentLoader родительского компонента. Имеются два варианта, как определить метаданные для под-элементов:

  • С помощью @StudioCollection - метаданные под-элементов можно указать прямо в интерфейсе компонента.

  • С помощью @StudioElementGroup и @StudioElement - метаданные под-элементов указываются на отдельных классах, которые отображают собой XML под-теги.

Аннотация com.haulmont.cuba.gui.meta.StudioCollection имеет следующие атрибуты:

  • xmlElement - название XML тега для коллекции.

  • itemXmlElement - название XML тега для элемента коллекции.

  • documentationURL - URL, указывающий на страницу документации для элемента. Используется действием CUBA Documentation в дизайнере экранов. Если путь к документации зависит от версии библиотеки, то используйте %VERSION% как заместитель. Он будет заменён минорной версией (например 1.2) библиотеки, содержащей UI компонент.

  • itemProperties - список аннотаций @StudioProperty, определяющих свойства элемента.

Ниже представлен пример.

Желаемая структура XML в дескрипторе экрана:

<layout>
    <langPicker>
        <options>
            <option caption="msg://lang.english" code="en" flagIcon="icons/english.png"/>
            <option caption="msg://lang.french" code="fr" flagIcon="icons/french.png"/>
        </options>
    </langPicker>
</layout>

Класс компонента с объявлением @StudioCollection:

@StudioComponent(xmlElement = "langPicker", category = "Samples")
public interface LanguagePicker extends Field<Locale> {

    @StudioCollection(xmlElement = "options", itemXmlElement = "option",
            itemProperties = {
                    @StudioProperty(name = "caption", type = PropertyType.LOCALIZED_STRING, required = true),
                    @StudioProperty(name = "code", type = PropertyType.STRING),
                    @StudioProperty(name = "flagIcon", type = PropertyType.ICON_ID)
            })
    void setOptions(List<LanguageOption> options);
    List<LanguageOption> getOptions();
}

Панель Inspector для главного элемента компонента дополнительно отображает кнопку Add{название элемента}, которая добавляет под-элемент в XML:

collection owner inspector

Если под-элемент выбран в верстке экрана, то панель Inspector отображает его свойства, указанные в аннотации StudioCollection:

collection element inspector
Определение метаданных под-элементов с помощью @StudioElementGroup и @StudioElement

Аннотация @StudioElementGroup используется для пометки setter-метода в интерфейсе компонента. Она дает указание для Studio, что метаданные под-элементов нужно искать в классе, который используется как параметр setter-метода.

@StudioElementsGroup(xmlElement = "subElementGroupTagName")
void setSubElements(List<ComponentSubElement> subElements);

Аннотация @StudioElementGroup имеет следующие атрибуты:

  • xmlElement - название XML тега группы под-элементов.

  • icon - путь к иконке, используемой в панелях Palette и Hierarchy для группы под-элементов, в формате SVG или PNG, относительно корня модуля.

  • documentationURL - URL, указывающий на страницу документации для группы под-элементов. Используется действием CUBA Documentation в дизайнере экранов. Если путь к документации зависит от версии библиотеки, то используйте %VERSION% как заместитель. Он будет заменён минорной версией (например 1.2) библиотеки, содержащей UI компонент.

Аннотация @StudioElement помечает класс, который соответствует под-элементу компонента. Доступные атрибуты для XML тега, соответствующего данному элементу, объявляются с помощью аннотаций @StudioProperty и @StudioProperties.

@StudioElement(xmlElement = "subElement", caption = "Sub Element")
public interface SubElement {
    @StudioProperty
    void setElementProperty(String elementProperty);
    // ...
}

Атрибуты аннотации @StudioElement похожи на атрибуты аннотации @StudioComponent:

  • xmlElement - название XML тега под-элемента.

  • caption - подпись элемента, отображается в панели Inspector.

  • description - дополнительное описание для элемента, отображается в панели Inspector как всплывающая подсказка при наведении мыши.

  • icon - путь к иконке под-элемента, отображаемой в панелях Palette и Hierarchy, формат SVG или PNG, относительно корня модуля.

  • xmlns - пространство имён XML, требуемое для элемента. Когда элемент добавляется в экран, Studio автоматически добавляет импорт пространства имён к корневому элементу дескриптора экрана.

  • xmlnsAlias - алиас пространства имён XML, требуемый для элемента. Например если алиас пространства имён - map, а название XML тега - это layer, то элемент будет добавлен в экран как тег <map:layer/>.

  • defaultProperty - имя свойства элемента по умолчанию, оно будет автоматически выбрано в панели Inspector, когда элемент выбирается в вёрстке.

  • unsupportedProperties - список свойств, которые унаследованы от родительских интерфейсов элемента, но на самом деле им не поддерживаются. Эти свойства будут скрыты в панели Inspector.

  • documentationURL - URL, указывающий на страницу документации для элемента. Используется действием CUBA Documentation в дизайнере экранов. Если путь к документации зависит от версии библиотеки, то используйте %VERSION% как заместитель. Он будет заменён минорной версией (например 1.2) библиотеки, содержащей UI компонент.

Ниже представлен пример.

Это желаемая структура XML в дескрипторе экрана:

<layout>
    <serialChart backgroundColor="#ffffff" caption="Weekly Stats">
        <graphs>
            <graph colorProperty="color" valueProperty="price"/>
            <graph colorProperty="costColor" valueProperty="cost"/>
        </graphs>
    </serialChart>
</layout>

Класс компонента с определением @StudioElementsGroup:

@StudioComponent(xmlElement = "serialChart", category = "Samples")
public interface SerialChart extends Component {

    @StudioProperty
    void setCaption(String caption);
    String getCaption();

    @StudioProperty(type = PropertyType.STRING)
    void setBackgroundColor(Color backgroundColor);
    Color getBackgroundColor();

    @StudioElementsGroup(xmlElement = "graphs")
    void setGraphs(List<ChartGraph> graphs);
    List<ChartGraph> getGraphs();
}

Класс под-элемента, проаннотированный как @StudioElement:

@StudioElement(xmlElement = "graph", caption = "Graph")
public interface ChartGraph {

    @StudioProperty(type = PropertyType.PROPERTY_PATH_REF)
    void setValueProperty(String valueProperty);
    String getValueProperty();

    @StudioProperty(type = PropertyType.PROPERTY_PATH_REF)
    void setColorProperty(String colorProperty);
    String getColorProperty();
}

Панель Inspector для главного элемента компонента дополнительно отображает кнопку Add{подпись элемента}, которая добавляет новый под-элемент в XML:

element group owner inspector

Если под-элемент выбран в верстке, то панель Inspector отображает его свойства, объявленные в классе под-элемента:

element group element inspector
Объявление атрибутов под-тега

Существует возможность задавать некоторые из свойств компонента не в главном теге, а единственном под-теге главного XML тега в дескрипторе экрана. В качестве примера рассмотрим следующий вариант XML верстки компонента:

<myChart>
    <scrollBar color="white" position="TOP"/>
</myChart>

Здесь scrollBar - это часть главного компонента myChart, а не самостоятельный компонент, и мы хотим объявить метаданные атрибутов в главном интерфейсе компонента.

Аннотации метаданных для атрибутов под-тега можно объявить прямо в интерфейсе компонента при помощи атрибута xmlElement аннотации @StudioProperty. Этот атрибут определяет название под-тега. Объявление компонента будет выглядеть следующим образом:

@StudioComponent(xmlElement = "myChart", category = "Samples")
public interface MyChart extends Component {

    @StudioProperty(name = "position", caption = "scrollbar position",
            xmlElement = "scrollBar", xmlAttribute = "position",
            type = PropertyType.ENUMERATION, options = {"TOP", "BOTTOM"})
    void setScrollBarPosition(String scrollBarPosition);
    String getScrollBarPosition();

    @StudioProperty(name = "color", caption = "scrollbar color",
            xmlElement = "scrollBar", xmlAttribute = "color")
    void setScrollBarColor(String scrollBarColor);
    String getScrollBarColor();
}
Вынесение атрибутов основного тега в POJO с помощью @StudioEmbedded

Существуют случаи, когда возникает потребность вынести часть свойств компонента в отдельный POJO (plain old Java object, обыкновенный Java-класс с геттерами и сеттерами). В то же время в схеме XML вынесенные свойства указываются как атрибуты основного XML тега компонента. В этом случае можно применить аннотацию @StudioEmbedded. Метод-setter, который принимает POJO-объект, нужно пометить как @StudioEmbedded, чтобы объявить, что этот POJO содержит объявления дополнительных свойств компонента.

Аннотация com.haulmont.cuba.gui.meta.StudioEmbedded не имеет атрибутов.

Пример представлен ниже.

Желаемая структура XML, обратите внимание, что все атрибуты указаны для главного XML тега компонента:

<layout>
    <richTextField textColor="blue" editable="false" id="rtf"/>
</layout>

POJO-класс с проаннотированными свойствами:

public class FormattingOptions {
    private String textColor = "black";
    private boolean foldComments = true;

    @StudioProperty(defaultValue = "black", description = "Main text color")
    public void setTextColor(String textColor) {
        this.textColor = textColor;
    }

    @StudioProperty(defaultValue = "true")
    public void setFoldComments(boolean foldComments) {
        this.foldComments = foldComments;
    }
}

Интерфейс компонента:

@StudioComponent(category = "Samples",
        unsupportedProperties = {"icon", "responsive"},
        description = "Text field with html support")
public interface RichTextField extends Field<String> {

    @StudioEmbedded
    void setFormattingOptions(FormattingOptions formattingOptions);
    FormattingOptions getFormattingOptions(FormattingOptions formattingOptions);
}

Панель Inspector показывает вынесенные свойства в общем списке:

embedded inspector
Поддержка обработчиков событий и делегирующих методов

Studio предоставляет такую же поддержку для событий и делегирующих методов в пользовательских UI компонентах и фасетах, как и для встроенных UI компонентов. При объявлении слушателя события или делегирующего метода в интерфейсе компонента не требуется никаких дополнительных аннотаций.

Пример компонента, объявляющего обработчик события и свой собственный класс события:

@StudioComponent(category = "Samples")
public interface LazyTreeTable extends Component {

    // ...

    Subscription addNodeExpandListener(Consumer<NodeExpandEvent> listener);

    class NodeExpandEvent extends EventObject {
        private final Object nodeId;

        public NodeExpandEvent(LazyTreeTable source, Object nodeId) {
            super(source);
            this.nodeId = nodeId;
        }

        public Object getNodeId() {
            return nodeId;
        }
    }
}

Объявленный обработчик события становится доступным в панели Inspector:

event handler in inspector

Реализация обработчика события, сгенерированная Studio в контроллере экрана, будет иметь вид:

@UiController("demo_Dashboard")
@UiDescriptor("dashboard.xml")
public class Dashboard extends Screen {
    // ...

    @Subscribe("regionTable")
    public void onRegionTableNodeExpand(LazyTreeTable.NodeExpandEvent event) {

    }
}

Следующий пример демонстрирует, как объявить делегирующие методы в фасете, который параметризован генерик-типом:

@StudioFacet
public interface LookupScreenFacet<E extends Entity> extends Facet {

    // ...

    void setSelectHandler(Consumer<Collection<E>> selectHandler);

    void setOptionsProvider(Supplier<ScreenOptions> optionsProvider);

    void setTransformation(Function<Collection<E>, Collection<E>> transformation);

    @StudioProperty(type = PropertyType.COMPONENT_REF, typeParameter = "E",
            options = "com.haulmont.cuba.gui.components.ListComponent")
    void setListComponent(ListComponent<E> listComponent);
}
Поддержка параметров генерик-типов

Studio поддерживает параметризацию интерфейса компонента генерик-типом. Параметр может быть классом сущности, классом экрана или произвольным Java классом. Параметр типа используется тогда, когда компонент инжектируется в контроллер экрана, или когда генерируются заглушки реализации делегирующих методов.

Studio выводит генерик-тип, используемый конкретным компонентом, глядя на свойства компонента, присвоенные ему в XML верстке. Свойство компонента может указать генерик-тип прямо или опосредованно. Например компонент table отображает список сущностей и параметризуется классом сущности. Чтобы определить используемый в конкретном случае тип сущности, Studio смотрит на атрибут dataContainer, а затем на определение контейнера коллекции данных. Если все атрибуты присвоены, тогда класс сущности, используемый контейнером данных, и берется как значение генерик-типа для данного компонента table.

Параметр typeParameter аннотации @StudioProperty указывает имя параметра типа для UI компонента или фасета, который предоставляется данным свойством компонента. Значение генерик-типа может быть предоставлено свойствами следующих типов:

  • PropertyType.JAVA_CLASS_NAME - используется указанный класс.

  • PropertyType.ENTITY_CLASS - используется указанный класс сущности.

  • PropertyType.SCREEN_CLASS_NAME - указывается указанный класс экрана.

  • PropertyType.DATACONTAINER_REF, PropertyType.COLLECTION_DATACONTAINER_REF - используется класс сущности указанного контейнера данных.

  • PropertyType.DATASOURCE_REF, PropertyType.COLLECTION_DATASOURCE_REF - используется класс сущности указанного источника данных.

  • PropertyType.COMPONENT_REF - используется класс сущности, к которому привязано данное поле ввода (класс определяется через привязанный контейнер данных или источник данных).

Ниже приведен пример.

Интерфейс UI компонента, который отображает список сущностей, поставляемый контейнером коллекции данных:

@StudioComponent(category = "Samples")
public interface MyTable<E extends Entity> extends Component { (1)

    @StudioProperty(type = PropertyType.COLLECTION_DATACONTAINER_REF,
            typeParameter = "E") (2)
    void setContainer(CollectionContainer<E> container);

    void setStyleProvider(@Nullable Function<? super E, String> styleProvider); (3)
}
1 - интерфейс компонента параметризован параметром E, который представляет класс сущности элементов, отображаемых в таблице.
2 - указав на свойстве типа COLLECTION_DATACONTAINER_REF атрибут аннотации typeParameter, можно дать указание для Studio, чтобы реально используемый тип сущности выводился на основании привязанного к компоненту контейнера данных.
3 - генерик-тип компонента также используется делегирующим методом, который компонент экспортирует для пользователей.

Чтобы Studio автоматически выводила генерик-тип, используемый компонентом, этот компонент должен быть связан с контейнером данных в дескрипторе экрана:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="msg://dashboard.caption"
        messagesPack="com.company.demo.web.screens">
    <data>
        <collection id="regionsDc" class="com.company.demo.entity.Region">
            <!-- ... -->
        </collection>
    </data>
    <layout>
        <myTable id="regionTable" container="regionsDc"/>
    </layout>
</window>

Тогда Studio будет генерировать подобный код в контроллере экрана:

@UiController("demo_Dashboard")
@UiDescriptor("dashboard.xml")
public class Dashboard extends Screen {
    @Inject
    private MyTable<Region> regionTable; (1)

    @Install(to = "regionTable", subject = "styleProvider")
    private String regionTableStyleProvider(Region region) { (2)
        return "bold-text";
    }
}
1 - компонент инжектируется в контроллер экрана с корректным генерик-типом.
2 - ожидаемый генерик-тип используется и в сигнатуре делегирующего метода.