After we have defined the constraints in the Java EE model layer and the database layer, we need to take care of validation in the user interface. In particular, we need to make sure that the user gets informed about issues by rendering visual indicators and informative validation error messages.
The WebContent/views/books/create.xhtml
file contains the JSF
facelet code for creating a new Book
record. We now use the JSF
validator
attribute for performing the uniqueness validation and JSF
message
elements for displaying validation error messages.
<ui:composition template="/WEB-INF/templates/page.xhtml"> <ui:define name="headerTitle"> <h1>Create a new book record</h1> </ui:define> <ui:define name="main"> <h:form id="createBookForm"> <div> <h:outputLabel for="isbn" value="ISBN: "> <h:inputText id="isbn" value="#{book.isbn}" validator="#{bookCtrl.checkIsbnAsId}" /> </h:outputLabel> <h:message for="isbn" errorClass="error" /> </div> <div> <h:outputLabel for="title" value="Title: "> <h:inputText id="title" value="#{book.title}" /> </h:outputLabel> <h:message for="title" errorClass="error" /> </div> ... </h:form> </ui:define> </ui:composition>
There are only a few changes compared to the same view used for the minimal app, where
no validation was performed. The first change is the new h:message
element which is
bound to a specific form element by the for
attribute. We create such an element
for each of our form input elements. Notice that we don't have to do anything else for seeing
the validation errors for all integrity constraint checks which are performed by using the
(built-in and custom) Bean Validation annotations. As soon as a constraint validation fails, the
message set by using the message
property of the integrity constraint annotation
(e.g. @Pattern
, @NotNull
, etc) is displayed in an HTML
span
element generated by JSF as a result of using the h:message
element.
For all the integrity constraints we have used Bean Validation annotations, but for the
uniqueness constraint we have used custom code, therefore no error message will be shown for it.
In the view code we can see that a new attribute, validator
in
h:inputText
, was used for the isbn
input field. It specifies which
custom method is used to perform validation of the provided value in this form field. In our
case, we use the checkIsbnAsId
method defined in the BookController
as
shown
below:
public void checkIsbnAsId( FacesContext context, UIComponent component, Object value) throws ValidatorException { String isbn = (String) value; try { Book.checkIsbnAsId( em, isbn); } catch ( UniquenessConstraintViolation e) { throw new ValidatorException( new FacesMessage( FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getMessage())); } catch ( MandatoryValueConstraintViolation e) { throw new ValidatorException( new FacesMessage( FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getMessage())); } }
The controller's check method throws a
ValidatorException
which is also used to deliver the error
message (the third parameter of the ValidatorException
constructor) to the corresponding JSF facelet for being displayed in the
UI. Methods used as JSF validators must have a specific syntax. The first two parameters of
type FacesContext
, respectively UIComponent
are used by the container to invoke the method with references to the
right view component and context, and they can be used in more complex
validation methods. The last one, of type Object
,
represents the value to be validated by the method. This value has to be
casted to the expected type (to String
, in our example). It
is important to know that, if a cast to a non-compatible type is
performed, the validation method fails and an exception is
thrown.
In the Update use case, the facelet file
update.xhtml
in
WebContent/views/books
was updated so it uses the
h:message
elements for being able to display validation
errors:
<ui:composition template="/WEB-INF/templates/page.xhtml"> <ui:define name="headerTitle"> <h1>Update a book record</h1> </ui:define> <ui:define name="main"> <h:form id="updateBookForm"> <div> <h:outputLabel for="selectBook" value="Select book: "> <h:selectOneMenu id="selectBook" value="#{book.isbn}"> ... </h:selectOneMenu> </h:outputLabel> <h:message for="selectBook" errorClass="error" /> </div> <div> <h:outputLabel for="isbn" value="ISBN: "> <h:outputText id="isbn" value="#{book.isbn}" /> </h:outputLabel> </div> ... </h:form> </ui:define> </ui:composition>
Since we do not allow to change the ISBN of a book, we create an
output field for the isbn
attribute with the JSF element
h:outputText
. This implies that no validation is
performed.
Using an h:outputText
element for showing the value of an entity attribute
results in an HTML span
element. This implies that the HTTP form submission message
contains no information about that attribute. If the validation fails, we expect to see the form
content together with the error messages. To get the expected result, we need to use the
annotation @ViewScoped
for the entity class pl.m.Book
instead of
@RequestScoped
, otherwise our bean instance referenced by the book
variable is initialized with a new value on every request, implying that the expression
#{book.isbn}
evaluates to null
and the ISBN value is not displayed.
The @ViewScoped
annotation specifies that the entity bean is alive as long as the
associated view is alive, so the ISBN value stored by the book
is available during
this time and it can be displayed in the view.
By contrast, h:inputText
elements result in HTML
input
elements which are part of the form submission
content, so the response contains the already existing values because
these values are known in this case. This consideration shows that it is
important to choose the right bean scope.