Диалоги

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

Методы createMessageDialog(), createOptionDialog() и createInputDialog() - это точки входа в fluent API, который позволяет создавать и отображать диалоговые окна.

Диалог сообщения

Диалог сообщения отображает сообщение пользователю.

message dialog

В следующем примере диалог сообщения отображается при нажатии кнопки пользователем:

@Autowired
private Dialogs dialogs;
@Subscribe("messageDialogButton")
public void onHelloButtonClick(ClickEvent<Button> event) {
    dialogs.createMessageDialog()
            .withHeader("Success") (1)
            .withText("Invitation sent successfully") (2)
            .open();
}
1 Добавляет заголовок диалога.
2 Добавляет текстовое сообщение для отображения в диалоге.

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

Диалог с вариантами

Диалог с вариантами отображает сообщение и набор кнопок для выбора пользователем.

option dialog

Используйте метод withActions() для предоставления действий, каждое из которых представлено кнопкой в диалоге. Например:

@Autowired
private Dialogs dialogs;
@Subscribe("selectOptionButton")
public void onSelectOptionButtonClick(ClickEvent<Button> event) {
    dialogs.createOptionDialog()
            .withHeader("Please confirm")
            .withText("Do you really want to add a customer?")
            .withActions(
                    new DialogAction(DialogAction.Type.YES)
                            .withHandler(e -> addCustomer()), (1)
                    new DialogAction(DialogAction.Type.NO)
            )
            .open();
}
1 Если пользователь нажмет "Yes", диалог закроется и вызовется метод addCustomer().

Базовый класс DialogAction предназначен для создания действий со стандартными именами и значками. Поддерживаются пять типов действий, определенных перечислением DialogAction.Type: OK, CANCEL, YES, NO, CLOSE. Названия кнопок извлекаются из пакета сообщений.

Следующие методы позволяют вам настроить внешний вид и поведение диалога с вариантами:

Диалог фоновой задачи

Диалог фоновой задачи обеспечивает удобный для пользователя опыт для длительных задач, где пользователь может отслеживать прогресс и отменять операцию при необходимости.

Для получения дополнительной информации о механизме фоновых задач, посетите статью Фоновые Задачи.
backgroundtask dialog

Чтобы настроить диалог, передайте в него объект фоновой задачи:

@Autowired
private Dialogs dialogs;
@Subscribe(id = "backgroundTaskButton", subject = "singleClickListener")
public void onBackgroundTaskClick(final ClickEvent<JmixButton> event) {
    dialogs.createBackgroundTaskDialog(new SampleTask(15, this, 10)) (1)
            .withHeader("Background task running")
            .withText("Please wait until the task is complete")
            .withTotal(10) (2)
            .withCancelAllowed(true) (3)
            .open();
}

protected class SampleTask extends BackgroundTask<Integer, Void> {
    int count;

    public SampleTask(long timeoutSeconds, View<?> view, int count) {
        super(timeoutSeconds, view);
        this.count = count;
    }

    @Override
    public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
        for (int i = 1; i < count + 1; i++) {
            Thread.sleep(1000);
            taskLifeCycle.publish(i);
        }
        return null;
    }
}
1 Передайте объект фоновой задачи в диалог.
2 Установите количество секций в индикаторе прогресса в соответствии с количеством задач.
3 Разрешить пользователю прерывать задачу.

Следующие методы позволяют вам настроить внешний вид и поведение диалога фоновой задачи:

Диалог ввода

Диалог ввода - универсальный инструмент, который позволяет создавать формы ввода с использованием API и часто позволяет избежать создания экранов для ввода тривиальных данных. Он позволяет вводить значения разных типов, выполнять проверку ввода и предоставлять различные действия, доступные для выбора пользователем.

input dialog

Следующие методы позволяют вам настроить внешний вид и поведение диалога ввода:

Рассмотрим несколько примеров.

Стандартные параметры

Используйте метод withParameters() для добавления параметров, каждый из которых будет представлен полем ввода в диалоге. Следующий пример создает диалоговое окно ввода с параметрами стандартных типов и стандартными действиями OK/Cancel:

