Question

I'm coming with a question on JSF2. I'm currently mixing two different pieces of code proposed by BalusC on his blog :

http://balusc.blogspot.com/2007/12/set-focus-in-jsf.html

http://balusc.blogspot.com/2007/12/validator-for-multiple-fields.html

The first let highlight in red the fields that do have an error message. The second let validation be performed on multiple fields.

I'm looking for a way to have a single error message (dont want the message to be rendered twice) in the FacesContext be linked to several client ids (because the message do concern several fields due to the multiple fields validator).

Is it possible with the language basics ? If I can, I'd like to avoid a hand-made system (it should work with a managed bean with "request" scope, the validator placing the clientIds that do have an error into a List accessed by the PhaseListener).

Thanks in advance for your tips. Could not see anything close to addMessage() on FacesContext that could do the work, but maybe there is a way...

Was it helpful?

Solution

If the message is appearing twice, then it means that you're either firing the same validator by both components or that the validator is fired once but implicitly adding the message to the other component.

I understand that you want to mark the both components as invalid (so that they get highlighted) and that you want only one message. In that case, you need to make sure that the validator is fired once and that the other component is retrieving an empty/null message.

You only need to change the validator to retrieve the whole component as attribute instead of its value (note: I've in the meanwhile edited the old article accordingly; it has another benefits) and you need to change the phase listener to remove empty/null messages.

E.g. in view:

<h:outputLabel for="password" value="Password" />
<h:inputSecret id="password" value="#{bean.password}" required="true">
    <f:validator validatorId="passwordValidator" />
    <f:attribute name="confirm" value="#{confirm}" />
</h:inputSecret>
<h:message for="password" styleClass="error" />

<h:outputLabel for="confirm" value="Confirm password" />
<h:inputSecret id="confirm" binding="#{confirm}" required="true" />
<h:message for="confirm" styleClass="error" />

and in validate() method:

String password = (String) value;
UIInput confirmComponent = (UIInput) component.getAttributes().get("confirm");
String confirm = confirmComponent.getSubmittedValue();

if (password == null || password.isEmpty() || confirm == null || confirm.isEmpty()) {
    return; // Let required="true" do its job.
}

if (!password.equals(confirm)) {
    confirmComponent.setValid(false);
    context.addMessage(confirmComponent.getClientId(context), new FacesMessage(null));
    throw new ValidatorException(new FacesMessage("Passwords are not equal."));
}

and in the phase listener:

Iterator<String> clientIdsWithMessages = facesContext.getClientIdsWithMessages();

while (clientIdsWithMessages.hasNext()) {
    String clientIdWithMessages = clientIdsWithMessages.next();

    if (focus == null) {
        focus = clientIdWithMessages;
    }

    highlight.append(clientIdWithMessages);

    if (clientIdsWithMessages.hasNext()) {
        highlight.append(",");
    }

    Iterator<FacesMessage> messages = facesContext.getMessages(clientIdWithMessages);

    while (messages.hasNext()) {
        if (messages.next().getSummary() == null) {
            messages.remove(); // Remove empty messages.
        }
    }
}

Related:


Unrelated to the concrete problem, there's in JSF2 by the way an other way to highlight invalid fields. You can do it by the new implicit #{component} variable in EL:

<h:inputText styleClass="#{component.valid ? '' : 'error'}" />
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top