Step 3 - Navigation using Navigator in Flow with MPR

Note
This step is needed in case your Vaadin 7 or 8 application uses Navigator. If it is not the case, go back to the framework selection.

The navigation with MPR can be done in three ways. You can choose the one most suitable for your application, but do not mix them together. Also, note that no other approach is supported at the moment and you are on your own if you do it your own way.

  1. Using the Navigator together Flow’s Router: this is suitable for creating new views in Flow while maintaining the old views to be routed by the Navigator.

  2. Using the Navigator without mixing with Flow: this is suitable for projects with complex custom navigators.

  3. Using only the Flow’s Router: this is suitable for basic navigation setups that can be easily ported, or as the final stage of an incremental porting process.

Note
Keep in mind that the old Navigator uses URLs with the "hash-bang" (#!) prefix. That prefix is not used at all by the Flow’s Router.

Mixing navigation and Flow routing

It is possible to use the legacy Navigator and Flow routing together.

Starting from the legacy application:

public class NavigatorUI extends UI {
  @Override
  protected void init(VaadinRequest request) {
    CssLayout viewDisplay = new CssLayout();
    Navigator navigator = new Navigator(this, viewDisplay);

    navigator.addView("", HomeView.class);
    navigator.addView("away", AwayView.class);

    VerticalLayout content = new VerticalLayout(viewDisplay);
    setContent(content);
  }
}

We would make the UI into a Flow route by extending MprNavigatorRoute.

@Route("")
public class MyNavigatorRoute extends MprNavigatorRoute {
    @Override
    public void configureNavigator(Navigator navigator) {
        navigator.addView("", HomeView.class);
        navigator.addView("away", AwayView.class);
    }
}

For a more complex sample we could have a MainMenu component that is visible at all times and used to navigate between the views.

public class MyNavigatorUI extends UI {
  @Override
  protected void init(VaadinRequest request) {
    CssLayout viewDisplay = new CssLayout();
    Navigator navigator = new Navigator(this, viewDisplay);

    navigator.addView("", HomeView.class);
    navigator.addView("away", AwayView.class);

    setContent(new VerticalLayout(new MainMenu(), viewDisplay));
  }
}

public class MainMenu extends HorizontalLayout {
    public MainMenu() {
        Button home = new Button("Home",
                event -> getUI().getNavigator().navigateTo(""));
        Button away = new Button("Away",
                event -> getUI().getNavigator().navigateTo("away"));

        addComponents(home, away);
    }
}

Here we can move the MainMenu to its own RouterLayout that will be used on all Routes that have it as the parent layout. All we need to do is to create a Flow component, e.g. MainLayout, that contains the MainMenu component and add that to the @Route annotation.

// Flow router target
@Route(value = "", layout = MainLayout.class)
public class MyNavigatorRoute extends MprNavigatorRoute {
    @Override
    public void configureNavigator(Navigator navigator) {
        navigator.addView("", HomeView.class);
        navigator.addView("away", AwayView.class);
    }
}

// Flow layout, used by the router
public class MainLayout extends VerticalLayout implements RouterLayout {
    public MainLayout() {
        add(new LegacyWrapper(new MainMenu()));
    }
}

This way we can make a single MainLayout that can be used both with the old navigator views as well as with the new Flow views.

To add a Flow view we just need to create the route target.

@Route(value = "flow", layout = MainLayout.class)
public class FlowView extends Div {
}

and then add it to the MainMenu as a Button.

public class MainMenu extends HorizontalLayout {
    public MainMenu() {
        Button home = new Button("Home",
                event -> getUI().getNavigator().navigateTo(""));
        Button away = new Button("Away",
                event -> getUI().getNavigator().navigateTo("away"));
        Button flow = new Button("Flow",
                event -> getUI().getNavigator().navigateTo("flow"));

        addComponents(home, away, flow);
    }
}

Now the menu can be used to navigate from a legacy view to a Flow view and back.

When requesting the Navigator to navigate to a view that isn’t registered in the Navigator we will navigate to a corresponding Flow view if available.

Also navigation from a Flow route to a legacy View will work through the Navigator.

Note
By default the MprNavigatorRoute creates a <div> on the client-side, but this can be changed by annotating the subclass with @Tag.
Note
MainMenu, HomeView and AwayView are legacy Vaadin 7 components and, FlowView and MainLayout are Flow components. HomeView and AwayView also implement View.

Use navigator without mixing with Flow

Navigator can be used as is by having a view display component that is wrapped in a LegacyWrapper.

Consider the following simple legacy navigator setup:

public class MyUI extends UI {

    @Override
    protected void init(VaadinRequest request) {
        Navigator navigator = new Navigator(this, this);
        navigator.addView("", DefaultView.class);
        navigator.addView("subview", SubView.class);
    }
}

This would just be changed to:

@Route("")
public class Root extends Div {
    private final CssLayout content = new CssLayout();

    public Root() {
        add(new LegacyWrapper(content));

        Navigator navigator = new Navigator(UI.getCurrent(), content);
        navigator.addView("", DefaultView.class);
        navigator.addView("subview", SubView.class);
    }
}

Now, navigation to localhost would show DefaultView and localhost#!subview would show SubView as is expected.

The thing to note in this case is that Flow doesn’t receive any view change events.

Upgrading Views to Flow Routes

Another open path for navigator upgrade is to wrap the existing View classes into a MprRouteAdapter<? extends View> and give the adapter class a Route.

So then the navigator.addView("away", AwayView.class); configuration in the previous example would be changed to:

@Route(value = "away", layout = MainLayout.class)
public class AwayRoute extends MprRouteAdapter<AwayView> {
}
Note
By default the MprRouteAdapter creates a <div> on the client-side, but this can be changed by annotating the subclass with @Tag.

Now, there is no need to setup a Navigator and the View will still receive a ViewChangeEvent as it did with the navigator.

Note
Any ViewChangeListener should be replaced with a BeforeEnterListener for the beforeViewChange and an AfterNavigationListener for the afterViewChange to the Flow UI. See Navigation Lifecycle documentation.