Search code examples
jspjsp-tags

How to create an iteration tag handler that define a variable


The problem

I'm trying to create a custom tag handler that its purpose is to loop over the given list and concatenate the items with the given delimiter. The tag's signature is: <custom:joinList list="${product.vendors}" delimiter=", " var="vendor">. A couple of notes. The list attribute should be a Collection.class object. The delimiter is always a String and the var is the variable that the body could have access within each loop. So the tag should always have a body to print each item and then the tag handler would append the delimiter at the end.

For example that's that's how to call the tag from a JSP:

<custom:joinList list="${product.vendors}" delimiter=", " var="vendor">
    ${vendor.id} // Vendor obviously has a getId() method
</custom:joinList>

What I've tried

Firstly I created a class extending the javax.servlet.jsp.tagext.SimpleTagSupport and in the doTag() method I was passing the next item in the list as an attribute in the pageContext.

Secondly I tried to extend javax.servlet.jsp.tagext.TagSupport but then I can't figure out how to write into the out writer after each body execution.

Code examples

The TLD that defines the tag:

<tag>
    <description>Joins a Collection with the given delimiter param</description>
    <name>joinList</name>
    <tag-class>com.myproject.tags.JoinListTag</tag-class>
    <body-content>tagdependent</body-content>
    <attribute>
        <description>The collection to be printed</description>
        <name>list</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <description>The delimiter that is going to be used</description>
        <name>delimiter</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <description>The item that will return on each loop to get a handle on each iteration</description>
        <name>var</name>
        <required>true</required>
    </attribute>
</tag>

And here is the custom tag handler, which I guess is pretty straight forward.

public class JoinListTag extends SimpleTagSupport {

    private Iterator iterator;
    private String delimiter;
    private String var;

    public void setList(Collection list) {
        if (list.size() > 0) {
            this.iterator = list.iterator();
        }
    }

    public void setDelimiter(String delimiter) {
        this.delimiter = delimiter;
    }

    public void setVar(String var) {
        this.var = var;
    }

    @Override
    public void doTag() throws JspException, IOException {
        if (iterator == null) {
            return;
        }

        while (iterator.hasNext()) {
            getJspContext().setAttribute(var, iterator.next()); // define the variable to the body
            getJspBody().invoke(null); // invoke the body

            if (iterator.hasNext()) {
                getJspContext().getOut().print(delimiter); // apply the delimiter
            }
        }
    }
}

From the above I was expecting to print 1, 2, 3 if the product.vendors List was populated like that, but instead I get ${vendor.id}, ${vendor.id}, ${vendor.id}


Solution

  • So eventually it was a single word change.

    In my TLD I define the body-content of the tag as tagdependent. I had to change that to scriptless. Apparently going through the docs once again won't hurt anybody...