Single page web applications have become more and more popular as they support responsiveness in an elegant way and create great user experiences with fast rendering times. Vaadin is a leading Java single page web application framework that has a large eco system with many official and 3rd party add-ons. One essential add-on is the CDI add-on that integrates JEE 6 / 7 dependency injection into your Vaadin web application. The add-on does also give us the CDIViewProvider class that can be used to support bookmarks and browser navigation buttons in our one page web application.

I created a small and simple vaadin web application to show the usage of the Vaadin CDI add-on with the CDIViewProvider class. The small project is located at bitbucket and can be downloaded or cloned from this URL:

For the latest update (Vaadin 8.3+ with CDI 3.0.0) I created a fresh post: An update to Vaadin, CDI and readable URIs  

The web application handles the classical master / detail problem:

  • show a list of persons in a table
  • select and edit one person’s data
  • save the changes and display the table with the current data

So, we have a master view with a Vaadin grid showing some peoples data:

When selecting one table row, the edit button will be enabled and after clicking it, the application will show the detail view to edit the person’s data:

The project has a straight-forward MVP (model-view-presenter) architecture that is quite easy to understand. I will explain the usage of CDI and eventing in a upcoming post of this blog. The topic of this post is the usage of CDI, the Navigator and the CDIViewProvider, a functionality that is contained in the Vaadin CDI addon. To use the add-on, we need to include it in our maven project configuration file:

<dependency>
  <groupId>com.vaadin</groupId>
  <artifactId>vaadin-cdi</artifactId>
  <version>${vaadin.cdi.version}</version>
</dependency>

CDI is part of the JEE 6 standard, so we need a JEE 6 (or later) container (or a CDI library) to enable CDI for our web application. The Vaadin Navigator class needs a view provider to register all views of our application, so that all views are mapped to an url automatically. This is done in the UI class of our application.

@Theme("valo")
@CDIUI("")
@Title("A Vaadin MVP / CDI application")
public class MasterDetailUI extends UI {
  @Inject
  private CDIViewProvider viewProvider;
  
    @Override
    protected void init(VaadinRequest request) {
        Navigator navigator = new Navigator(this, this);
        navigator.addProvider(viewProvider);
    }
}

The init method initializes a Vaadin navigator with a CDIViewProvider, that is injected automatically with the CDI annotation @Inject in the UI class.

Now we need the master and the detail view to be available to the navigator.

@CDIView(MasterView.VIEW_NAME)
public class MasterView extends CustomComponent implements View {
  public static final String VIEW_NAME = "";
  @Inject
  private javax.enterprise.event.Event navigationManager;
  
  private MasterLayout layout;
  
  @PostConstruct
  public void init() {
    this.layout = new MasterLayoutImpl();
  }
  
  @Override
  public void enter(ViewChangeEvent event) {
    super.setCompositionRoot(this.layout.getRootComponent());
  }
  ...
}

The View is annotated with the @CDIView annotation (that has the view name as parameter). The view name will later be used in the url to access the view. Because the master view is the default view that will be show when the user enters our application, the view name is empty. The actual layout of this view (the UI elements) is created in the init() method, that will be called automatically by the CDI container when the object is created (because it is annotated with @PostConstruct). The view class implements the Vaadin view interface, so it needs to override the enter(ViewChangedEvent event) method. In the enter method (that is called whenever the user enters the view), I set the composition root of the view and show the UI elements. The same must be done for the detail view:

@CDIView(DetailView.VIEW_NAME)
public class DetailView extends CustomComponent implements View {
  public static final String VIEW_NAME = "detail";
  @Inject
  private javax.enterprise.event.Event savePersonEventManager;
  @Inject
  private javax.enterprise.event.Event editPersonEventLauncher;
  
  private DetailLayout layout;
  private FieldGroup formBinder;
  
  @PostConstruct
  public void init() {
    this.layout = new DetailLayoutImpl();
  }
  
  @Override
  public void enter(ViewChangeEvent event) {
    super.setCompositionRoot(this.layout.getRootComponent());
    addViewBehaviour();
    String personId = ParameterHandler.findPersonId(event.getParameters());
    this.editPersonEventLauncher.fire(new EditPersonEvent(personId));
  }
  ...
}

The detail view has the view name “detail” and will be accessed with the view name and the person id of the person, that was selected by the user in the master view. The URL you can see in your browser has now changed from

/vaadin-cdi-mvp    to    /vaadin-cdi-mvp/#!detail/personId=[unique id for the selected person]

This url can be bookmarked and will show the data of the person, that is identified by the person id. The navigation between the master and the detail view is done by using CDI events. I created a simple navigation manager class, that can handle the navigation by calling the navigateTo method of the UI navigator:

@ApplicationScoped
public class NavigationManager {

  public void navigateTo(@Observes String view) {
    UI.getCurrent().getNavigator().navigateTo(view);
  }
}

The handling of all URL parameters like the personID must be done programatically. The handling is enapsulated in the ParameterHandler class:

public class ParameterHandler {
  private static final String PARA_SEP = "/";
  private static final String PARA_FORMAT = PARA_SEP + "%s=%s";
  private enum Parameter { personId };

  public static String addPersonID(String personId) {
    return String.format(PARA_FORMAT, Parameter.personId.name(), personId);
  }
  
  public static String findPersonId(String parameterString) {
    return findParameterValue(Parameter.personId, parameterString);
  }
  
  private static String findParameterValue(Parameter parameter, String parameterString) {
    if (parameterString == null || parameterString.isEmpty()) {
      return null;
    }
    return createParameterMap(parameterString).get(parameter);
  }
  
  private static Map<Parameter, String> createParameterMap(String paraString) {
    Map<Parameter, String> paraMap = new HashMap<>();
    String [] tokens = paraString.split(PARA_SEP);
    for (String token : tokens) {
      int sep = token.indexOf("=");
      String key = token.substring(0, sep);
      String value = token.substring(sep + 1);
      Parameter para = Parameter.valueOf(key);
      paraMap.put(para, value);
    }
    return paraMap;
  }
}

The usage of CDI and the Vaadin Navigator with a CDIViewProvider creates a well defined project structure where all views are accessible and bookmarkable. By combining this approach with a light-weight model-view-presenter architecture, you are able to give Vaadin projects of all sizes a solid structure and an elegant and easy to use programming model.

By Meik Kaufmann

I am a certified oracle enterprise architect and like to share my knowledge and experience that I gain in the projects I am working on.

One thought on “Use the Vaadin CDI Navigator to support bookmarks and browser navigation buttons”
  1. You should participate in a contest for one of the finest java / architecture sites online.

    I ‘m going to recommend this site!

Leave a Reply

Your email address will not be published. Required fields are marked *