May 3, 2015

JSF: Validation Localization (I18N) by example (part 3 of 3)


Pages: 1 2 3

Input masking / filtering

From a usability point of view (as well as from a throughput point of view, however typically not significant), it’s desirable to prevent the user from submitting any wrong data in the first place, instead of letting him send whatever he wants over the wire, only to then annoy him with “wrong input” warnings. A very effective way of preventing wrong input is input masking and filtering, and the two component libraries PrimeFaces and PrimeFaces Extensions offer some very useful components for that out of the box.

Warning: Note that all these components work on the client side (e.g. based on JavaScript) and are thus typically not save, i.e. the user can bypass them by disabling / manipulating JavaScript execution. From an architectural point of view as well it is highly recommended to use these enhancements just as additional guidance to the user and to still implement actual validation on the model (using one of the techniques discussed earlier). As a simple best practices rule: any of these GUI layer visual guidelines should be backed by and match exactly a server-side validation constraint.

Maxlength

The maxlength attribute is available on all input components and it simply prevents the user from entering “too much text” into the respective field by blocking input if the maximum has been reached. This is very straightforward and you should make heavy use of this for basically any input component. Remember that most typically, if user input is processed, the backend will have some ultimate upper storage limit (e.g. DB column maximum length). You should always take this as the upper limit for user input length; otherwise, it may trigger ugly backend errors while processing.

For example, you can prevent the user from violating
@Size(min = 1, max = 100, message = "{javax.validation.constraints.Size.String.message}")
by defining
<p:inputText id="title" value="#{bookController.item.title}" required="true" maxlength="100">

Masking and formatting

Masking prevents the user from entering an illegal input format and is most typically used for numeric input. Here, you have two out-of-the-box choices:

PrimeFaces provides the <p:inputMask> component which supports a very simple input mask format. It’s a pitty that its mask attribute pattern isn’t officially documented, but it’s known that it is backed by the jQuery masked input plugin, thus you can rely on the documentation on their page. However, this component comes with a slightly surprising and sometimes undesired behavior: If there is no “valid” (according to the mask pattern) input present or if the user leaves (onBlur()) the component leaving it with an invalid (e.g. incompleted) value, it simply switches back to “empty”.

In the demo application, <p:inputMask> is applied to the “release year” input component as in this case, the described behavior matches the requirement:
<p:inputMask id="releaseYear" value="#{bookController.item.releaseYear}"
             converterMessage="#{val:labeledMsg(component, 'model.book.releaseYear.date')}"
             title="#{msg['model.book.releaseYear.help']}"
             mask="9999">
    <f:convertDateTime pattern="yyyy" />
</p:inputMask>
For more sophisticated input masking and formatting, you may want to use PrimeFaces Extensions’ <pe:inputNumber> component instead. For this component, you can freely specify symbols, thousand separators, and more, without the undesired “wipe input” behavior that <p:inputMask> shows.

However, I found out that this component has a bug (v. 3.2.0). But fear not, I found a JavaScript workaround for it and implemented it in the demo application you are free to use and adapt:

The (default) behavior to hide the “symbol” (as controlled by the symbol / emptyValue attributes) does not work if the component value is backed by a primitive (e.g. int) instead of a Wrapper (e.g. Integer) as the “check for empty input” works by recognizing a literally empty input field whereas JSF will render “empty” primitive int values as 0.
  • The workaround is to replace 0 by an empty String on document ready and on component onblur.
  • To apply it, you need to include the bugfixes.js from the demo application and activate it with <o:onloadScript>Bugfixes.apply();</o:onloadScript>. Because this problem is local to those <pe:inputNumber> components which are backed by an int, you have to manually activate it on these components with styleClass="bugfix-pe-inputNumber-zeroToEmpty".
(Another bug I found previously was that when hitting the ENTER key on the component (which typically triggers form submit), the form submit is triggered, but the value of the component is ignored upon submit, e.g. the value from the previous submit is restored. This has been fixed with V. 3.2.0.)

Key filtering

