After we have defined the validations in the Java 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 human readable error messages are displayed.
The WebContent/views/books/create.xhtml
file contains the JSF code (see
below) used to define the view which allows to create a new Book
record. We
like to improve the code so we can see the validation errors as well as enforce the
uniqueness validation check.
<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="isbn" value="ISBN: " /> <h:inputText id="isbn" value="#{book.isbn}" validator="#{bookController.checkIsbnAsId}"/> <h:message id="isbnMessages" for="isbn" errorClass="error" /> <h:outputLabel for="title" value="Title: " /> <h:inputText id="title" value="#{book.title}" /> <h:message id="titleMessages" for="title" errorClass="error" /> <h:outputLabel for="year" value="Year: " /> <h:inputText id="year" p:type="number" value="#{book.year}" /> <h:message id="yearMessages" for="year" errorClass="error" /> </h:panelGrid> <h:commandButton value="Create" action="#{bookController.add( book.isbn, book.title, book.year)}" /> </h:form> <h:button value="Main menu" outcome="index" /> </ui:define> </ui:composition>
There are only a few changes comparing with the same view used for the minimal application,
when no validation was performed. The first change is the new h:message
element which is bound to a specific form element by using the
h:message/@for
attribute. We create a such an element for each of
our form input elements. Please notice that we don't have to do nothing else for
seeing the validation errors for all integrity constraint checks which are performed
by using the Validation API annotations (builtin and custom defined ones). 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 the JSF generated HTML5
span
element, created as a result of using the
h:message
element.
For all the integrity constraints we have used Validation API annotations (builtin and the
UpToNextYear
custom one we've created), 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, h:inputText/@validator
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
controller 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()));
}
}
The controller 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 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
is mostly used
by the method validation code and it contains the value which was provided in the
form field. This value has to be casted to the expected value type (i.e.
String
in our case). It is important to know that, if the a cast to
a non compatible type is requested, the validation method fails and an exception is
thrown.
In the update object test case, the WebContent/views/books/update.xhtml
file
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="content"> <h:form id="updateBookForm" 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="#{bookController.books}" var="b" itemValue="#{b.isbn}" itemLabel="#{b.title}" /> <f:ajax listener="#{bookController.refreshObject( book)}" render="isbn title year"/> </h:selectOneMenu> <h:outputLabel for="isbn" value="ISBN: " /> <h:outputText id="isbn" value="#{book.isbn}" /> <h:outputLabel for="title" value="Title: " /> <h:inputText id="title" value="#{book.title}" /> <h:message id="titleMessages" for="title" errorClass="error" /> <h:outputLabel for="year" value="Year: " /> <h:inputText id="year" p:type="number" value="#{book.year}" /> <h:message id="yearMessages" for="year" errorClass="error" /> </h:panelGrid> <h:commandButton value="Update" action="#{bookController.update( book.isbn, book.title, book.year)}"/> </h:form> <h:button value="Main menu" outcome="index" /> </ui:define> </ui:composition>
Since we do not allow to change the isbn value, it is created by using
h:outputText
, it does not require to be validated in the view, so a
validator is not defined as in the create object
use case.
The result of an h:outputText
JSF element is a HTML5
span
element. This means that the form submission which occurs on
pressing the "Update" h:commandButton
contains no information about
that specific element. If the validation fail, we expect to see the form content
together with the error messages. To get the expected result, we need to use
@ViewScoped
annotation for the bean class (i.e.
pl.model.Book
) instead of @RequestScoped
, otherwise
our bean instance referenced by the book
variable is initialized with a
new value on every request (this is what @RequestScoped
annotation is
for), and then #{book.isbn}"
is equal with null
. As a
result, our ISBN value is not shown. The @ViewScoped
annotation
specifies that the bean instance is alive as long as the associated view is alive,
so the ISBN value stored by the book
is available for all this time and
it can be displayed in the view.
By contrast, the h:inputText
JSF elements results in
input
HTML5 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. From this we learn that choosing a bean scope is important, and it can
create logical errors if we do not pay attention to it.