@Autowired
private Dialogs dialogs;
@Subscribe("standardParametersButton")
public void onStandardParametersButtonClick(ClickEvent<Button> event) {
    dialogs.createInputDialog(this)
            .withHeader("Enter values")
            .withParameters(
                    stringParameter("name").withLabel("Name").withRequired(true), (1)
                    intParameter("amount").withLabel("Amount").withDefaultValue(1), (2)
                    entityParameter("user", User.class).withLabel("User"), (3)
                    enumParameter("status", OnboardingStatus.class).withLabel("Status") (4)
            )
            .withActions(DialogActions.OK_CANCEL) (5)
            .withCloseListener(closeEvent -> {
                if (closeEvent.closedWith(DialogOutcome.OK)) { (6)
                    String name = closeEvent.getValue("name"); (7)
                    int amount = closeEvent.getValue("amount");
                    User user = closeEvent.getValue("user");
                    OnboardingStatus status = closeEvent.getValue("status");
                    // process entered values...
                }
            })
            .open();

}
1 Указывает обязательный строковый параметр.
2 Указывает целочисленный параметр с значением по умолчанию.
3 Указывает параметр-сущность.
4 Указывает параметр-перечисление.
5 Указывает стандартные действия OK/Cancel, представленные кнопками внизу диалога.
6 В обработчике закрытия можно проверить, какое действие выбрал пользователь.
7 Событие закрытия содержит введенные значения, которые можно получить с использованием идентификаторов параметров.

Пользовательские параметры

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

@Autowired
private Dialogs dialogs;
@Autowired
private DataManager dataManager;
@Autowired
private UiComponents uiComponents;

@Subscribe("customParameterButton")
public void onCustomParameterButtonClick(ClickEvent<Button> event) {
    dialogs.createInputDialog(this)
            .withHeader("Enter values")
            .withParameters(
                    stringParameter("name").withLabel("Name").withRequired(true),
                    intParameter("amount").withLabel("Amount").withDefaultValue(1),
                    parameter("user") (1)
                            .withLabel("User")
                            .withField(() -> {
                                EntityComboBox<User> field = uiComponents.create(EntityComboBox.class); (2)
                                field.setItems(dataManager.load(User.class).all().list()); (3)
                                field.setWidthFull();
                                return field;
                            }),
                    enumParameter("status", OnboardingStatus.class).withLabel("Status")
            )
            .withActions(DialogActions.OK_CANCEL).withCloseListener(closeEvent -> {
                if (closeEvent.closedWith(DialogOutcome.OK)) {
                    String name = closeEvent.getValue("name");
                    int amount = closeEvent.getValue("amount");
                    User user = closeEvent.getValue("user");
                    OnboardingStatus status = closeEvent.getValue("status");
                    // process entered values...
                }
            })
            .open();
}
1 Указывает пользовательский параметр.
2 Создает выпадающий список внутри поля пользовательского параметра.
3 Загружает список вариантов в выпадающий список.

При использовании метода InputParameter.withField() в создаваемое поле передается только атрибут label из InputParameter. Другие атрибуты, заданные в InputParameter, не применяются автоматически к полю, создаваемому внутри лямбда-выражения withField().

Следующие атрибуты InputParameter не передаются при использовании withField():

  • required

  • requiredMessage

  • datatype

  • defaultValue

Это связано с тем, что вы полностью отвечаете за ручное создание и настройку поля внутри лямбда-выражения. Поэтому все дополнительные настройки, кроме label, должны задаваться явно.

В приведённом ниже примере важные атрибуты, такие как datatype и required, необходимо устанавливать вручную для созданного поля. Если полагаться только на withField() без явной настройки этих атрибутов, они применяться автоматически не будут.

parameter("passedDate")
        .withLabel("Date")
        .withField(() -> {
            TypedDatePicker<LocalDate> datePicker = uiComponents.create(TypedDatePicker.class);
            datePicker.setDatatype(datatypeRegistry.get(LocalDate.class));
            datePicker.setRequired(true);
            return datePicker;
        })

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

