Search code examples
javaajaxeventsjsfrichfaces

Validation Error: Value is required error for list selection event


Background

Use Ajax to fire an event to the web server when a list of items are selected. The element is a JSF rich:orderingList item.

Problem

The class that must receive the event:

public class BusinessAreaListHandler extends ListHandler<ListElement> {
  private static final long serialVersionUID = -581048454118449233L;

  public BusinessAreaListHandler() { load(); }
  public List<ListElement> getBusinessAreas() { return getList(); }
  public Set<ListElement> getSelection() { return getSet(); }

  public void select() {
    System.out.println( "Clicked Element" );
  }

  protected void load() {
    appendList( new ListElement( "employee" ) );
    appendList( new ListElement( "company" ) );
    appendList( new ListElement( "payroll" ) );
  }
}

The ListElement container object:

public class ListElement implements Serializable {
  private static final long serialVersionUID = 5396525520175914504L;

  public final static char SEPARATOR = ':';

  private String id, value;

  protected ListElement( String value ) {
    this( Integer.toString( value.hashCode() ), value );
  }

  public ListElement( String id, String value ) {
    setId( id );
    setValue( value );
  }

  @Override
  public int hashCode() {
    return (53 * (getId().hashCode() + 5)) + (53 * (getValue().hashCode() + 5));
  }

  @Override
  public boolean equals( Object obj ) {
    ListElement le = null;

    if( obj != null && obj instanceof ListElement ) {
      le = (ListElement)obj;
    }

    return le == null ? false : compare( le );
  }

  private boolean compare( ListElement le ) {
    return getId().equals( le.getId() ) && getValue().equals( le.getValue() );
  }

  public String getId() {
    return this.id == null ? "" : this.id;
  }

  public void setId( String id ) {
    this.id = id;
  }

  public String getValue() {
    return this.value == null ? "" : this.value;
  }

  public void setValue( String value ) {
    this.value = value;
  }

  @Override
  public String toString() {
    return getValue();
  }
}

The ListConverter:

public class ListConverter implements Converter {
  @Override
  public Object getAsObject( FacesContext fc, UIComponent ui, String value ) {
    ListElement result = new ListElement( value );
    int index = value.lastIndexOf( ListElement.SEPARATOR );

    System.out.println( "Convert FROM: " + value );

    if( index > 0 ) {
      String id = value.substring( 0, index );
      String v = value.substring( index + 1 );
      result = new ListElement( id, v );
    }

    System.out.println( "Convert TO  : " + result.toString() );

    return result;
  }

  @Override
  public String getAsString( FacesContext fc, UIComponent ui, Object value ) {
    return value.toString();
  }
}

The XHTML fragment that sets up the list and event actions:

<h:panelGrid columns="2">
  <h:panelGroup>
    <rich:orderingList value="#{businessAreas.list}" var="businessArea" converter="businessAreaConverter" immediate="true" orderControlsVisible="false" fastOrderControlsVisible="false" selection="#{businessAreas.selection}" id="BusinessAreas">
      <f:facet name="caption">
        <h:outputText value="Business Areas" />
      </f:facet>
      <rich:column>
        <h:outputText value="#{businessArea}" />
      </rich:column>
      <a4j:support event="onclick" ignoreDupResponses="true" requestDelay="500" action="#{businessAreas.select}" reRender="ColumnClusters" />
      <a4j:support event="onkeyup" ignoreDupResponses="true" requestDelay="500" action="#{businessAreas.select}" reRender="ColumnClusters" />
    </rich:orderingList>
  </h:panelGroup>
  <h:panelGroup>
    <rich:listShuttle sourceValue="#{columnClusters.list}" targetValue="#{domainContent.list}" var="columnCluster" converter="columnClusterConverter" sourceRequired="false" targetRequired="true" moveControlsVisible="true" orderControlsVisible="false" fastOrderControlsVisible="false" sourceCaptionLabel="Column Clusters" targetCaptionLabel="Domain Content" copyTitle="Move" copyControlLabel="Move" copyAllControlLabel="Move All" copyAllTitle="Move All" id="ColumnClusters">
      <rich:column>
        <h:outputText value="#{columnCluster}" />
      </rich:column>
    </rich:listShuttle>
  </h:panelGroup>
  <h:panelGroup>
    <h:commandButton action="action" value="Create Domain" />
  </h:panelGroup>
</h:panelGrid>

The corresponding managed bean is set in faces-config.xml correctly.

Browser output:

Error Message

The following error message is displayed:

INFO: WARNING: FacesMessage(s) have been enqueued, but may not have been displayed. sourceId=j_id2:ColumnClusters[severity=(ERROR 2), summary=(j_id2:ColumnClusters: Validation Error: Value is required.), detail=(j_id2:ColumnClusters: Validation Error: Value is required.)]

Logging

New Element (Hash:Value): 1193469614:employee
New Element (Hash:Value): 950484093:company
New Element (Hash:Value): -786522843:payroll
New Element (Hash:Value): 3373707:name
New Element (Hash:Value): -1147692044:address
New Element (Hash:Value): 114603:tax
New Element (Hash:Value): 1193469614:employee
Convert FROM: employee
Convert TO  : employee
New Element (Hash:Value): 950484093:company
Convert FROM: company
Convert TO  : company
New Element (Hash:Value): -786522843:payroll
Convert FROM: payroll
Convert TO  : payroll
New Element (Hash:Value): 3373707:name
Convert FROM: name
Convert TO  : name
New Element (Hash:Value): -1147692044:address
Convert FROM: address
Convert TO  : address
New Element (Hash:Value): 114603:tax
Convert FROM: tax
Convert TO  : tax

Question

What is required to trigger a call to select() in BusinessAreaListHandler when users select (or deselect) items?

References

Thank you!


Solution

  • The problem is this attribute:

    targetRequired="true"
    

    Since the click triggers an immediate Ajax request to the server, it is expected that the right-hand shuttle (the target) has data. Without data, the validation error will be posted.