Thursday, March 22, 2007

Setting the var attribute of dataTable in a facelet

I'm currently working on a small little facelets suite to aid in portlet development. Initially, the idea is just to focus on making correct use of the portlet CSS styles implicit. So, for example, the following is the kind of code needed to set the portlet tables styles for the h:dataTable JSF component:


<h:dataTable value="#{mybean.myData}" var="row"
styleClass="portlet-section-body"
rowClasses="portlet-section-body,portlet-section-alternate"
headerClass="portlet-section-header">


So, I'd like to replace that with a facelet instead, so that it looks like this:


<p:dataTable value="#{mybean.myData}" var="row">


And the facelet would be defined once in a .xhtml file as:


<ui:composition>
<h:dataTable value="#{value}" var="#{var}"
styleClass="portlet-section-body"
rowClasses="portlet-section-body,portlet-section-alternate"
headerClass="portlet-section-header">
<ui:insert />
</h:dataTable>
</ui:composition>


However, this doesn't quite work. The problem is that it tries to set the var attribute of the dataTable component to a value expression, but dataTable requires a simple string for var. So we have to figure out some way of getting the var variable from the facelet into the dataTable component. What I came up with was a custom TagHandler that evaluates the value expression and then sets the var property of the dataTable component directly:


package portletfacelets;

import java.io.IOException;

import javax.el.ELException;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.html.HtmlDataTable;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.FaceletException;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;

public class SetVarHandler extends TagHandler {

private final TagAttribute var;

public SetVarHandler(TagConfig config) {
super(config);
this.var = this.getAttribute("var");
}
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException, FacesException, FaceletException, ELException {

System.out.println("var=" + this.var.getValue(ctx));

if (parent instanceof HtmlDataTable) {
HtmlDataTable table = (HtmlDataTable) parent;
table.setVar(this.var.getValue(ctx));
}


this.nextHandler.apply(ctx, parent);
}

}


I add this to my facelets taglib.xml file as such:


<tag>
<tag-name>setVar</tag-name>
<handler-class>portletfacelets.SetVarHandler</handler-class>
</tag>


And then use it like this in my facelet:


<ui:composition>
<h:dataTable value="#{value}"
styleClass="portlet-section-body"
rowClasses="portlet-section-body,portlet-section-alternate"
headerClass="portlet-section-header">
<p:setVar var="#{var}" />
<ui:insert />
</h:dataTable>
</ui:composition>


Voila! However, seeing as this might come in handy in future instances, I wondered if I couldn't make something a bit more generic. So I created this TagHandler:


package portletfacelets;

import java.io.IOException;
import java.lang.reflect.Method;

import javax.el.ELException;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.FaceletException;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;

public class SetValueHandler extends TagHandler {

private final TagAttribute methodName;
private final TagAttribute value;

public SetValueHandler(TagConfig config) {
super(config);
this.methodName = this.getRequiredAttribute("methodName");
this.value = this.getRequiredAttribute("value");
}
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException, FacesException, FaceletException, ELException {
try {

Method m = parent.getClass().getMethod(this.methodName.getValue(ctx),
new Class[]{String.class});
m.invoke(parent, new Object[]{this.value.getValue(ctx)});

} catch (Exception e) {
e.printStackTrace();
}

this.nextHandler.apply(ctx, parent);
}

}


So I'm using reflection to call any method on the parent JSF component and passing in the specified value. Then I use it in my facelet like so:


<ui:composition>
<h:dataTable value="#{value}"
styleClass="portlet-section-body"
rowClasses="portlet-section-body,portlet-section-alternate"
headerClass="portlet-section-header">
<p:setValue methodName="setVar" value="#{var}" />
<ui:insert />
</h:dataTable>
</ui:composition>


There you have it. Turns out there is more than one way to skin this cat, so I'll also share in a future blog post a different approach to this issue.

8 comments:

Yoann Ciabaud said...

Thank you, I was having the same problem and you gave me an elegant solution!

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Unknown said...

hi..
have u ever tried 2 datatable facelets composition in 1 xhtml ?

for example..
< k:table id="x" value="null" >
< k:column title="A" >
< h:outputText value="a"/ >
< /k:column >
< /k:table >
< k:table id="y" value="null" >
< k:column title="B" >
< h:outputText value="b"/ >
< /k:column >
< /k:table >

renders 1 table with 2 columns(A,B) and 1 row (a,b) ??

any idea ?

thanks..

Luke said...

Thankyou! This solution saved me a lot of mucking around!

Just a heads up for anybody trying to wrap a RichData table, if you use the SetVarHandler code, be sure you import org.richfaces.component.html.HtmlDataTable instead of the JSF one. Otherwise the instanceof will evaluate to false.

Luke said...

I've enhanced the code a bit more - I didn't like the reflection approach, because it relies on knowing the java API for the tag you are trying to modify. Instead I found you can just change the attribute on the tag directly like so:


public class SetTagAttributeHandler extends TagHandler {

private TagAttribute value;
private TagAttribute attribute;

public SetTagAttributeHandler(TagConfig config) {
super(config);
this.value = this.getAttribute("value");
this.attribute = this.getAttribute("attribute");
}
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException, FacesException, FaceletException, ELException {

if (parent instanceof org.richfaces.component.html.HtmlDataTable) {
org.richfaces.component.html.HtmlDataTable table = (
org.richfaces.component.html.HtmlDataTable) parent;

table.getAttributes().put(attribute.getValue(ctx), value.getValue(ctx));
}

this.nextHandler.apply(ctx, parent);
}
}


No reflection needed or knowledge of the tag java classes needed! This is helpful because the are a number of attributes that it would be useful to set to EL, but you cannot.

Johan Arnaud said...

A step further from paradox's code, no need to cast or to limit the functionality to datatable.


public class SetTagAttributeHandler extends TagHandler {

private final TagAttribute attribute;
private final TagAttribute value;

public SetTagAttributeHandler(TagConfig config) {
super(config);
this.attribute = this.getAttribute("attribute");
this.value = this.getAttribute("value");
}

@Override
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException, FacesException, FaceletException, ELException {

parent.getAttributes().put(attribute.getValue(ctx), value.getValue(ctx));

this.nextHandler.apply(ctx, parent);
}