Mixing custom tag with facelet ui taglibs 

Joined:
08/13/2009
Posts:
164

November 21, 2011 15:57:49    Last update: November 22, 2011 09:51:26
The improved custom taglib works with existing facelet ui taglibs. For example:
<ui:param name="theName" value="John"/>
<my:hello name="#{theName}"/>

produces the expected output.

However, a problem exists with the ui:repeat tag:
<h3>With ui:repeat</h3>
<ui:repeat var="theName2" value="#{paramValues['name']}">
    <b>My Tag:</b> <my:hello name="#{theName2}"/>
    <p><b>Raw EL:</b> #{theName2}</p>
</ui:repeat>

When tested with a URL like:
http://localhost:8080/facelet-demo/?name=Zack&name=John

the raw EL prints out the correct names, but my custom tag substitutes empty string for theName2!

In theory, the response is rendered in the Render Response phase, way after the Apply Request Values phase, actual values should be available to EL. The answer to this anomaly turned out to be very deep! Yes, right there in the code! I would consider this a bug in facelets implementation, but the JSF spec did not tell what the expected behavior should be.

In my custom tag, the EL is evaluated when apply() is called, but the ui:repeat tag sets the variable in encodeChildren(), which happens after apply() returns.

To make our custom tag work with ui:repeat, we need to delay the EL evaluation. Change the HelloTagHandler to:
package com.example;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagHandler;
import javax.faces.view.facelets.TagConfig;

/**
 * Custom facelet tag that says hello.
 */
public class HelloTagHandler extends TagHandler {
    private String name = "Anonymous";

    public HelloTagHandler(TagConfig config) {
	super(config);
    }

    public void apply(final FaceletContext context, UIComponent parent) 
		throws IOException {
	final TagAttribute a = tag.getAttributes().get("name");

	UIComponentBase c = new UIComponentBase() {
	    public void encodeEnd(FacesContext ctx) throws IOException {
		if (a != null) {
		    name = a.getValue(context);
		}

		ResponseWriter w = ctx.getResponseWriter();
		w.write(String.format(
		    "<p>Hello %s! I am FaceletTag.</p>",
		    name
		));
	    }

	    // abstract method in base, must override
	    public String getFamily() {
		return "com.example.facelettag.test";
	    }
	};
	parent.getChildren().add(c);
    }
}


Alternative implementation:
package com.example;

import java.io.IOException;

import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagHandler;
import javax.faces.view.facelets.TagConfig;

/**
 * Custom facelet tag that says hello.
 */
public class HelloTagHandler extends TagHandler {
    private TagAttribute nameAttr;

    public HelloTagHandler(TagConfig config) {
	super(config);
	nameAttr = getAttribute("name");
    }

    public void apply(FaceletContext context, UIComponent parent) 
		throws IOException {
	final ValueExpression vex = (this.nameAttr == null) ? null :
				    this.nameAttr.getValueExpression(
					context,
					Object.class
				    );
	UIComponentBase c = new UIComponentBase() {
	    public void encodeEnd(FacesContext ctx) throws IOException {
		ResponseWriter w = ctx.getResponseWriter();
		w.write(String.format(
		    "<p>Hello %s! I am FaceletTag.</p>",
		    (vex == null) ? "Anonymous" : vex.getValue(ctx.getELContext())
		));
	    }

	    // abstract method in base, must override
	    public String getFamily() {
		return "com.example.facelettag.test";
	    }
	};
	parent.getChildren().add(c);
    }
}

Share |
| Comment  | Tags