InputParameter.parameter("passedDate")
        .withLabel("Date")
        .withRequired(true)
        .withDatatype(datatypeRegistry.get(LocalDate.class))

В этом случае Jmix автоматически создаст поле и применит все указанные атрибуты.

Пользовательский валидатор

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

Следующий пример добавляет валидатор, проверяющий, что хотя бы один параметр введен:

@Autowired
private Dialogs dialogs;
@Subscribe("validationButton")
public void onValidationButtonClick(ClickEvent<Button> event) {
    dialogs.createInputDialog(this)
            .withHeader("Enter at least one value")
            .withParameters(
                    stringParameter("name").withLabel("Name").withRequired(true),
                    entityParameter("User", User.class).withLabel("User")
            )
            .withValidator(context -> { (1)
                String name = context.getValue("name"); (2)
                User user = context.getValue("user");
                if (Strings.isNullOrEmpty(name) && user == null) {
                    return ValidationErrors.of("Enter name or select a customer"); (3)
                }
                return ValidationErrors.none();
            })
            .withActions(DialogActions.OK_CANCEL)
            .withCloseListener(closeEvent -> {
                if (closeEvent.closedWith(DialogOutcome.OK)) {
                    String name = closeEvent.getValue("name");
                    User user = closeEvent.getValue("user");
                    // process entered values...
                }
            })
            .open();
}
1 Пользовательский валидатор, добавляющий логику для обеспечения ввода хотя бы одного параметра.
2 В валидаторе можно получить значения параметров из объекта контекста.
3 Валидатор возвращает ошибки валидации, если параметры не введены.

Side Dialog

Диалог, который появляется с одной из сторон экрана.

side dialog

Диалог можно создать с помощью бина Dialogs:

@Autowired
private Dialogs dialogs;
@ViewComponent
private MessageBundle messageBundle;

@Subscribe(id = "sideDialogButton", subject = "clickListener")
public void onSimpleSideDialogButtonClick(final ClickEvent<JmixButton> event) {
    dialogs.createSideDialog()
            .withHorizontalSize("14em")
            .withHeaderProvider(this::createHeader)
            .withContentComponents(createContent())
            .open();
}

private HorizontalLayout createHeader(SideDialog sideDialog) {
    HorizontalLayout header = new HorizontalLayout();
    header.setWidthFull();
    header.add(new H2(messageBundle.getMessage("sideDialogHeader")));
    header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
    Button closeButton = new Button(LumoIcon.CROSS.create(), event -> sideDialog.close());
    closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ICON);
    header.add(closeButton);

    return header;
}

private Component createContent() {
    VirtualList<String> list = new VirtualList<>();
    list.setWidthFull();
    list.setItems("Item 1", "Item 2", "Item 3");
    list.setRenderer(new ComponentRenderer<>((item -> {
        Card root = new Card();
        root.setTitle(item);
        root.setHeaderSuffix(LumoIcon.CROSS.create());
        root.add(messageBundle.getMessage("activityDescription"));
        root.addThemeVariants(CardVariant.LUMO_HORIZONTAL);
        root.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
        return root;
    })));
    return list;
}

Расположение и размер

Используйте withSideDialogPosition(), чтобы задать сторону появления диалога. Возможные значения: RIGHT, TOP, BOTTOM, LEFT, INLINE_START, INLINE_END. INLINE_START и INLINE_END учитывают направление чтения экрана (LTR/RTL).

Размер диалога настраивается по-разному в зависимости от его ориентации:

Для горизонтальных диалогов (LEFT, RIGHT, INLINE_START, INLINE_END):

  • setHorizontalSize()

  • setHorizontalMaxSize()

  • setHorizontalMinSize()

Для вертикальных диалогов (TOP, BOTTOM):

  • setVerticalSize()

  • setVerticalMaxSize()

  • setVerticalMinSize()

Содержимое

Содержимое диалога определяется через провайдеры:

Метод

Описание

withContentComponents()

Задаёт компоненты для основной области диалога.

withHeaderProvider()

Задаёт пользовательские компоненты для заголовка.

withFooterProvider()