Finally, if you have really sophisticated needs to filter user input in an input component, you may want to implement a true client-side key filter which prevents illegal key presses.

Luckily, you don’t have to come up with your own JavaScript hacks as PrimeFaces Extensions’ <pe:keyFilter> provides the desired functionality (and this time, it works).

For example, you can prevent the user from violating
@Pattern(regexp = "[A-Za-z\\. ]+", message = "{model.book.author}")
by defining
<p:inputText id="author" value="#{bookController.item.author}" required="true" maxlength="100" 
             title="#{val:msg('model.book.author')}">
    <pe:keyFilter regEx="/[A-Za-z\. ]+/i"/>  
</p:inputText>
Note that from a usability point of view, key filtering is tricky. Although it prevents the user from entering improper data, he may as well be surprised as to why certain key strokes are not accepted, leading to frustration unless properly explained. I would thus recommend to always use at least a tooltip to explain which input is allowed and which isn’t. Remember that because we now actually prevent any illegal input, the user would never see a validation error message which would explain his input errors.

For example, the very simple “author” regex I used above would prevent the user from using diacritics in the author’s name. This clearly should be explained in an accompanying tooltip.

Accessibility

Optimizing user input components for accessibility is not actually part of validation / localization handling. Still I’d like to quickly put some tips and best practices about this topics here as well as I think it really should be perceived as giving some finishing touches to a smooth user experience, as we designed it so far, and it really should be part of the same work.

Key Focus

From desktop applications as well as from some web pages (did I hear Google?), the user expects the keyboard focus to work properly and being able to navigate through input components with the mouse as well as with the keyboard.

Tab key usage is enabled by default and can be modified using plain HTML tabindex attribute, thus I will not cover it here.

First of all however, the user expects to have the appropriate component in focus when the page loads. PrimeFaces’ <p:focus> component is designed to meet this requirement. From the PrimeFaces showcase: “Focus is also aware of failed validations, first invalid input component receives a focus automatically.”

However, I found out that the implementation apparently is buggy (v. 5.2). In the demo application, setting the focus does not respect invalid components. Therefore, I created a custom JavaScript function you are free to use and adapt. To apply it, you need to include the focus.js from the demo application and activate it with <o:onloadScript>Focus.setFocus();</o:onloadScript>. The setFocus() function optionally takes the clientId of the root component to search input components as a parameter. Default root is the HTML document body. Note that this function also works when added to the onsuccess JavaScript callback on an "ajaxified" PrimeFaces <p:commandButton>, re-triggering focus setting.

In the example application, this is used by both the "clear" button as well as by the "select language" dropdown. For example, here is the appropriate source code for the button (using the non-validation aware version here):
<p:commandButton id="clearAction" action="#{bookController.clear}" value="#{msg['general.action.clear']}"
                 immediate="true" process="@this" update="@form"
                 onsuccess="Focus.setFocusWithoutValidationAwareness();">
    <p:resetInput target="panel" />
</p:commandButton>
Update November 2015: This issue, amongst other common challenges related to I18N / validation and JSF in general, is also addressed and resolved (even more cleanly) by CrudFaces, a JSF framework I’m currently developing. You may want to take a look at that.

Default “ENTER” key action

The user also typically expects to be able to submit the form by hitting the ENTER key when focussed on an input. This is actually HTML default behavior you would have to disable manually with a JavaScript hack.

By default, ENTER triggers the first button in the form. If you want to specify any other button as the default one, use PrimeFaces’ <p:defaultCommand> component.

This is also implemented in the example application where the "save" button is explicitly set as the default button (ENTER would otherwise trigger the "clear" button):
<p:defaultCommand target="saveAction"/>
Note: Whenever you make an AJAX call within a form which features a <p:defaultCommand>, make sure to set update="@form"; otherwise, subsequently hitting the ENTER key may not trigger any behavior at all! I’m not sure whether this is a bug in PrimeFaces.

Tooltips

