How to check cross section tree <p: ββtree>
It seems that the right <p:tree> are not EditableValueHolder, although it does offer the option of choosing a tree. For me, this is similar to the definition of EditableValueHolder itself, since it contains values ββ(a list of selected nodes) and is edited (you can change the selection). When choosing a tree, it basically turns it into selectOneXxx / selectManyXxx. This is the mod in which I use this widget. However, not being an EditableValueHolder, I cannot directly connect a validator to it. I can add confirmation to the form submit action using the actionListener , but then it got out of the corresponding phase of the life cycle and it is much harder to get the UITree component to check for attributes like i18n message for failed validation. Has anyone dealt with this before? What are you doing?
---------- EDIT ----------
I found a problem posted in python's error tracker that seems to be released:
And the forum post:
---------- EDIT ----------
This is the solution I came across. Some of jQuery are quite hairy, as it uses server-side email to generate client-side javascript. But for the most part it works. You just need to find out why an empty array skips checking ... but this is another story.
<h:panelGroup id="pnpCois" styleClass="pnp-input-group pnp-cois"> <h:outputLabel for="inputCois" value="#{i18n['communities-of-interest']}" /> <p:tree id="inputCois" value="#{subscriptions.selected.coiTreeRootNode}" var="node" selectionMode="checkbox" selection="#{subscriptions.selected.selectedCoiNodes}"> <p:ajax event="select" process="@this :#{component.clientId}_validator" update="@this" onstart="$('##{component.clientId}_validator'.replace(':','\\:')).val($('##{component.clientId}_selection'.replace(':','\\:')).val());" /> <p:ajax event="unselect" process="@this :#{component.clientId}_validator" update="@this" onstart="$('##{component.clientId}_validator'.replace(':','\\:')).val($('##{component.clientId}_selection'.replace(':','\\:')).val());" /> <p:treeNode> <h:outputText value="#{node}" /> </p:treeNode> </p:tree> <h:inputHidden id="inputCois_validator"> <f:converter converterId="asias.stringCsvToArray" /> <f:validator validatorId="asias.atLeastOneSelected" /> <f:attribute name="atLeastOneSelectedMessage" value="#{i18n['at-least-one-coi-must-be-selected']}" /> </h:inputHidden> </h:panelGroup> ---------- EDIT ----------
After a few suggestions with BalusC, I think I will give up <p:tree> and find another way ... :(
You can trick it with the necessary hidden input field, whose value changes to node. You can use the selections property of the <p:tree> widget variable to get an available selection as an array.
eg.
<h:form id="form"> <p:tree widgetVar="tree" onNodeClick="$('#form\\:treeSelections').val(tree.selections.length != 0 ? 'ok' : '')"> ... </p:tree> <h:inputHidden id="treeSelections" required="true" requiredMessage="Please select at least one tree node" /> <p:message for="treeSelections" /> </h:form> The value of 'ok' is purely arbitrary. The fact is that the hidden field is filled in, so the required validator does not start.
Bear with me, this is a long answer ...
Since the tree of structural objects is not an EditableValueHolder , it cannot be verified during the standard JSF life cycle validation process (without any major hack). And, as far as possible, I could not fix the code of the surface tree to make it EditableValueHolder (the tree will not be displayed according to the selected values, but according to the state of the nodes supporting the tree), Given these limitations, the only solutions are to create my own tree component (I donβt have time), using another component (best suited for the tree) or checking at the application stage.
I chose the third solution, and at the same time tried to make it look like a regular check as much as possible. The main idea is to use an actionListener that first starts (before any other actionListener or main action (save the form) to handle validation. If the checks fail, I add the component failure information in user attributes, call facesContext.validationFailed() so that I could skip the action and then add the preRenderView system event preRenderView to change the components according to their check state before the render response phase.This is done so that you can refine the validation like this in the same way, using a custom component instead of <f:validator> . Here is the code:
web.xml:
... <context-param> <param-name>javax.faces.FACELETS_LIBRARIES</param-name> <param-value>/WEB-INF/somenamespace.taglib.xml</param-value> </context-param> ... somenamespace.taglib.xml:
<facelet-taglib version="2.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"> <namespace>http://ns.my.com/ui/extensions</namespace> <tag> <description><![CDATA[ Add an actionListener validator to a component ]]></description> <tag-name>actionListenerValidator</tag-name> <handler-class>com.my.ns.actionlistenervalidator.ActionListenerValidatorHandler</handler-class> <attribute> <description><![CDATA[ The validatorId. ]]></description> <name>validatorId</name> <type>java.lang.String</type> </attribute> <attribute> <description><![CDATA[ A ValueExpression that evaluates to an instance of Validator. ]]></description> <name>binding</name> <type>javax.el.ValueExpression</type> </attribute> <attribute> <description><![CDATA[ The styleClass added to the end of the component style class when a validation error occurs ]]></description> <name>errorStyleClass</name> <type>java.lang.String</type> </attribute> </tag> </facelet-taglib> ActionListenerHandler.java:
package com.my.ns.actionlistenervalidator; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.faces.component.UIComponent; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.TagAttribute; import javax.faces.view.facelets.TagAttributes; import javax.faces.view.facelets.TagConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.sun.faces.facelets.tag.TagHandlerImpl; public class ActionListenerValidatorHandler extends TagHandlerImpl { private static Logger logger = LoggerFactory.getLogger( ActionListenerValidatorHandler.class ); public static enum AttributeKeys { errorStyleClass("hack.jsf.actionlistenervalidator.errorStyleClass"), messages("hack.jsf.actionlistenervalidator.messages"), valid("hack.jsf.actionlistenervalidator.valid"), validators("hack.jsf.actionlistenervalidator.validators"); private String key; private AttributeKeys( String key ) { this.key = key; } public String getKey() { return key; } } public ActionListenerValidatorHandler( TagConfig config ) { super( config ); } @Override public void apply( FaceletContext ctx, UIComponent parent ) throws IOException { ActionListenerValidatorWrapper validator = new ActionListenerValidatorWrapper( ctx.getFacesContext(), tagAttributesToMap( ctx, this.tag.getAttributes() ) ); logger.trace( "adding actionListener validator {} to {}", validator, parent ); @SuppressWarnings( "unchecked" ) List<ActionListenerValidatorWrapper> validators = (List<ActionListenerValidatorWrapper>) parent.getAttributes().get( AttributeKeys.validators.getKey() ); if ( validators == null ) { validators = new ArrayList<ActionListenerValidatorWrapper>(); parent.getAttributes().put( AttributeKeys.validators.getKey(), validators ); } validators.add( validator ); } private Map<String, Object> tagAttributesToMap( FaceletContext ctx, TagAttributes tagAttributes ) { Map<String, Object> map = new HashMap<String, Object>(); for ( TagAttribute attribute : tagAttributes.getAll() ) { map.put( attribute.getLocalName(), attribute.getValue( ctx ) ); } return map; } } ActionListenerValidatorWrapper.java:
package com.my.ns.actionlistenervalidator; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import javax.el.ELContext; import javax.el.ExpressionFactory; import javax.el.ValueExpression; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.validator.Validator; import javax.faces.validator.ValidatorException; import javax.faces.view.facelets.FaceletException; import com.sun.faces.el.ELUtils; public class ActionListenerValidatorWrapper { private Validator validator; private String errorStyleClass; public ActionListenerValidatorWrapper( FacesContext context, Map<String, Object> attributes ) { String binding = (String) attributes.get( "binding" ); String validatorId = (String) attributes.get( "validatorId" ); if ( binding != null ) { ExpressionFactory factory = context.getApplication().getExpressionFactory(); ELContext elContext = context.getELContext(); ValueExpression valueExpression = factory.createValueExpression( elContext, binding, String.class ); this.validator = (Validator) ELUtils.evaluateValueExpression( valueExpression, context.getELContext() ); } else if ( validatorId != null ) { this.validator = context.getApplication().createValidator( validatorId ); this.errorStyleClass = (String) attributes.get( "errorStyleClass" ); // inject all attributes for ( Method method : validator.getClass().getMethods() ) { String methodName = method.getName(); Class<?>[] types = method.getParameterTypes(); if ( methodName.startsWith( "set" ) && types.length == 1 ) { String property = Character.toLowerCase( methodName.charAt( 3 ) ) + methodName.substring( 4 ); if ( attributes.containsKey( property ) ) { // convert value type Object value = null; if ( types[0] == Integer.TYPE ) { value = intValue( context, attributes.get( property ) ); } else { value = attributes.get( property ); } // call setter try { method.invoke( validator, value ); } catch ( IllegalArgumentException e ) { throw new FaceletException( e ); } catch ( IllegalAccessException e ) { throw new FaceletException( e ); } catch ( InvocationTargetException e ) { throw new FaceletException( e ); } } } } } else { throw new FaceletException( "ActionListenerValidator requires either validatorId or binding" ); } } @Override public boolean equals( Object otherObj ) { if ( !(otherObj instanceof ActionListenerValidatorWrapper) ) { return false; } ActionListenerValidatorWrapper other = (ActionListenerValidatorWrapper) otherObj; return (this.getValidator().equals( other.getValidator() )) && (this.getErrorStyleClass().equals( other.getErrorStyleClass() )); } public String getErrorStyleClass() { return errorStyleClass; } public Validator getValidator() { return validator; } @Override public int hashCode() { int hashCode = (getValidator().hashCode() + getErrorStyleClass().hashCode()); return (hashCode); } private Integer intValue( FacesContext context, Object value ) { ExpressionFactory factory = context.getApplication().getExpressionFactory(); ELContext elContext = context.getELContext(); ValueExpression valueExpression = factory.createValueExpression( elContext, value.toString(), String.class ); if ( !valueExpression.isLiteralText() ) { return ((Number) ELUtils.evaluateValueExpression( valueExpression, elContext )).intValue(); } else { return Integer.valueOf( valueExpression.getExpressionString() ); } } @Override public String toString() { return validator.getClass().getName(); } public void validate( FacesContext context, UIComponent component, Object value ) throws ValidatorException { validator.validate( context, component, value ); } } ActionListenerValidatorManager.java:
package com.my.ns.actionlistenervalidator; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.faces.application.FacesMessage; import javax.faces.bean.ManagedBean; import javax.faces.bean.ManagedProperty; import javax.faces.bean.ViewScoped; import javax.faces.component.EditableValueHolder; import javax.faces.component.UIComponent; import javax.faces.component.UIViewRoot; import javax.faces.component.visit.VisitCallback; import javax.faces.component.visit.VisitContext; import javax.faces.component.visit.VisitResult; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import javax.faces.event.ComponentSystemEvent; import javax.faces.validator.ValidatorException; import com.my.ns.controller.MyBean; import com.my.ns.actionlistenervalidator.ActionListenerValidatorHandler.AttributeKeys; import org.primefaces.component.tree.Tree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ManagedBean @ViewScoped public class ActionListenerValidatorManager implements Serializable { private static final long serialVersionUID = -696487579396819893L; private static Logger logger = LoggerFactory.getLogger( ActionListenerValidatorManager.class ); @ManagedProperty( "#{myBean}" ) private MyBean myBean; private void addValidationToComponent( Map<String, Object> attributes, Collection<FacesMessage> facesMessages, Set<String> errorStyleClasses ) { attributes.put( AttributeKeys.valid.getKey(), false ); attributes.put( AttributeKeys.messages.getKey(), facesMessages ); StringBuilder builder = new StringBuilder(); if ( errorStyleClasses != null ) { for ( String styleClass : errorStyleClasses ) { builder.append( styleClass ); } attributes.put( AttributeKeys.errorStyleClass.getKey(), builder.toString() ); } } public void applyValidationStateToComponentTreeBecausePrimefacesDidntMakeTreeAnEditableValueHolder( ComponentSystemEvent event ) { applyValidationStateToComponentTree( FacesContext.getCurrentInstance() ); } private void applyValidationStateToComponentTree( FacesContext context ) { UIViewRoot viewRoot = context.getViewRoot(); logger.trace( "pre render view for {}", viewRoot ); viewRoot.visitTree( VisitContext.createVisitContext( context ), new VisitCallback() { @Override public VisitResult visit( VisitContext context, UIComponent component ) { Map<String, Object> attributes = component.getAttributes(); if ( attributes.containsKey( AttributeKeys.valid.getKey() ) && !((Boolean) attributes.get( AttributeKeys.valid.getKey() )) ) { // validation state if ( component instanceof EditableValueHolder ) { ((EditableValueHolder) component).setValid( false ); } // validation messages FacesContext facesContext = context.getFacesContext(); @SuppressWarnings( "unchecked" ) List<FacesMessage> messages = (List<FacesMessage>) attributes.get( AttributeKeys.messages.getKey() ); if ( messages != null ) { for ( FacesMessage message : messages ) { facesContext.addMessage( component.getClientId(), message ); } } // style class String errorStyleClass = (String) attributes.get( AttributeKeys.errorStyleClass.getKey() ); if ( errorStyleClass != null ) { String styleClass = (String) attributes.get( "styleClass" ); styleClass = styleClass == null ? errorStyleClass : styleClass + " " + errorStyleClass; attributes.put( "styleClass", styleClass ); } } return VisitResult.ACCEPT; } } ); } private void clearValidationFromTree( FacesContext context, UIComponent component ) { component.visitTree( VisitContext.createVisitContext( context ), new VisitCallback() { @Override public VisitResult visit( VisitContext context, UIComponent target ) { clearValidationFromComponent( target.getAttributes() ); return VisitResult.ACCEPT; } } ); } private void clearValidationFromComponent( Map<String, Object> attributes ) { if ( attributes.containsKey( AttributeKeys.validators.getKey() ) ) { String errorStyleClass = (String) attributes.get( AttributeKeys.errorStyleClass.getKey() ); if ( errorStyleClass != null ) { String styleClass = (String) attributes.get( "styleClass" ); styleClass = styleClass.replace( errorStyleClass, "" ); attributes.put( "styleClass", styleClass ); } attributes.remove( AttributeKeys.valid.getKey() ); attributes.remove( AttributeKeys.messages.getKey() ); attributes.remove( AttributeKeys.errorStyleClass.getKey() ); } } private Object getValue( FacesContext facesContext, UIComponent component ) { Object value = null; if ( component instanceof EditableValueHolder ) { value = ((EditableValueHolder) component).getValue(); } else if ( component instanceof Tree ) { value = myBean.getSelectedIds(); } return value; } public void setMyBean( MyBean myBean ) { this.myBean = myBean; } private void validate( FacesContext context ) { logger.trace( "entering validation" ); final List<String> validationFailed = new ArrayList<String>(); UIViewRoot viewRoot = context.getViewRoot(); viewRoot.visitTree( VisitContext.createVisitContext( context ), new VisitCallback() { @Override public VisitResult visit( VisitContext context, UIComponent component ) { if ( !component.isRendered() ) { // remove all validation from subtree as validation // is not performed unless the component is // rendered. clearValidationFromTree( context.getFacesContext(), component ); return VisitResult.REJECT; } Map<String, Object> attributes = component.getAttributes(); if ( attributes.containsKey( AttributeKeys.validators.getKey() ) ) { Object value = getValue( context.getFacesContext(), component ); boolean valid = true; Collection<FacesMessage> facesMessages = null; Set<String> errorStyleClasses = null; @SuppressWarnings( "unchecked" ) List<ActionListenerValidatorWrapper> validators = (List<ActionListenerValidatorWrapper>) attributes.get( AttributeKeys.validators.getKey() ); for ( ActionListenerValidatorWrapper validator : validators ) { try { validator.validate( context.getFacesContext(), component, value ); } catch ( ValidatorException validatorException ) { valid = false; Collection<FacesMessage> innerMessages = validatorException.getFacesMessages(); if ( innerMessages == null ) { FacesMessage innerMessage = validatorException.getFacesMessage(); if ( innerMessage != null ) { innerMessages = Arrays.asList( new FacesMessage[] { innerMessage } ); } } if ( facesMessages == null ) { facesMessages = new ArrayList<FacesMessage>(); } facesMessages.addAll( innerMessages ); String errorStyleClass = validator.getErrorStyleClass(); if ( errorStyleClass != null ) { if ( errorStyleClasses == null ) { errorStyleClasses = new TreeSet<String>(); } errorStyleClasses.add( errorStyleClass ); } } } if ( valid ) { // remove previous validation state clearValidationFromComponent( attributes ); } else { // add validation state addValidationToComponent( attributes, facesMessages, errorStyleClasses ); validationFailed.add( "Yes, it did, but cant update final boolean so we use a list" ); } } return VisitResult.ACCEPT; } } ); if ( validationFailed.size() > 0 ) { context.validationFailed(); } } public void validateThisFormBecausePrimefacesDidntMakeTreeAnEditableValueHolder( ActionEvent event ) { validate( FacesContext.getCurrentInstance() ); } } And finally, the page that uses it:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:myns="http://ns.my.com/ui/extensions"> <h:head /> <h:body> <f:event type="preRenderView" listener="#{actionListenerValidatorManager.applyValidationStateToComponentTreeBecausePrimefacesDidntMakeTreeAnEditableValueHolder}" /> ... <h:panelGroup id="treeGroup"> <h:outputLabel for="treeInput" value="#{i18n['my-tree']}" /> <p:tree id="treeInput" value="#{myBean.treeRootNode}" var="node" selectionMode="checkbox" selection="#{myBean.selectedNodes}"> <pe:actionListenerValidator validatorId="javax.faces.Required" errorStyleClass="ui-state-error" /> <p:treeNode> <h:outputText value="#{node}" /> </p:treeNode> </p:tree> </h:panelGroup> ... </h:body> </html> I know this is not an answer like cut / paste, but it fully describes the process. The main advantage of this approach is that it feels just like a standard check on how it is used and how it is processed. In addition, it uses existing validators. If someone else is stuck with <p:tree> and should check the selection, I hope this helps ...