Wouter has posted 5 posts at DZone. View Full User Profile

Creating a Custom JSF 1.2 Component - With Facets, Resource Handling, Events and Listeners

10.07.2009
| 29646 views |
  • submit to reddit

Publishing a JSF event and supporting event listeners

When the shuffle (click) event has occurred and has been established by the ShufflerRenderer that in turn has notified the Shuffler component that takes appropriate action, it is very well possible that other interested parties would like to know about the event as well. A first step in the direction of publishing an event to interested parties is the introduction of a single attribute shuffleListener, a methodExpression that can be used to configure a listener method (very much like the valueChangeListener and actionListener attributes on input components and action components respectively. This attribute can be used as follows:

  <shf:shuffler styleClass="h1" facetOrder="random" id="s1"
shuffleListener="#{bean.shuffleEventHandler}"> ...

where the method shuffleEventHandler is implemented as follows:

    public void shuffleEventHandler(Shuffler source, String comment) {
System.out.println("Shuffle Event received - with message "+comment );
}

which is extremely unexciting of course. However, this method can be extended to create a List in some creative way based on the list of facets that is passed in.

In order to add this methodExpression type of attribute, we have to go through the same steps we saw before:

  • adding an attribute entry in the TLD
  • adding support for processing the attribute in the Tag Handler (a setter and a line of code in setProperties())
  • adding the attribute in saveState() and restoreState() in the Component class

TLD entry:

...
<attribute>
<name>shuffleListener</name>
<required>false</required>
<deferred-method>
<method-signature>void listener(nl.amis.jsf.shuffler.Shuffler ,
java.lang.String)</method-signature>
</deferred-method>
</attribute>
</tag>

Tag Handler:

    private MethodExpression shuffleListener;
protected void setProperties(UIComponent component) {
super.setProperties(component);
processProperty(component, styleClass, Shuffler.STYLECLASS_ATTRIBUTE_KEY);
processProperty(component, facetOrder, Shuffler.FACETORDER_ATTRIBUTE_KEY);
((Shuffler)component).setShuffleListener( shuffleListener); }

public void release() {
super.release();
styleClass= null;
facetOrder= null;
shuffleListener = null; }
    public void setShuffleProcessor(MethodExpression shuffleProcessor) {
this.shuffleProcessor = shuffleProcessor; }

saveState() and restoreState() in Shuffler:

    @Override
public Object saveState(FacesContext facesContext) {
Object values[] = new Object[4];
values[0] = super.saveState(facesContext);
values[1] = this.getAttributes().get(STYLECLASS_ATTRIBUTE_KEY);
values[2] = this.getAttributes().get(FACETORDER_ATTRIBUTE_KEY);
values[3] = this.shuffleListener; return values;
}

@Override
public void restoreState(FacesContext facesContext, Object state) {
Object values[] = (Object[])state;
super.restoreState(facesContext, values[0]);
this.getAttributes().put(STYLECLASS_ATTRIBUTE_KEY, values[1]);
this.getAttributes().put(FACETORDER_ATTRIBUTE_KEY, values[2]);
this.setShuffleListener((MethodExpression)values[3]);}

Then we have to add the code in the Shuffler Component class that actually calls the method.

    void notifyOfShuffleEvent(FacesContext facesContext) {
String facetOrder =
(String)getAttributes().get(Shuffler.FACETORDER_ATTRIBUTE_KEY);
facetOrder = "reverse".equalsIgnoreCase(facetOrder)?"normal":
("normal".equalsIgnoreCase(facetOrder)?"reverse":facetOrder);
getAttributes().put(Shuffler.FACETORDER_ATTRIBUTE_KEY, facetOrder);
if (getShuffleListener()!=null) {
try {
Object[] args = { this, "ShuffleEvent" };
getShuffleListener().invoke(facesContext.getELContext(), args);
}
catch (Exception e){}
}
}

This is one way to go about events and listeners. However, it is not the best way. For starters, we can only register a single listener in this fashion. We also have the event ‘published’ very early in the JSF lifecycle - during Apply Request Values (from the decode method). JavaServer Faces has a built in mechanism for dealing with events. We can leverage this standard facility in the following way:

  • implement the ShuffleEvent class that extends from the FacesEvent interface
  • create the ShuffleEventListener interface that extends from FacesListener
  • define the shuffleListener tag entry in the tld
  • implement the ShuffleEventListenerTag class to process shuffleEventListener tags in the jsp file
  • add the addShuffleEventListener() method in the Shuffler component class that can be used from the ShuffleEventListenerTag to register a ShuffleEventListener on the component
  • broadcast the ShuffleEvent through the JSF eventing infrastructure to all registered listeners

With the new shuffleEventListeners, we can add as many interested parties to Shuffler as we care to:

<shf:shuffler styleClass="h1" facetOrder="random" id="s1" shuffleProcessor="#{shuffleBean.specialShuffle}"
shuffleListener="#{bean.shuffleEventHandler}">
<shf:shuffleListener type="view.ShuffleListener"/>
<shf:shuffleListener type="view.SomeOtherShuffleListener"/>
<shf:shuffleListener type="view.AndYetAnotherOneShuffleListener"/>
<f:facet name="1">
... content
</f:facet>
<f:facet name="2">
... content
</f:facet>
<f:facet name="3">
... content
</f:facet>
</shf:shuffler>

The class view.ShuffleListener implements the ShuffleEventListener interface that is defined along with the JSF component. This implementation can be as simple as:

package view;

import nl.amis.jsf.shuffler.ShuffleEvent;
import nl.amis.jsf.shuffler.ShuffleEventListener;

public class ShuffleListener implements ShuffleEventListener {
public void processEvent(ShuffleEvent shuffleEvent) {
System.out.println("The listener reports a shuffle event!");
}
}

when the user clicks on the shuffle image and the event is published, an instance of ShuffleListener is created and its processEvent() method is invoked by the JSF event broadcast system. In this simple case, the listener will do nothing but write a message to the system output.

The implementation according to the list of steps discussed before:

Implement the ShuffleEvent class that extends from the FacesEvent interface

package nl.amis.jsf.shuffler;

import javax.faces.component.UIComponent;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;

public class ShuffleEvent extends FacesEvent {
public ShuffleEvent(UIComponent source) {
super(source);
setPhaseId(PhaseId.INVOKE_APPLICATION);
}

public boolean isAppropriateListener(FacesListener facesListener) {
return (facesListener instanceof ShuffleEventListener);
}

public void processListener(FacesListener facesListener) {
((ShuffleEventListener)facesListener).processEvent(this);
}
}

Create the ShuffleEventListener interface that extends from FacesListener

package nl.amis.jsf.shuffler;

import javax.faces.event.FacesListener;

public interface ShuffleEventListener extends FacesListener{

public void processEvent( ShuffleEvent event) ;
}

TLD entry:

...
<tag>
<name>shuffleListener</name>
<tag-class>nl.amis.jsf.shuffler.ShuffleEventListenerTag</tag-class>
<body-content>empty</body-content>
<attribute>
<description>
The fully qualified class name for the shuffle event listener.
</description>
<name>type</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>

The Tag Handler class ShuffleEventListenerTag- note that it extends TagSupport rather than UIComponentELTag. That is because in this case we do not want a proper JSF component to be added to the View tree based on the shuffleListener tags. For each tag, we will register a listener on the parent Shuffler component - that is our integration point with JSF in this case

package nl.amis.jsf.shuffler;

import javax.faces.component.UIComponent;
import javax.faces.webapp.UIComponentClassicTagBase;
import javax.faces.webapp.UIComponentELTag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

public class ShuffleEventListenerTag extends TagSupport {

String shuffleEventListenerType;

/**
* @return SKIP_BODY, always
* @throws JspException if an error condition occurs
*/
public int doStartTag() throws JspException {
UIComponentClassicTagBase tag =
UIComponentELTag.getParentUIComponentClassicTagBase(pageContext);
if (tag == null)
throw new JspException("Not inside UIComponentTag");

if (tag.getCreated()) {
UIComponent component = tag.getComponentInstance();
if (component == null)
throw new JspException("Component instance is null");
if (!(component instanceof Shuffler))
throw new JspException("Component is not a Shuffler");

Shuffler shuffler = (Shuffler)component;
String className = shuffleEventListenerType;
ShuffleEventListener listener =
createShuffleEventListener(className);
shuffler.addShuffleEventListener(listener);
}
return (SKIP_BODY);
}

/**
* Sets the fully qualified class name of the
* shuffleEventListenerType instance to be created.
*
* @param type the class name
*/
public void setType(String type) {
shuffleEventListenerType = type;
}

protected ShuffleEventListener createShuffleEventListener(String className) throws JspException {
try {
ClassLoader loader =
Thread.currentThread().getContextClassLoader();
Class clazz = loader.loadClass(className);
return ((ShuffleEventListener)clazz.newInstance());
} catch (Exception e) {
throw new JspException(e);
}
}
}

Add the addShuffleEventListener() method in the Shuffler component class that can be used from the ShuffleEventListenerTag to register a ShuffleEventListener on the component

    public void addShuffleEventListener(ShuffleEventListener listener) {
addFacesListener(listener);
}

Broadcast the ShuffleEvent through the JSF eventing infrastructure to all registered listeners

    void notifyOfShuffleEvent(FacesContext facesContext) {
String facetOrder =
(String)getAttributes().get(Shuffler.FACETORDER_ATTRIBUTE_KEY);
facetOrder = "reverse".equalsIgnoreCase(facetOrder)?"normal"
:("normal".equalsIgnoreCase(facetOrder)?"reverse":facetOrder);
getAttributes().put(Shuffler.FACETORDER_ATTRIBUTE_KEY, facetOrder);
if (getShuffleListener()!=null) {
try {
Object[] args = { this, "ShuffleEvent" };
getShuffleListener().invoke(facesContext.getELContext(), args);
}
catch (Exception e){}
}
// queue the event to be broadcast at the indicated phase
// (inside the ShuffleEvent, invoke application) to the registered listeners
ShuffleEvent shuffleEvent = new ShuffleEvent(this);
shuffleEvent.queue();
}

Registration of a custom shuffle-processor MethodExpression

Sometimes we may want to pass a MethodExpression in one of the attributes on a custom JSF component. For example to provide the component with a method it can call in order to pre or post process some values. In our Shuffler example, we will add a shuffleProcessor attribute. This attribute can be set to a methodExpression that refers to a method with a specific signature: java.util.List processor(java.util.List). That means: a method that a List as an input parameter and that returns a List as result. This method will be called by the Shuffler to have the list of facets re-ordered by the externally supplied method that may implement other shuffle patterns besides normal, reverse and random. This attribute can be used as follows:

  <shf:shuffler styleClass="h1" facetOrder="random" id="s1"
shuffleProcessor="#{shuffleBean.specialShuffle}"
shuffleListener="#{bean.shuffleEventHandler}">
...

where the method specialShuffle is implemented as follows:

    public List<UIComponent> specialShuffle( List<UIComponent>  facetList){
return facetList;
}

which is extremely unexciting of course. However, this method can be extended to create a List in some creative way based on the list of facets that is passed in.

In order to add this methodExpression type of attribute, we have to go through the same steps we saw before:

  • adding an attribute entry in the TLD
  • adding support for processing the attribute in the Tag Handler (a setter and a line of code in setProperties())
  • adding the attribute in saveState() and restoreState() in the Component class

TLD entry:

...
<attribute>
<name>shuffleProcessor</name>
<required>false</required>
<deferred-method>
<method-signature>java.util.List processor(java.util.List)</method-signature>
</deferred-method>
</attribute>
</tag>

Tag Handler:

   private MethodExpression shuffleProcessor;
protected void setProperties(UIComponent component) {
super.setProperties(component);
processProperty(component, styleClass, Shuffler.STYLECLASS_ATTRIBUTE_KEY);
processProperty(component, facetOrder, Shuffler.FACETORDER_ATTRIBUTE_KEY);
((Shuffler)component).setShuffleListener( shuffleListener);
((Shuffler)component).setShuffleProcessor( shuffleProcessor); }

public void release() {
super.release();
styleClass= null;
facetOrder= null;
shuffleListener = null;
shuffleProcessor = null; }

public void setShuffleProcessor(MethodExpression shuffleProcessor) {
this.shuffleProcessor = shuffleProcessor;
}

saveState() and restoreState() in Shuffler:

    @Override
public Object saveState(FacesContext facesContext) {
Object values[] = new Object[5];
values[0] = super.saveState(facesContext);
values[1] = this.getAttributes().get(STYLECLASS_ATTRIBUTE_KEY);
values[2] = this.getAttributes().get(FACETORDER_ATTRIBUTE_KEY);
values[3] = this.shuffleListener;
values[4] = this.shuffleProcessor; return values;
}

@Override
public void restoreState(FacesContext facesContext, Object state) {
Object values[] = (Object[])state;
super.restoreState(facesContext, values[0]);
this.getAttributes().put(STYLECLASS_ATTRIBUTE_KEY, values[1]);
this.getAttributes().put(FACETORDER_ATTRIBUTE_KEY, values[2]);
this.setShuffleListener((MethodExpression)values[3]);
this.setShuffleProcessor((MethodExpression)values[4]); }

Then we have to add the code in the Shuffler Component class that actually calls the method.

    public List<UIComponent> getOrderedFacets(FacesContext facesContext) {
... same as before
// if a shuffleProcessor is configured, we need to invoke it to provide us with the list of facets in the order in which to render them
// not that strictly speaking the shuffleProcessor can decide to leave out certain facets for whatever reason
if (shuffleProcessor != null) {
try {
Object[] args = { orderedFacets };
orderedFacets =
(List<UIComponent>)shuffleProcessor.invoke(facesContext.getELContext(),
args);
} catch (Exception e) {
}
}
return orderedFacets;
}

Deploying the Custom Component

The custom component is deployed in a JAR file - just like for example any other bunch of custom JSP tags.Use your favorite build tool for constructing the JAR file. It should look something like this:

Consume the Custom JSF Component:

In your JSF 1.2 web application, add the JAR file created during deployment as a tag library. To include the Shuffler in a page, add components from the tag-library to a JSF page (the taglib’;s namespace should be registered in the header of the JSP(X) file) and configure its attributes, facets and listeners

A snippet from a page that is using the Shuffler:

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:shf="/nl.amis.jsf/ShufflerLib">
<jsp:directive.page contentType="text/html;charset=windows-1252"/>
<f:view>
<af:document id="d1">
<af:form id="f1">
<af:panelBox text="PanelBox1" id="pb1">
<shf:shuffler styleClass="h1" facetOrder="random" id="s1" shuffleProcessor="#{shuffleBean.specialShuffle}"
shuffleListener="#{bean.shuffleEventHandler}">
<f:facet name="1">
<af:commandButton text="commandButton 1" id="cb1"/>
</f:facet>
<f:facet name="3">
<af:commandButton text="commandButton 2" id="cb2"/>
</f:facet>
...
<shf:shuffleListener type="view.ShuffleListener"/>
</shf:shuffler>
</af:panelBox>
</af:form>
</af:document>
</f:view>
</jsp:root>


In this example, the application has registered a bean.shuffleEventHandler method expression (on the attribute shuffleListener), a shuffleBean.specialShuffle method expression (on the attribute shuffleProcessor) and a shuffleListener event listener child component. These methods and the view.ShuffleListener have to be implemented - as was already discussed earlier in this article.

Resources

Download the JAR file for the custom Shuffler component- for use in your own project (as if….): ShufflerLib.zip.

Download JDeveloper 11g Application with Shuffler Component: Shufflercustomcomponent_jdeveloper11gapplication.zip

Download JDeveloper 11g Application that consumes the Shuffler and uses it in an ADF Faces 11g RC web application: Customjsfconsumer_jdeveloper11gapplication.zip.

Pro JSF and Ajax: Building Rich Internet Components -  John R. Fallows , Jonas Jacobi, APRESS,  ISBN13: 978-1-59059-580-0 -

Java Server Faces; The Complete Reference - Chris Schalk, Ed Burns, James Holmes - McGrawHill - ISBN:  9780072262407 - 

IBM Developer Works - Craft Ajax applications using JSF with CSS and JavaScript

This article was originally publishedat the AMIS Technology Weblog.

Published at DZone with permission of its author, Wouter van Reeven.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Jacek Furmankiewicz replied on Wed, 2009/10/07 - 12:25pm

Don't get me wrong, it's a nice article...but I think I'd rather just use jQuery UI which can do custom components using a plugin and 1 line of Javascript.

I think instead of looking at these heavy server-side frameworks Java programmers should look at jQuery UI instead (which makes modern Javascript easy) and just expose data from the server-side Java apps via REST or something like that.

Here's the jquery code to turn an order list of divs into a proper accordion component

<script type="text/javascript">
$(function() {
$("#accordion").accordion();
});
</script

 http://jqueryui.com/demos/accordion/

Compared to that, the JSF 1.2 approach looks insanely compilicated.

 

 

Nicolas Frankel replied on Thu, 2009/10/08 - 3:00am in response to: Jacek Furmankiewicz

Jacek,

Executing code client-side or server-side is a major architectural decision that you don't have the luxury to make! For most clients I have worked for, these choices were made before I came. It has nothing to do with this article whatsoever.

Besides, you'll get the same problem in developing from scratch for whatever framework you're using (Swing, .Net, what have you) if you don't have the component you want when needed, which is a common occurence. Making reusable components libraries for your organization is a big step in speeding up development.

I must concede though that JSF architecture favors ease of use over ease of development so new components are a bit complex to develop.

Michael Mosmann replied on Fri, 2009/10/09 - 2:54am in response to: Nicolas Frankel

Hi,

> Executing code client-side or server-side is a major architectural decision that you don't have the luxury to make!

And if you have the luxury sometimes you should not...

> Besides, you'll get the same problem in developing from scratch for whatever framework you're using (Swing, .Net, what have you) if you don't have the component you want when needed, which is a common occurence.

Sure.. but: JSF makes this too heavy.. 

I wonder if anyone has fun in doing component development with JSF. It's a pain and i see no chance that JSF2 can reduce this much. I do not know any UI-Framework which makes this more painfull.

> Making reusable components libraries for your organization is a big step in speeding up development.

Then anybody should use a framework which has its strength in this particular aspect.Why should anybody use JSF? I don't get it.

> I must concede though that JSF architecture favors ease of use over ease of development so new components are a bit complex to develop.

There are ways out of this.. wicket, gwt, vaadin.

Bill Bryson replied on Sat, 2009/10/10 - 11:12am

A good well written article but also a reminder why I'm never going to willingly go near a JSF project again.

Jose Maria Arranz replied on Mon, 2009/10/12 - 4:53am

@nfrankel: Besides, you'll get the same problem in developing from scratch for whatever framework you're using (Swing, .Net, what have you) if you don't have the component you want when needed, which is a common occurence.

Really?

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.