Easy Page Actions with Facelets

By , 28 August 2007

Easy Page Actions with Facelets

It appears that everybody has come up with their own way to implement page actions in JSF. i.e. execute some code before a page is processed/rendered, like you did in Struts. Well, it turns out that I occasionally need this too (surprisingly infrequently though), so I had a look at how Shale and Seam solve the problem.

Shale uses faces-config.xml to map view ids to managed beans which implement a ViewController interface [1]. Okay, that seems alright, but it means more xml and more classes. Often I just need to put a request-scope attribute in place, so this feels like too much work to me. Seam allows you to map view ids to method expressions via a pages.xml configuration file [2]. This means no extra beans, but does mean more configuration. Can it be done with no xml and no new beans?

Yes! And the tool for the job has probably been in your toolkit all along - Facelets TagHandlers.

Easy Page Actions with Facelets

There are two advantages to using TagHandlers to implement page actions:

  1. They are only executed once during the tree build, so don't interfere with the rest of the lifecycle or cause EL to be evaluated multiple times (like some components).
  2. There is no need to map the view id since the TagHandler is placed directly in the template for the view.

The Java code for the TagHandler is very simple:

/**
 * This tag evaluates a method expression, so it can be used to implement
 * 'page actions' since it is only executed when the view is built.
 */
public class ActionHandler extends TagHandler {
    private final TagAttribute method;
    
    public ActionHandler(TagConfig config) {
        super(config);
        this.method = this.getRequiredAttribute("method");
    }

    /** evaluate the method expression */
    public void apply(FaceletContext ctx, UIComponent parent) {
        method.getMethodExpression(ctx, null, new Class[] {}).
                invoke(ctx.getFacesContext().getELContext(), null);
    }
}

To use the action, you must map it in your facelets taglib:

...
  <tag>
    <tag-name>action</tag-name>
    <handler-class>furnace.core.tags.ActionHandler</handler-class>
  </tag>

and then put it into the template where it is needed, e.g.:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:fc="https://rogerkeays.com/furnace/core">
  
  <!-- merge the user into the current persistence context -->
  <fc:action method="${modules.forums.mergeUser}"/>
  
  <!-- then show their profile -->
  <h:outputText value="${user.name}"/>
  ...
</ui:composition>

You should note though, that the TagHandlers aren't invoked on a postback, so if this is a part of your requirements you could use a PhaseListener, or the Seam or Shale approach.

This code will be available in the next version of the Furnace Webapp Framework.

References

[1] http://shale.apache.org/shale-view/index.html
[2] http://docs.jboss.org/seam/1.2.1.GA/reference/en/html/events.html#d0e3776

About Roger Keays

Easy Page Actions with Facelets

Roger Keays is an artist, an engineer, and a student of life. He has no fixed address and has left footprints on 40-something different countries around the world. Roger is addicted to surfing. His other interests are music, psychology, languages, the proper use of semicolons, and finding good food.

Leave a Comment

Please visit https://rogerkeays.com/blog/easy-page-actions-with-facelets to add your comments.

Comment posted by: Shane Genschaw, 15 years ago

Tim, change it "invoke(ctx, null);" instead.  (FaceletsContext extends ELContext).

-Shane

Comment posted by: Tim Büthe, 16 years ago

Hi,

can I somehow make this work with JSF 1.1? The method getELContext is new in 1.2, so I get an error in this line:

invoke(ctx.getFacesContext().getELContext(), null);

regards,

Tim

Comment posted by: cp, 16 years ago

cool. tnx 

Comment posted by: , 16 years ago

The method you bind to can just have no parameters and a void return type, e.g:

public void mergeUser() {
   user = em.merge(user);
}