5. The View and Controller Layers

The user interface (UI) consists of a start page for navigating to the data management UI pages and one UI page for each object type and data management use case. All these UI pages are defined in the form of JSF view files in subfolders of the WebContent/views/ folder. We create the Main Menu page index.xhtml in the subfolder WebContent/views/app with the following content:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="..." xmlns:h="..." xmlns:f="...">
  <ui:composition template="/WEB-INF/templates/page.xhtml">
    <ui:define name="content">
      <h2>Public Library</h2>
      <h:button value="Manage publishers" 
          outcome="/views/publishers/index" />
      <h:button value="Manage books" outcome="/views/books/index" />
      <h:form>
        <h:commandButton value="Clear database" 
            action="#{appCtrl.clearData()}" />
        <h:commandButton value="Create test data" 
            action="#{appCtrl.createTestData()}" />
      </h:form>
    </ui:define>
  </ui:composition>
</html>

It creates the menu buttons which provide the redirects to corresponding views for each of the management pages. Additionally, we need to create a corresponding AppController class which is responsible for the creation and deletion of the test data. The controller is used to combine code of the Publisher.createTestData and Publisher.clearData methods with Book.createTestData and Book.clearData methods as follows:

@ManagedBean( name="appCtrl") @SessionScoped
public class AppController {
  @PersistenceContext( unitName="UnidirAssocApp")
  private EntityManager em;
  @Resource()
  UserTransaction ut;

  public String clearData() {
    try {
      Book.clearData( em, ut);
      Publisher.clearData( em, ut);
    } catch ( Exception e) {
      e.printStackTrace();
    }
    return "index";
  }
  public String createTestData() {
    try {
      Publisher.createTestData( em, ut);
      Book.createTestData( em, ut);
    } catch ( Exception e) {
      e.printStackTrace();
    }
    return "index";
  }
}

The deletion of Book and Publisher data must be done in a particular order for avoiding referential integrity violations (books have to be deleted first, before their publishers are deleted).

5.1. Initialize the app

Since the code runs in a Tomcat container, the initialization is made internally by the container.

5.2. Show information about associated objects in Retrieve/List All

In our example we have only one reference property, Book::publisher, which is functional. For showing information about the optional publisher of a book in the retriev/list all books use case, the corresponding column in the HTML table is filled with the names of publishers, if there is any:

<ui:composition template="/WEB-INF/templates/page.xhtml">
  <ui:define name="content">
    <h:dataTable value="#{bookCtrl.books}" var="b">
      ...
      <h:column>
        <f:facet name="header">Publisher</f:facet>
        #{b.publisher.name}
      </h:column>
    </h:dataTable>
    <h:button value="Main menu" outcome="index" />
  </ui:define>
</ui:composition>

Notice the cascade call used in the #{b.publisher.name} JSF expression: accessing the property name of the publisher which is a property of the book instance b.

5.3. Allow selecting associated objects in Create and Update

In this section, we discuss how to create and update an object which has reference properties, such as a Book object with the reference property publisher.

5.3.1. Create the Object-to-String converter

HTML requires string or number values to populate forms. In our case, the property publisher of the Book class is an object reference. JSF allows converting objects to strings and vice versa by means of converters, which are classes annotated with @FacesConverter and implementing the javax.faces.convert.Converter interface:

@FacesConverter( value="pl.m.converter.PublisherConverter")
public class PublisherConverter implements Converter {
  @Override
  public Object getAsObject( FacesContext context, 
      UIComponent component, String value) {
    PublisherController ac = FacesContext
        .getCurrentInstance()
        .getApplication()
        .evaluateExpressionGet( context, "#{publisherCtrl}",
            PublisherController.class);
    EntityManager em = ac.getEntityManager();
    if (value == null) return null;
    else return em.find( Publisher.class, value);
  }
  @Override
  public String getAsString( FacesContext context, 
      UIComponent component, Object value) {
    if (value == null) return null;
    else if (value instanceof Publisher) {
      return ((Publisher) value).getName();
    }
    else return null;
  }
}

A converter defines two methods: getAsObject and getAsString, which are responsible for the two conversions. For being able to retrieve an object from a database we need an EntityManager, which can be obtained from a controller (e.g., the managed bean publisherCtrl). The controller can be obtained via the FacesContext singleton:

PublisherController ac = FacesContext.getCurrentInstance()
     .getApplication()
     .evaluateExpressionGet( context, "#{publisherCtrl}", 
          PublisherController.class);

In addition, we need to add a getEntityManager method in the controller class as follows:

