2
votes

I am looking for some guidance/opinion on the best way forward to add field error highlighting in JSF 2.0. So far I have successfully implemented using Cagatay's example with a few minor adjustments to logic.

String styleClass = ( String ) uiInput.getAttributes().get("styleClass");
        //Check the valid flag
        if ( !uiInput.isValid() )
        {
            //Component already has a styleclass
            if ( styleClass != null )
            {
                //check if it's already highlighted
                if ( !styleClass.contains("ui-input-invalid") )
                {
                    //if not add the error class to it
                    styleClass = styleClass + " ui-input-invalid";
                    //and put the new styleclass back on the component
                    uiInput.getAttributes().put("styleClass", styleClass);
                }
            } else
            {
                //no current style class so just add the error class
                uiInput.getAttributes().put("styleClass", "ui-input-invalid");
            }       
        } else  //component is valid so we might need to remove a highlight
        {
            //component has a styleclass
            if ( styleClass != null )
            {
                //check if it is already highlighted
                if ( styleClass.contains("ui-input-invalid") )
                {
                    //remove error class from the string
                    styleClass = styleClass.replace("ui-input-invalid", "");
                    //and put the new styleclass back on the component
                    uiInput.getAttributes().put("styleClass", styleClass);
                }
            }    
        }

I have also used the suggestion of adding el to each component's styleclass - styleClass="#{component.valid ? '' : 'ui-input-invalid'}".
Both methods work like a charm when used in conjunction with Bean Validation JSR303. However I also have 2 additional validation stages. 1 to validate the form as a whole i.e. correct combination of fields and 1 to validate our general business rules once the field and form validation is successful. In order for these stages to also add the highlighting I need to do some manual work. For both highlighting approaches I have to manually set the component(s) valid flag to false. To have access to the component I have bound it to its own object in the form vo. Now for Cagatay's example I need to add all of the components to a List and then pass this list to the highlighting method. If I use the styleclass approach I don't have to worry about a component list and passing to a highlighting method. This seems the better approach to me however I'm a little concerned that it is adding logic to the JSF page which is something I want to stay away from.

What do you feel is the best approach or is there another way I am not aware of? Also I assume there is no other way of setting the valid flags without binding to the component? At the moment I having to bind every component so I can set its valid flag.

1

1 Answers

0
votes

It's been a while since I asked this and in the mean time we have actually partially implemented JSF into our enterprise app. The solution for the field highlighted was quite complicated and in order for it to work, be reusable and be customisable it really has to fit snuggly into a framework that encompasses JSF/Input Value Objects, associated Input Field objects and Bean Validation. Therefore someone stumbling here may not get the answer they are looking for. However I going to include an portion of our internal design docs which describes the mechanism. Hopefully this will help anyone else:

Field Highlighting and Error Messages Field highlighting in JSF proved to be quite a complicated task. This is because the html id/name is generated by jsf at runtime and can include many prefixes. JSF will add a prefix to an id when it is inside a form/tab/datatable/composite component. An id of ucn may end up becoming tab1:contentForm:jsfDataTable:1:ucn. It is therefore virtually impossible to try and predeterime what an id will be and then to try and highlight. To resolve this issue a preRenderComponentEventListener class is used. This is registered in the facesConfig.xml along with an input type. This tells the JSF lifecycle to run this class whenever that type of input is about to be rendered. We then have access to the short and long ids of the input which we can then save and look up later. The full procedure is as follows:

  1. Entry in facesConfig.xml registers a preRenderComponentEventListener and the associated type of input. Each time before the input is rendered the processEvent method is called.
  2. Entry in facesConfig.xml registers a HashMap called componentMap which is used to store the input ids.
  3. When the processEvent method is called the Input element is retrieved. The componentMap is retrieved and input’s id/clientID is added to the map. The id of the input is the Key (this is the short id i.e. ucn) and the ClientID is the value (this is the long JSF generated id i.e. csn01Form:jsf454:ucn:1). To handle multiple inputs of the same name like in DataTables the key is added with an Incrementing Index. E.g. if ucn3 is already in the map ucn4 will then we added.
  4. Once this process is complete the componentMap will contain entries for every input field. Therefore by using the short id the full clientID can be retrieved.
  5. In the BackingBean FormatValidationExceptions and RuleExceptions are caught and handled.
  6. FormatValidationExceptions. These derive from BeanValidation. A subclass of an InputVO is created which contains subclasses of Destin8Input (See CSN01InputVO). If format validation is needed on an Input they should be annotated with a custom bean validation. E.g. @StringCheck (See Bean Validation).
  7. The FormatValidation class has a method called validateFormats which takes in a type of InputVO and runs it through the Bean Validator (when using a Transaction Controller this method will automatically be run). The Bean Validator will automatically take each annotated field and run the isValid() method associated with the annotation. The @StringCheck for example checks the value entered matches a regular expression and that the number of characters entered is correct. Any failures will result in a ConstraintViolation. At this point the error message is generated using the Error Display Name from the Destin8Input. All validation checks are performed and will result in a Set of ConstraintViolations.
  8. In the validateFormats method the Set of ConstraintViolations is looped round and each message is added to an Error Message List in the ValidationFormatException. Each htmlID in error is also added to a Field Error List.
  9. The ValidationFormatException is caught in the BackingBean. All Error messages are added as a FacesMessage to be displayed at the top of the page.
  10. RuleExceptions are similar except messages and fields to highlight are manually added when throwing an exception. The message code will be looked up from the DB.
  11. The List of ids in error is now looped round and is used to retrieve the associated full ClientID from the componentMap. Each ClientID is added to a List.
  12. This List is passed to the JSFUtils.highlightFields method where each ClientID is added to a ~ Delimited String.
  13. This delimited String is then added to the RequestMap with a key of ‘errors’ (this is a map automatically available as per of each request).
  14. This String is then retrieved as part of the Destin8Template.xhtml -
  15. A javascript method called highlightFields in then immediately called. This effectively loops through the delimited String, gets the full id and then adds the Error css class using jQuery.
  16. It is important to note that the link between a Java Input and a JSF Input is the id. In order to connect the 2 it is vital they have the same ID. This is achieved by using the IDConstants class. This is an ENUM which contains entries for different fields. An entry is also added to the IDConstants managed bean which allows access via a facelet. This is then added as the id attribute for the applicable input element.