最近更新

本章节包含 Jmix 框架和 Studio 1.5 的新功能介绍,以及在升级框架版本时需要注意的一些破坏性改动。

如何升级

如需新建 Jmix 1.5 项目或者升级已有项目,需要使用 Studio 1.5 以上版本。因此,请先 升级 Jmix Studio 插件。

IntelliJ IDEA 的最低版本要求是 2022.3。

参阅 升级项目 部分的介绍了解如何使用 Studio 升级项目。自动升级迁移过程会对项目做如下修改:

  • 升级 Jmix BOM 的版本,BOM 又定义了所有依赖的版本。

  • 升级 Jmix Gradle 插件的版本。

  • gradle/wrapper/gradle-wrapper.properties 文件中升级 Gradle wrapper 的版本至 7.6。

  • 将所有必须包含的内容添加至根(root) Liquibase Changelog 文件中。

  • application.properties 中添加了 main.liquibase.change-log 配置。

  • 将所有的 jmix.liquibase.* 应用程序属性都重命名为 main.liquibase.*

  • 更新附件数据存储的 Liquibase 配置(如果有的话)。

  • 在 Flow UI 的项目中创建 UiMinimalRole 角色。

  • 如果项目中使用了表格导出操作 / GrapesJS 扩展组件,则会修改这两个制件的名称。

请参考破坏性改动的 完整列表,并在升级后做相应修复。

新功能和改进

Liquibase Changelog

主数据存储的 root Liquibase changelog 现在包含 include 指令,用于引入项目中扩展组件的更改日志。这样一来,更改日志能兼容 Liquibase Gradle 插件 和 Liquibase CLI,并可以在不使用 Studio 或者不直接运行程序的情况下运行 Liquibase,例如在 CI 服务器中。

项目必须用 main.liquibase.change-log 配置 root 更改日志的路径,示例:

main.liquibase.change-log=com/company/demo/liquibase/changelog.xml

当添加扩展组件时,Studio 会自动维护更改日志的引入。另外,当应用程序启动时,会检查 root 更改日志中的引入项是否与项目的扩展组件匹配。如果检测到不匹配项,Studio 会显示一个通知弹窗,建议添加或删除引入。这里可以选择忽略,忽略之后不会再次出现对已忽略引入的提示。

Studio 的版本迁移程序会自动更新 root 更改日志并设置正确的应用程序属性。

更多信息,请参阅 下面 的破坏性改动章节。

Flow UI

Flow UI 模块更新至 Vaadin 23.3。

