Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSP: Make a reusable code (tag, macro) and use it on the same page

Is there any way to make some sort of parametrized macro on one JSP page and reuse it few times on the same page. JSP tags could be used but I would have to make one file per tag.

like image 425
Mihajlo Brankovic Avatar asked Oct 29 '25 22:10

Mihajlo Brankovic


2 Answers

I've been wanting this functionality for years, and after googling yet again, I wrote my own. I think tag / jsp files & custom tag classes are great, but are often overkill for simple one-offs like you describe.

This is how my new "macro" tag now works (here used for simple html rendering of sortable table headers):

<%@ taglib prefix="tt" uri="/WEB-INF/tld/tags.tld" %>

<!-- define a macro to render a sortable header -->
<tt:macro id="sortable">
    <th class="sortable">${headerName}
        <span class="asc" >&uarr;</span>
        <span class="desc">&darr;</span>
    </th>
</tt:macro>

<table><thead><tr>
    <!-- use the macro for named headers -->
    <tt:macro id="sortable" headerName="Name (this is sortable)" />
    <tt:macro id="sortable" headerName="Age (this is sortable)" />
    <th>Sex (not sortable)</th>
    <!-- etc, etc -->

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());
            for (String key : attributes.keySet()) {
                ctx.removeAttribute(key);
            }
        }
    }

    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 pretty basic. If the tag has a body, we assume we're defining a macro, and we store that JspFragment. Otherwise, we assume we're calling a macro, so we look it up, and copy any dynamic attributes into its context so it be properly parameterized, and render it into the calling output stream.

Crazy this isn't built into JSP.

like image 102
JohnnyMarnell Avatar answered Nov 01 '25 17:11

JohnnyMarnell


I tried the solution from Johnny and found, that if you use the macro multiple times, there is a bug.

you have to remove the attributes from the page context after renering

jspFragment.invoke(getJspContext().getOut());

for (String key : attributes.keySet()) {
    ctx.removeAttribute(key);
}
like image 42
Stefan Avatar answered Nov 01 '25 17:11

Stefan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!