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
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 attributeboolean requiredAttribute; @PostConstruct public void init{ Field theField = this.getClass().getField("theField"); NotNull theAnnotation = theField.getAnnotation(NotNull.class); if(theAnnotation != null){ requiredAttribute = true; } }
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 UIInput
s 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
UILabel
s instead ofUIInput
s - get the
UIInput
associated with thefor
/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.
0 comments:
Post a Comment