Problem Solver

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. Since he left Australia in 2009, he has been living as a digital nomad in over 40 different countries around the world. Roger is addicted to surfing. His other interests are music, psychology, languages, and finding good food. Click here to subscribe to his weekly blog, or stalk him on Facebook and Twitter.

Leave a Comment

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

Comment posted by: Shane Genschaw, 7 years ago

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

-Shane

Comment posted by: Tim Büthe, 8 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, 8 years ago

cool. tnx 

Comment posted by: , 8 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);
}

Join Over 1000 Subscribers

I write every Sunday about travel, psychology, and technology. Thousands of people just like you have already subscribed—and for good reason. It'll change your life. And it's free :)

Get My New Book

“A spellbinding true story of love, passion and adventure. One can’t help but be swept away by 100% Love Guaranteed.” —Dr Tammie Matson, author of Elephant Dance.

Chat For A While

Have Your Say

I want to read more about ...
Travel
Psychology
Technology
Productivity
Investment
Dating