Задаёт пользовательские компоненты для футера.

События

Доступны следующие события:

Событие

Описание

SideDialogOpenedChangeEvent

Срабатывает при открытии или закрытии диалога.

SideDialogCloseActionEvent

Срабатывает при нажатии ESC или клике вне диалога; можно использовать, чтобы отменить закрытие.

Стилизация

Внешний вид и размеры диалога можно настроить с помощью CSS.

Размеры

Размер диалога можно задать глобально через CSS-переменные. Эти значения применяются ко всем экземплярам SideDialog.

Если размеры заданы программно через Java API, они имеют приоритет над CSS.

Горизонтальные размеры (для LEFT, RIGHT, INLINE_START, INLINE_END):

Переменная

Описание

По умолчанию

--jmix-side-dialog-horizontal-size

Базовая ширина диалога.

auto

--jmix-side-dialog-horizontal-max-size

Максимальная ширина.

50%

--jmix-side-dialog-horizontal-min-size

Минимальная ширина.

16em

Вертикальные размеры (для TOP, BOTTOM):

Переменная

Описание

По умолчанию

--jmix-side-dialog-vertical-size

Базовая высота диалога.

auto

--jmix-side-dialog-vertical-max-size

Максимальная высота.

50vh

--jmix-side-dialog-vertical-min-size

Минимальная высота.

16em

Внешний вид

Переменная

Описание

По умолчанию

--jmix-side-dialog-transition-duration

Длительность анимации открытия и закрытия.

200ms

Части

Side dialog включает следующие стилизируемые части:

Часть

Описание

jmix-side-dialog-overlay::part(backdrop)

Модальный фон (затемнение за диалогом).

jmix-side-dialog-overlay::part(overlay)

Основной контейнер диалога.

jmix-side-dialog-overlay::part(header)

Заголовок диалога.

jmix-side-dialog-overlay::part(title)

Обёртка заголовка.

jmix-side-dialog-overlay > [slot="title"]

Элемент заголовка.

jmix-side-dialog-overlay::part(header-content)

Контейнер пользовательского заголовка.

jmix-side-dialog-overlay::part(content)

Основная область содержимого.

jmix-side-dialog-overlay::part(footer)

Нижняя часть диалога.

Состояния

Side dialog поддерживает следующие состояния:

Состояние

Описание

jmix-side-dialog-overlay[side-dialog-position='right']

Применяется в зависимости от позиции диалога (например, left, top, bottom)

jmix-side-dialog-overlay[fullscreen]

Применяется, когда диалог отображается в полноэкранном режиме

Настройка диалогов

Вы можете настроить заголовок, размер и положение всех диалоговых окон, используя следующие методы:

  • withHeader() - устанавливает текст заголовка.

  • withWidth() - устанавливает ширину диалогового окна.

  • withHeight() - устанавливает высоту диалогового окна.

  • withLeft() - устанавливает отступ диалогового окна слева от его контейнера (значения без единиц измерения интерпретируются как пиксели).

  • withTop() - устанавливает отступ диалогового окна сверху (значения без единиц измерения интерпретируются как пиксели).

Например:

@Subscribe(id = "configDialogButton", subject = "clickListener")
public void onConfigDialogButtonClick(final ClickEvent<JmixButton> event) {
    dialogs.createMessageDialog()
            .withHeader("Information")
            .withWidth("600px")
            .withHeight("200px")
            .withTop("100px")
            .open();
}

