Best way to validate ajax updates in JSF 2.0?

2019-03-14 00:17发布

问题:

Our team is writing its first JSF 2.0 application since using Stripes for many years, and I have some questions on the best way to use the f:ajax tag and validate the input.

A lot of questions I've seen answered have a form with multiple inputs and then a submit button), but we would like to maintain individual input fields updated immediately upon change and persisted to the database (with no submit button. We had this working fine in Stripes using Prototype's Ajax.Request, but it was an extra step I'd like to avoid if possible.

Essentially we have a page with a bunch of inputs on it directly backed by beans, for example:

<h:inputText id="name" value="#{personController.name}" >
    <f:ajax listener="#{personController.ajax}" />
</h:inputText>

As you may know, by the time the listener is invoked, the value of name has already been changed on the bean. This would be convenient, but I have a few problems with it:

  • the listener doesn't obviously know which value of the bean was changed
  • the value has already been changed, I can't perform any server side validation on it
  • I don't know what the old value of name is even if I could perform some sort of validation on it, I wouldn't know what to set the value back to

Right now it's looking like we'll have to implement some kind of javascript middleman to take in what property changed and the new value, send that to the Controller, and have it perform validation, updating the database, sending back something to render, etc. But like I said, this is what we used to do with Stripes and I'd really like to use something more native.

I did see that if we wanted some kind of Submit button on the page we could use something like the valueChangeListener attribute, but I'd also like to avoid massive submits.

I included the OpenFaces tag because we're already using that for datatables, so if there's something nice in there we're open to using it. But as far as I can tell their o:ajax tag isn't that much more powerful than JSF's f:ajax.

Thanks!

回答1:

You're looking in the wrong direction to achieve the concrete functional requirement of validating an input field. You should use a normal JSF validator for this, not some ajax listener method which runs at the wrong moment (the INVOKE_ACTION phase instead of PROCESS_VALIDATIONS phase) and where you don't directly have a hand at the model value. The ajax listener method is merely to be used to execute some business logic based on the current model value(s).

JSF has several builtin validators behind the required attribute and several <f:validateXxx> tags. You can even create custom validators by implementing the Validator interface.

E.g. checking the requireness:

<h:inputText ... required="true">
    <f:ajax />
</h:inputText>

Or checking if it matches a pattern using one of the various <f:validateXxx> tags:

<h:inputText ...>
    <f:validateRegex pattern="[a-z]+" />
    <f:ajax />
</h:inputText>

Or using a custom validator:

<h:inputText ...>
    <f:validator validatorId="myValidator" />
    <f:ajax />
</h:inputText>

with

@FacesValidator("myValidator")
public class MyValidator implements Validator {

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) {
        if (value is not valid) {
            throw new ValidatorException(new FacesMessage(...));
        }
    }

}

The <f:ajax> is merely there to submit the current input field during the HTML DOM change event (or click event in case of checkboxes/radiobuttons). You don't necessarily need a <f:ajax listener> method in order to submit the current input field by ajax. If you want to hook on a value change event, just use valueChangeListener.

<h:inputText ... valueChangeListener="#{bean.valueChanged}">
    <f:ajax />
</h:inputText>

with

public void valueChanged(ValueChangeEvent event) {
    Object oldValue = event.getOldValue();
    Object newValue = event.getValue();
    UIComponent component = event.getComponent();
    // ...
}

Note that this will only be invoked when validation has passed on the particular component.