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

Обычный (не корневой) экран можно открыть либо перейдя на его URL (указанный в аннотации @Route), либо на текущей веб-странице, открыв диалоговое окно.

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

Минусом навигации является относительная сложность передачи параметров в открываемый экран. Их можно передавать только в URL в качестве параметров route или query. Кроме того, невозможно вернуть в вызывающий код какие-либо значения из экрана после его закрытия.

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

Главное меню открывает экраны с помощью навигации. Стандартные действия list_create и list_edit также открывают экраны деталей с помощью навигации, но их можно настроить на использование диалоговых окон. Стандартное действие entity_lookup всегда открывает экран списка в диалоговом окне, чтобы иметь возможность вернуть выбранные сущности.

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

Чтобы открыть экран с помощью навигации на его URL, инжектируйте бин ViewNavigators и используйте его fluent-интерфейс для указания текущего экран и класса открываемого экрана:

@Autowired
private ViewNavigators viewNavigators;

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

navigate() - это терминальный метод, выполняющий навигацию.

Если вызывающий класс не является экраном, и вы не можете передать this в качестве текущего экрана, используйте UiComponentUtils.getCurrentView(), чтобы найти открытый в данный момент экран.

Если необходимо вернуться к вызывающему экрану после закрытия открываемого экрана, вызовите метод withBackwardNavigation(true):

@Autowired
private ViewNavigators viewNavigators;

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

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

@Autowired
private ViewNavigators viewNavigators;

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

Идентификатор или класс экрана списка можно указать явно, например:

@Autowired
private ViewNavigators viewNavigators;

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

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

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

@Autowired
private ViewNavigators viewNavigators;

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

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

@Autowired
private ViewNavigators viewNavigators;

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

Идентификатор или класс экрана деталей можно указать явно, используя методы withViewId() и withViewClass().

Передача параметров

Рекомендуемый способ передачи параметров в экран, открываемый при навигации — использование метода withQueryParameters():

@Autowired
private ViewNavigators viewNavigators;

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

В этом случае к URL будет добавлен параметр, например:

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

В открываемом экране для получения значения параметра используйте обработчик QueryParametersChangeEvent:

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

Другой вариант — использовать метод withAfterNavigationHandler() и передать параметр непосредственно объекту открываемого экрана:

@Autowired
private ViewNavigators viewNavigators;

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

В этом случае URL не будет содержать параметр:

http://localhost:8080/FancyMessageView

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

Открытие диалоговых окон

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

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

@Autowired
private DialogWindows dialogWindows;

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

Если вам нужно передать параметры в открываемый экран, вызовите терминальный метод build(), задайте параметры для экрана, затем откройте диалоговое окно:

@Autowired
private DialogWindows dialogWindows;

private void openViewWithParams(String username) {
    DialogWindow<MyOnboardingView> window =
            dialogWindows.view(this, MyOnboardingView.class).build();
    window.getView().setUsername(username);
    window.open();
}
Когда вы вызываете метод build(), в экране генерируется событие InitEvent. Если вы открываете диалоговое окно для создания новой сущности, при вызове метода build() также будет сгенерировано событие InitEntityEvent. Остальные события жизненного цикла экрана срабатывают при вызове метода open().

Чтобы получить результат из открываемого экрана после его закрытия, добавьте в диалоговое окно слушатель AfterCloseEvent:

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

Объект AfterCloseEvent содержит действие закрытия (CloseAction) переданное методу close() экрана. Например, когда стандартный экран деталей сущности закрывается с помощью кнопки ОК , действие закрытия имеет значение save. Вы можете проанализировать действие закрытия, используя методы getCloseAction() или closedWith() объекта события.

Слушатель AfterCloseEvent можно также добавить, используя fluent-интерфейс:

@Autowired
private DialogWindows dialogWindows;

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

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

@Autowired
private DialogWindows dialogWindows;

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

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

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

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

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

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

Входные параметры для экрана списка и экрана деталей можно предоставить так же, как описано для простого экрана в начале этого раздела: вызовите терминальный метод build(), установите параметры экрана, затем откройте диалоговое окно.

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

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

При переходе к экрану списка с помощью viewNavigators.listView(SomeEntity.class).navigate(), фреймворк выбирает экран в следующем порядке:

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

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

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

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

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

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

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

При переходе к экрану деталей или открытии его в диалоговом окне фреймворк выбирает экран в следующем порядке:

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

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