There is a list that is dynamically generated at runtime. I am using a dataTable to represent it. Inside each row is a dropdown list. As soon as the user selects a value from the drop down list in a row, then all the other rows must be disabled.?
<h:dataTable value="#{user.orderList}" var="item">
<h:column>
<h:selectOneMenu value="#{user.sometuff}" >
<f:selectItems value="#{user.someItems}" />
</h:selectOneMenu>
</h:column>
</h:dataTable>
How can I achieve this with <f:ajax>?
here is a working example.
imho, it's way easier to implement with ajax4jsf (richfaces), using a4j:repeat and a4j:ajax tags.
xhtml code:
<h:form id="form">
<h:dataTable id="tableId" value="#{user.orderList}" var="item">
<h:column>
<h:selectOneMenu value="#{item.selectedItem}" disabled="#{user.oneItemSelected and (item.selectedItem == null || item.selectedItem == '')}">
<f:selectItems value="#{user.selectItemList}" />
<f:ajax execute="#this" render="#form" listener="#{user.updateSelectionFlag}"></f:ajax>
</h:selectOneMenu>
</h:column>
</h:dataTable>
</h:form>
order item code:
public class Order implements Serializable
{
private static final long serialVersionUID = 1L;
private String selectedItem;
public String getSelectedItem() {
return selectedItem;
}
public void setSelectedItem(String selectedItem) {
this.selectedItem = selectedItem;
}
}
managed bean code:
#Named
#ViewScoped
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private List<Order> orderList;
private List<SelectItem> selectItemList;
private boolean oneItemSelected;
public List<SelectItem> getSelectItemList() {
if (selectItemList == null)
{
selectItemList = new ArrayList<SelectItem>();
selectItemList.add(new SelectItem("", "---Please Choose---"));
selectItemList.add(new SelectItem("1", "Item 1"));
selectItemList.add(new SelectItem("2", "Item 2"));
selectItemList.add(new SelectItem("3", "Item 3"));
selectItemList.add(new SelectItem("4", "Item 4"));
}
return selectItemList;
}
public List<Order> getOrderList() {
if (orderList == null)
{
orderList = new ArrayList<Order>();
for (int i=0 ; i<4 ; i++)
{
orderList.add(new Order());
}
}
return orderList;
}
public boolean isOneItemSelected() {
return oneItemSelected;
}
public void updateSelectionFlag()
{
oneItemSelected = false;
for (int i=0 ; i<getOrderList().size() ; i++)
{
Order order = getOrderList().get(i);
if (order.getSelectedItem() != null && !order.getSelectedItem().equals(""))
{
oneItemSelected = true;
break;
}
}
}
}
by the way, instead of checking the whole array in updateSelectionFlag, it's better to check only the submitted item's value. but i couldn't figure out how to get the clicked row index of h:dataTable. doing a binding prevents combo values from being submitted, and datatable does not provide a varStatus attribute like ui:repeat does.
I can't figure out the problem. In listener part of ajax, it gives me the listener reference not found. I don't know what I am doing wrong. I'm working on Oracle JDeveloper IDE. Here is my code.
Here is part of my JSF page:
<af:menuBar id="menuBar">
<af:menu text="Add" id="m1">
<af:commandMenuItem text="Text Box" id="addTextBox" >
<f:ajax event="click" render="msg" listener="#{addItem.start}"/>
</af:commandMenuItem>
</af:menu>
<af:menu text="Contact" id="m2"/>
</af:menuBar>
<af:message id="msg" message="#{addItem.text}" >
Here is my AddItems.java file:
import javax.faces.bean.ManagedBean;
import javax.faces.event.AjaxBehaviorEvent;
#ManagedBean(name="addItem")
public class AddItems {
public AddItems() {
}
String text = "first";
public void start(AjaxBehaviorEvent event) throws javax.faces.event.AbortProcessingException {
setText("changed");
}
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
Since i've asked my last question (which still unanswered) i continued searching for a solution and lastly i found this topic which i think can help achieve what i want.
So , i tried that solution (which itself is a workaround) but it still didn't work for me.
Here is the code, it s all just a test for this issue :
the index.xhtml :
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>dyn add comps test </title>
</h:head>
<body>
<h:form id="form1">
<!-- once this first commandLink is clisked it will generate another commandlink below it -->
<h:commandLink value="cliick me">
<f:ajax event="click" listener="#{myBean.firstcmdLinkListenerHandler}"/>
</h:commandLink>
</h:form>
</body>
</html>
The managed Bean : MyBean.java
package mybeans;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.faces.application.Application;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.component.html.HtmlCommandLink;
import javax.faces.context.FacesContext;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.event.BehaviorEvent;
import org.primefaces.component.behavior.ajax.AjaxBehaviorListenerImpl;
import org.primefaces.context.RequestContext;
#ManagedBean
#SessionScoped
public class MyBean {
public void handleClose(AjaxBehaviorEvent abe){
System.out.println("!!!-->>>>> the Ajax Behaviour Works !!!!! ");
}
public void reLoadCityList( BehaviorEvent event ){
System.out.println("!!!-->>>>> the reLoadCityList method Works !!!!! ");
}
public void firstcmdLinkListenerHandler(AjaxBehaviorEvent abe){
System.out.println("firstcmdLinkListenerHandleris running ! ");
FacesContext fc = FacesContext.getCurrentInstance();
Application application = fc.getApplication();
ExpressionFactory ef = fc.getApplication().getExpressionFactory();
UIComponent form1 = fc.getViewRoot().findComponent("form1");
if(form1!=null){
//Creating the commandLink
HtmlCommandLink mynewcmdlink = (HtmlCommandLink)application.createComponent(HtmlCommandLink.COMPONENT_TYPE);
mynewcmdlink.setId("mynewcmdlink");
mynewcmdlink.setValue("clickme2!!");
MyAjaxBehavior pajax = new MyAjaxBehavior();
Class[] par = new Class[1];
par[0] = BehaviorEvent.class;
//par[0] = AjaxBehaviorEvent.class; (*)
//pajax.setListener( myCreateMetExpression( "reLoadCityList", true, //void.class, par ) );
//i tried with both AjaxBehaviorEvent and BehaviorEvent as in (*)
//MethodExpression me = ef.createMethodExpression( //fc.getELContext(), "#{myBean.handleClose}", void.class, par);
MethodExpression me = ef.createMethodExpression( fc.getELContext(), "#{myBean.reLoadCityList}", void.class, par);
//pajax.setListener(me); //i've tried with this too but it wasn't sucesseful
pajax.addAjaxBehaviorListener( new AjaxBehaviorListenerImpl( me ) );
pajax.setProcess( "#this" );
mynewcmdlink.addClientBehavior( "change", pajax );
//adding thecommanLink to the form
form1.getChildren().add(mynewcmdlink);
//Refreshing the form to see the added commandLink :
RequestContext context = RequestContext.getCurrentInstance();
context.update("form1");
context.update("form1:foo");
}else
System.out.println("form1 is null!!");
}
and the MyAjaxBehavior.java used as the workaround in the primefaces forum article:
package mybeans;
import java.util.HashMap;
import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.BehaviorEvent;
import org.primefaces.component.behavior.ajax.AjaxBehavior;
public class MyAjaxBehavior extends AjaxBehavior {
#Override
public Object saveState(FacesContext context) {
HashMap<String, Object> map;
map = new HashMap<String, Object>();
map.put("update", getUpdate());
map.put("process", getProcess());
map.put("oncomplete", getOncomplete());
map.put("onerror", getOnerror());
map.put("onsuccess", getOnsuccess());
map.put("onstart", getOnstart());
map.put("listener", getListener());
if (initialStateMarked())
return null;
return UIComponentBase.saveAttachedState(context, map);
}
#SuppressWarnings("unchecked")
#Override
public void restoreState(FacesContext context, Object state) {
if (state != null) {
HashMap<String, Object> map;
map = (HashMap<String, Object>) UIComponentBase
.restoreAttachedState(context, state);
setUpdate((String) map.get("update"));
setProcess((String) map.get("process"));
setOncomplete((String) map.get("oncomplete"));
setOnerror((String) map.get("onerror"));
setOnsuccess((String) map.get("onsuccess"));
setOnstart((String) map.get("onstart"));
setListener((MethodExpression) map.get("listener"));
}
}
#Override
public void broadcast(BehaviorEvent event) throws AbortProcessingException {
ELContext eLContext = FacesContext.getCurrentInstance().getELContext();
// Backward compatible implementation of listener invocation
if (getListener() != null) {
try {
getListener().invoke(eLContext, new Object[] { event });
} catch (IllegalArgumentException exception) {
getListener().invoke(eLContext, new Object[0]);
}
}
}
}
Do something like this
Panel tp = new Panel();
FacesContext fc = FacesContext.getCurrentInstance();
ExpressionFactory ef = fc.getApplication().getExpressionFactory();
MethodExpression me = ef.createMethodExpression(fc.getELContext(), "#{myView.closeIt}", null, new Class<?>[]{BehaviorEvent.class});
AjaxBehavior ajaxBehavior = (AjaxBehavior) fc.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);
ajaxBehavior.setProcess("#this");
ajaxBehavior.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl(me, me));
tp.addClientBehavior("close", ajaxBehavior);
component.getChildren().add(tp);
myView.closeIt()
public void closeIt(CloseEvent ce){
Panel p = (Panel) ce.getComponent();
System.out.println("Do what ever you want");
}
It appears that primefaces <p:tree> is not an EditableValueHolder, even though it offers the ability to make the tree selectable. To me this seems like the very definition of EditableValueHolder as it both holds values (the list of nodes that are selected) and is editable (you can change the selection). In making the tree selectable, it basically turns it into a selectOneXxx/selectManyXxx. This is the fashion in which I use this widget. However, not being an EditableValueHolder, I cannot attach a validator to it directly. I could add validation to the form submission action with an actionListener but then it is out of the appropriate lifecycle phase and is much more difficult to get at the UITree component to check for attributes like the i18n message for failed validation. Has anyone dealt with this before? What do you do?
---------- EDIT ----------
I found an issue posted in the primefaces bug tracker that seems releated:
http://code.google.com/p/primefaces/issues/detail?id=4137
And a forum post:
http://forum.primefaces.org/viewtopic.php?f=3&t=22340
---------- EDIT ----------
This is the solution I came up with. Some of the jQuery is pretty hairy as it uses server side el to generate the client side javascript. But for the most part it works. Just have to figure out why an empty array skips validation... but thats 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 working through some suggestions with BalusC, I think I'm gonna give up on <p:tree> and find another way... :(
You can trick it with a required hidden input field whose value is altered on node click. You can use the selections property of the <p:tree> widget variable to get the available selections as an array.
E.g.
<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 'ok' value is purely arbitrary. The point is that the hidden field is filled, so that the required validator doesn't get triggered.
Bear with me, this is a long answer...
Since primefaces tree is not an EditableValueHolder it cannot be validated during the standard process validations phase of the JSF lifecycle (without some major hacking). And to the best of my ability, I was not able to patch the primefaces tree code to make it an EditableValueHolder (the tree does not get rendered according to the selected values, but according to the state of the nodes backing the tree). Given these constraints, the only solutions are create my own tree component (I don't have the time), use a different component (a tree fit best), or validate in the invoke application phase.
I chose the 3rd solution, and in doing so, tried to make it as similar to regular validation as possible. The main idea is, use an actionListener that gets fired first (before any other actionListeners or the main action (save the form) to process validations. If validations failed, I add information about the failure to the component in custom attributes, call facesContext.validationFailed() so I can skip the action, then add a preRenderView system event listener to modify the components according to their validation state before the render response phase. This is done in a fashion that allows you to still specify the validation in the same fashion using a custom component instead of an <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 a cut/paste type answer, but it fully outlines the process. The main benefit of this approach is that it feels the same as standard validation in the way it is used and the way it is processed. Plus it leverages existing validators. If anyone else is stuck using <p:tree> and must validate the selection, I hope this helps...
I want to generate selecOneMenu content when the user types in an inputText field, and respond to combo box selection changes.
The below code updates the contents of the selecOneMenu as the user types. (The typed and the next 9 numbers gets added to the combo box. This is just a simplified example code.)
When the page is loaded, the change event of the selecOneMenu correctly gets fired.
However after typing in the inputValue field, content of selecOneMenu is changed, and the change event is not fired when I select an item.
The code works if ComboBean is session scoped, but I want to avoid this solution if possible.
Is it possible at all to do this?
What is the reason if it is not possible with request scope?
PrimeFaces 2.2
Mojarra 2.0.2
GlassFish 3.0.1
Browser: Chrome, Firefox, IE
combo.xhtml:
<h:head>
<title>Combo box example</title>
</h:head>
<h:body>
<h:form>
<p:panel id="mainPanel">
<h:panelGroup id="formToSubmit" layout="block">
<p:messages id="messages" />
<h:panelGrid columns="2">
<h:outputLabel value="Enter a number" />
<h:inputText id="inputValue" value="#{comboBean.inputValue}">
<p:ajax event="keyup" update="combo"
listener="#{comboBean.onKeyUp}" />
</h:inputText>
<h:outputLabel value="Select a value:" />
<h:selectOneMenu id="combo" value="#{comboBean.selectedValue}">
<f:selectItem itemLabel="Select a value..."
noSelectionOption="true" />
<f:selectItems value="#{comboBean.values}" />
<p:ajax event="change" update="selectedValue"
listener="#{comboBean.valueSelected}" />
</h:selectOneMenu>
<h:outputLabel value="Selected value:" />
<h:inputText id="selectedValue" value="#{comboBean.selectedValue}" />
</h:panelGrid>
</h:panelGroup>
</p:panel>
</h:form>
</h:body>
</html>
ComboBean.java
package x;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
#Named
#RequestScoped
public class ComboBean implements Serializable
{
private static final long serialVersionUID = 1L;
private String inputValue;
private String selectedValue;
private List<String> values;
#PostConstruct
void init()
{
System.out.println("init");
setValues(new LinkedList<String>());
for(int i = 0; i<10 ; i++)
{
getValues().add(""+i);
}
}
public void onKeyUp()
{
System.out.println("onkeyUp " + getInputValue());
setValues(new LinkedList<String>());
if (inputValue != null)
{
try
{
int v = Integer.parseInt(inputValue);
for(int i = 0; i<10 ; i++)
{
getValues().add(""+(v+i));
}
}
catch (NumberFormatException ne)
{
//doesn't matter
}
}
}
public void valueSelected()
{
System.out.println("valueSelected " + getSelectedValue());
}
public void submit()
{
System.out.println("submit " + getInputValue());
}
public void setInputValue(String inputValue)
{
this.inputValue = inputValue;
}
public String getInputValue()
{
return inputValue;
}
public void setSelectedValue(String selectedValue)
{
this.selectedValue = selectedValue;
}
public String getSelectedValue()
{
return selectedValue;
}
public void setValues(List<String> values)
{
this.values = values;
}
public List<String> getValues()
{
return values;
}
}
The problem is that you reset your list during each request in the init() method. So your selected element will no longer be there.
If you don't want to use SessionScope, maybe the ViewScope would be a solution: then the bean won't be reset if the same page is reloaded.