Opening Views

A regular (non-main) view can be opened either by navigating to its URL (specified in the @Route annotation) or on the current page by opening a dialog window.

When using navigation, the web browser URL reflects the state of the application UI, which allows for deep linking. A user of the application can copy and share the URL to provide the link to a specific view and its current state, for example, to the details of a particular entity instance.

The downside of the navigation is the relative complexity of passing parameters to the opened view. They can be passed only in the URL as the route or query parameters. Also, it is impossible to return any values from the view, after it is closed, to the calling code.

When opening a view in a dialog window, the browser URL is not changed, but the opened view instance is available to the calling code. It enables passing any values directly to the view instance and returning results after the view is closed. This approach is used when opening list views for selecting and returning entity instances in lookup fields.

The main menu opens views by navigation. Standard list_create and list_edit actions also open detail views by navigation, but can be configured to use dialogs. The entity_lookup standard action always opens a list view in a dialog to be able to get back selected entities.

Below we describe how to open views programmatically in your application code.

To open a view by navigating to its URL, inject the ViewNavigators bean and use its fluent interface to specify the current view and the opened view class:

@Autowired
private ViewNavigators viewNavigators;

private void navigateToView() {
    viewNavigators.view(this, MyOnboardingView.class).navigate();
}

navigate() is the terminal method that performs the navigation.

If the calling class is not a view, and you cannot pass this as a current view, use UiComponentUtils.getCurrentView() to determine the currently opened view.

If you want to return to the calling view after the opened view is closed, invoke the withBackwardNavigation(true) method:

@Autowired
private ViewNavigators viewNavigators;

private void navigateToViewThenBack() {
    viewNavigators.view(this, MyOnboardingView.class)
            .withBackwardNavigation(true)
            .navigate();
}

To navigate to a list view, use the listView() method accepting the entity class. The list view class will be chosen by convention. For example:

@Autowired
private ViewNavigators viewNavigators;

private void navigateToListView() {
    viewNavigators.listView(this, Department.class).navigate();
}

You can specify the list view id or class explicitly, for example:

@Autowired
private ViewNavigators viewNavigators;

private void navigateToListViewWithClass() {
    viewNavigators.listView(this, Department.class)
            .withViewClass(DepartmentListView.class)
            .navigate();
}

To navigate to a detail view, use the detailView() method, accepting the entity class or a visual component bound to the entity. The detail view class will be chosen by convention.

To create a new entity instance, invoke newEntity(). For example:

@Autowired
private ViewNavigators viewNavigators;

private void navigateToCreateEntity() {
    viewNavigators.detailView(this, Department.class)
            .newEntity()
            .navigate();
}

To edit an entity instance, invoke editEntity(). For example:

@Autowired
private ViewNavigators viewNavigators;

private void navigateToEditEntity(Department entity) {
    viewNavigators.detailView(this, Department.class)
            .editEntity(entity)
            .navigate();
}

You can specify the detail view id or class explicitly using withViewId() and withViewClass() methods.

Passing Parameters

The recommended way to pass parameters to a view opened by navigation is using the withQueryParameters() method of the fluent interface:

@Autowired
private ViewNavigators viewNavigators;

private void navigateToViewWithQueryParameters() {
    viewNavigators.view(this, FancyMessageView.class)
            .withQueryParameters(QueryParameters.of("message", "Hello World!"))
            .navigate();
}

In this case, the parameter will be added to the URL, for example:

http://localhost:8080/FancyMessageView?message=Hello%20World!

In the opened view, use QueryParametersChangeEvent handler to get the parameter value:

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

Another option is to use the withAfterNavigationHandler() method of the fluent interface and pass the parameter to the opened view object directly:

@Autowired
private ViewNavigators viewNavigators;

private void navigateToViewWithAfterNavigationHandler() {
    viewNavigators.view(this, FancyMessageView.class)
            .withAfterNavigationHandler(afterViewNavigationEvent -> {
                FancyMessageView view = afterViewNavigationEvent.getView();
                view.setMessage("Hello World!");
            })
            .navigate();
}

In this case, the URL will not contain the parameter:

http://localhost:8080/FancyMessageView

This approach is simpler and allows you to pass complex types, but the downside is the same as with opening views in dialog windows: it doesn’t provide a deep link and the view state will be lost if the user refreshes the web page.

Opening Dialog Windows

The DialogWindows bean provides a fluent interface for opening views in dialog windows. 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 as a dialog, inject the DialogWindows bean and invoke the view() method passing the current view and the opened view class to it. Then invoke the open() terminal method:

@Autowired
private DialogWindows dialogWindows;

private void openView() {
    dialogWindows.view(this, MyOnboardingView.class).open();
}

If you need to pass parameters to the opened view, invoke the build() terminal method, set parameters to the view, then open the window:

@Autowired
private DialogWindows dialogWindows;

private void openViewWithParams(String username) {
    DialogWindow<MyOnboardingView> window =
            dialogWindows.view(this, MyOnboardingView.class).build();
    window.getView().setUsername(username);
    window.open();
}
When you invoke the build() method, the InitEvent is fired. If you open a dialog window to create a new entity, the InitEntityEvent will also be fired when you call the build() method. The rest of the lifecycle events for the view are triggered when you call the open() method.

To get a result from the opened view after it is closed, add AfterCloseEvent listener to the dialog window:

@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();
}

The AfterCloseEvent object contains CloseAction passed to the view’s close() method. For example, when a standard entity detail view is closed with OK button, the close action is save. You can analyze the close action using the getCloseAction() or closedWith() methods of the event object.

You can also add AfterCloseEvent listener using the fluent interface:

@Autowired
private DialogWindows dialogWindows;

private void openViewWithResults() {
    dialogWindows.view(this, MyOnboardingView.class)
            .withAfterCloseListener(afterCloseEvent -> {
                if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
                    // ...
                }
            })
            .open();
}

To select entities from a list view, open the view using the lookup() method:

@Autowired
private DialogWindows dialogWindows;

private void openLookupView() {
    dialogWindows.lookup(this, Department.class)
            .withSelectHandler(departments -> {
                // ...
            })
            .open();
}

Use the withSelectHandler() method to provide a lambda which accepts the collection of entity instances selected in the opened view.

To create a new entity instance in a detail view, specify the view class and invoke newEntity(). Use AfterCloseEvent listener to get the created entity. For example:

@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();
}

To edit an existing entity in a detail view, provide the instance to edit using the editEntity() method:

@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();
}

You can provide input parameters to list and detail views in the same way as described for the simple view in the beginning of this section: invoke the build() terminal method, set parameters to the view, then open the window.

View Inference Conventions

A list or detail view can be inferred from the entity class.

When navigating to a list view using viewNavigators.listView(SomeEntity.class).navigate(), the framework selects a view in the following order:

  1. A view with SomeEntity.list id.

  2. A view annotated with @PrimaryLookupView(SomeEntity.class).

  3. A view with SomeEntity.lookup id.

When opening a list view for lookup using dialogWindows.lookup(this, SomeEntity.class).open(), the framework selects a view in the following order:

  1. A view annotated with @PrimaryLookupView(SomeEntity.class).

  2. A view with SomeEntity.lookup id.

  3. A view with SomeEntity.list id.

When navigating to a detail view or opening it in a dialog window, the framework selects a view in the following order:

  1. A view annotated with @PrimaryDetailView(SomeEntity.class).

  2. A view with SomeEntity.detail id.