The most desired feature in JSF is a multi-component validation. At the moment there is a lack of support for this feature because there is no standard way to attach one validator to multiple fields. There are proposed enhancements to JSF 2.1 which should cover this lack. These are "generic subtree validation" and "multi-component validation using bean validation". The aim of this article is to give an idea for multi-component validation. The main point is the validation of required fields in a real project.
Starting position:
Many required fields with required="true" attribute. They produce default messages from JSF implementation like: j_id_id19: Validation Error: Value is required. An end-user gets kinda confused.
Task definition:
Improve the required validation message with less costs and without any touch of existing code if possible. We would like to have messages with exact labels belong to fields, like "The required field 'User Name' is not filled in". The message severity should be WARNING and not ERROR as produced with the JSF standard validator. The solution should be reliable, maintainable and have a good performance.
We will go step by step, evaluate available solutions and figure out the best one.
1) The first Ad-hoc solution coming in mind is one with a custom validator for required fields I described in the previous post. Sure, it's not an ideal solution because we need to define the label text twice and to attach the validator to each field to be validated. It's not good maintainable as well, but there are cases (discussed below) where we need it.
2) BalusC wrote a useful post "Validator for multiple fields". His solution illustrates an idea how to attach a validator to the last component of the group (components are validated in the same order as defined) and gather Ids of the all component(s) to be validated. Nice, but hard-coded, not configurable and still needs many adjustments in the existing code.
3) Cagatay Civici presented in his blog a nice approach based on SystemEventListener and PreValidateEvent for HtmlOutputLabel components. Really interesting idea. But the first question coming in mind - why do we need to scan all labels and do the same work as it's already done by labels self? I mean, LabelRenderer looks for the corresponding editable component in order to find out a proper client Id in the RENDER_RESPONSE phase. It seems, we are doing the same job twice. I'm developing document management systems and we sometimes have about 100 labels on a form. Document's or folder's properties have them in DMS. If we have 100 labels and only 1-2 required fields, we don't need to treat all 100 labels from the view point of performance. We need to treat 1-2 required fields and only when validation has failed (in best case is nothing to do at all). In my opinion, we should look for invalid fields at first and then for corresponding labels. Not inverted as in the proposed approach. Furthermore, there are some limitations. This solution doesn't consider the cross-implementations algorithm for component referencing. Simple findComponent is not enough. Mojarra walks up the ancestor chain and call findComponent() for each component in the ancestor chain. If the root is reached, it walks down the successor chain. Obviously (if I got the picture), the value in "for" attribute and the Id of the corresponding editable component may not match in Mojarra. Result: If a label and its corresponding editable field are located in different forms for example, we are not always able to find the editable field starting from the label component. The last point is the fact that we can't use h:outputLabel in certain circumstances and depend on h:outputText. h:outputText should be considered too. Message severity is not customizable, so that we need a custom validator.
4) Another great idea which gave me the right direction comes from the Mkyong blog. The idea is to group components, e.g. with a h:panelGrid component, and attach the event tag f:event with PostValidateEvent to the group. f:event expects "listener" and "type" attributes which have to be registered declarative. We are worried with this approach about the listener method for PostValidateEvent, where we place it and how we write it, etc. It would be nice to hide this information and accomplish all work programmatically. The autor could probably achieve an optimization by extension of h:panelGrid like
<h:panelGrid columns="..." validateRequired="true">The component HtmlPanelGrid would have processEvent method.
@ListenerFor(systemEventClass = PostValidateEvent.class) public class HtmlPanelGrid extends javax.faces.component.html.HtmlPanelGrid { protected enum PropertyKeys { validateRequired; ... } ... public void processEvent(ComponentSystemEvent event) throws AbortProcessingException { if (isValidateRequired()) { .... } } }I would like to avoid extensions of any components, but general I like the idea of component subtree validation by means of PostValidateEvent. My idea is based on this system event and a custom TagHandler. I want to write a tag handler with a zero configuration and apply it to the body tag
<h:body> <custom:requiredFieldsValidator/> ... </h:body>or some other component acts as parent for required fields and their labels. Good candidates would be h:panelGroup, h:panelGrid or h:dataTable. Note: For Ajax requests <custom:requiredFieldsValidator/> should be placed within area being processed. For cases where we can't use h:outputLabel we would like to use h:outputText with <f:attribite name="for" value="..."/>. The custom validator for single editable fields (Ad-hoc solution mentioned above) should be applicable too because sometimes there is one label for two and more fields and other rare scenarios. It should also have the higher precedence than a multi-component validator. An example covering all cases:
<h:panelGrid columns="2"> <custom:requiredFieldsValidator/> <h:outputLabel for="username" value="User name"/> <h:inputText id="username" value="#{bean.username}" required="true"/> <h:outputLabel for="password" value="Password"/> <h:inputText id="password" value="#{bean.password}" required="true"/> <h:outputText value="Confirm Password"> <f:attribite name="for" value="confirmPassword"/> </h:outputText> <h:inputText id="confirmPassword" value="#{bean.confirmPassword}" required="true"/> <h:outputText value="Start date - End date"> <h:panelGroup> <h:inputText value="#{bean.startDate}" required="true"> <custom:requiredFieldValidator label="Start date"/> </h:inputText> <h:inputText value="#{bean.endDate}" required="true"> <custom:requiredFieldValidator label="End date"/> </h:inputText> </h:panelGroup> </h:panelGrid>The tag handler is straightforward. It calls subscribeToEvent for PostValidateEvent and its listener ValidateRequiredFieldsEventListener (explained below).
import javax.faces.component.UIComponent; import javax.faces.event.PostValidateEvent; import javax.faces.event.SystemEvent; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.TagConfig; import javax.faces.view.facelets.TagHandler; import java.io.IOException; public class ValidateRequiredFieldsHandler extends TagHandler { public ValidateRequiredFieldsHandler(TagConfig config) { super(config); } public void apply(FaceletContext fc, UIComponent parent) throws IOException { if (isNew(parent)) { Class<? extends SystemEvent> eventClass = PostValidateEvent.class; parent.subscribeToEvent(eventClass, new ValidateRequiredFieldsEventListener()); } } protected static boolean isNew(UIComponent component) { UIComponent c = component; if (c != null) { UIComponent parent = c.getParent(); if (parent != null) { if (UIComponent.isCompositeComponent(parent)) { c = parent; } } return c.getParent() == null; } else { return false; } } }ValidateRequiredFieldsHandler is registered in a tag library
<tag> <description>Handler for validation of required fields</description> <tag-name>requiredFieldsValidator</tag-name> <handler-class> xyz.webapp.jsf.validator.ValidateRequiredFieldsHandler </handler-class> </tag>The message key for any empty required field is defined in the JSF 2 specification, chapter 2.5.2.4 "Localized Application Messages". We are going to redefine the message of this key in the own message bundle (registered in faces-config.xml) as follows
javax.faces.component.UIInput.REQUIRED=xyz.faces.component.UIInput.REQUIREDThe event listener ValidateRequiredFieldsEventListener iterates through all messages and looks for required fields which were empty. For each such field the corresponding label will be found and the message will be regenerated. The label will be present in the message which is more user friendly now.
import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.ComponentSystemEvent; import javax.faces.event.ComponentSystemEventListener; import java.io.Serializable; import java.util.Iterator; import java.util.ResourceBundle; public class ValidateRequiredFieldsEventListener implements ComponentSystemEventListener, Serializable { private static final long serialVersionUID = 20110227L; private static final String MESSAGE_SUMMARY_PLACEHOLDER = "xyz.faces.component.UIInput.REQUIRED"; public void processEvent(ComponentSystemEvent event) throws AbortProcessingException { FacesContext fc = FacesContext.getCurrentInstance(); UIComponent parent = event.getComponent(); if (parent == null) { return; } Iterator<String> iterClientIds = fc.getClientIdsWithMessages(); while (iterClientIds.hasNext()) { String clientId = iterClientIds.next(); Iterator<FacesMessage> iterFacesMessages = fc.getMessages(clientId); while (iterFacesMessages.hasNext()) { FacesMessage facesMessage = iterFacesMessages.next(); if (MESSAGE_SUMMARY_PLACEHOLDER.equals(facesMessage.getSummary())) { iterFacesMessages.remove(); // find label ValidateRequiredFieldsCallback vrfCallback = new ValidateRequiredFieldsCallback(parent); parent.invokeOnComponent(fc, clientId, vrfCallback); String message; ResourceBundle resourceBundle = MessageUtils.getMessageBundle(fc); // create new user friendly message if (vrfCallback.getLabel() == null) { message = MessageUtils.getMessageText( resourceBundle, "validator.emptyMandatoryField.1"); } else { message = MessageUtils.getMessageText( resourceBundle, "validator.emptyMandatoryField.2", vrfCallback.getLabel()); } fc.addMessage(clientId, new FacesMessage(FacesMessage.SEVERITY_WARN, "", message)); break; } } } } }ValidateRequiredFieldsCallback is responsible for mentioned above cross-implementations algorithm for component referencing. I use visitTree method for component traversing. VisitLabelCallback encapsulates a check whether the corresponding label is encountered during the component traversing.
import javax.faces.component.ContextCallback; import javax.faces.component.UIComponent; import javax.faces.component.visit.VisitContext; import javax.faces.context.FacesContext; public class ValidateRequiredFieldsCallback implements ContextCallback { private UIComponent parent; private String label; public ValidateRequiredFieldsCallback(UIComponent parent) { this.parent = parent; } public void invokeContextCallback(FacesContext fc, UIComponent component) { UIComponent currentParent = parent; VisitLabelCallback visitLabelCallback = new VisitLabelCallback(fc, component.getId()); while (currentParent != null) { currentParent.visitTree(VisitContext.createVisitContext(fc), visitLabelCallback); if (visitLabelCallback.getLabel() != null) { label = visitLabelCallback.getLabel(); return; } currentParent = currentParent.getParent(); } fc.getViewRoot().visitTree(VisitContext.createVisitContext(fc), visitLabelCallback); label = visitLabelCallback.getLabel(); } public String getLabel() { return label; } }Note: Start point from invalid fields brings us a nice bonus. We could highlight invalid fields. e. g. with red borders. After a new message has been added (s. ValidateRequiredFieldsEventListener), we could update the styleClass attribute
String originalStyleClass = component.getAttributes().get("styleClass"); component.getAttributes().put("styleClass", originalStyleClass + " invalidField");CSS according to this
div.invalidField, select.invalidField, input.invalidField, .invalidField input { border: solid 1px red; line-height: 1.4em; height: 1.4em; }What does VisitLabelCallback do exactly? It checks HtmlOutputLabel, UIOutput (like h:outputText) and a custom composite component (not considered here custom:requiredLabel). It uses a regular expression for values in „for“ attribute, so that valid definitions like
<h:outputLabel for=":mainForm:myTable:userName" .../> <h:outputLabel for="mainForm:userName" .../> <h:outputLabel for=":userName" .../>are considered.
import javax.faces.component.UIComponent; import javax.faces.component.UINamingContainer; import javax.faces.component.UIOutput; import javax.faces.component.html.HtmlOutputLabel; import javax.faces.component.visit.VisitCallback; import javax.faces.component.visit.VisitContext; import javax.faces.component.visit.VisitResult; import javax.faces.context.FacesContext; public class VisitLabelCallback implements VisitCallback { private String regExprForAttr; private String label; public VisitLabelCallback(FacesContext context, String idOfInvalidComp) { regExprForAttr = "(" + idOfInvalidComp + ")|.*[" + UINamingContainer.getSeparatorChar(context) + "]{1}(" + idOfInvalidComp + ")$"; } public VisitResult visit(VisitContext context, UIComponent target) { if (target instanceof HtmlOutputLabel && ((HtmlOutputLabel) target).getFor() != null) { String forAttr = ((HtmlOutputLabel) target).getFor(); if (forAttr.matches(regExprForAttr)) { label = (String) ((HtmlOutputLabel) target).getValue(); return VisitResult.COMPLETE; } } else if (target instanceof UIOutput && target.getAttributes().get("for") != null) { String forAttr = (String) target.getAttributes().get("for"); if (forAttr.matches(regExprForAttr)) { label = (String) ((UIOutput) target).getValue(); return VisitResult.COMPLETE; } } else if (target != null && UIComponent.isCompositeComponent(target) && target.getAttributes().get("for") != null) { // TODO: do more reliable check for requiredLabel.xhtml, e.g. check resource name String forAttr = (String) target.getAttributes().get("for"); if (forAttr.matches(regExprForAttr)) { label = (String) target.getAttributes().get("value"); return VisitResult.COMPLETE; } } return VisitResult.ACCEPT; } public String getLabel() { return label; } }Other validators are possible too by this way. The validator from BaluC's example can be rewritten by means of PostValidateEvent, any "single" Date / Time / Number validators can be done multi-component capable, etc.
The end of a long story. Your feedback is welcome.
If the only task is to get a useful label, you should have asked in a forum or mailing list because it's way simpler:
ReplyDelete-> A message with a strange message (as you pointed out at the beginning).
The solution is as simple as that:
This label will be used for any kind of error message which belongs to this component (if the message text contains the placeholder for the label).
I don't see your code (you should escape code e.g. by http://accessify.com/tools-and-wizards/developer-tools/quick-escape/default.php). If you meant the "label" attribute of some editable components, it was not acceptable for us. The solution should be more or less dynamic, without replications of label text and without many changes in existing code.
ReplyDeleteThis task is done a long time ago by a simple way (can not post here). The purpose of this post was evaluation of various solutions for cross-component validation.
I just saw it after submitting the comment and I can't edit it.
ReplyDeleteYes it's the label attribute. Ok, in this case you should mention it in the post. Posting a long entry just for a simple thing is misleading for new users and it just introduce a performance overhead which is not acceptable for big projects. Some component libraries offer components which fix the redundant usages or you can implement your own which then supports more than just one case and the result has a way better performance.