3.5.6. 对话框消息

Dialogs 接口设计用来展示标准对话框窗口。其 createMessageDialog()createOptionDialog()createInputDialog() 方法是流式 API 的入口点,可以用来创建和显示对话框。

对话框的展示可以使用带 $cuba-window-modal-* 前缀的 SCSS 变量进行自定义。可以在创建一个 主题扩展 或者一个 自定义主题 之后在可视化编辑器里修改这些变量的值。

消息对话框

下面的例子中,当用户点击按钮时,会显示一个消息对话框:

@Inject
private Dialogs dialogs;

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

使用 withMessage() 方法传递消息文本。

可以在消息中使用 \n 字符来换行。如果要显示 HTML,可以用 withContentMode() 方法带 ContentMode.HTML 参数。当使用 HTML 时,别忘了转移数据内容以防恶意代码注入。

withHtmlSanitizer() 方法传参 true 可以启用对话框内容的 HTML 清理功能。此时,必须为 withContentMode() 方法传递 ContentMode.HTML 参数。

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();
}
选项对话框

选项对话框展示了一个消息和一组用户交互的按钮。使用 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 枚举定义的操作类型:OKCANCELYESNOCLOSE。对应的按钮名称通过主语言消息包获取。

DialogAction 构造器的第二个参数用来为操作的按钮设置特殊的可视化样式。c-primary-action 样式提供的 Status.PRIMARY 会高亮对应的按钮并使得它被选中。如果对话框中有多个操作使用了 Status.PRIMARY,只有第一个操作的按钮能使用正确的样式和图标。

输入对话框

输入对话框是一个多功能的工具,可以使用 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 - 关闭事件包含输入的值,可以用参数标识符获取。