injecting dynamic-attributes in a spring tag from a custom tag

2.2k views Asked by At

I have a custom jsp tag that outputs a spring form input field (plus other layout elements). What I need to do is to be able to accept dynamic attributes and use them in the input field as-is.

Eg of use:

<mytag:myinputtag arbitraryAttribute="value"/>

Should output

<input:form arbitraryAttribute="value" />

Unfortunately it doesn't work as expected because it throws an unterminated form:input tag exception. Following is the code I used:

<%@ tag dynamic-attributes="attributes" %>
<c:set var="expandedAttributes">
  <c:forEach var="a" items="${ attributes }">
    ${a.key}="${a.value}"<%= " " %>
  </c:forEach> 
</c:set>

<form:input (...) ${expandedAttributes} />

I can understand why this doesn't work as expected because of the resolution order of the EL expr and the tags. Therefore I have also tested to inject directly using scriptlets

<form:input (...) <%= (String)jspContext.getAttribute("expandedAttributes")%>  --%>

So I need a solution to this issue as I cannot preview all the attributes that could be added to the input. Therefore I thought of the following possibilities:

  • Using <input instead of <form:input, meaning that I have to replicate exactly the spring code for the "path" attribute (not good idea)
  • Extend form:input tag, copy dynamic attributes into default attributes and do standard tag rendering (don't know if is a feasible solution and if spring change its input tag implementation then it could not work properly anymore)
  • Enumerating all the attributes I need, this make the tag code huge and less maintainable

I would like to know if maybe there is a better solution I haven't though of, or if the second possibility is feasible at all.

Thanks

1

There are 1 answers

0
Rasael On

After a month of work on this issue I've considered surrending and removing the tag file alltogheter.

There is no implementation that allows me to inject the attributes as I want so I changed approach and tried to generate a tag that generates a body and then evaluates it. Also this approach doesn't work because the jsp and tags are compiled before execution and therefore its not possible to inject JSP code in the tag body.

So I have found a solution that currently works and it goes like this:

I have created my own input tag that extends from spring's InputTag.java. This tag accepts a special attribute called dynAttrs of type java.util.Map; these are the dynamic attributes passed from my parent tag. The rest of valid/accepted tag attributes is the same as spring input tag.

Then in the writeTagContent method I check if the dynAttrs are avaiable and for each attribute i do:

  • if the attribute is a class field, use a PropertyAccessor to set the value (e.g. onClick)
  • if the attribute is not a class field (or the field is not writeable) then it will be injected in the dynamic attributes (e.g. data-customData )

From here on I simply call the super.writeTagContent() and voilĂ , spring does all the magic for me!

Example of usage:

<my:customTextField path="model.path" attr1="val1" attr2="val2" disabled="true"/>

it will be transformed in

<div class="bla bla bla">
  <my:input path="model.path" disabled="true" dynAttrs="[{attr1,val1},{attr2,val2}]"/>
</div>

and down the stream transformed in

<div class="bla bla bla">
  <form:input path="model.path" disabled="disabled" attr1="val1" attr2="val2"/>
</div>

and then will be rendered as

<div class="bla bla bla">
  <input id="model.path" name="model.path" disabled="disabled" attr1="val1" attr2="val2"/>
</div>

So I can dynamically inject any number of dynamic attributes, at runtime, in spring's input tag!


NOTE: Since I'm not fond of this approach yet I have prepared two other implementations of this solution:

  • use apache common's BeanUtils.populate to populate the tag with the dynAttrs directly but error management in this case can be critical.

  • cache in a hashmap all the accessible fields in this tag and update them manually with a field.setValue(this,dynAttrs.get(attrName)) (should be faster since it doesn't need to retrieve the field each call, but maybe the PropertyAccessor does the same?).