Question

I want to add a variable list of parameters to a Struts2 URL tag. I have a map of the parameters (name value pairs) in an object in the session. I'm struggling to find a good approach to this. Here is the relevant JSP code:

    <s:iterator value="%{#session['com.strutsschool.interceptors.breadcrumbs']}" status="status">
    <s:if test="#status.index > 0">
        &#187;
    </s:if>
    <s:url id="uri" action="%{action}" namespace="%{nameSpace}">
            <s:param name="parameters" value="%{parameters}"/>
    </s:url>
    <nobr><s:a href="%{uri}"><s:property value="displayName"/></s:a></nobr>
</s:iterator>

The parameters variable is a Map that contains the params. This, of course does not work but I cannot see a way to approach this at the moment. I'm thinking at the moment that I might need a custom freemarker template for this. Can anyone suggest a better way?

Was it helpful?

Solution

The Parameter tag populates parameters only to its direct antecessor. Wrappings an iterator tag around the parameter tag has no effect. :) To solve this you can easily write an alternative parameter tag wich can use a map direclty The tag may look like this.

package my.taglibs;

import java.io.Writer;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.components.Component;
import org.apache.struts2.views.jsp.ComponentTagSupport;
import com.opensymphony.xwork2.util.ValueStack;

public class ParamTag extends ComponentTagSupport {

    private String map;
    private static final long serialVersionUID = 2522878390854066408L;
    Log log = LogFactory.getLog(ParamTag.class);

    @Override
    public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
        return new Param(stack);
    }

    @Override
    protected void populateParams() {
        super.populateParams();

        Param param = (Param) component;
        param.setMap(map);
    }

    public void setMap(String map) {
        this.map = map;
    }

    public class Param extends Component {

        private String map; 

        public Param(ValueStack stack) {
            super(stack);
        }

        @Override
        public boolean end(Writer writer, String body) {
            Component component = findAncestor(Component.class);
            if (this.map == null) {       
                log.warn("Attribute map is mandatory.");
                return super.end(writer, "");
            }
            Object o = findValue(this.map); //find map in vs
            if(o == null) {
                log.warn("map not found in ValueStack");
                return super.end(writer, "");
            }
            if(!(o instanceof Map)) {
                log.warn("Error in JSP. Attribute map must evaluate to java.util.Map. Found type: " + o.getClass().getName());
                return super.end(writer, "");
            }

            component.addAllParameters((Map) o);
            return super.end(writer, "");
        }

        public void setMap(String map) {
            this.map = map;
        }
    }
}

And you need a corresponding tld-entry

<tag>
    <description><![CDATA[Parametrize other tags]]></description>
    <name>param</name>
    <tag-class>my.taglibs.ParamTag</tag-class>
    <body-content>JSP</body-content>
    <attribute>
        <name>map</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <dynamic-attributes>false</dynamic-attributes>
</tag>

OTHER TIPS

Maybe you can use JSTL tags like this

 <c:url var="uri" value="${namespace}/${action}.action">                
                    <c:forEach items="${parameters}" var="p">
                        <c:param name="${p.key}" value="${p.value}"/>
                    </c:forEach>
</c:url>
<a href="${uri}">Your Link</a>

I know there is some hesitation to mix EL with OGNL etc but this works...

You can iterate over a map using

<s:url var="url" action="%{link}" >
  <s:iterator value="parameters.keySet()" var="key">
    <s:param name="%{key}" value="%{parameters.get(#key)}"/>
  </s:iterator>
</s:url>

but the iterator will consume the parameters as it's the parent component of the param.

Check outAdding arbitrary parameters to a URL in Struts2 for how to do this with a custom tag which results in the similar

<s:url var="url" action="%{link}" >
  <s:iterator value="parameters.keySet()" var="key">
    <ob:iterable-param name="%{key}" value="%{parameters.get(#key)}"/>
  </s:iterator>
</s:url>

iterable-param is a custom component that returns the grandparent of the param in the case where the parent is an iterator.

With reference to comment 3, please show how would you use this customized tag in your jsp to achive the required behaviour.

Also, to use the solution mentioned in comment 1, what is the entry that we need to do in a tld file?

Please let me know this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top