The standard JSF iteration tags tend to bury the HTML code in Java. And using a JSTL iteration across JSF tags can be problematic.
For my previous solution, I showed how an Apache MyFaces extension tag called "dataList" allows iteration over a list of objects, while still giving you reasonable HTML control with the f:verbatim tags.
Today I have a "still even more better" solution. I've written my own custom JSF tags that are drop-in replacements for the HTML tags that I want to use in my iteration. (In my case, the dl, dt, and dd HTML tags.)
Here is what my final JSP page looks like using my new tags:
<%@ taglib uri="/WEB-INF/myLib.tld" prefix="g" %>
.
.
.
<x:dataList rows="#{constants.PAGE_SIZE}"
value="#{myHandler.dataModel}"
var="dat" rowIndexVar="loop">
<g:dl>
<g:dt styleClass="number">
<h:outputText value="<a href='JavaScript:
document.mv.myFunc(#{loop});'>" escape="false"/>
<h:outputText value="#{dat.Id}"/>
<h:outputText value="</a>" escape="false"/>
</g:dt>
<g:dt styleClass="name">
<h:outputText value="#{dat.name}"/>
</g:dt>
<g:dd styleClass="distance">
<h:outputText value="#{dat.distance}"/>
<h:outputText value=" " escape="false"/>
</g:dd>
<g:dd styleClass="action">
<h:commandLink action="select"
actionListener="#{myHandler.select}">
<f:param name="locationId" value="#{dat.Id}"/>
<h:outputText value="Select" />
</h:commandLink>
</g:dd>
</g:dl>
</x:dataList>
This generates my list of widgets using dl, dt, and df tags instead of table, tr, td tags. So our Web Design team can easily edit this page and customize the HTML generated. They do have to know that "g:dt" is the same as a "dt" tag, but that's not much of a learning curve. At least this page looks more HTML-ish than it did before.
Here is how this works:
First we create a component that can render the tags. Since all three of my tags render the same way (dl, dt, and dd all have the same attributes), I chose to use just one render class for all three. I call it "DefinitionRenderer". And the tag class passes in an attribute called "tagType" that tells the renderer whether to render a "dl", "dt" or "dd" tag.
DefinitionRenderer
package com.mycompany.faces.components;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import java.io.IOException;
public class DefinitionRenderer extends UIOutput {
public void encodeBegin(FacesContext context)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
String tagType = (String)getAttributes().get("tagType");
writer.startElement(tagType, this);
writer.writeAttribute("id", getClientId(context), null);
String styleClass = (String) getAttributes().get("styleClass");
if (styleClass != null && !"".equals(styleClass))
writer.writeAttribute("class", styleClass, "styleClass");
String style = (String) getAttributes().get("style");
if (style != null && !"".equals(style))
writer.writeAttribute("style", style, "style");
}
public void encodeEnd(FacesContext context)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
String tagType = (String) getAttributes().get("tagType");
writer.endElement(tagType);
}
}
And then I define this component in my faces-config.xml file, and give it the name "definition" like so:
<component>
<component-type>definition</component-type>
<component-class>
com.mycompany.faces.components.DefinitionRenderer
</component-class>
</component>
And next, I need to make three tag classes, one for "dl", one for "dt" and one for "dd". After I coded these three classes, I noticed that had nearly identical code in all three, so I made one "parent" tag glass that all three inherit from. The parent is called "DefinitionTag". An even better solution would be if I could just have one tag class that could dynamically be "dl" or "dt" or "dd" depending on the JSP tag that originated the call. But as of yet I haven't found a way to accomplish that.:
DefinitionTag
package com.mycompany.faces.components;
import java.util.List;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;
public class DefinitionTag extends UIComponentTag {
private String styleClass = null;
public void setStyleClass(String styleClass) {
this.styleClass = styleClass;
}
private String style = null;
public void setStyle(String style) {
this.style = style;
}
public String getComponentType() {
return ("definition");
}
public String getRendererType() {
return null;
}
public void release() {
super.release();
styleClass = null;
style = null;
}
protected void setProperties(UIComponent component) {
super.setProperties(component);
if (styleClass != null)
component.getAttributes().put("styleClass", styleClass);
if (style != null)
component.getAttributes().put("style", style);
}
}
And here is the class for the "DD" tag:
DefinitionDataTag
package com.mycompany.faces.components;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;
public class DefinitionDataTag extends DefinitionTag {
protected void setProperties(UIComponent component) {
super.setProperties(component);
component.getAttributes().put("tagType", "dd");
}
}
And here is the class for the "DL" tag:
DefinitionListTag
package com.mycompany.faces.components;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;
public class DefinitionListTag extends DefinitionTag {
protected void setProperties(UIComponent component) {
super.setProperties(component);
component.getAttributes().put("tagType", "dl");
}
}
And here is the code for the "DT" tag:
DefinitionTypeTag
package com.mycompany.faces.components;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;
import com.mycompany.util.Constants;
public class DefinitionTermTag extends DefinitionTag {
protected void setProperties(UIComponent component) {
super.setProperties(component);
component.getAttributes().put("tagType", "dt");
}
}
And here is how all three tags are defined in my "tld" file:
<tag>
<name>dl</name>
<tag-class>
com.mycompany.faces.components.DefinitionListTag
</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>styleClass</name>
</attribute>
<attribute>
<name>style</name>
</attribute>
<attribute>
<name>id</name>
</attribute>
</tag>
<tag>
<name>dt</name>
<tag-class>
com.mycompany.faces.components.DefinitionTermTag
</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>style</name>
</attribute>
<attribute>
<name>styleClass</name>
</attribute>
<attribute>
<name>id</name>
</attribute>
</tag>
<tag>
<name>dd</name>
<tag-class>
com.mycompany.faces.components.DefinitionDataTag
</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>style</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>styleClass</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
Note: I don't have all of the standard HTML attributes for these tags implemented. I just have style and styleClass and id. (id is necessary for the internal plumbing of JSF to work). I figure I'll implement the other attributes as needed.
And this solution takes us another step closer to preserving the "spirit" of HTML in our presentation layer. :)
0 comments:
Post a Comment