4

I am trying to implement an audit trail functionality for my web application that records:

  • lastModified (timestamp)
  • modifiedBy (user information)
  • userComment (reason for value change)

for each of my input fields (input fields are spread over several forms with different backing beans and different valueHolder classes).

The first two (lastModified and modifiedBy) are easily done with the help of an JPA AuditListener and @PrePersit and @PreUpdate methods.

The third one is a bit tricky since it requires user interaction. Best would be a dialog that asks for the user comment.

So there are (at least) two open issues: Can I establish a "global" valueChangeListener for all input fields in my application? Is this possible without attaching <f:valueChangeListener> to each single input component? Second: How can I grab the user comment. My idea is to put a p:dialog in my web page template but this dialog needs to know from which input component it is called.

Matt Handy
  • 29,855
  • 2
  • 89
  • 112

1 Answers1

4

Can I establish a "global" valueChangeListener for all input fields in my application? Is this possible without attaching to each single input component?

Yes, with a SystemEventListener which get executed during PreRenderViewEvent. You need to walk through the component tree as obtained by FacesContext#getViewRoot() to find all components which are an instanceofEditableValueHolder (or something more finer-grained) and then add the new YourValueChangeListener() by the addValueChangeListener() method. See also this answer how to register the system event listener: How to apply a JSF2 phaselistener after viewroot gets built?


Second: How can I grab the user comment. My idea is to put a p:dialog in my web page template but this dialog needs to know from which input component it is called.

You could in YourValueChangeListener#processValueChange() set the component in question as a property of some request or view scoped which you grab by evaluateExpressionGet().

Recorder recorder = (Recorder) context.getApplication().evaluateExpressionGet(context, "#{recorder}", Recorder.class);
recorder.setComponent(event.getComponent());
// ...

It will execute the EL and auto-create the bean in its scope if necessary. The bean in turn should also hold the property representing the user comment. Finally, you can use it in your <p:dialog>.

<p>You have edited #{recorder.component.label}, please mention the reason:</p>
...
<h:inputText value="#{recorder.comment}" />
Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks a lot BalusC! The first part of your answer is clear. The second needs some clarification: Could you give a short example how to use `evaluateExpressionGet()`? – Matt Handy Sep 20 '11 at 06:32
  • Thanks, will try it. One short comment on the first part of your answer: the `UIViewRoot#getChildren()` in my `PhaseListener` only gives one single child `UIInstructions`. Any idea, why? – Matt Handy Sep 20 '11 at 11:37
  • Oh, that was bad from me. I didn't realize that things in this area have changed in JSF2 (it works in JSF1). As per my colleague Arjan, you need a to register a system event listener for `PreRenderViewEvent`: http://stackoverflow.com/questions/7107296/how-to-apply-a-jsf2-phaselistener-after-viewroot-gets-built I'll update the answer. – BalusC Sep 20 '11 at 12:09
  • Component tree traversal now working! Thanks! (Second part has to wait until tomorrow;-) – Matt Handy Sep 20 '11 at 14:11
  • Just noticed that the component traversal approach will not work for input components inside an `h:dataTable`. I know that dataTables aren't expanded in component tree. (It is no problem for me since I only have a few. Added the valueChangeListener in facelet) – Matt Handy Sep 26 '11 at 20:06
  • What happens? Do you got nothing? Or do you get only one? (which should be just fine, there's also only one in the tree). – BalusC Sep 26 '11 at 20:07
  • Didn't trace it back yet. Just noticed that the valueChangeListener isn't attached for components inside the table. – Matt Handy Sep 26 '11 at 20:09