Opening Views

You can open views from the main view, by standard actions when working with list and detail entity views or programmatically from another view.

Below we describe 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 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 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, provide the instance to edit using the editEntity() method:

@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/update the entity, but the caller view will not receive the updated instance.

Often you need to edit an entity displayed by some list data view component, e.g. dataGrid. Then you should use the following form of invocation, which is more concise and automatically updates the dataGrid:

@ViewComponent
private DataGrid<User> usersDataGrid;

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewDataGridToEdit() {
    viewBuilders.detail(usersDataGrid)
            .open();
}

In order to create a new entity instance and open the detail view for it, 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 provides a lot of methods for setting optional parameters for the opened view. For example, the following code creates a new entity, initializes it and edits in a particular detail view 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 mostly open such views using standard actions such as entity_lookup. The examples below shows the usage of ViewBuilders API and can be useful 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, e.g. dataGrid, for example:

@ViewComponent
private DataGrid<User> usersDataGrid;

@Autowired
private ViewBuilders viewBuilders;

private void openLookupViewDataGrid() {
    viewBuilders.lookup(usersDataGrid)
            .open();
}

As with detail views, use the builder methods to set optional parameters of the opened view. For example, the following code looks up the 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 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 when it closes. When using ViewBuilders, the listener can be provided in 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 obtained in two ways:

  • By testing whether the view was closed with one of standard outcomes defined by the StandardOutcome enum.

  • By getting the CloseAction object. The former approach is simpler, while the latter is much more flexible.

Let’s consider the first approach: close a view with a standard outcome and test it in the calling code. Here is the view that we 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 By clicking the Save button sets some result state and close the view with StandardOutcome.SAVE enum value.
2 By clicking the Close button closes the view with StandardOutcome.SAVE.

In the AfterCloseEvent listener, you can check how the view was closed using the closedWith() method of the event, and read the result value if needed:

@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 of returning values from views is using custom CloseAction implementations. Let’s rewrite the above 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;
    }
}

Then we can 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 By clicking the Ok button creates the custom close action and sets the result value in it.
2 By clicking the Close button closes with a default action provided by the framework.

In the AfterCloseEvent listener, you can get 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 or 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 in the following order:

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

  2. A view with SomeEntity.lookup id.

  3. A view annotated with @PrimaryListView(SomeEntity.class).

  4. A view with SomeEntity.list id.

When opening a detail view using viewBuilders.detail(this, SomeEntity.class), the framework selects a view in the following order:

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

  2. A view with SomeEntity.detail id.