Opening Views
You can open views from the main view through standard actions when working with list and detail entity views, or programmatically from another view.
The following section describes how to open views programmatically in your application code.
Using ViewBuilders Bean
The ViewBuilders
bean provides a fluent interface for opening views. Its terminal methods give access to the opened view instance. It allows you to pass input parameters directly to the view instance and add listeners for getting results back from the opened view after it is closed.
To open a view, inject the ViewBuilders
bean and invoke the view()
method, passing the current view and either the opened view’s class or ID to it. Then, invoke the open()
terminal method:
@Autowired
private ViewBuilders viewBuilders;
private void openView() {
viewBuilders.view(this, OtherView.class).open();
}
Opening Detail Views
In most cases, you can open detail views using standard actions such as list_create. Let’s look at the examples when you can use the ViewBuilders
API directly to open a detail view from an action or button handler.
To create a new entity instance in a detail view, invoke the newEntity()
method. For example:
@Autowired
private ViewBuilders viewBuilders;
private void openDetailViewToCreate() {
viewBuilders.detail(this, User.class)
.newEntity()
.open();
}
To edit an existing entity in a detail view, use the editEntity()
method, providing the entity instance to edit:
@Autowired
private ViewBuilders viewBuilders;
private void openDetailViewToEdit(User user) {
viewBuilders.detail(this, User.class)
.editEntity(user)
.open();
}
In the examples above, the detail view will create or update the entity, but the caller view will not receive the updated instance.
If you need to edit an entity displayed by a list data view component (for example, a dataGrid), use the following invocation method. It’s more concise and automatically updates the dataGrid
:
@ViewComponent
private DataGrid<User> usersDataGrid;
@Autowired
private ViewBuilders viewBuilders;
private void openDetailViewDataGridToEdit() {
viewBuilders.detail(usersDataGrid)
.open();
}
To create a new entity instance and open its detail view, just call the newEntity()
method on the builder:
@ViewComponent
private DataGrid<User> usersDataGrid;
@Autowired
private ViewBuilders viewBuilders;
private void openDetailViewDataGridToCreate() {
viewBuilders.detail(usersDataGrid)
.newEntity()
.open();
}
Use the same concise form if you want to create or edit an entity set to a field:
@ViewComponent
private EntityPicker<User> userPicker;
@Autowired
private ViewBuilders viewBuilders;
private void openDetailViewFieldToEdit() {
viewBuilders.detail(userPicker)
.open();
}
ViewBuilders
offers many methods for setting optional parameters of the opened view. For example, the following code creates a new entity, initializes it, and then edits it in a detail view that’s opened as a dialog:
@Autowired
private ViewBuilders viewBuilders;
private void openDetailViewDialog() {
viewBuilders.detail(this, User.class)
.newEntity()
.withInitializer(user -> {
user.setTimeZoneId(getDefaultTimeZone());
})
.withOpenMode(ViewOpenMode.DIALOG)
.open();
}
Opening Lookup Views
Let’s look at some examples of working with lookup views. As with detail views, you typically open them using standard actions like entity_lookup. The examples below demonstrate using the ViewBuilders API, which can be helpful if you don’t use standard actions.
To select entities from a list view, open the view using the lookup()
method:
@Autowired
private ViewBuilders viewBuilders;
private void openLookupView() {
viewBuilders.lookup(this, User.class)
.withSelectHandler(users -> {
User user = users.iterator().next();
// ...
})
.open();
}
If you need to set the looked up entity to a field, use the more concise form:
@ViewComponent
private EntityPicker<User> userPicker;
@Autowired
private ViewBuilders viewBuilders;
private void openLookupViewToSelect() {
viewBuilders.lookup(userPicker)
.open();
}
Use the same concise form if you want to add the looked-up entity to a list data component, such as a dataGrid
, for example:
@ViewComponent
private DataGrid<User> usersDataGrid;
@Autowired
private ViewBuilders viewBuilders;
private void openLookupViewDataGrid() {
viewBuilders.lookup(usersDataGrid)
.open();
}
Similar to detail views, use builder methods to set optional parameters for the opened view. For example, the following code demonstrates how to look up a User
entity using a particular lookup view opened as a dialog:
@Autowired
private ViewBuilders viewBuilders;
private void openLookupViewDialog() {
viewBuilders.lookup(this, User.class, UserLookupView.class)
.withOpenMode(ViewOpenMode.DIALOG)
.withSelectHandler(users -> {
User user = users.iterator().next();
// ...
})
.open();
}
Passing Parameters to Views
If you need to pass parameters to the opened view, add a view configurer handler using the withViewConfigurer
method.
Consider the following view:
@Route(value = "fancy-message-view", layout = DefaultMainViewParent.class)
@ViewController(id = "FancyMessageView")
@ViewDescriptor(path = "fancy-message-view.xml")
public class FancyMessageView extends StandardView {
@ViewComponent
private H1 fancyMessage;
public void setMessage(String message) {
fancyMessage.setText(message);
}
}
In the ViewConfigurer
handler, you can invoke public setters of the opened view:
@Autowired
private ViewBuilders viewBuilders;
private void openViewWithParameters(String message) {
viewBuilders.view(this, FancyMessageView.class)
.withViewConfigurer(fancyMessageView -> {
fancyMessageView.setMessage(message);
})
.open();
}
Executing Code after Close and Returning Values
Each view sends AfterCloseEvent upon closing. When using ViewBuilders
, you can provide a listener using the withAfterCloseListener()
method:
@Autowired
private ViewBuilders viewBuilders;
@Autowired
private Notifications notifications;
private void openViewWithCloseListener() {
viewBuilders.view(this, OtherView.class)
.withAfterCloseListener(afterCloseEvent -> {
notifications.show("Closed: " + afterCloseEvent.getSource());
})
.open();
}
The event object provides information about how the view was closed. This information can be accessed in two ways:
-
By testing whether the view was closed with one of the standard outcomes defined by the
StandardOutcome
enum. -
By retrieving the
CloseAction
object.
The former approach is simpler, while the latter offers greater flexibility.
Let’s examine the first approach: closing a view with a standard outcome and testing it in the calling code. Here is the view we will invoke:
@Route(value = "other-view", layout = DefaultMainViewParent.class)
@ViewController(id = "OtherView")
@ViewDescriptor(path = "other-view.xml")
public class OtherView extends StandardView {
private String result;
public String getResult() {
return result;
}
@Subscribe(id = "saveBtn", subject = "clickListener")
public void onSaveBtnClick(final ClickEvent<JmixButton> event) {
result = "Save";
close(StandardOutcome.SAVE); (1)
}
@Subscribe(id = "closeBtn", subject = "clickListener")
public void onCloseBtnClick(final ClickEvent<JmixButton> event) {
result = "Close";
close(StandardOutcome.CLOSE); (2)
}
}
1 | Clicking the Save button sets a result state and closes the view with the StandardOutcome.SAVE enum value. |
2 | Clicking the Close button closes the view with StandardOutcome.SAVE . |
In the AfterCloseEvent
listener, you can use the closedWith()
method of the event to check how the view was closed. If needed, you can also read the result value:
@Autowired
private ViewBuilders viewBuilders;
@Autowired
private Notifications notifications;
private void openViewWithResult() {
viewBuilders.view(this, OtherView.class)
.withAfterCloseListener(afterCloseEvent -> {
if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
OtherView otherView = afterCloseEvent.getSource();
notifications.show("Result: " + otherView.getResult());
}
})
.open();
}
Using Custom CloseAction
Another way to return values from views is by using custom CloseAction
implementations. Let’s rewrite the previous example to use the following action class:
public class MyCloseAction extends StandardCloseAction {
private final String result;
public MyCloseAction(String result) {
super("myCloseAction");
this.result = result;
}
public String getResult() {
return result;
}
}
We can then use this action when closing the view:
@Route(value = "other-view", layout = DefaultMainViewParent.class)
@ViewController(id = "OtherView")
@ViewDescriptor(path = "other-view.xml")
public class OtherView extends StandardView {
@Subscribe(id = "okBtn", subject = "clickListener")
public void onOkBtnClick(final ClickEvent<JmixButton> event) {
close(new MyCloseAction("Done")); (1)
}
@Subscribe(id = "cancelBtn", subject = "clickListener")
public void onCancelBtnClick(final ClickEvent<JmixButton> event) {
closeWithDefaultAction(); (2)
}
}
1 | Clicking the Ok button creates a custom close action and sets the result value within it. |
2 | Clicking the Close button closes the view with a default action provided by the framework. |
In the AfterCloseEvent
listener, you can retrieve the CloseAction
from the event and read the result value:
@Autowired
private ViewBuilders viewBuilders;
@Autowired
private Notifications notifications;
private void openViewWithCloseAction() {
viewBuilders.view(this, "OtherView")
.withAfterCloseListener(afterCloseEvent -> {
CloseAction closeAction = afterCloseEvent.getCloseAction();
if (closeAction instanceof MyCloseAction myCloseAction) {
notifications.show("Result: " + myCloseAction.getResult());
}
})
.open();
}
As you can see, when values are returned through a custom CloseAction
, the caller doesn’t have to know the opened view class because it doesn’t invoke methods of the specific view controller. So the view can be created by its string id.
View Inference Conventions
A lookup view or a detail view can be inferred from the entity class.
When opening a list view for lookup using viewBuilders.lookup(this, SomeEntity.class)
, the framework selects a view according the following order:
-
A view annotated with
@PrimaryLookupView(SomeEntity.class)
. -
A view with the
SomeEntity.lookup
id. -
A view annotated with
@PrimaryListView(SomeEntity.class)
. -
A view with the
SomeEntity.list
id.
When opening a detail view using viewBuilders.detail(this, SomeEntity.class)
, the framework selects a view according the following order:
-
A view annotated with
@PrimaryDetailView(SomeEntity.class)
. -
A view with the
SomeEntity.detail
id.