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

Обычный (не корневой) экран можно открыть либо перейдя на его 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(MyOnboardingView.class).navigate();
}

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

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

@Autowired
private ViewNavigators viewNavigators;

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

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

@Autowired
private ViewNavigators viewNavigators;

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

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

@Autowired
private ViewNavigators viewNavigators;

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

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

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

@Autowired
private ViewNavigators viewNavigators;

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

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

@Autowired
private ViewNavigators viewNavigators;

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

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

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

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

@Autowired
private ViewNavigators viewNavigators;

private void navigateToViewWithQueryParameters() {
    viewNavigators.view(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(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();
}

Чтобы получить результат из открываемого экрана после его закрытия, добавьте в диалоговое окно слушатель 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.