我慢してください、これは長い答えです...
primefaces ツリーは ではないため、JSF ライフサイクルの標準プロセス検証フェーズでは検証できません(大規模なハッキングは必要ありません)。そして、私の能力の限りでは、primefaces ツリー コードにパッチを適用して(ツリーは、選択した値に従ってレンダリングされるのではなく、ツリーをバッキングするノードの状態に従ってレンダリングされます) にすることができませんでした。これらの制約を考えると、唯一の解決策は、独自のツリー コンポーネントを作成する (時間がない)、別のコンポーネントを使用する (ツリーが最適)、またはアプリケーションの呼び出しフェーズで検証することです。EditableValueHolder
EditableValueHolder
私は 3 番目の解決策を選びました。そうすることで、通常の検証とできるだけ同じようにしようとしました。主なアイデアは、actionListener
最初に (他のすべての s またはメイン アクション (フォームの保存) の前に) 起動される を使用してactionListener
検証を処理することです。検証が失敗した場合は、カスタム属性のコンポーネントに失敗に関する情報を追加し、呼び出しfacesContext.validationFailed()
てできるようにします。アクションをスキップしてから、preRenderView
システム イベント リスナーを追加して、応答のレンダリング フェーズの前に検証状態に従ってコンポーネントを変更します.<f:validator>
コードは次のとおりです。
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() );
}
}
最後に、それを使用するページ:
<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>
これは切り貼りタイプの回答ではないことはわかっていますが、プロセスの概要を完全に説明しています。このアプローチの主な利点は、使用方法と処理方法が標準の検証と同じように感じられることです。さらに、既存のバリデーターを活用します。他の誰かが使用<p:tree>
に行き詰まり、選択を検証する必要がある場合、これが役立つことを願っています...