Открытие экранов

Экраны открываются из главного экрана через стандартные действия либо программно из других экранов.

В этом разделе описывается, как открывать экраны программно в коде приложения.

Использование бина ViewBuilders

Бин ViewBuilders предоставляет fluent-интерфейс для открытия экранов. Его терминальные методы предоставляют доступ к экземпляру открытого экрана. Он позволяет передавать входные параметры непосредственно в экземпляр экрана и добавлять слушатели для получения результатов из открываемого экрана после его закрытия.

Чтобы открыть экран, инжектируйте бин ViewBuilders и вызовите метод view(), передав ему текущий экран и класс или идентификатор открываемого экрана. Затем вызовите терминальный метод open():

@Autowired
private ViewBuilders viewBuilders;

private void openView() {
    viewBuilders.view(this, OtherView.class).open();
}

Открытие экранов деталей

В большинстве случаев вы можете открывать экраны деталей с помощью стандартных действий, таких как list_create. Рассмотрим примеры, когда можно использовать API ViewBuilders напрямую, чтобы открыть экран деталей из действия или обработчика кнопки.

Чтобы создать новый экземпляр сущности в экране деталей, вызовите метод newEntity(). Например:

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewToCreate() {
    viewBuilders.detail(this, User.class)
            .newEntity()
            .open();
}

Чтобы отредактировать существующую сущность в экране деталей, используйте метод editEntity(), передав редактируемый экземпляр сущности:

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewToEdit(User user) {
    viewBuilders.detail(this, User.class)
            .editEntity(user)
            .open();
}

В приведенных выше примерах экран деталей создаст или обновит сущность, но вызывающий экран не получит обновленный экземпляр.

Если вам нужно отредактировать сущность, отображаемую компонентом списка данных (например, dataGrid), используйте следующий способ вызова. Он более лаконичен и автоматически обновляет dataGrid:

@ViewComponent
private DataGrid<User> usersDataGrid;

@Autowired
private ViewBuilders viewBuilders;

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

Чтобы создать новый экземпляр сущности и открыть его экран деталей, просто вызовите метод newEntity():

@ViewComponent
private DataGrid<User> usersDataGrid;

@Autowired
private ViewBuilders viewBuilders;

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

Используйте ту же лаконичную форму, если хотите создать или отредактировать сущность, установленную в поле:

@ViewComponent
private EntityPicker<User> userPicker;

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewFieldToEdit() {
    viewBuilders.detail(userPicker)
            .open();
}

ViewBuilders предоставляет множество методов для задания необязательных параметров открываемого экрана. Например, следующий код создает новый экземпляр сущности, инициализирует его, а затем редактирует в экране деталей, открытом в виде диалогового окна:

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewDialog() {
    viewBuilders.detail(this, User.class)
            .newEntity()
            .withInitializer(user -> {
                user.setTimeZoneId(getDefaultTimeZone());
            })
            .withOpenMode(ViewOpenMode.DIALOG)
            .open();
}

Открытие экранов выбора

Рассмотрим несколько примеров работы с экранами выбора. Как и экраны деталей, их обычно открывают с помощью стандартных действий, таких как entity_lookup. Приведенные ниже примеры демонстрируют использование API ViewBuilders, которое может быть полезно, если вы не используете стандартные действия.

Чтобы выбрать сущности из экрана списка, откройте экран с помощью метода lookup():

@Autowired
private ViewBuilders viewBuilders;

private void openLookupView() {
    viewBuilders.lookup(this, User.class)
            .withSelectHandler(users -> {
                User user = users.iterator().next();
                // ...
            })
            .open();
}

Если вам нужно установить выбранную сущность в поле, используйте более лаконичную форму:

@ViewComponent
private EntityPicker<User> userPicker;

@Autowired
private ViewBuilders viewBuilders;

private void openLookupViewToSelect() {
    viewBuilders.lookup(userPicker)
            .open();
}

Используйте ту же лаконичную форму, если хотите добавить выбранную сущность в компонент списка данных, например dataGrid:

@ViewComponent
private DataGrid<User> usersDataGrid;

