I have long wanted this functionality, and after I started working again, I wrote my own. I think the / jsp tag files and custom tag classes are great, but often overflowing with simple one-time files, as you describe.
This is how my new macro tag works (used here to simply display html sortable table headers):
<%@ taglib prefix="tt" uri="/WEB-INF/tld/tags.tld" %> <tt:macro id="sortable"> <th class="sortable">${headerName} <span class="asc" >↑</span> <span class="desc">↓</span> </th> </tt:macro> <table><thead><tr> <tt:macro id="sortable" headerName="Name (this is sortable)" /> <tt:macro id="sortable" headerName="Age (this is sortable)" /> <th>Sex (not sortable)</th>
In / WEB-INF / tld / tags.tld I added:
<tag> <name>macro</name> <tag-class>com.acme.web.taglib.MacroTag</tag-class> <body-content>scriptless</body-content> <attribute> <description>ID of macro to call or define</description> <name>id</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <dynamic-attributes>true</dynamic-attributes> </tag>
And finally, the Java tag class:
public class MacroTag extends SimpleTagSupport implements DynamicAttributes { public static final String PREFIX = "MacroTag_"; private boolean bodyless = true; private String id; private Map<String, Object> attributes = new HashMap<String, Object>(); @Override public void setJspBody(JspFragment jspFragment) { super.setJspBody(jspFragment); getJspContext().setAttribute(PREFIX + id, jspFragment, PageContext.REQUEST_SCOPE); bodyless = false; } @Override public void doTag() throws JspException, IOException { if (bodyless) { JspFragment jspFragment = (JspFragment) getJspContext().getAttribute(PREFIX + id, PageContext.REQUEST_SCOPE); JspContext ctx = jspFragment.getJspContext(); for (String key : attributes.keySet()) ctx.setAttribute(key, attributes.get(key)); jspFragment.invoke(getJspContext().getOut()); } } public void setId(String id) { this.id = id; } @Override public void setDynamicAttribute(String uri, String key, Object val) throws JspException { attributes.put(key, val); } }
The implementation is quite simple. If the tag has a body, we assume that we define the macro, and we save this JspFragment. Otherwise, we assume that we call the macro, so we look at it and copy any dynamic attributes into its context so that it is correctly parameterized and maps it to the calling output stream.
Crazy, this is not built into the JSP.