3.5.1.3. Opening Screens
A screen can be opened from the main menu, by navigating to a URL, by a standard action (when working with browse and edit entity screens), or programmatically from another screen. In this section, we explain how to open screens programmatically.
- Using the Screens interface
-
The
Screens
interface allows you to create and show screens of any type.Suppose we have a screen to show a message with some special formatting:
Screen controller@UiController("demo_FancyMessageScreen") @UiDescriptor("fancy-message-screen.xml") @DialogMode(forceDialog = true, width = "300px") public class FancyMessageScreen extends Screen { @Inject private Label<String> messageLabel; public void setFancyMessage(String message) { (1) messageLabel.setValue(message); } @Subscribe("closeBtn") protected void onCloseBtnClick(Button.ClickEvent event) { closeWithDefaultAction(); } }
1 - a screen parameter Screen descriptor<?xml version="1.0" encoding="UTF-8" standalone="no"?> <window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd" caption="Fancy Message"> <layout> <label id="messageLabel" value="A message" stylename="h1"/> <button id="closeBtn" caption="Close"/> </layout> </window>
Then we can create and open it from another screen as follows:
@Inject private Screens screens; private void showFancyMessage(String message) { FancyMessageScreen screen = screens.create(FancyMessageScreen.class); screen.setFancyMessage(message); screens.show(screen); }
Notice how we create the screen instance, provide a parameter for it and then show the screen.
If the screen does not require any parameters from the caller code, you can create and open it in one line:
@Inject private Screens screens; private void showDefaultFancyMessage() { screens.create(FancyMessageScreen.class).show(); }
Screens
is not a Spring bean, so you can only inject it to screen controllers or obtain usingComponentsHelper.getScreenContext(component).getScreens()
static method.
- Using the ScreenBuilders bean
-
The
ScreenBuilders
bean allows you to open all kinds of screens with various parameters.See Initial Entity Values guide to learn how to use external initialization via
ScreenBuilders
.Below is an example of using it for opening a screen and executing some code after the screen is closed (see more details here):
@Inject private ScreenBuilders screenBuilders; @Inject private Notifications notifications; private void openOtherScreen() { screenBuilders.screen(this) .withScreenClass(OtherScreen.class) .withAfterCloseListener(e -> { notifications.create().withCaption("Closed").show(); }) .build() .show(); }
Next we’ll consider working with editor and lookup screens. Note that in most cases you open such screens using standard actions (such as CreateAction or LookupAction), so you don’t have to use the
ScreenBuilders
API directly. However, the examples below can be useful if you don’t use standard actions but open a screen from a BaseAction or Button handler.Example of opening a default editor for the
Customer
entity instance:@Inject private ScreenBuilders screenBuilders; private void editSelectedEntity(Customer entity) { screenBuilders.editor(Customer.class, this) .editEntity(entity) .build() .show(); }
In this case, the editor will update the entity, but the caller screen will not receive the updated instance.
Often you need to edit an entity displayed by some
Table
orDataGrid
component. Then you should use the following form of invocation, which is more concise and automatically updates the table:@Inject private GroupTable<Customer> customersTable; @Inject private ScreenBuilders screenBuilders; private void editSelectedEntity() { screenBuilders.editor(customersTable).build().show(); }
In order to create a new entity instance and open the editor screen for it, just call the
newEntity()
method on the builder:@Inject private GroupTable<Customer> customersTable; @Inject private ScreenBuilders screenBuilders; private void createNewEntity() { screenBuilders.editor(customersTable) .newEntity() .build() .show(); }
The default editor screen is determined by the following procedure:
-
If an editor screen annotated with @PrimaryEditorScreen exists, it is used.
-
Otherwise, an editor screen with
<entity_name>.edit
id is used (for example,sales_Customer.edit
).
The builder provides a lot of methods to set optional parameters of the opened screen. For example, the following code creates an entity first initializing the new instance, in a particular editor opened as a dialog:
@Inject private GroupTable<Customer> customersTable; @Inject private ScreenBuilders screenBuilders; private void editSelectedEntity() { screenBuilders.editor(customersTable).build().show(); } private void createNewEntity() { screenBuilders.editor(customersTable) .newEntity() .withInitializer(customer -> { // lambda to initialize new instance customer.setName("New customer"); }) .withScreenClass(CustomerEdit.class) // specific editor screen .withLaunchMode(OpenMode.DIALOG) // open as modal dialog .build() .show(); }
Entity lookup screens can also be opened with various parameters.
Below is an example of opening a default lookup screen of the
User
entity:@Inject private TextField<String> userField; @Inject private ScreenBuilders screenBuilders; private void lookupUser() { screenBuilders.lookup(User.class, this) .withSelectHandler(users -> { User user = users.iterator().next(); userField.setValue(user.getName()); }) .build() .show(); }
If you need to set the looked up entity to a field, use the more concise form:
@Inject private PickerField<User> userPickerField; @Inject private ScreenBuilders screenBuilders; private void lookupUser() { screenBuilders.lookup(User.class, this) .withField(userPickerField) // set result to the field .build() .show(); }
The default lookup screen is determined by the following procedure:
-
If a lookup screen annotated with @PrimaryLookupScreen exists, it is used.
-
Otherwise, if a screen with
<entity_name>.lookup
id exists, it is used (for example,sales_Customer.lookup
). -
Otherwise, a screen with
<entity_name>.browse
id is used (for example,sales_Customer.browse
).
As with edit screens, use the builder methods to set optional parameters of the opened screen. For example, the following code looks up the
User
entity using a particular lookup screen opened as a dialog:@Inject private TextField<String> userField; @Inject private ScreenBuilders screenBuilders; private void lookupUser() { screenBuilders.lookup(User.class, this) .withScreenId("sec$User.browse") // specific lookup screen .withLaunchMode(OpenMode.DIALOG) // open as modal dialog .withSelectHandler(users -> { User user = users.iterator().next(); userField.setValue(user.getName()); }) .build() .show(); }
-
- Passing parameters to screens
-
The recommended way of passing parameters to an opened screen is to use public setters of the screen controller, as demonstrated in the example above.
With this approach, you can pass parameters to screens of any type, including entity edit and lookup screens opened using ScreenBuilders or from the main menu. The invocation of the same
FancyMessageScreen
usingScreenBuilders
with passing the parameter looks as follows:@Inject private ScreenBuilders screenBuilders; private void showFancyMessage(String message) { FancyMessageScreen screen = screenBuilders.screen(this) .withScreenClass(FancyMessageScreen.class) .build(); screen.setFancyMessage(message); screen.show(); }
If you are opening a screen using a standard action such as CreateAction, use its
screenConfigurer
handler to pass parameters via screen public setters.Another way is to define a special class for parameters and pass its instance to the standard
withOptions()
method of the screen builder. The parameters class must implement theScreenOptions
marker interface. For example:import com.haulmont.cuba.gui.screen.ScreenOptions; public class FancyMessageOptions implements ScreenOptions { private String message; public FancyMessageOptions(String message) { this.message = message; } public String getMessage() { return message; } }
In the opened
FancyMessageScreen
screen, the options can be obtained in InitEvent and AfterInitEvent handlers:@Subscribe private void onInit(InitEvent event) { ScreenOptions options = event.getOptions(); if (options instanceof FancyMessageOptions) { String message = ((FancyMessageOptions) options).getMessage(); messageLabel.setValue(message); } }
The invocation of the
FancyMessageScreen
screen usingScreenBuilders
with passingScreenOptions
looks as follows:@Inject private ScreenBuilders screenBuilders; private void showFancyMessage(String message) { screenBuilders.screen(this) .withScreenClass(FancyMessageScreen.class) .withOptions(new FancyMessageOptions(message)) .build() .show(); }
As you can see, this approach requires type casting in the controller receiving the parameters, so use it wisely and prefer the type-safe setters approach explained above.
If you are opening a screen using a standard action such as CreateAction, use its
screenOptionsSupplier
handler to create and initialize the requiredScreenOptions
object.Usage of the
ScreenOptions
object is the only way to get parameters if the screen is opened from a screen based on the legacy API. In this case, the options object is of typeMapScreenOptions
and you can handle it in the opened screen as follows:@Subscribe private void onInit(InitEvent event) { ScreenOptions options = event.getOptions(); if (options instanceof MapScreenOptions) { String message = (String) ((MapScreenOptions) options).getParams().get("message"); messageLabel.setValue(message); } }
- Executing code after close and returning values
-
Each screen sends
AfterCloseEvent
when it closes. You can add a listener to a screen to be notified when the screen is closed, for example:@Inject private Screens screens; @Inject private Notifications notifications; private void openOtherScreen() { OtherScreen otherScreen = screens.create(OtherScreen.class); otherScreen.addAfterCloseListener(afterCloseEvent -> { notifications.create().withCaption("Closed " + afterCloseEvent.getScreen()).show(); }); otherScreen.show(); }
When using
ScreenBuilders
, the listener can be provided in thewithAfterCloseListener()
method:@Inject private ScreenBuilders screenBuilders; @Inject private Notifications notifications; private void openOtherScreen() { screenBuilders.screen(this) .withScreenClass(OtherScreen.class) .withAfterCloseListener(afterCloseEvent -> { notifications.create().withCaption("Closed " + afterCloseEvent.getScreen()).show(); }) .build() .show(); }
The event object provides an information about how the screen was closed. This information can be obtained in two ways: by testing whether the screen was closed with one of standard outcomes defined by the
StandardOutcome
enum, or by getting theCloseAction
object. The former approach is simpler while the latter is much more flexible.Let’s consider the first approach: to close a screen with a standard outcome and test for it in the calling code.
The following screen is to be invoked:
package com.company.demo.web.screens; import com.haulmont.cuba.gui.components.Button; import com.haulmont.cuba.gui.screen.*; @UiController("demo_OtherScreen") @UiDescriptor("other-screen.xml") public class OtherScreen extends Screen { private String result; public String getResult() { return result; } @Subscribe("okBtn") public void onOkBtnClick(Button.ClickEvent event) { result = "Done"; close(StandardOutcome.COMMIT); (1) } @Subscribe("cancelBtn") public void onCancelBtnClick(Button.ClickEvent event) { close(StandardOutcome.CLOSE); (2) } }
1 - on "OK" button click, set some result state and close the screen with StandardOutcome.COMMIT
enum value.2 - on "Cancel" button click, close the with StandardOutcome.CLOSE
.In the
AfterCloseEvent
listener, you can check how the screen was closed using theclosedWith()
method of the event, and read the result value if needed:@Inject private ScreenBuilders screenBuilders; @Inject private Notifications notifications; private void openOtherScreen() { screenBuilders.screen(this) .withScreenClass(OtherScreen.class) .withAfterCloseListener(afterCloseEvent -> { OtherScreen otherScreen = afterCloseEvent.getScreen(); if (afterCloseEvent.closedWith(StandardOutcome.COMMIT)) { String result = otherScreen.getResult(); notifications.create().withCaption("Result: " + result).show(); } }) .build() .show(); }
Another way of returning values from screens is using custom
CloseAction
implementations. Let’s rewrite the above example to use the following action class:package com.company.demo.web.screens; import com.haulmont.cuba.gui.screen.StandardCloseAction; public class MyCloseAction extends StandardCloseAction { private String result; public MyCloseAction(String result) { super("myCloseAction"); this.result = result; } public String getResult() { return result; } }
Then we can use this action when closing the screen:
package com.company.demo.web.screens; import com.haulmont.cuba.gui.components.Button; import com.haulmont.cuba.gui.screen.*; @UiController("demo_OtherScreen2") @UiDescriptor("other-screen.xml") public class OtherScreen2 extends Screen { @Subscribe("okBtn") public void onOkBtnClick(Button.ClickEvent event) { close(new MyCloseAction("Done")); (1) } @Subscribe("cancelBtn") public void onCancelBtnClick(Button.ClickEvent event) { closeWithDefaultAction(); (2) } }
1 - on "OK" button click, create the custom close action and set the result value in it. 2 - on "Cancel" button click, close with a default action provided by the framework. In the
AfterCloseEvent
listener, you can get theCloseAction
from the event and read the result value:@Inject private Screens screens; @Inject private Notifications notifications; private void openOtherScreen() { Screen otherScreen = screens.create("demo_OtherScreen2", OpenMode.THIS_TAB); otherScreen.addAfterCloseListener(afterCloseEvent -> { CloseAction closeAction = afterCloseEvent.getCloseAction(); if (closeAction instanceof MyCloseAction) { String result = ((MyCloseAction) closeAction).getResult(); notifications.create().withCaption("Result: " + result).show(); } }); otherScreen.show(); }
As you can see, when values are returned through a custom
CloseAction
, the caller doesn’t have to know the opened screen class because it doesn’t invoke methods of the concrete screen controller. So the screen can be created by its string id.Of course, the same approach for returning values through close actions can be used when opening screens using
ScreenBuilders
.