tags:

views:

558

answers:

2

I need to validate two user input fields against each other in seam. Field1 must be greater than Field2 for each row in a ui:repeat tag. As of now, i have the two fields wrapped in an s:decorate tag that wraps all input in an s:validateAll tag. This allows me to float an error message out to the right of the fields if validation fails for any of them.

For example (i can't insert an image, so i have to use ascii picture, forgive the low-quality please, italics indicate red text):

Label: | Yellow | 0|% Red: | 0%| | Yellow and Red must be between 0 and 100, and Yellow must be greater than Red.

Label: | Yellow | 0|% Red: | 0%| | Yellow and Red must be between 0 and 100, and Yellow must be greater than Red.

The two controls and the decorate xhtml are below. NOTE: The "value between 0 and 100" validation is already taken care of via hibernate annotation. I only need to know how to validate these 2 fields against each other to make sure yellow is greater than red, and still have the error message show up.

My desired solution would be to set the #{invalid} property for the corresponding s:decorate tag, so the error message will show up, but i'll take any ideas.

The inputs:

<table>
    <ui:repeat value="#{action.List}" var="var">
        <s:decorate template="/layout/decorateMultipleInputs.xhtml" >
            <ui:define name="label">
                Label:
            </ui:define>
            <ui:define name="input">
                <h:panelGrid columns="8" frame="border">
                    <h:outputText value="Yellow:" />
                    <h:inputText value="#{var.yellow}" style="width:25px; text-align:right" maxlength="3"/>
                    %

                    <h:outputText value="Red:" />
                    <h:inputText value="#{var.red}" style="width:25px; text-align:right" maxlength="3"/>
                    %
                </h:panelGrid>
            </ui:define>
            <ui:define name="message">Yellow and Red must be between 0 and 100, and Yellow must be greater than Red.
            </ui:define>
        </s:decorate>
    </ui:repeat>
</table>

and the decorateMultipleInputs.xhtml:

<ui:composition  xmlns="http://www.w3.org/1999/xhtml"
             xmlns:ui="http://java.sun.com/jsf/facelets"
             xmlns:h="http://java.sun.com/jsf/html"
             xmlns:f="http://java.sun.com/jsf/core"
             xmlns:s="http://jboss.com/products/seam/taglib"&gt;
<tr>
<td>        
    <s:label styleClass="#{invalid?'error':''}">
        <ui:insert name="label"/>
        <s:span styleClass="required" rendered="#{required}">*</s:span>
    </s:label>
</td>
<td>
    <s:validateAll>
        <ui:insert name="input"/>
    </s:validateAll>
</td>
<td>
    <s:div styleClass="error" rendered="#{invalid}">
        <h:graphicImage value="/images/error.gif" />
    </s:div>    
</td>
<td>
    <s:div styleClass="error" rendered="#{invalid}">
        <ui:insert name="message"/>
    </s:div>    
</td>
</tr>
</ui:composition>
A: 

Seam does not currently have a way to do multi-field validation. This is currently on the docket for the JSR-299 WebBeans but there is not a clean nor clear cut way how to do this.

You can achieve this by validating after a form-submit as part of your usual action handler. ie.

public String processRedsAndYellows() {
   for(RedYellow var : ActionBean.getList()) {
      if(var.getYellow() <= var.getRed()) {
         messages.addMessage("All Yellows must be greater than Reds");
         return null;
      }
   }
   return "success";
}

Or something to that effect. :)

Drew
Thanks for your quick response, i was afraid i'd have to go that route. :)
Jason
+1  A: 

I would attach your own custom JSF validator to the red <h:inputText/>

<h:inputText value="#{var.red}" style="width:25px; text-align:right" maxlength="3">
  <f:validator validatorId="rowValidator"/>
</inputText>

Implement a JSF validator (since you're using Seam you can use the @Validator annotation).

@Validator
public class RowValidator implements javax.faces.validator.Validator
{
    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException 
    {
        <snip/>  
    }
}

The key here is the UIComponent object passed to the validate() method. This is the <h:inputText/> you've bound the validator to. From here you can call getParent() to get the <h:inputText/> parent (<h:panelGrid/>). You can now enumerate the <h:panelGrid/> children objects to find the yellow <h:inputText/>, retrieve the value passed in to it and compare with the value parameter passed to the validate() method.

If yellow is less than red you can do the following in your validate() method:

FacesMessage message = new FacesMessage();
message.setDetail("Yellow must be greater than red");
message.setSummary("Yellow must be greater than red");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
mtpettyp
Great idea, I'll try that, Thanks!
Jason
This works great! It feels like hack going through the parent to get to the sibling controls, but it works simply enough to be useful. Thanks again!
Jason
Just be aware of the tight coupling between the markup and the validator. If the mark up changes, it may break the validator. :)
Drew