3.5.6. Диалоговые окна

Интерфейс Dialogs предназначен для отображения стандартных диалоговых окон. Его методы createMessageDialog(), createOptionDialog() и createInputDialog() являются точками входа в fluent API, позволяющий конструировать и отображать диалоги.

Внешний вид диалоговых окон можно настроить с помощью переменных SCSS с префиксом $cuba-window-modal-*. Эти переменные можно изменить в визуальном редакторе после расширения темы или создания новой темы.

Message Dialog

В примере ниже диалог отображает сообщение при нажатии кнопки:

@Inject
private Dialogs dialogs;

@Subscribe("showDialogBtn")
protected void onShowDialogBtnClick(Button.ClickEvent event) {
    dialogs.createMessageDialog().withCaption("Information").withMessage("Message").show();
}

С помощью метода withMessage() можно передавать текст сообщения.

В тексте можно использовать символы \n для перевода строк. Для отображения HTML необходимо передать ContentMode.HTML в метод withContentMode(). При использовании HTML обязательно экранируйте данные, полученные из БД, во избежание инжекции вредоносного кода.

Вы можете передать значение true в метод withHtmlSanitizer(), чтобы сделать доступной HTML санитизацию для содержимого диалога. Также в этом случае параметр ContentMode.HTML должен быть передан в метод withContentMode().

protected static final String UNSAFE_HTML = "<i>Jackdaws </i><u>love</u> <font size=\"javascript:alert(1)\" " +
            "color=\"moccasin\">my</font> " +
            "<font size=\"7\">big</font> <sup>sphinx</sup> " +
            "<font face=\"Verdana\">of</font> <span style=\"background-color: " +
            "red;\">quartz</span><svg/onload=alert(\"XSS\")>";

@Inject
private Dialogs dialogs;

@Subscribe("showMessageDialogOnBtn")
public void onShowMessageDialogOnBtnClick(Button.ClickEvent event) {
    dialogs.createMessageDialog()
            .withCaption("MessageDialog with Sanitizer")
            .withMessage(UNSAFE_HTML)
            .withContentMode(ContentMode.HTML)
            .withHtmlSanitizer(true)
            .show();
}

@Subscribe("showMessageDialogOffBtn")
public void onShowMessageDialogOffBtnClick(Button.ClickEvent event) {
    dialogs.createMessageDialog()
            .withCaption("MessageDialog without Sanitizer")
            .withMessage(UNSAFE_HTML)
            .withContentMode(ContentMode.HTML)
            .withHtmlSanitizer(false)
            .show();
}

Значение, переданное в метод withHtmlSanitizer(), имеет приоритет над значением глобального свойства cuba.web.htmlSanitizerEnabled.

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

  • withModal() – если передано false, диалог отображается как немодальный, что позволяет пользователю взаимодействовать с другими частями приложения.

  • withCloseOnClickOutside() – если передано true и диалог модальный, то пользователь может закрыть диалог, щелкнув на любой части окна приложения вне диалога.

  • withMaximized() – если передано true, диалог будет развёрнут во весь экран.

  • withWidth(), withHeight() позволяют указать желаемую геометрию диалога.

Например:

@Inject
private Dialogs dialogs;

@Subscribe("showDialogBtn")
protected void onShowDialogBtnClick(Button.ClickEvent event) {
    dialogs.createMessageDialog()
            .withCaption("Information")
            .withMessage("<i>Message<i/>")
            .withContentMode(ContentMode.HTML)
            .withCloseOnClickOutside(true)
            .withWidth("100px")
            .withHeight("300px")
            .show();
}
Option Dialog

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

@Inject
private Dialogs dialogs;

@Subscribe("showDialogBtn")
protected void onShowDialogBtnClick(Button.ClickEvent event) {
    dialogs.createOptionDialog()
            .withCaption("Confirm")
            .withMessage("Are you sure?")
            .withActions(
                new DialogAction(DialogAction.Type.YES, Action.Status.PRIMARY).withHandler(e -> {
                    doSomething();
                }),
                new DialogAction(DialogAction.Type.NO)
            )
            .show();
}

При нажатии на кнопку диалог закрывается и вызывается метод actionPerform() соответствующего действия.

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

Второй параметр конструктора DialogAction используется для задания визуального стиля кнопки, к которой привязано данное действие. Статус Status.PRIMARY подсвечивает кнопку и задаёт ей выделение по умолчанию, что обеспечивается стилем c-primary-action. Если для диалога задано несколько действий с Status.PRIMARY, то фокус и стиль получает только кнопка первого такого действия в списке.