Defining a tooltip (additional information on hover over component) is really straightforward. You can use the title attribute on any component to define the tooltip text:
<p:outputLabel value="#{msg['model.book.releaseYear']}" for="releaseYear" 
               title="#{msg['model.book.releaseYear.help']}"/>
Nevertheless, you should carefully check whether a tooltip is enough in each individual case. For extended or more obvious online help, a highly visible link to the online help system may be more appropriate. In the demo application, I try to catch the user’s attention to the tooltip feature by changing the mouse cursor over elements which have a tooltip. Note that this does not help if the user navigates through the form without using the mouse.

Placeholder / Watermark

Another frequently implemented feature the user may expect to find is a “placeholder” / “watermark” overlay over an input component providing additional hints about its usage (e.g. “search” for a search bar input field).

Here, I recommend using PrimeFaces’ <p:watermark> component. This will use HTML5 standard placeholder attribute on compatible browsers, or a JavaScript shiv on older ones.

In the example application, a watermark is placed over the „release year“ component:
<p:inputMask id="releaseYear" value="#{bookController.item.releaseYear}"
             converterMessage="#{val:labeledMsg(component, 'model.book.releaseYear.date')}"
             title="#{msg['model.book.releaseYear.help']}"
             mask="9999">
    <f:convertDateTime pattern="yyyy" />
</p:inputMask>
<p:watermark for="releaseYear" value="yyyy"/>
Note that the PrimeFaces user guide states that when relying on the JavaScript implementations for older browsers, you’ll have to manually clear the watermark before form submit; otherwise, it would be set as the component’s actual input value.

Default input types

HTML5 introduces a bunch of new input types to streamline application design and enhance accessibility, especially on mobile devices. In JSF, the respective additional type attribute can be specified for any input component using so-called pass-through attributes.

Note that however I found out that only PrimeFaces’ default <p:inputText> seems to accept the type passthrough attribute. For any other components, and PrimeFaces Extensions’ <pe:inputNumber> in particular, the attribute is ignored.

On the other hand, when using OmniFaces’ HTML5 render kit instead, the opposite is the case: the type attribute is correctly interpreted, but this overrides any special styling / behavior of components such as <pe:inputNumber>.

So you really have to choose between optimizing for mobile accessibility but losing any other UI perks, or the very opposite. For the demo application, I have thus not made use of any HTML5 input type attributes.

Note that if you opt for HTML5 attributes, you certainly want to make sure that you disable client-side validation which is implicitly applied by these attributes as this would clearly conflict with JSF’s server-side validation.

I prepared this for the demo application already – it should become a standard for all your <form> components:
<h:form id="form" lang="#{localeManager.language}" pt:novalidate="novalidate">

Conclusion

Input validation, I18N and accessibility are the bread and butter of any web frontend layer. Yet, this article shows that even these basic tasks are not trivial in JSF. It’s not that the features weren’t there, much in contrary: There are too many different standards and frameworks which play together in a sometimes surprising and in general not very well documented way.

As soon as you include third party frameworks for advanced features, you risk breaking other framework’s code or introducing buggy implementations.

For validation in particular, JSF clearly shows lack of ease of development. Even though the tools are there, it’s hard to make them work in many real-world scenarios.

I hope that this article helps you with basic kick-start tips as well as best practices advices whenever you find yourself in trouble with JSF validation / localization. Feel free to use the demo project and in particular its ValidationMessages.properties file as part of a blueprint for any JSF project.

If on the other hand you think that I missed any important topic or if you found any error, please do not hesitate to report it back to me using the comments section below.

Update November 15, 2015: You may also be interested in CrudFaces, a lightweight JSF framework which addresses many common issues of JSF developments, including I18N / validation handling, by applying reasonable default configurations out-of-the-box and providing generally useful JSF helper components.

For quick reference – the most important links in this article:

Updated September 27, 2015: part 1 and 3 updated for new PrimeFaces / PrimeFaces extensions releases; small precisions and fixes; GitHub repository updated accordingly.



Pages: 1 2 3

No comments:

Post a Comment