How to Create Event Interceptors in JSF Composite Components
JSF composite components can be a powerful weapon when wielded properly. Learn how to get started using this great piece of tech!
Join the DZone community and get the full member experience.
Join For FreeJSF composite components are one of the most interesting features we find when using JSF. They allow us to create new components in our application providing cohesion, defining common behavior, styles, and a better structure. It is a perfect way to make better and bigger apps easier.
Components Interface — Events
One of the most important sections in the component definition is the interface section. Within this section, we will define the way the component is revealed to a host page, a template, or even other components. In this section we'll decide how we interact with it, modifying its aspect or behavior.
We can define properties or methods, inject into some of the pieces of our composite, or define other properties or methods we use to modify our component in a more general way.
In this article, it is explained how we can control the events and methods defined in our components with event interceptors, allowing or preventing their execution based in some logic, acting as prehooks or posthooks on it. The idea is to refine component behavior, exposing a complex interface which allows pages to use the component with a full range of possibilities.
As an example, let's see a component definition...
<composite:interface>
<composite:attributename="name"/>
<composite:attributename="value"/>
<composite:attributename="textForSubmit"/>
<composite:attributename="registerActionListener"
method-signature="void action(javax.faces.event.ActionEvent)"/>
[...]
<composite:interface>
<composite:implementation>
[...]
<h:commandButton value="test"
actionListener="#{cc.attrs.registerActionListener}">
[...]
<composite:implementation>
The interface defines an event which will be invoked when the h:commandButton
defined in the implementation section invokes the action. In particular, the action listener is used.
In this case, the event in the parameter, registerAction
, is defined when the component is instantiated in a page and is directly invoked by the h:commandButton
when the button is clicked.
What We Want
We want to intercept the event defined in the interface of the component, execute our own component code before the instance code and after the instance code if needed.
In this way, we can add functionality to the component not only related to the composite components but to our defined component itself.
Let’s See How We Do It
We define the event in the backing bean of the component and this event is invoked from the definition.
<composite:interface>
<composite:attributename="name"/>
<composite:attributename="value"/>
<composite:attributename="textForSubmit"/>
<composite:attributename="registerActionListener"
method-signature="void action(javax.faces.event.ActionEvent)"/>
[…]
<composite:interface>
<composite:implementation>
[...]
<h:commandButton value="test"
action="#{cc.ourComponentEventInterceptor}">
[...]
<composite:implementation>
@FacesComponent(value="myComponent")
public class MyComponent {
[...]
public void preValidateHandlerAndInvocation(ActionEvent ae) {
try {
// Get method and arguments for listener defined in component.
ContextualCompositeMethodExpression meth = (ContextualCompositeMethodExpression) this.getAttributes().get("registerAction");
// My code prehook.
[...]
// Listener invocation.
meth.invoke(FacesContext.getCurrentInstance().getELContext(), objs);
// My code after invocation
[...]
} catch (Exceptions e) {
LOGGER.log(Level.SEVERE, "Error when processing.", e);
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR,"error Message",null);
FacesContext.getCurrentInstance().addMessage(this.getClientId(), msg);
}
}
[...]
}
The event defined by the user is caught in the variable meth
and invoked dynamically after the component code. This method could be invoked under certain conditions, so we can make the invocation completely conditional.
How can we prevent the execution of the instance event? We use AbortProcessingException
to abort execution in any circumstances, or if we want or only invoke under certain conditions, we can invoke after verifications.
To communicate with the component from the backing bean we can use FacesMessage
in the current FacesContext
attached to our component and allow the page to re-update.
<ui:fragment rendered="#{(not empty facesContext.getMessageList(cc.clientId) and
not empty facesContext.getMessageList(cc.clientId).get(0).getSummary()}">
[Renders error message]
</ui:fragment>
Why Don't We use an Action Listener to Hook an Event and Publish an Action to Instance Defined Events?
As per their definition, action events are used when the action on the component provokes navigation on the Faces navigation defined in the faces-config.xml file (or direct URL redirection). In so many cases, this is not the goal of a button in an AJAX application, where some actions only update some parts of the page as a result of an interaction. In this case, an action with an empty String is returned as result of an action.
The actionListener
is used to prehook an event as we are doing here. But our goal is to allow the component actionlistener
to be used in component instances.
This can happen, usually, if we use third party components where the actionListener
has a special importance (by example the fileUpload
event in a primefaces
component). If we want to prehook those events as composite components and also be able to publish in the interface, we need an approach like this one.
Why Do We Not Use a Validator?
Validation is a specific functionality in JSF that can prevent an event execution or form submission based on data validation. These validators are executed in an earlier stage of the JSF lifecycle. So, it is something completely different and not related to our purpose.
Conclusions
With the goal of obtaining better, complex ,and more specific components to our libraries or applications, this approach can help us to add some functionalities such as logging, audits, and behavior control, thus allowing us to adapt our components more precisely to our requirements, moving logic from the use of the component to the definition of the component itself. I think this can be useful in so many cases and, of course, if we use components for more than creating 'templates' to include in our pages.
Opinions expressed by DZone contributors are their own.
Comments