We can now exploit the derived inverse reference properties
Publisher::publishedBooks
and Author::authoredBooks
for more
efficiently creating a list of associated books in the list
publishers and list authors use cases.
For showing information about published books in the list
publishers use case, we can now exploit the derived inverse reference property
publishedBooks
:
<ui:composition template="/WEB-INF/templates/page.xhtml">
<ui:define name="content">
<h:dataTable value="#{publisherController.publishers}" var="p">
...
<h:column>
<f:facet name="header">Published books</f:facet>
<h:outputText value="#{p.publishedBooks}" escape="false" converter="pl.model.converter.BookListConverter"/>
</h:column>
</h:dataTable>
<h:button value="Back" outcome="index" />
</ui:define>
</ui:composition>
In the case of unidirectional associations, for the case of list
books use case, we have used a method in the Book
model
class, i.e., getAuthorNames()
, which returns a text containing the
comma separed author names of the
book:
public class Book { ... public String getAuthorNames() { String result = ""; int i = 0, n = 0; if ( this.authors != null) { n = this.authors.size(); for ( Author author : this.authors) { result += author.getName(); if ( i < n - 1) { result += ", "; } i++; } } return result; } }
This
makes sense in the case of a book, since the number of authors is in general limited
to a small number. However, a Publisher
may have a large number of
published books. As a better alternative to our string serialization, we can use a
JSF converter class which allows us to present the list of authors in a custom way.
In our case, we choose to present it as list of names, where every name is presented
on a separat line. For this, we implement the
pl.model.converter.BookListConverter
class and we annotate it with
@FacesConverter as
follows:
@FacesConverter( value = "pl.model.converter.BookListConverter") public class BookListConverter implements Converter { @Override public Object getAsObject( FacesContext context, UIComponent component, String value) { // no need for mapping to object... return null; } @Override public String getAsString( FacesContext context, UIComponent component, Object value) { String result = ""; int i = 0, n = 0; if ( value == null) { return null; } else if ( value instanceof Set<?>) { n = ((Set<Book>) value).size(); for ( Book b : (Set<Book>) value) { result += b.getTitle(); if ( i < n - 1) { result += "<br />"; } i++; } return result; } return null; } }
Then, we use the converter
attribute in the JSF view to specify that our
converter class, e.g., pl.model.converter.BookListConverter
, has to be
used when the respective view component needs to be
rendered:
<h:column>
<f:facet name="header">Published books</f:facet>
<h:outputText value="#{p.publishedBooks}" escape="false" converter="pl.model.converter.BookListConverter"/>
</h:column>
Since the serialization text contains HTML elements, i.e., <br />
to produce
HTML new lines, we have to specify the @escape =
"false" attribute, otherwise <
and >
will be replaced with the corresponding <
and
>
entities.
The main advantage of using JSF converters is that we do not mix model code with view specific code, so our model classes remains clean and later, if we like to replace JSF with other framework, then our model class reamins unchanged, and we just need to take care of a corresponding converter for the new framework.