@Autowired
private ViewBuilders viewBuilders;

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

Аналогично экранам деталей, используйте методы билдера для задания необязательных параметров открываемого экрана. Например, следующий код демонстрирует выбор сущности User с помощью определенного экрана выбора, открытого в виде диалогового окна:

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

Передача параметров в экраны

Если вам нужно передать параметры в открываемый экран, добавьте обработчик конфигурации экрана с помощью метода withViewConfigurer.

Рассмотрим следующий экран:

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

В обработчике ViewConfigurer вы можете вызывать публичные сеттеры открываемого экрана:

@Autowired
private ViewBuilders viewBuilders;

private void openViewWithParameters(String message) {
    viewBuilders.view(this, FancyMessageView.class)
            .withViewConfigurer(fancyMessageView -> {
                fancyMessageView.setMessage(message);
            })
            .open();
}

Выполнение кода после закрытия и возврат значений

Каждый экран отправляет AfterCloseEvent при закрытии. При использовании ViewBuilders вы можете предоставить слушатель с помощью метода withAfterCloseListener():

@Autowired
private ViewBuilders viewBuilders;

@Autowired
private Notifications notifications;

private void openViewWithCloseListener() {
    viewBuilders.view(this, OtherView.class)
            .withAfterCloseListener(afterCloseEvent -> {
                notifications.show("Closed: " + afterCloseEvent.getSource());
            })
            .open();
}

Объект события предоставляет информацию о том, как был закрыт экран. Эту информацию можно получить двумя способами:

  • Проверив, был ли экран закрыт с одним из стандартных результатов, определенных перечислением StandardOutcome.

  • Получив объект CloseAction.

Первый подход проще, второй предоставляет большую гибкость.

Рассмотрим первый подход: закрытие экрана со стандартным результатом и его проверку в вызывающем коде. Вот экран, который мы будем открывать:

@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 Нажатие кнопки Save устанавливает состояние результата и закрывает экран со значением перечисления StandardOutcome.SAVE.
2 Нажатие кнопки Close закрывает экран со значением StandardOutcome.CLOSE.

В слушателе AfterCloseEvent вы можете использовать метод closedWith() события, чтобы проверить, как был закрыт экран. При необходимости вы также можете прочитать значение результата:

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

Использование пользовательского CloseAction

Другой способ возвращать значения из экранов — использование пользовательских реализаций CloseAction. Перепишем предыдущий пример с использованием следующего класса действия:

public class MyCloseAction extends StandardCloseAction {

    private final String result;

    public MyCloseAction(String result) {
        super("myCloseAction");

        this.result = result;
    }

    public String getResult() {
        return result;
    }
}

Затем мы можем использовать это действие при закрытии экрана:

@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 Нажатие кнопки Ok создает пользовательское действие закрытия и устанавливает в нем значение результата.
2 Нажатие кнопки Close закрывает экран со стандартным действием, предоставляемым фреймворком.

В слушателе AfterCloseEvent вы можете получить CloseAction из события и прочитать значение результата:

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

Как видите, когда значения возвращаются через пользовательский CloseAction, вызывающему коду не нужно знать класс открытого экрана, поскольку он не вызывает методы конкретного контроллера экрана. Поэтому экран может быть создан по своему строковому идентификатору.

Соглашения о выводе экранов

Экран выбора или экран деталей можно открыть, указав класс сущности.

При вызове viewBuilders.lookup(this, SomeEntity.class) фреймворк определяет экран выбора в следующем порядке порядке:

  1. Экран, помеченный аннотацией @PrimaryLookupView(SomeEntity.class).

  2. Экран с идентификатором SomeEntity.lookup.

  3. Экран, помеченный аннотацией @PrimaryListView(SomeEntity.class).

  4. Экран с идентификатором SomeEntity.list.

При вызове viewBuilders.detail(this, SomeEntity.class) фреймворк определяет экран деталей в следующем порядке:

  1. Экран, помеченный аннотацией @PrimaryDetailView(SomeEntity.class).

  2. Экран с идентификатором SomeEntity.detail.