Monday, May 29, 2017

JSF 2.0 Required Field Label Decorator for properties with @NotNull Constraint

Leave a Comment

I have a JSF 2.0 application which also uses Primefaces 3.3. Currently there is a nice feature where a label is decorated with an asterisk if the related <p:inputText> uses a required="true" attribute.

This field is bound to a bean property which is annotated with @NotNull validation constraint. It seems redundant and error prone to have to also add required="true" in the XHTML when the bean property is already annotated with @NotNull.

Is there a hook or some way to automatically decorate labels for components that are bound to properties with @NotNull?

Any thoughts or suggestions are greatly appreciated.

2 Answers

Answers 1

Note: This is a hack. It may have performance implications due to it's use of introspection

  1. At a basic level, what you need to know if the field is annotated with @NotNull. Perform this check in a sensible place like @PostConstruct for a view scoped bean. Declare a global variable to determine the required attribute

    boolean  requiredAttribute;          @PostConstruct public void init{  Field theField = this.getClass().getField("theField"); NotNull theAnnotation = theField.getAnnotation(NotNull.class); if(theAnnotation != null){    requiredAttribute = true;    } } 
  2. Bind the required attribute to the variable in the backing bean

    <p:inputText id="inputit" required="#{myBean.requiredAttribute}"/> 

Answers 2

This solution is based on PF 6.0, I don't remember if BeanValidationMetadataExtractor is available in previous versions. Anyway, creating a DIY extractor is a simple task.

I had a similar problem. In my specific case:

  • User should be informed that a certain field (read UIInput) is required
  • I don't want to repeat required="true" on the comp since it's already bound to a @NotNull/@NotBlank property/field
  • In my case, a label component may not be present (and I don't like asterisked-labels)

So, here is what I have done:

import java.util.Set; import javax.el.ValueExpression; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.PreRenderComponentEvent; import javax.faces.event.SystemEvent; import javax.faces.event.SystemEventListener; import javax.validation.constraints.NotNull; import javax.validation.metadata.ConstraintDescriptor; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotEmpty; import org.omnifaces.util.Faces; import org.primefaces.context.RequestContext; import org.primefaces.metadata.BeanValidationMetadataExtractor; import one.util.streamex.StreamEx;   public class InputValidatorConstraintListener implements SystemEventListener {     @Override     public boolean isListenerForSource(Object source)     {         return source instanceof UIInput;     }      @Override     public void processEvent(SystemEvent event) throws AbortProcessingException     {         if(event instanceof PreRenderComponentEvent)         {             UIInput component = (UIInput) event.getSource();              boolean required = component.isRequired();             if(!required)             {                 FacesContext context = Faces.getContext();                 ValueExpression valueExpression = component.getValueExpression("value");                 RequestContext requestContext = RequestContext.getCurrentInstance();                  Set<ConstraintDescriptor<?>> constraints = null;                 try                 {                     constraints = BeanValidationMetadataExtractor.extractAllConstraintDescriptors(context, requestContext, valueExpression);                 }                 catch(Exception e)                 {                     // ignore                 }                  if(constraints != null && !constraints.isEmpty())                 {                     required = StreamEx.of(constraints)                         .map(ConstraintDescriptor::getAnnotation)                         .anyMatch(x -> x instanceof NotNull || x instanceof NotBlank || x instanceof NotEmpty);                 }             }              component.getPassThroughAttributes().put("data-required", required);              return;         }     } } 

and declare it in faces-config.xml:

<?xml version="1.0" encoding="utf-8"?> <faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">      <application>         <system-event-listener>             <system-event-listener-class>it.shape.core.jsf.listener.InputValidatorConstraintListener</system-event-listener-class>             <system-event-class>javax.faces.event.PreRenderComponentEvent</system-event-class>         </system-event-listener>     </application>  </faces-config> 

With this listener UIInputs are rendered with a data-required passthrough attribute:

<input      id="form:editPanelMain:j_idt253"      name="form:editPanelMain:j_idt253"      type="text"      value="Rack Assemply"      size="80"      data-required="true"    <============================ NOTE THIS!!     data-widget="widget_form_editPanelMain_j_idt253"      class="ui-inputfield ui-inputtext ui-widget ui-state-default ui-corner-all"      role="textbox"      aria-disabled="false"      aria-readonly="false"> 

Now, I use a css rule to highlight these fields:

input[data-required='true'],  .ui-inputfield[data-required='true'],  *[data-required='true']  .ui-inputfield {     box-shadow: inset 0px 2px 2px #bf8f8f; } 

You can adapt this listener to either set the component as required or use another approach that suits your specific needs.

Another approach could be:

  • listen for UILabels instead of UIInputs
  • get the UIInput associated with the for/forValue ValueExpression of the label
  • check the UIInput for validation constraint
  • eventually invoke UIInput.setRequired(true)

The performance impact is negligible, as I have tested complex pages with ~3000 components.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment