> Markdown version of [Handle Dialogs and Navigation](https://vaadin.com/docs/latest/building-apps/views/mvvm/dialogs-and-navigation). Section index: [llms.txt](https://vaadin.com/docs/latest/building-apps/llms.txt)

# Handle Dialogs and Navigation

Wizards, modes, questions to the user, and navigation are flows: the view moves from one state to another as the user acts. The ViewModel keeps the state of a flow and its rules, and the view handles what only a UI can do, such as opening a dialog or postponing a navigation.

> **Tip: Part of a Series**
>
> This is part 6 of the **Structure Views with MVVM** series. It builds on the view from [Bind the View](https://vaadin.com/docs/latest/building-apps/views/mvvm/bind-view.md).

## <a id="switch-between-steps-and-modes"></a>Switch Between Steps and Modes

Keep the step of a wizard, or the mode of a view, as an enum in a signal. Derive whether the user can go forward or back from the step, and from the validity of the step’s fields:

```java
enum Step {
    CUSTOMER, LINES, CONFIRM
}

private final ValueSignal<Step> step = new ValueSignal<>(Step.CUSTOMER);
private final Signal<Boolean> canGoNext = Signal.computed(() -> {
    Step current = step.get();
    boolean customerValid = customerError.get() == null;
    boolean linesValid = lines.valid().get();
    return switch (current) {
    case CUSTOMER -> customerValid;
    case LINES -> linesValid;
    case CONFIRM -> false;
    };
});
private final Signal<Boolean> canGoBack = step.map(s -> s != Step.CUSTOMER);

void next() {
    if (canGoNext.peek()) {
        step.update(s -> Step.values()[s.ordinal() + 1]);
    }
}

void back() {
    if (canGoBack.peek()) {
        step.update(s -> Step.values()[s.ordinal() - 1]);
    }
}
```

The view shows each step with `bindVisible()`:

```java
customerStep.bindVisible(viewModel.step().map(s -> s == Step.CUSTOMER));
linesStep.bindVisible(viewModel.step().map(s -> s == Step.LINES));
```

The `back()` command changes only the step. Whatever the user entered stays in the draft, and the fields show it again when the user returns to the step.

## <a id="ask-the-user"></a>Ask the User

A question that the ViewModel asks the user is state. In the ticket triage example, the `confirmingDiscard` signal is `true` while the view is waiting for the user to keep or discard their changes. The view opens a dialog from an effect, and wires the dialog’s buttons to commands:

`TicketsView.java`

```java
private void createDiscardDialog() {
    ConfirmDialog dialog = new ConfirmDialog();
    dialog.setHeader("Unsaved changes");
    dialog.setText("Discard your changes to this ticket?");
    dialog.setConfirmButton("Discard", event -> viewModel.discardChanges());
    dialog.setCancelable(true);
    dialog.setCancelButton("Keep editing",
            event -> viewModel.keepEditing());
    Signal.effect(this,
            () -> dialog.setOpened(viewModel.confirmingDiscard().get()));
}
```

The view owns the effect, not the dialog. A closed dialog isn’t attached to the UI, and an effect only runs while its owner is attached.

The dialog closes itself when the user clicks one of its buttons. The command that the button calls makes `confirmingDiscard` `false` at the same time, so the dialog and the ViewModel agree.

## <a id="protect-unsaved-changes"></a>Protect Unsaved Changes

The router asks the view before the user navigates away, and it needs the answer at once. The view reads the `dirty` signal of the ViewModel with `peek()`, and postpones the navigation if there are unsaved changes:

`TicketsView.java`

```java
@Override
public void beforeLeave(BeforeLeaveEvent event) {
    if (viewModel.dirty().peek()) {
        ContinueNavigationAction navigation = event.postpone();
        new ConfirmDialog("Unsaved changes",
                "Leave and discard your changes?", "Leave",
                confirm -> navigation.proceed(), "Stay",
                cancel -> navigation.cancel()).open();
    }
}
```

The postponed navigation is a router object, so it stays in the view, and so does the dialog that continues or cancels it. Always call `cancel()` when the user stays. Otherwise, the postponed navigation stays pending, and the links in the application stop working until the user reloads the page.

`beforeLeave()` only sees navigation within the application, including the back button of the browser, and a change of a route parameter that keeps the same view. When the user reloads the page or closes the browser tab, the browser can warn instead. [Warning Before the Page Unloads](https://vaadin.com/docs/latest/building-apps/forms-data/add-form/unsaved-changes.md#warning-before-the-page-unloads) shows how to turn the warning on and off. With a ViewModel, turn it on and off from an effect:

```java
Signal.effect(this, () -> setUnloadWarning(viewModel.dirty().get()));
```

## <a id="open-an-item-from-a-route-parameter"></a>Open an Item from a Route Parameter

When the URL says which item to open, the view reads the route parameter and passes it to a command. The command’s result decides whether to show an error instead:

```java
// In the view
@Override
public void setParameter(BeforeEvent event, Long ticketId) {
    if (!viewModel.open(ticketId)) {
        event.rerouteToError(NotFoundException.class);
    }
}
```

```java
// In the ViewModel
boolean open(long ticketId) {
    Optional<Ticket> ticket = ticketService.findById(ticketId);
    ticket.ifPresent(this::open);
    return ticket.isPresent();
}
```

The ViewModel never sees the router. It only knows that the user wants to open a ticket, and reports whether the ticket exists. For more about route parameters, see [Route Parameters](https://vaadin.com/docs/latest/building-apps/views/pass-data/route-parameters.md).

Next, [handle background work and changes in the Model](https://vaadin.com/docs/latest/building-apps/views/mvvm/background-work.md).
