1

I'm attempting to display each row from an array using Thymeleaf - following their documentation I am unable to use any of the following attributes from th:each:

  • The current iteration index, starting with 0. This is the index property.

  • The current iteration index, starting with 1. This is the count property.

userinput.html:

 <tr th:each="year : ${years}">
      <th scope="row" th:text="${years}"></th>
      <th scope="row" th:text="${bdcInterest}"></th>
      <td th:text="${bdAmount}"></td>
 </tr>

CalculatorController.java:

    @RequestMapping(value = "/submit", method = RequestMethod.GET)
public String userInput(Model model, BigDecimal lumpsum, BigDecimal interestrate, BigDecimal monthlywithdrawal) {
    
    BigDecimal initialinvestment = lumpsum;
    
    BigDecimal[] bdAmount = new BigDecimal[11];
    BigDecimal[] bdcInterest = new BigDecimal[11];
    BigDecimal[] initialInvestment = new BigDecimal[11];
    int[] years = new int[11];
    
    bdcInterest[0] = new BigDecimal(0);
    initialInvestment[0] = initialinvestment;
    
    int increment = 1;

    while(increment < 10) {

        BigDecimal amount = lumpsum
                .multiply(BigDecimal
                        .valueOf(1)
                        .add(interestrate
                                .divide(BigDecimal
                                        .valueOf(100)))
                        .subtract(monthlywithdrawal
                                .multiply(BigDecimal
                                        .valueOf(12)))); // Calculate the total yearly amount
        
        BigDecimal cInterest = amount.subtract(initialinvestment); // Calculate only the interest earned 

        bdAmount[increment] = amount;
        bdcInterest[increment] = cInterest;
        initialInvestment[increment] = initialinvestment;
        years[increment] = increment;
        
        lumpsum = amount;

        increment++;
    }
    
    model.addAttribute("years", years);
    model.addAttribute("initialInvestment", initialInvestment);
    
    model.addAttribute("bdAmount", bdAmount);
    model.addAttribute("bdcInterest", bdcInterest);

    return "userinput";

}

The necessary data is submitted correctly in each respective array, however I believe I've misunderstood the documentation: enter image description here

  • 1
    In your Thymeleaf expression `th:each="year,id : ${increment}"`, what is `${increment}`? It's supposed to evaluate to a list or an array - something that Thymeleaf can iterate over. In your case, it appears to not evaluate to anything. Are you trying to iterate over `${year}` or something like that? It's not clear what the expected outcome is. (It might also be a bit clearer if your Java variables were pluralized for lists/arrays - so, for example, `years` not `year`.) – andrewJames Dec 05 '21 at 14:58
  • 1
    My guess, based on the above comment: `th:each="year : ${years}"` - assuming you change the Java variable from `year` to `years`. – andrewJames Dec 05 '21 at 15:00
  • @andrewJames Thanks for your reply - I've just updated my code above. Hopefully this clarifies - the Thymeleaf expression `` is meant to iterate through the array, for each year display associated information. It [currently displays the memory location](https://gyazo.com/dcbb8a3faeb26bca6c3f78444269efac) for each array, rather than the information inside. Where have I gone wrong here? Thanks for your time – PublicDisplayName Dec 05 '21 at 16:12
  • You are still trying to display `${years}` here: ``. It should be `th:text="${year}"`. And then you are also trying to display an array of objects here: `th:text="${bdcInterest}"` and also here: `th:text="${bdAmount}"`, instead of accessing a specific index in each of those arrays. – andrewJames Dec 05 '21 at 16:26
  • Given your approach (using multiple arrays) you are also making things more complex than they need to be. Instead, you should consider creating a Java `List` containing objects, where each object contains one value for year, along with the related values for interest, investment, amount. Thymeleaf iteration becomes much cleaner and simpler in that case. – andrewJames Dec 05 '21 at 16:26

1 Answers1

1

Thymeleaf maintains the iteration status of the th:each tag in a special variable. Please, see the relevant documentation.

Among the different information provided in that variable, you can find an index property, which corresponds to the actual iteration index.

In you example, you probably could iterate your results like this:

 <tr th:each="year, iterStat : ${years}">
      <th scope="row" th:text="${year}"></th>
      <th scope="row" th:text="${bdcInterest[iterStat.index]}"></th>
      <td th:text="${bdAmount[iterStat.index]}"></td>
 </tr>

To avoid this kind of problems, please, consider define in your code a simple java object that agglutinates the four properties you are iterating:

public class MuCustomObject {
  private BigDecimal bdAmount;
  private BigDecimal bdcInterest;
  private BigDecimal initialInvestment;
  private int year;

  // getters and setters omitted for brevity
}

Then, use the object in your controller:

    @RequestMapping(value = "/submit", method = RequestMethod.GET)
public String userInput(Model model, BigDecimal lumpsum, BigDecimal interestrate, BigDecimal monthlywithdrawal) {
    
    BigDecimal initialinvestment = lumpsum;
    
    List<MyCustomObject> myCustomObjectList = new ArrayList<MyCustomObject>();
    
    MyCustomObject myCustomObject = new MyCustomObject();
    myCustomObject.setBdcInterest(new BigDecimal(0));
    myCustomObject.setInitialInvestment(initialinvestment);
    myCustomObjectList.add(myCustomObject);
    
    int increment = 1;

    while(increment < 10) {

        BigDecimal amount = lumpsum
                .multiply(BigDecimal
                        .valueOf(1)
                        .add(interestrate
                                .divide(BigDecimal
                                        .valueOf(100)))
                        .subtract(monthlywithdrawal
                                .multiply(BigDecimal
                                        .valueOf(12)))); // Calculate the total yearly amount
        
        BigDecimal cInterest = amount.subtract(initialinvestment); // Calculate only the interest earned 

        myCustomObject = new MyCustomObject();
        myCustomObject.setBdAmount(amount);
        myCustomObject.setBdcInterest(cInterest);
        myCustomObject.setInitialInvestment(initialinvestment);
        myCustomObject.setYear(increment);
        myCustomObjectList.add(myCustomObject);
        
        lumpsum = amount;

        increment++;
    }
    
    model.addAttribute("myCustomObjects", myCustomObjectList);

    return "userinput";

}

With that information you could directly iterate the collection:

 <tr th:each="myCustomObject, iterStat : ${myCustomObjects}">
      <th scope="row" th:text="${myCustomObject.year}"></th>
      <th scope="row" th:text="${myCustomObject.bdcInterest}"></th>
      <td th:text="${myCustomObject.bdAmount}"></td>
 </tr>
jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • 1
    You are welcome @PublicDisplayName. I am happy to hear that it worked properly. I updated the answer in any case, to provide you a possible optimization. Please, use the naming convention you consider appropriate - I mean, get rid of `MyCustomObject`. I hope it helps. – jccampanero Dec 05 '21 at 16:40