在 Flow UI 中集成了下列 Vaadin 组件:

  • TabSheetJmixTabSheet(XML 元素:tabSheet

    <tabSheet width="100%">
        <tab id="mainTab" label="Main">
            <formLayout id="form" dataContainer="userDc">...</formLayout>
        </tab>
        <tab id="additionalTab" label="Onboarding steps">
            <vbox>
                <hbox>...</hbox>
                <dataGrid width="100%" dataContainer="stepsDc">...</dataGrid>
            </vbox>
        </tab>
    </tabSheet>
  • MultiSelectComboBoxJmixMultiSelectComboBox(XML 元素:multiSelectComboBox

    <instance id="productDc"
              class="com.company.demo.entity.Product">
        <fetchPlan extends="_base">
            <property name="tags" fetchPlan="_base"/>
        </fetchPlan>
        <loader/>
    </instance>
    <collection class="com.company.demo.entity.Tag" id="allTagsDc">
        <fetchPlan extends="_base"/>
        <loader id="allTagsDl">
            <query>
                <![CDATA[select e from Tag e]]>
            </query>
        </loader>
    </collection>
    <!-- ... -->
    <formLayout id="form" dataContainer="productDc">
        <textField id="nameField" property="name"/>
        <multiSelectComboBox property="tags" itemsContainer="allTagsDc"/>
    </formLayout>
  • UploadFileStorageUploadField(XML 元素:fileStorageUploadField)和 FileUploadField(XML 元素:fileUploadField

    <fileStorageUploadField id="uploadField"
            dataContainer="userDc" property="picture"/>
  • ImageJmixImage(XML 元素:image

    <image id="image"
            dataContainer="userDc" property="picture"
            height="280px" width="200px" classNames="user-picture"/>
  • 很多组件现在支持 Tooltip,示例:

    <textField id="nameField" property="name">
        <tooltip text="Product name" position="BOTTOM_START"/>
    </textField>

    在 Studio 中,通过 Jmix UI 组件面板中的 Add 按钮添加 tooltips。

输入对话框

Dialogs 接口增加了 createInputDialog() 方法,支持使用任意参数展示一个输入对话框。示例:

dialogs.createInputDialog(this)
        .withParameters(
                stringParameter("name").withLabel("Name"),
                intParameter("age").withLabel("Age"))
        .withCloseListener(dialogCloseEvent -> {
            if (dialogCloseEvent.closedWith(DialogOutcome.OK)) {
                System.out.println("Name: " + dialogCloseEvent.getValue("name"));
                System.out.println("Age: " + dialogCloseEvent.getValue("age"));
            }
        })
        .open();

后台任务

经典 UI(Vaadin 8)中可以执行不阻塞用户的异步后台任务,Flow UI 现在也支持该机制。

执行后台任务并在一个 label 中展示进度的示例:

@Autowired
private BackgroundWorker backgroundWorker;

@ViewComponent
private Span taskProgress;

@Subscribe("testBtn")
public void onTestBtnClick(ClickEvent<Button> event) {
    BackgroundTaskHandler<Void> handler = backgroundWorker.handle(new SampleTask(15, null, 10));
    handler.execute();
}

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;
    }

    @Override
    public void progress(List<Integer> changes) {
        taskProgress.setText(changes.get(0) + "");
    }
}

运行后台任务并在模态弹窗展示进度的示例:

@Autowired
private Dialogs dialogs;

@Subscribe("testBtn")
public void onTestBtnClick(ClickEvent<Button> event) {
    dialogs.createBackgroundTaskDialog(new SampleTask(15, this, 10))
            .withTotal(10)
            .withCancelAllowed(true)
            .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;
   }
}

支持 Flow UI 的扩展组件

以下扩展组件目前已经支持 Flow UI:

  • Multitenancy - 多租户

  • Quartz - 定时任务

  • Application Settings - 应用程序设置

  • Grid Export Actions - 表格导出操作(支持导出所有数据,参阅 Excel 导出)。

Flow UI 菜单设计器

Flow UI 菜单设计器的 “Single mode” 做了很大提升。当切换至 “Single mode” 时,Studio 仍然会提供添加项目扩展组件菜单的功能。左侧会有单独的面板展示可用的菜单项,可以拖拽至菜单目录中,方便自定义组合菜单。

Excel 导出

表格导出操作组件现在支持将当前过滤条件产生的所有数据导出至 Excel 文件。excelExport 操作会展示包含 All rows | Current page | Selected rows 选项的弹窗。

以前的版本仅支持导出当前能看到的数据或者选中的数据。

悲观锁管理 UI

UI 核心模块添加了悲观锁管理界面。界面位于经典 UI 的 Administration 菜单或者 Flow UI 的 System 菜单下。

UI 设计器工具窗口

经典 UI 和 Flow UI 的设计器工具窗口现在都统一放在了右侧:Jmix UI,整合了组件结构和组件属性面板。

组件工具箱默认不展示,需要时可通过点击 Add component 操作打开,该操作有以下几个位置:组件结构的右键菜单、XML 描述的顶部操作区、或者 Generate 菜单。

代码片段

代码片段工具箱可以用 Spring bean 或者 UI 控制器编辑窗口顶部的 Code Snippets 按钮打开。

这样改进的原因是可以提高代码片段的使用率(我们发现顶部的操作面板使用率较高)。

预览功能

Flow UI 通用过滤器

GenericFilter 组件(XML 元素:genericFilter)支持用户通过运行时创建的任意条件对数据进行过滤。

示例:

<facets>
    <dataLoadCoordinator auto="true"/>
    <queryParameters>
        <genericFilter component="filter"/>
    </queryParameters>
</facets>
<layout>
        <genericFilter id="filter" dataLoader="usersDl"
                       summaryText="My filter">
            <responsiveSteps>
                <responsiveStep minWidth="0" columns="1"/>
                <responsiveStep minWidth="800px" columns="2"/>
                <responsiveStep minWidth="1200px" columns="3"/>
            </responsiveSteps>
        </genericFilter>

queryParameters facet 中需要有 genericFilter 元素,用来将过滤器的状态反映至 URL 参数中。这样一来,当用户从详情视图返回列表视图或者刷新浏览器网页时,能使用与之前一样的过滤器条件。

现在通用过滤器仅支持属性条件。经典 UI 过滤器的其他功能(JPQL、自定义条件、分组、保存过滤器配置等)将在下一版本提供。

破坏性改动

扩展组件重命名

表格导出操作

修改了 starter 制件名:

  • io.jmix.ui:io.jmix.ui:jmix-ui-export-starterio.jmix.gridexport:jmix-gridexport-ui-starter

GrapesJS

修改了 starter 制件名:

  • io.jmix.grapesjs:jmix-grapesjs-starterio.jmix.grapesjs:jmix-grapesjs-ui-starter

以及主题制件名:

  • io.jmix.grapesjs:jmix-grapesjsio.jmix.grapesjs:jmix-grapesjs-ui

还有基础包名:

  • io.jmix.uiexportio.jmix.gridexportui

Studio 的迁移过程会自动修改 build.gradle 中的制件名,但是对于代码中的包名,需要手动修改。

Quartz

UI 需要添加额外的 starter:io.jmix.quartz:jmix-quartz-ui-starter

Liquibase 属性

  1. jmix.liquibase.* 前缀更名为 main.liquibase.*,目的是为了遵循数据源属性的命名模式(例如,main.datasource.url,这里 main 是数据存储名称)。如果添加一个名为 second 的数据存储,Liquibase 配置属性则是 second.liquibase.*

  2. application.properties 必须 包含每个数据存储的 root Liquibase 更改日志的路径。示例:

    main.liquibase.change-log=com/company/demo/liquibase/changelog.xml
    
    second.liquibase.change-log=com/company/demo/liquibase/second-changelog.xml

数据存储配置

附件数据存储的配置必须更改。

LiquibaseChangeLogProcessor 类已被移除。

以前的版本中,如果我们定义一个附加数据存储并设置 DB Schema ManagementCreate and Update(即,通过 Jmix 创建并维护该附件数据存储),则 Studio 会生成一个类似下面这样定义的 bean:

@Bean
public SpringLiquibase thirdLiquibase(
            LiquibaseChangeLogProcessor processor,
            @Qualifier("thirdDataSource") DataSource dataSource) {
   return JmixLiquibaseCreator.create(
                dataSource,
                new LiquibaseProperties(),
                processor,
                "third");
}

新的 bean 定义必须与下面这个类似(“third” 是数据存储名称):

@Bean("thirdLiquibaseProperties")
@ConfigurationProperties(prefix = "third.liquibase")
public LiquibaseProperties thirdLiquibaseProperties() {
   return new LiquibaseProperties();
}

@Bean("thirdLiquibase")
public SpringLiquibase thirdLiquibase(
            @Qualifier("thirdDataSource") DataSource dataSource,
            @Qualifier("thirdLiquibaseProperties") LiquibaseProperties liquibaseProperties) {
    return JmixLiquibaseCreator.create(
                dataSource,
                liquibaseProperties);
}

Formatters 中对消息键值的引用

我们修复了在 XML 中定义的 formatter 对外部消息引用的错误处理问题。

现在,与其他消息一样,对于 msg://myFormat 引用,将查找当前界面消息组内的键值,例如,com.company.app.screen.foo/myFormat。对带有三个斜杠前缀的键值,将查找没有组的消息,例如 myFormat

项目如果需要采用这个改动,需要查找所有这类用法,并将双斜杠改成三斜杠,示例:

<formatter>
     <date format="msg:///myDateFormat"/>
</formatter>

Flow UI 项目中的 UiMinimalRole 角色

UiMinimalRole 定义访问登录页和主页面(Flow UI 称为视图)的权限。为了支持能修改这些视图,角色从框架移至项目。示例:

package com.company.demo.security;

import io.jmix.core.entity.KeyValueEntity;
import io.jmix.security.model.*;
import io.jmix.security.role.annotation.*;
import io.jmix.securityflowui.role.annotation.ViewPolicy;

@ResourceRole(name = "Flow UI: minimal access", code = UiMinimalRole.CODE, scope = SecurityScope.UI)
public interface UiMinimalRole {

    String CODE = "flowui-minimal";

    @ViewPolicy(viewIds = "MainView")
    void main();

    @ViewPolicy(viewIds = "LoginView")
    @SpecificPolicy(resources = "flowui.loginToUi")
    void login();

    @EntityPolicy(entityClass = KeyValueEntity.class, actions = EntityPolicyAction.READ)
    @EntityAttributePolicy(entityClass = KeyValueEntity.class, attributes = "*", action = EntityAttributePolicyAction.VIEW)
    void keyValueEntity();
}

Flow UI DataGrid

  1. DataGridTreeDataGridgetColumns() 方法现在仅返回那些用户有权限看到的列(由于安全配置引起的不可见)。以前的版本中,该方法会返回所有的列,包括隐藏列。

  2. 由于安全控制隐藏的列不会修改其可见性配置。以前的版本中,这种情况会设置 visible 属性为 false。

ui-test-assist

ui-test-assist 模块不再提供对 Spock 和 Groovy 的传递依赖。此外,也不包含 UiTestAssistSpecificationScreenSpecificationTestMainScreen 类。

Spock 和 Groovy 已经移至 ui-test-assist-spock 模块。

如果你的项目包含基于 ScreenSpecificationUiTestAssistSpecification 的测试用例,请在 build.gradle 添加如下依赖:

testImplementation 'io.jmix.ui:jmix-ui-test-assist-spock'

并修改对 ScreenSpecificationUiTestAssistSpecificationTestMainScreen 的 import 为 io.jmix.ui.testassistspock.*

变更日志