1

For a forms framework in which I like to use JSF as the real UI frontend, I am searching for a way that a parent component gets informed if in a child component the value is changed. The facelet of a basic 'control' looks like this(body/head omitted since no-one can run it anyway without a dozen classes):

<xf:input ref="/my/xpath/value">
    <xf:label>Label</xf:label>
</xf:input>

The xf:input component which I developed, dynamically creates a real ui component (PrimeFaces ones) based on the type of the value that ref="/my/xpath/value" points to. This real ui component is created in a preRenderView event like is done in this example. It is handled in the following method in the 'parent' control

@Override
public void processEvent(SystemEvent event) throws AbortProcessingException {

    FacesContext context = FacesContext.getCurrentInstance();

    if (!context.isPostback()) {
        control = createControl(context);
//context.getApplication().unsubscribeFromEvent(PostValidateEvent.class, getControl().getClass(), this);
        control.subscribeToEvent(PostValidateEvent.class, this);
    }
}

The actual controls all have an programmatically added ajax handler added to it, which makes it possible to just process the specific input ('implicit ajax'). Default JSF component validations are normally applied and this all works great.

The issue/challenge is that in this 'wrapper' component I'd like to be informed of value changes after the validation. My first idea was to us the subscribeEvent on the dynamically added control like this:

control.subscribeToEvent(PostValidateEvent.class, this);

The subscribing works, but on the postback, an NPE is thrown in the UIComponent (Mojarra 2.2.9) because the wrapped is null in the following method

public boolean isListenerForSource(Object component) {

    if (wrapped instanceof SystemEventListener) {
        return ((SystemEventListener) wrapped).isListenerForSource(component);
    } else {
        return instanceClass.isAssignableFrom(component.getClass());
    }

}

This might be because the actual component seems to be newly created when the data is submitted en hence the 'subscription' is lost.

Registering on the ViewRoot does not work since the source of the event is always the ViewRoot and registering on the Application is plain wrong.

It might be that I'm looking for a solution in the wrong direction but for now I'm clueless. Keep in mind that I have no direct control over the created ui controls, nor do I want to override their renderers if I can prevent to. So signalling the parent from the child control is not an option to.

Other things I tried:

  • Using valueChangeListeners but that did not work either with lots of other problems (including ways to make it extensible)
  • Using composite components with binding but that failed including them dynamically, requiring naming containers that conflict with the id's required by the rest of the framework, the positions of labels, hints and alerts in the xhtml and/or resulting dom
  • Taghandlers to manipulate the tree when creating them

This all is with Mojarra up to 2.2.9 (did not check newer yet or MyFaces)

Kukeltje
  • 12,223
  • 4
  • 24
  • 47
  • Components are best to be created during view build time. The `preRenderView` should be a `postAddToView`. Have you tried it? – BalusC Mar 20 '15 at 07:31
  • Yep, I can sort of understand this, but all the serious examples I found (including the posts from Kennard consulting about MetaWidget) do it this way. Although now I search for it, I see [one that uses the postAddToView](http://www.beyondjava.net/blog/change-jsf-2-2-component-tree-tagdecorators-taghandlers/). Let me try... – Kukeltje Mar 20 '15 at 11:33
  • @BalusC: I tried changing it to a `PostAddToView` event and then there is no NPE in `UIComponent` on the `PostValidateEvent`, but there is no event at all anymore :-(... There is an even bigger difference, the dynamically added child is totally lost, at least when dumping the children in the `PostRestoreStateEvent`. It is just not there anymore. I then thought of adding the `postValidateEvent` to the control in the PostRestoreStateEvent cause that is the moment I actually need it. Remarkably it works, but only once. On the second request to the same component the NPE is there again. – Kukeltje Mar 20 '15 at 15:34
  • `postAddToView` of which component? It should be the one where you'd like to hook the dynamically created child to, thus that `` and not e.g. `UIViewRoot` or some other parent or so. In XHTML terms, that would be ``. See also a.o. option #2 of http://stackoverflow.com/questions/3510614/how-to-create-dynamic-jsf-1-2-form-fields/3522489#3522489 for an example. – BalusC Mar 20 '15 at 15:52
  • In custom component terms, that would be adding `@ListenerFor(systemEventClass=PostAddToViewEvent.class)` and doing the job in `processEvent(ComponentSystemEvent)` on `event instanceof PostAddToView`. Let me know if that indeed does it, then I'll add an answer. – BalusC Mar 20 '15 at 16:00
  • @BalusC Let me check, but in the mean time, I found out that everything works (using the PreRenderView) if I turn off partial state saving. I was triggered by this for two reasons. One was a [comment by Arjan Tijms](http://blog.kennardconsulting.com/2010/10/safely-manipulating-component-tree-with.html) and the second thing was that I tried a `control.unsubscribeEvent(....)` and got a state saving error. – Kukeltje Mar 20 '15 at 16:06
  • Well, dynamically creating components using Java is generally a pain. It's still the best to just use tagfiles and taghandlers (JSTL!) for that. You end up with clear XML instead of verbose Java code. – BalusC Mar 20 '15 at 16:06
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/73429/discussion-between-kukeltje-and-balusc). – Kukeltje Mar 20 '15 at 16:07

1 Answers1

2

Adding the component in the PreRenderViewEvent works nicely. The thing is that you do not seem to be able to have subscriptions to events survive a request. The actual components are recreated (in the RestoreViewPhase I assume, did not check) and then the subscription to the event is still there, just the 'wrapped' context where it should be called is empty.

Adding the PostValidationEvent event in the PostRestoreStateEvent of this specific component (it is the only one in the FacesContext.getCurrentInstance().getPartialViewContext().getExecuteIds()) makes it fire as mentioned in the comments. The trick (hack/workaround/...) to get rid of the NPE in the next request is to actually remove the event again.

((UIComponent) event.getSource()).unsubscribeFromEvent(PostValidateEvent.class, this);

I'll try to create an example without any PrimeFaces or OmniFaces and see what happens then since they both seem to be wrappers around the context and I want to make sure they are not the cause of the behaviour.

Kukeltje
  • 12,223
  • 4
  • 24
  • 47