Input Dialog

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

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

  1. Диалог ввода с параметрами стандартных типов и стандартными действиями OK/Cancel:

    @Inject
    private Dialogs dialogs;
    
    @Subscribe("showDialogBtn")
    private void onShowDialogBtnClick(Button.ClickEvent event) {
        dialogs.createInputDialog(this)
                .withCaption("Enter some values")
                .withParameters(
                        InputParameter.stringParameter("name")
                            .withCaption("Name").withRequired(true), (1)
                        InputParameter.doubleParameter("quantity")
                            .withCaption("Quantity").withDefaultValue(1.0), (2)
                        InputParameter.entityParameter("customer", Customer.class)
                            .withCaption("Customer"), (3)
                        InputParameter.enumParameter("status", Status.class)
                            .withCaption("Status") (4)
                )
                .withActions(DialogActions.OK_CANCEL) (5)
                .withCloseListener(closeEvent -> {
                    if (closeEvent.closedWith(DialogOutcome.OK)) { (6)
                        String name = closeEvent.getValue("name"); (7)
                        Double quantity = closeEvent.getValue("quantity");
                        Optional<Customer> customer = closeEvent.getOptional("customer"); (8)
                        Status status = closeEvent.getValue("status");
                        // process entered values...
                    }
                })
                .show();
    }
    1 - задает строковый обязательный параметр.
    2 - задает числовой параметр со значением по умолчанию.
    3 - задает параметр типа сущность.
    4 - задает параметр типа перечисление.
    5 - задает набор действий, представляемых кнопками внизу диалога.
    6 - в слушателе на закрытие можно определить, какое действие было выбрано пользователем.
    7 - событие закрытия содержит введенные значения, которые можно получить по идентификаторам параметров.
    8 - можно получить значение, завернутое в Optional.
  2. Диалог ввода с нестандартным параметром:

    @Inject
    private Dialogs dialogs;
    @Inject
    private UiComponents uiComponents;
    
    @Subscribe("showDialogBtn")
    private void onShowDialogBtnClick(Button.ClickEvent event) {
        dialogs.createInputDialog(this)
                .withCaption("Enter some values")
                .withParameters(
                        InputParameter.stringParameter("name").withCaption("Name"),
                        InputParameter.parameter("customer") (1)
                                .withField(() -> {
                                    LookupField<Customer> field = uiComponents.create(
                                            LookupField.of(Customer.class));
                                    field.setOptionsList(dataManager.load(Customer.class).list());
                                    field.setCaption("Customer"); (2)
                                    field.setWidthFull();
                                    return field;
                                })
                )
                .withActions(DialogActions.OK_CANCEL)
                .withCloseListener(closeEvent -> {
                    if (closeEvent.closedWith(DialogOutcome.OK)) {
                        String name = closeEvent.getValue("name");
                        Customer customer = closeEvent.getValue("customer"); (3)
                        // process entered values...
                    }
                })
                .show();
    }
    1 - задает нестандартный параметр.
    2 - заголовок нестандартного параметра задается в создаваемом компоненте.
    3 - значение нестандартного параметра получается таким же способом, как и стандартного.
  3. Диалог ввода с нестандартными действиями:

    @Inject
    private Dialogs dialogs;
    
    @Subscribe("showDialogBtn")
    private void onShowDialogBtnClick(Button.ClickEvent event) {
        dialogs.createInputDialog(this)
                .withCaption("Enter some values")
                .withParameters(
                    InputParameter.stringParameter("name").withCaption("Name")
                )
                .withActions( (1)
                        InputDialogAction.action("confirm")
                                .withCaption("Confirm")
                                .withPrimary(true)
                                .withHandler(actionEvent -> {
                                    InputDialog dialog = actionEvent.getInputDialog();
                                    String name = dialog.getValue("name"); (2)
                                    dialog.closeWithDefaultAction(); (3)
                                    // process entered values...
                                }),
                        InputDialogAction.action("refuse")
                                .withCaption("Refuse")
                                .withValidationRequired(false)
                                .withHandler(actionEvent ->
                                    actionEvent.getInputDialog().closeWithDefaultAction())
                )
                .show();
    }
    1 - метод withActions() может принимать массив кастомных действий.
    2 - в обработчике действия можно получить значение параметра из объекта диалога.
    3 - кастомное действие не закрывает диалог само, поэтому это надо сделать в какой-то момент явно.
  4. Диалог ввода с валидатором:

    @Inject
    private Dialogs dialogs;
    
    @Subscribe("showDialogBtn")
    private void onShowDialogBtnClick(Button.ClickEvent event) {
        dialogs.createInputDialog(this)
                .withCaption("Enter some values")
                .withParameters(
                        InputParameter.stringParameter("name").withCaption("Name"),
                        InputParameter.entityParameter("customer", Customer.class).withCaption("Customer")
                )
                .withValidator(context -> { (1)
                    String name = context.getValue("name"); (2)
                    Customer customer = context.getValue("customer");
                    if (Strings.isNullOrEmpty(name) && customer == null) {
                        return ValidationErrors.of("Enter name or select a customer");
                    }
                    return ValidationErrors.none();
                })
                .withActions(DialogActions.OK_CANCEL)
                .withCloseListener(closeEvent -> {
                    if (closeEvent.closedWith(DialogOutcome.OK)) {
                        String name = closeEvent.getValue("name");
                        Customer customer = closeEvent.getValue("customer");
                        // process entered values...
                    }
                })
                .show();
    }
    1 - кастомный валидатор в данном примере необходим для того, чтобы обеспечить ввод как минимум одного параметра из двух.
    2 - значения параметров в валидаторе можно получить через объект контекста.
  5. Диалог ввода с параметром типа FileDescriptor:

    @Inject
    private Dialogs dialogs;
    
    @Subscribe("showDialogBtn")
    public void onShowDialogBtnClick(Button.ClickEvent event) {
        dialogs.createInputDialog(this)
                .withCaption("Select the file")
                .withParameters(
                        InputParameter.fileParameter("fileField") (1)
                                .withCaption("File"))
                .withCloseListener(closeEvent -> {
                    if (closeEvent.closedWith(DialogOutcome.OK)) {
                        FileDescriptor fileDescriptor = closeEvent.getValue("fileField");  (2)
                    }
                })
                .show();
    }
    1 - задает параметр типа FileDescriptor.
    2 - событие закрытия содержит введенное значение, которое можно получить по идентификатору параметра.