Frage

In the bad old days in my codebase we relied quite heavily on event requeuing, which I suspect worked due to implementation details in ICEfaces or MyFaces rather than standard-specified behavior. One thing we used to do frequently was this kind of thing:

<ice:inputText value="#{bb.frequency}" valueChangeListener="#{bb.valueChanged}"/>

The goal is to arrange for retune to be called after setFrequency whenever the frequency changes.

Then we had some fairly disgusting code in the backing bean which would requeue the event. It usually looked something like this:

class BB {
  // this happens first, thanks to UPDATE_MODEL_VALUES
  public void setFrequency(Frequency f) {
    this.model.f = f;
  }

  public void valueChanged(ValueChangeEvent event) {
    if (event.getOldValue().equals(event.getNewValue())
      return; // nothing changed, so leave

    if (FacesContext.getCurrentInstance().getPhaseId() != INVOKE_APPLICATION) {
      OurMagicEventUtils.requeueEvent(event, INVOKE_APPLICATION);
    }
    else {
      // do the post-setter work here (the setter happened recently during 
      // UPDATE_MODEL_VALUES so we're up-to-date by here
      this.model.retune();
    }
  }
}

This isn't a good way to live. I haven't found a reliable way to requeue events for later phases and it clearly isn't the kind of thing people do. I see two solutions:

  1. Move the retune intelligence to the BB#setFrequency method.

    I can't get away with this in many cases because I'm directly addressing a lower-level model class and I don't want to disturb its behavior for other clients.

  2. Create a custom component and move the logic into the setFoo method there.

    I don't love this because there are a lot of issues with Mojarra and custom components when embedded in other containers. It also seems like overkill for what I need to do—I literally just need to call retune after setting some properties.

  3. Create backing beans for everything. Delegate most methods directly to the inner thing, but catch setFoo and perform the retune there. This is very similar to what we used to do, and it means a lot of boilerplate, wrappers, and glue code, so I don't love it.

In my mind I imagine something like this:

<ice:inputText value="#{bb.frequency}" afterChange=#{bb.retune}"/>

but that obviously doesn't work, nor would attaching an <f:actionListener> since that requires a class name but has no association to whatever you're currently doing, and besides it can only be set on UICommands which UIInputs are not.

What's the elegant/correct way to solve this dilemma?

War es hilfreich?

Lösung

As you're using JSF2 already, just use <f:ajax>.

<ice:inputText value="#{bb.frequency}">
    <f:ajax listener="#{bb.retune}"/>
</ice:inputText>

with

public void retune(AjaxBehaviorEvent event) { // Note: the argument is optional.
    // ...
}

This will be invoked during invoke action phase when the HTML DOM change event has occured.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top