spring-mvcthymeleaf

using thymeleaf +spring to create custom tags (just like JSP)


I am trying to use Thymeleaf to create custom tags, just like in JSP. The tag I have now is:

<select th:include="fragments/combobox :: combobox_beans (beans=${@accountService.getAccounts()}, innerHTML='id,description,currency', separator=' - ', dumbHtmlName='List of accounts', name='sender' )" th:remove="tag"></select>

The purpose is just defining the beans list, the properties of the bean to show on screen, the separator between them, the default value when shown as a native template, and the property name of the original bean we are processing here.

combobox.html:

<div th:fragment="combobox_beans (beans, innerHTML, separator, dumbHtmlName, name)">
<select th:field="*{__${name}__}" class="combobox form-control" required="required">
    <option th:each="obj : ${beans}" th:with="valueAsString=${#strings.replace( 'obj.' + innerHTML, ',', '+''  __${separator}__ ''+ obj.')}"
        th:value="${obj}" th:text="${valueAsString}" >            
        <p th:text="${dumbHtmlName}" th:remove="tag"></p>
    </option>
</select>

I need the text of the option tag to be based on the properties set in innerHTML property (innerHTML='id,description,devise') of the fragment. I end up having an option with this text:

<option value="...">obj.id+' - '+ obj.description+' - '+ obj.currency</option>

instead of the desired result

<option value="...">2 - primary - USD</option>

I know this is due to the usage of Strings library which results in a string. Is there a way Thymeleaf can re-evaluate this string to be understood as an object again?

Maybe using strings library is just so wrong in this situation... Maybe I need to use a th:each to process the each bean as an object and read its properties, but yet again, how to only get the properties specified in innerHtml ?

Anyone has a solution or work-around for this ?
thanks.


Solution

  • If there is a way to do what you want in Thymeleaf/Spring expression alone, it most certainly very complicated and long winded, plus it would probably be a pain to read.

    The easier way to do it would be add a custom utility object to the expression context. Very little code is needed. This answer shows it.

    Then you need to add you new dialect as additional dialect to the template engine in your Spring xml config. Assuming you have a fairly standard Spring config, it should be similar to this.

    <bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
      <property name="templateResolver" ref="templateResolver" />
      <property name="additionalDialects">
        <set>
          <bean class="mypackage.MyUtilityDialect" />
        </set>
      </property>
    </bean>
    

    Now for the utility object

    What you want is to get the properties from objects by name, and combine their values with a separator. It seems that the list of property names can be of any size. For accessing properties by name, the most convenient thing is to use a library like the Apache beanutils.

    Your custom utility object could the look something like this using the Java 8 streams library, lambdas and Beanutils:

    public class MyUtil {
    
      public String joinProperties(Object obj, List<String> props, String separator){
        return props.stream()
              .map(p -> PropertyUtils.getProperty(obj,p).toString())
              .collect(Collectors.joining(separator))
      }
    }
    

    Then when you add you dialect to SpringTemplateEngine you can call your utility:

    th:with="valueAsString=${#myutils.joinProperties(obj,properties,separator)}"
    

    I have replaced you innerHTML parameter with properties which is a List<String>, because it makes more sense. It is essentially a list of property names, and Spring EL supports inline lists.

    Your calling tag should then look like this.

    <select th:include="fragments/combobox :: combobox_beans (beans=${@accountService.getAccounts()}, properties=${ {'id','description','currency'} }, separator=' - ', dumbHtmlName='List of accounts', name='sender' )" th:remove="tag"></select>