@ManagedBean( name="publisherCtrl") @SessionScoped
public class PublisherController {
  @PersistenceContext( unitName="UnidirAssocApp")
  private EntityManager em;
  @Resource()
  UserTransaction ut;

  public EntityManager getEntityManager() {
    return this.em;
  }
  ...
}

JSF needs to compare two publisher instances, when the publisher list has to auto-select the current publisher, in the update book use case. For this reason, the Publisher model class needs to implement the equals method. In our case, two publishers are "one and the same", if the values of their name property are equal:

@Override
public boolean equals( Object obj) {
  if (obj instanceof Publisher) {
    Publisher publisher = (Publisher) obj;
    return ( this.name.equals( publisher.name));
  } else return false;
}

5.3.2. Write the view code

To allow selection of objects which have to be associated with the currently edited object from a list in the create and update use cases, an HTML selection list (a select element) is populated with the instances of the associated object type with the help of a h:selectOneMenu element. The create.xhtml file in the folder WebContent/views/books/ is updated as follows:

<ui:composition template="/WEB-INF/templates/page.xhtml">
 <ui:define name="content">
  <h:form id="createBookForm" styleClass="pure-form pure-form-aligned">
   <h:panelGrid columns="3">
    ...
    <h:outputLabel for="publisher" value="Publisher:" />
    <h:selectOneMenu id="publisher" value="#{book.publisher}">
     <f:selectItem itemValue="" itemLabel="---" />
     <f:selectItems value="#{publisherCtrl.publishers}" var="p" 
                    itemLabel="#{p.name}" itemValue="#{p}" />
     <f:converter converterId="pl.m.converter.PublisherConverter" />
    </h:selectOneMenu>
    <h:message for="publisher" errorClass="error" />
    </h:panelGrid>
     <h:commandButton value="Create" 
         action="#{bookCtrl.create( book.isbn, book.title, 
         book.year, book.publisher.name)}" />
  </h:form>
 </ui:define>
</ui:composition>

The converter is specified by using the f:converter element and its associated @converterId attribute. The value of this attribute must be the same as the one specified by the value attribute of the annotation @FacesConverter used when the converter class is defined, e.g.,

@FacesConverter( value = "pl.m.converter.PublisherConverter")

In general, the converter's local class name can be used (e.g., PublisherConverter), or the fully qualified class name, if there is a risk of naming conflicts (e.g., pl.m.converter.PublisherConverter). We recommend using the fully qualified name in all cases.

The #{publisherCtrl.publishers} expression results in calling the getPublishers method of the PublisherController class, which is responsible to return the list of available publishers:

public List<Publisher> getPublishers() {
  return Publisher.retrieveAll( em);
}

The creation of the book is obtained by using the h:commandButton element. It results in the invocation of the create method of the BookController class:

public String create( String isbn, String title, Integer year, 
    Publisher publisher) {
  try {
    Book.create( em, ut, isbn, title, year, publisher);
    // Enforce clearing the form after creating the Book row
    FacesContext facesContext = FacesContext.getCurrentInstance();
    facesContext.getExternalContext().getRequestMap().remove( "book");
  } catch ( EntityExistsException e) {
    try {
      ut.rollback();
    } catch ( Exception e1) {
      e1.printStackTrace();
    }
    e.printStackTrace();
  } catch ( Exception e) {
    e.printStackTrace();
  }
  return "create";
}

It simply calls the Book.create method for creating a new row in the books table.

The code for the update book use case is very similar, the only important difference is the addition of the select element (i.e., using the h:selectOneMenu JSF element) for allowing to select the book to update:

<ui:composition template="/WEB-INF/templates/page.xhtml">
 <ui:define name="content">
  <h:form id="createBookForm" styleClass="pure-form pure-form-aligned">
   <h:panelGrid columns="3">
    ...
    <h:outputLabel for="selectBook" value="Select book: " />
    <h:selectOneMenu id="selectBook" value="#{book.isbn}">
     <f:selectItem itemValue="" itemLabel="---" /> 
     <f:selectItems value="#{bookCtrl.books}" var="b" 
         itemValue="#{b.isbn}" itemLabel="#{b.title}" /> 
     <f:ajax listener="#{bookCtrl.refreshObject( book)}" 
         render="isbn title year publisher"/>
    </h:selectOneMenu>
    <h:message for="selectedBook"/>
     ...
   </h:panelGrid>
   <h:commandButton value="Create" action="#{bookCtrl.create( 
       book.isbn, book.title, book.year, book.publisher.name)}" />
  </h:form>
 </ui:define>
</ui:composition>