打开视图
一个常规视图(非主视图)可以通过两种方式打开:通过 URL(在 @Route 注解指定)导航打开;在当前页面通过对话框形式打开。
使用导航方式时,浏览器的 URL 会根据应用程序 UI 的状态展示不同的 URL,并支持 深度链接。应用程序的用户可以复制特定视图和内容的 URL 并分享,例如,展示某个实体详情的 URL。
当通过对话框窗口的形式打开视图时,浏览器 URL 不会有变化,但是调用方代码可以访问打开的视图实例。因此,可以支持给视图实例传递参数或者在关闭视图时返回一些结果值。例如,在选择控件中打开列表视图选择并返回实体实例时就使用了这种方式。
主菜单通过导航的方式打开视图。标准操作 list_create、list_edit 和 list_read 也是通过导航方式打开详情视图,但是可以配置使用对话框方式打开。The entity_lookup 标准操作始终使用对话框的方式打开列表视图,以便能返回选中的实体。entity_read 操作也始终使用对话框。
下面我们介绍如何在应用程序代码中以编程的方式打开视图。
导航
如需通过 URL 打开视图,可以注入 ViewNavigators bean,然后使用流式接口指定当前视图和打开的视图类:
@Autowired
private ViewNavigators viewNavigators;
private void navigateToView() {
viewNavigators.view(this, MyOnboardingView.class).navigate();
}
navigate() 是最终实现导航跳转的方法。
如果调用的类不是一个视图,则不能以当前视图的身份传递 this,而应该使用 UiComponentUtils.getCurrentView() 获取当前打开的视图。
如果希望在打开的视图关闭后返回调用的视图,则使用 withBackwardNavigation(true) 方法:
@Autowired
private ViewNavigators viewNavigators;
private void navigateToViewThenBack() {
viewNavigators.view(this, MyOnboardingView.class)
.withBackwardNavigation(true)
.navigate();
}
|
后退导航限制 后退导航仅支持单层视图嵌套。导航栈不会存储整个视图转换链。 由于此限制:
|
如需导航至一个列表视图,使用 listView() 方法并传递实体类。会根据 视图推断约定 选择列表视图的类。示例:
@Autowired
private ViewNavigators viewNavigators;
private void navigateToListView() {
viewNavigators.listView(this, Department.class).navigate();
}
也可以显式指定列表视图类或 id,示例:
@Autowired
private ViewNavigators viewNavigators;
private void navigateToListViewWithClass() {
viewNavigators.listView(this, Department.class)
.withViewClass(DepartmentListView.class)
.navigate();
}
如需导航至详情视图,使用 detailView() 方法并传递实体类或一个绑定至该实体的可视化组件。会根据 视图推断约定 选择详情视图的类。
如需新建一个实体,调用 newEntity(),示例:
@Autowired
private ViewNavigators viewNavigators;
private void navigateToCreateEntity() {
viewNavigators.detailView(this, Department.class)
.newEntity()
.navigate();
}
编辑实体实例,调用 editEntity(),示例:
@Autowired
private ViewNavigators viewNavigators;
private void navigateToEditEntity(Department entity) {
viewNavigators.detailView(this, Department.class)
.editEntity(entity)
.navigate();
}
也可以显式指定详情视图类或 id,使用 withViewId() 和 withViewClass() 方法。
Jmix 3.1+
@Autowired
private ViewNavigators viewNavigators;
private void navigateToReadEntity(Department entity) {
viewNavigators.readView(this, Department.class)
.readEntity(entity)
.navigate();
}
如果将列表组件(如 dataGrid)或实体选择器传递给 readView(),导航会读取表格中选中的实体或组件的值。可以使用 withViewId() 和 withViewClass() 方法显式指定只读视图的 id 或类。
传递参数
推荐使用流式接口的 withQueryParameters() 方法为导航打开的视图传递参数:
@Autowired
private ViewNavigators viewNavigators;
private void navigateToViewWithQueryParameters() {
viewNavigators.view(this, FancyMessageView.class)
.withQueryParameters(QueryParameters.of("message", "Hello World!"))
.navigate();
}
此时,会在 URL 中添加参数,例如:
http://localhost:8080/FancyMessageView?message=Hello%20World!
在打开的视图中,可以使用 QueryParametersChangeEvent 处理器
获取参数:
@ViewComponent
private H1 messageLabel;
public void setMessage(String message) {
messageLabel.setText(message);
}
@Subscribe
public void onQueryParametersChange(final QueryParametersChangeEvent event) {
List<String> messageParams = event.getQueryParameters().getParameters().get("message");
if (messageParams != null && !messageParams.isEmpty())
setMessage(messageParams.get(0));
}
另一个方法是使用流式接口的 withAfterNavigationHandler() 方法直接将参数传递给打开的视图对象:
@Autowired
private ViewNavigators viewNavigators;
private void navigateToViewWithAfterNavigationHandler() {
viewNavigators.view(this, FancyMessageView.class)
.withAfterNavigationHandler(afterViewNavigationEvent -> {
FancyMessageView view = afterViewNavigationEvent.getView();
view.setMessage("Hello World!");
})
.navigate();
}
此时,URL 中不包含参数:
http://localhost:8080/FancyMessageView
这种方式更简单,并且支持传递复杂类型的参数,但是缺点也很明显,与在弹窗中打开视图一样,这种方式不会提供深度链接,用户在刷新网页时会丢失视图状态。
打开弹窗
DialogWindows bean 提供流式接口用于以对话框窗口的形式打开视图。流式接口的结束方法提供对打开视图实例的访问。支持直接向视图实例传递参数,并可以添加监听器,在打开的视图关闭后获取结果。
基本方法
如需以对话框的形式打开一个视图,可以注入 DialogWindows bean,调用 view() 方法,传递当前视图和需要打开的视图类。然后调用 open() 方法:
@Autowired
private DialogWindows dialogWindows;
private void openView() {
dialogWindows.view(this, MyOnboardingView.class).open();
}
设置弹窗参数
用几种方式可以设置打开的弹窗参数,包括尺寸:
-
使用
@DialogMode注解在需要以弹窗方式打开的视图类上添加
@DialogMode注解。这里可以设置视图弹窗的默认大小、是否模态窗以及其他弹窗参数。@ViewController("MyOnboardingView") @ViewDescriptor("my-onboarding-view.xml") @DialogMode(width = "50em", height = "40em", modality = DialogModalityMode.STRICT) public class MyOnboardingView extends StandardView { // some code }使用
modality属性控制对话框与 UI 其余部分的交互方式。可选值包括:-
STRICT- 显示模态遮罩,将焦点限制在对话框内,阻止鼠标指针事件,并阻止后台请求。 -
VISUAL- 行为与STRICT相同,但不阻止后台请求。 -
MODELESS- 不使用模态:无模态遮罩、无焦点限制、无后台阻止。 -
DEFAULT- 兼容模式,保留已弃用的DialogMode.modal()属性配置的行为。
-
-
在打开的视图内部设置大小
可以在视图控制器中修改视图弹窗的大小。有助于依赖运行时条件动态调整弹窗的大小,例如需要展示或隐藏某些页面组件。
@Subscribe public void onBeforeShow(BeforeShowEvent event) { Dialog dialog = UiComponentUtils.findDialog(this); if (dialog != null) { dialog.setWidth("80em"); dialog.setHeight("60em"); } }如果当前视图不是以对话框形式打开的,则 findDialog()方法返回null。因此需要检查对话框实例是否为null。 -
打开弹窗时设置大小
使用
DialogWindowsbean 时,可以在构建对话框窗口之后、打开之前设置大小。@Autowired private DialogWindows dialogWindows; private void openViewWithSize() { DialogWindow<MyOnboardingView> window = dialogWindows.view(this, MyOnboardingView.class).build(); window.setWidth("40em"); window.setHeight("30em"); window.open(); }
对话框窗口的最大化显示和还原
在 DialogWindow 中打开的视图可以最大化以填满视口,并可以还原到之前的大小和位置。默认情况下,最大化按钮是隐藏的。在 @DialogMode 中设置 maximizable = true,即可在对话框顶部关闭按钮旁边显示该按钮:
@DialogMode(width = "50em", height = "40em", modality = DialogModalityMode.STRICT,
resizable = true, maximizable = true)
public class MyOnboardingView extends StandardView {
点击该按钮会最大化对话框。再次点击会还原对话框为最大化之前通过拖动或调整大小设置的尺寸和位置。
如需以编程方式配置该行为,请使用 setMaximizable(boolean)。setMaximized(true) 最大化对话框,而 setMaximized(false) 还原。这些方法在 AbstractDialogWindow(DialogWindow 的基类)上可用。isMaximizable() 和 isMaximized() 方法返回相应的设置。
以下示例打开一个最大化的对话框,并在更改状态之前注册监听器:
@Autowired
private DialogWindows dialogWindows;
private void openMaximizedDialog() {
DialogWindow<MyOnboardingView> window =
dialogWindows.view(this, MyOnboardingView.class).build();
window.setMaximizable(true);
window.addMaximizedChangeListener(event ->
log.debug("Maximized: {}, from client: {}",
event.isMaximized(), event.isFromClient()));
window.setMaximized(true);
window.open();
}
MaximizedChangeEvent 的 isMaximized() 方法提供当前状态。当用户点击顶部按钮时,事件的 isFromClient() 方法返回 true;当状态以编程方式更改时,返回 false。使用 getSource() 可获取对话框窗口对象,使用 getView() 获取视图。
对话框最大化时,会禁用拖动和调整大小的功能。如果在此时调用 setDraggable() 和 setResizable(),这些设置会在对话框还原后生效。相应的 getter 返回这些配置的设置。
Aura 和 Lumo 主题支持以下用于 CSS 自定义属性:
-
--jmix-dialog-maximized-position -
--jmix-dialog-maximized-inset -
--jmix-dialog-maximized-width -
--jmix-dialog-maximized-height
当对话框元素有 jmix-maximized 属性(即,最大化后)时,这些样式会生效。
传递参数
如果希望为打开的视图传递参数,调用 build(),然后设置参数,再打开窗口:
@Autowired
private DialogWindows dialogWindows;
private void openViewWithParams(String username) {
DialogWindow<MyOnboardingView> window =
dialogWindows.view(this, MyOnboardingView.class).build();
window.getView().setUsername(username);
window.open();
}
调用 build() 方法时,会触发 InitEvent。如果打开一个对话框创建新实体,则在调用 build() 方法时还会触发 InitEntityEvent。视图的其他 生命周期事件 将在调用 open() 方法时触发。
|
结果处理
如需获取视图关闭后的结果,可以添加对话框的 AfterCloseEvent 监听器:
@Autowired
private DialogWindows dialogWindows;
private void openViewWithParamsAndResults(String username) {
DialogWindow<MyOnboardingView> window =
dialogWindows.view(this, MyOnboardingView.class).build();
window.getView().setUsername(username);
window.addAfterCloseListener(afterCloseEvent -> {
if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
// ...
}
});
window.open();
}
AfterCloseEvent 对象包含传递给视图 close() 方法的 CloseAction(关闭操作)参数值。例如,当使用 OK 按钮关闭标准实体详情视图时,关闭操作为 save。可以使用事件对象的 getCloseAction() 或 closedWith() 方法分析关闭操作。
使用流式接口也可以添加 AfterCloseEvent 监听器:
@Autowired
private DialogWindows dialogWindows;
private void openViewWithResults() {
dialogWindows.view(this, MyOnboardingView.class)
.withAfterCloseListener(afterCloseEvent -> {
if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
// ...
}
})
.open();
}
获取已打开的对话框窗口
DialogWindows bean 也支持获取 OpenedDialogWindows,该对象包含以对话框形式打开的视图列表。例如,在 UI 集成测试中需要访问以 OpenMode.DIALOG 打开的视图时,可以用该方法。
@Autowired
private DialogWindows dialogWindows;
private DepartmentDetailView getOpenedDepartmentDetailView() {
return dialogWindows.getOpenedDialogWindows()
.getDialogs()
.stream()
.filter(DepartmentDetailView.class::isInstance)
.map(DepartmentDetailView.class::cast)
.reduce((previous, current) -> current)
.orElseThrow();
}
getDialogs() 方法返回当前 UI 的所有对话框视图。getDialogs(UI) 方法返回 UI 参数中的对话框视图。
对话框视图按打开顺序添加到列表中,因此最后打开的对话框是列表中的最后一项。
OpenedDialogWindows 有 Vaadin 会话作用域,仅在 Vaadin 会话处于活动状态时可用。
|
选择实体
如需使用列表视图选择实体,请使用 lookup() 方法打开视图:
@Autowired
private DialogWindows dialogWindows;
private void openLookupView() {
dialogWindows.lookup(this, Department.class)
.withSelectHandler(departments -> {
// ...
})
.open();
}
使用 withSelectHandler() 方法提供一个 lambda,用于接收打开视图中选择的实体实例集合。
创建并编辑实体
如需在详情视图中新建一个实体,先指定视图类,然后调用 newEntity()。使用 AfterCloseEvent 监听器获取创建的实体。示例:
@Autowired
private DialogWindows dialogWindows;
private void openDetailViewToCreateEntity() {
dialogWindows.detail(this, Department.class)
.withViewClass(DepartmentDetailView.class)
.newEntity()
.withAfterCloseListener(afterCloseEvent -> {
if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
Department department = afterCloseEvent.getView().getEditedEntity();
// ...
}
})
.open();
}
如需在详情视图编辑已有实体,使用 editEntity() 方法提供需要编辑的实体实例:
@Autowired
private DialogWindows dialogWindows;
private void openDetailViewToEditEntity(Department department) {
dialogWindows.detail(this, Department.class)
.withViewClass(DepartmentDetailView.class)
.editEntity(department)
.withAfterCloseListener(afterCloseEvent -> {
if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
Department editedDepartment = afterCloseEvent.getView().getEditedEntity();
// ...
}
})
.open();
}
可以用本章开始时介绍的方法为列表和详情视图提供输入参数:先调用 build(),然后给视图设置参数,最后再打开窗口。
其他构建方法
DetailWindowBuilder 和 DetailWindowClassBuilder 提供了额外的方法,可将打开的详情视图与调用视图集成:
| 方法 | 描述 |
|---|---|
|
在详情视图打开之前初始化一个新的实体实例。初始化器仅在创建模式下被调用,即在 |
|
指定在详情视图以 |
|
指定一个 |
|
为打开的详情视图设置父 |
|
将保存的实体写回字段、容器或列表数据组件之前对其进行特定的转换。 |
|
设置一个字段组件,该组件在详情视图以 |
|
当使用独立的集合容器时,定义新实体是否应添加到集合容器的开头位置。对于嵌套容器,新实体始终添加到末尾。 |
|
支持在对话框显示之前配置打开的视图实例。 |
这些构建器方法通常在为子实体打开详情对话框时使用。
使用 withListDataComponent() 或 withContainer() 将对话框与当前视图中显示的集合进行关联,使用 withParentDataContext() 将编辑后的子实体合并到父视图数据上下文中。
在只读视图中显示实体
Jmix 3.1+
如需在 只读视图 中显示实体实例,调用 read() 方法并使用 readEntity() 设置实体实例。
@Autowired
private DialogWindows dialogWindows;
private void openReadViewToReadEntity(Department department) {
dialogWindows.read(this, Department.class)
.readEntity(department)
.open();
}
read() 方法也可以使用列表组件作为参数,例如 dataGrid 或实体选择器。此时,builder 会从中读取选中的实体或组件值。
只读视图的类通过 约定 确定。如果需要打开特定的视图,使用 builder 的 withViewId() 或 withViewClass() 方法。
视图推断约定
框架支持从实体类推断列表、详情或只读视图。
当使用 viewNavigators.listView(this, SomeEntity.class) 方法导航至列表视图时,框架会按照下列顺序选择视图类:
-
视图使用了注解
@PrimaryLookupView(SomeEntity.class)。 -
视图的 id 为
SomeEntity.list。
当使用 dialogWindows.lookup(this, SomeEntity.class) 方法打开一个列表视图用于查找时,框架会按照下列顺序选择视图类:
-
视图使用了注解
@PrimaryLookupView(SomeEntity.class)。 -
视图的 id 为
SomeEntity.lookup。 -
视图使用了注解
@PrimaryListView(SomeEntity.class)。 -
视图的 id 为
SomeEntity.list。
当导航至一个详情视图,或者在对话框打开详情视图时,框架会按照下列顺序选择视图类:
-
视图使用了注解
@PrimaryDetailView(SomeEntity.class)。 -
视图的 id 为
SomeEntity.detail。
当导航至一个只读视图,或者在对话框打开只读视图时,框架会按照下列顺序选择视图类:
-
视图使用了注解
@PrimaryReadView(SomeEntity.class)。 -
视图的 id 为
SomeEntity.read。 -
视图使用了注解
@PrimaryDetailView(SomeEntity.class)。 -
视图的 id 为
SomeEntity.detail。
如果框架选中了一个详情视图,则会以只读模式打开。