immediate attribute on a commandLink and another component
Hi,
I've got an interesting problem where validation is running on a selectOneRadio component and the error is displaying on thenext page.
I've got a commandLink that has the immediate attribute set to true. When this link is clicked, I want the form validation to be skipped and the user to be returned to the previous page viewed (the application is in a wizard style navigation).
On the same page I have a selectOneRadio with a valueChangeListener and also with immediate true. Immediate must be true on the radio because when a change occurs on the radio, I submit the form. I do not want to run the other validation on the page. The selectOneRadio defaults to no selection. It also has required set to true.
The problem appears when the user clicks the commandLink without setting a selection on the selectOneRadio. The previous page is properly shown to the user, except the error message for the required on selectOneRadioappears on the new page. i.e. The validation error from the starting page is appearing on the destination page (two completely different pages).
I know that this problem is because the selectOneRadio is running its validation immediately. Is there any way I can tell it not to run validation at all if the commandLink is clicked?
If you're interested, the code is displayed below.
Thanks for any help,
CowKing
<TABLE bgcolor="white" border="0" cellpadding="0" cellspacing="0" width="100%">
<TR>
<TD align="left">
<h:commandLink id="backButtonLinkTop" type="submit"
action="#{navigationBean.back}" styleClass="navLink"
immediate="true">
<h:outputText id="backTextTop" value="Return" />
<f:actionListener
type="package.LoggingActionListener" />
</h:commandLink>
</TD>
</TR>
</TABLE>
<h:selectOneRadio id="accountOption"
value="#{commonInfoBean.accountOption}" layout="pageDirection"
valueChangeListener="#{commonInfoBean.accountSwitched}"
onclick="submit()" immediate="true" required="true">
<f:selectItems id="accountOptions"
value="#{commonInfoBean.accountOptions}" />
</h:selectOneRadio>
And the results are none of the above. =(
They were good ideas that I thought would work too. But here's why they don't.
Dividing the form
I can't divide the form up because there's more inputs, other fields, and duplicate commandLinks. My specifications require me to lay certain components out in specific spots on the screen. This layout makes it impossible to divide the form in the way I would need to. Essentially, I'd need to have an independent form inside another form. Not possible.
Binding the SelectOneRadio
This will require some serious hacking of my system and JSF. I'm steering away from this.
I have a bean that controls navigation on every single one of my pages. Thus, the "Action" method is quite generalized. Adding a binding would make me add a specific piece of code in JUST for this one page. It would be difficult to determine that the current page is the one that requires a speicifc piece of code to be run.
The second problem with this is that my beans are mostly all in session scope. The one that should get this component binding is definitely in session. Which means that if I were to set the selectOneRadio to be not required, this status would persist after the user returns to the page. I'd have to rig up ANOTHER special case in my navigation system to turn required back on when the user returns to the appropriate page.
The third problem with this, as described in more detail next, is that UISelectOne runs it's own validateValue method that throws an error if no selection is made. Even if required is false.
Run Validation from Code
I honestly thought this one would work. It SHOULD, but doesn't.
HtmlSelectOneRadio inherits UISelectOne. UISelectOne overrides the validateValue method from UIInput. In UISelectOne's validateValue method, it checks to see if there is a valid selection made. Believe it or not, I CANNOT have a non-selected list.
Here's the UISelectOne code:
protected void validateValue(FacesContext context, Object value) {
// Skip validation if it is not necessary
super.validateValue(context, value);
if (!isValid() || (value == null)) {
return;
}
// Ensure that the value matches one of the available options
boolean found = matchValue(value, new SelectItemsIterator(this));
// Enqueue an error message if an invalid value was specified
if (!found) {
FacesMessage message =
MessageFactory.getMessage(context, INVALID_MESSAGE_ID);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
context.addMessage(getClientId(context), message);
setValid(false);
}
}
From looking at this, I thought it'll skip this if the selected value is null. So I ensured my selected value was null. I'm almost certain it does NOT pass that exact value around because the above code ran every time.
Essentially now, I've replaced one error message with another. I guess that won't work either.
My Own Idea: Removing the Messages from the Context
Basically, I just want to get rid of the error messages from displaying on an unrelated page. So I figured I should just remove all error messages from the faces context anytime the commandLink with immediate is clicked.
Apparently I can't do that either. I'm so frustrated! I get the messages from the FacesContext and start running through the Iterator that is returned. I remove each entry in the Iterator (shown below). However, the message appears to be removed from the Iterator, but NOT from the context. It looks as if the Iterator contains only a copy of the messages. I couldn't find any other hook into removing those messages from the context.
Here's the code I used to do this:
FacesContext context = FacesContext.getCurrentInstance();
if (context.getMessages().hasNext()) {
Iterator it = context.getMessages();
while(it.hasNext()) {
it.next();
it.remove();
}
System.out.println("All errors removed.");
System.out.println("Messages in Context? " + context.getMessages().hasNext());
}
The console outputs "true" in the last system.out statement. And of course, the message still appears on the next page.
Remarks
Sorry this is long guys. I wanted to be concise though.
I'm very frustrated with this and think it is a serious flaw in JSF. It should at least be possible to avoid this situation (ie. you can have this behaviour as default, but allow SOME way to change it).
If anybody has any more ideas, I would love to hear them.
Thanks,
CowKing