Дополнительные настройки внешнего вида и поведения доступны для определенных типов диалоговых окон:

  • withText() - устанавливает текстовое сообщение, отображаемое в диалоговом окне.

  • withContent() - устанавливает содержимое диалогового окна. Это текст, отображаемый внутри компонента Paragraph. Обратите внимание, что это переопределяет любой текст, ранее установленный с помощью withText().

    Форматирование текста доступно с использованием HTML - диалоговое окно может отображать HTML-контент с помощью метода withContent(). Заданный HTML-фрагмент должен быть заключен в компонент:

    @Autowired
    private Dialogs dialogs;
    Html htmlContent = new Html("<p>Here starts a paragraph. A new line starts after this.<br />" +
            "<b>This text is bold.</b> <i>This text is italic.</i></p>");
    
    @Subscribe("htmlContentButton")
    public void onHtmlContentButtonClick(ClickEvent<Button> event) {
        dialogs.createMessageDialog()
                .withHeader("HTML Formatting")
                .withContent(htmlContent)
                .open();
    }
  • withModal() - если установлено значение false, отображает диалоговое окно как немодальное, позволяя взаимодействовать с другими компонентами приложения. Диалоги по умолчанию модальные.

  • withThemeName() - устанавливает имя (или имена) темы диалога, заменяя любые ранее установленные значения.

  • withClassName() - устанавливает имя (или имена) CSS-класса для компонента, заменяя все ранее определенные классы.

  • withDraggable() - определяет, может ли пользователь перетаскивать диалог. Диалоги можно перетаскивать по умолчанию.

  • withResizable() - определяет, может ли пользователь изменять размер диалога. По умолчанию размер диалога изменить нельзя.

  • withMinWidth(), withMaxWidth(), withMaxWidth(), withMaxHeight() - устанавливают минимальную/максимальную ширину и высоту диалога, соответственно.

  • withCloseOnOutsideClick() - управляет тем, закрывается ли диалог при щелчке за его пределами. По умолчанию диалог закрывается при щелчке снаружи.

  • withCloseOnEsc() - управляет тем, закрывается ли диалог при нажатии клавиши ESC. По умолчанию диалог закрывается клавишей ESC.

  • withDraggedListener() - регистрирует callback, вызываемый после завершения перетаскивания пользователем. Вызывается только в том случае, если перетаскивание включено.

    По умолчанию компонент синхронизирует значения top/left после каждого перетаскивания.

    Например:

    @Subscribe(id = "dragDialogButton", subject = "clickListener")
    public void onDragDialogButtonClick(final ClickEvent<JmixButton> event) {
        dialogs.createMessageDialog()
                .withHeader("Drag this dialog")
                .withDraggedListener(dialogDraggedEvent -> {
                    String left = dialogDraggedEvent.getLeft();
                    String top = dialogDraggedEvent.getTop();
    
                    try {
                        int leftValue = Integer.parseInt(left.replace("px", ""));
                        int topValue = Integer.parseInt(top.replace("px", ""));
    
                        if (leftValue < 300 && topValue < 200) {
                            notifications.create("Dialog is in the upper left corner").show();
                        } else if (leftValue > 800 && topValue > 500) {
                            notifications.create("Dialog is in the lower right corner").show();
                        } else {
                            notifications.create("Dialog is in a neutral area").show();
                        }
                    } catch (NumberFormatException e) {
                        notifications.create("Error: Invalid coordinates")
                                .withType(Notifications.Type.WARNING)
                                .show();
                    }
                })
                .open();
    }
  • withResizeListener() - регистрирует callback, вызываемый после завершения изменения размера пользователем. Вызывается только в том случае, если изменение размера включено.

    По умолчанию компонент синхронизирует значения ширины/высоты и верхнего/левого отступов после каждого изменения размера.

    Например:

    @Subscribe(id = "resizeDialogButton", subject = "clickListener")
    public void onResizeDialogButtonClick(final ClickEvent<JmixButton> event) {
        dialogs.createMessageDialog()
                .withHeader("Resize this dialog")
                .withResizable(true)
                .withResizeListener(dialogResizeEvent -> {
                    String width = dialogResizeEvent.getWidth();
                    String height = dialogResizeEvent.getHeight();
                    try {
                        int widthValue = Integer.parseInt(width);
                        int heightValue = Integer.parseInt(height);
    
                        if (widthValue < 400 || heightValue < 300) {
                            notifications.create("Minimum size: 400×300")
                                    .withType(Notifications.Type.WARNING)
                                    .show();
                        }
                    } catch (NumberFormatException e) {
                        notifications.create("Error: Invalid coordinates")
                                .withType(Notifications.Type.WARNING)
                                .show();
                    }
                })
                .open();
    }