Search code examples
javaspring-bootthymeleaf

How to pass two different atributtes to the same `tr` with the same `th:each` in SpringBoot with ThymeLeaf?


I am using the basic SpringBoot with ThymeLeaf template.

This is a basic and simple controller class. I did not create many classes or MVC.

@Controller
public class HomeController 
{

@GetMapping("/")
 public String home(Model model)
 {
  int[] days = IntStream.range(1, 46).toArray();
  int golds[] = {15000, 12000, 11000, 9000, 7200, 16400, 6600, 5400, 7000, 6300, 4700, 5400, 10600, 7000, 4400, 4500, 4400, 4400, 4400, 6300, 3400, 3600, 5100, 3600, 3700, 3800, 5700, 3700, 3500, 16700, 3600, 3700, 3500, 36000, 3400, 3500, 4000, 3500, 3700, 4000, 4800, 4400, 3600, 4300, 3500};

  model.addAttribute("days", days);
  model.addAttribute("golds", golds);

  return "home";
 }
}

I tried to render both days and golds in two data cells of the same table row with th:each.

I have already thought to create a object and to save two attributes on it so I would assign them to a single the List and iterate the list. But it creates many unnecessary classes. I found it excessive. I tried it once and it did not work.

<table>
  <thead>
      <tr>
          <th> Dia </th>
          <th> Ouros </th>
      </tr>
  </thead>
  <tbody>
      <tr th:each="day, gold : ${days}">
          <td>[[${day}]]</td>
          <td>[[${gold}]]</td>
      </tr>
  </tbody>
</table>

Update 2

I followed @andrewjames's re-design, but it does not work:

@Controller
public class HomeController implements Serializable 
{
  public static class GoldData 
  {
    private int[] day;
    private int[] gold;

    public int[] getDay() {
      return day;
    }

    public int[] getGold() {
      return gold;
    }

    public void setGold(int[] gold) {
      gold = new int[]{15000, 12000, 11000, 9000, 7200, 16400, 6600, 5400, 7000, 6300, 4700, 5400, 10600, 7000, 4400, 4500, 4400, 4400, 4400, 6300, 3400, 3600, 5100, 3600, 3700, 3800, 5700, 3700, 3500, 16700, 3600, 3700, 3500, 36000, 3400, 3500, 4000, 3500, 3700, 4000, 4800, 4400, 3600, 4300, 3500};
      this.gold = gold;
    }

    public void setDay(int[] day) {
      day = IntStream.range(1, 46).toArray();
      this.day = day;
    }

  }

  List<GoldData> goldData = new ArrayList<>();

  @GetMapping("/")
  public String home(Model model) 
  {
    int total = Arrays.stream(golds).sum();
    int reais = total / 10000;

    model.addAttribute("soma", total);
    model.addAttribute("reais", "R$" + reais);
    model.addAttribute("goldData", goldData);

    return "home";
  }
}

Update 3

No exit:

@Controller
public class HomeController implements Serializable 
{
  private int[] day;
  private int[] gold;

  day = IntStream.range(1, 46).toArray();
  gold = new int[]{15000, 12000, 11000, 9000, 7200, 16400, 6600, 5400, 7000, 6300, 4700, 5400, 10600, 7000, 4400, 4500, 4400, 4400, 4400, 6300, 3400, 3600, 5100, 3600, 3700, 3800, 5700, 3700, 3500, 16700, 3600, 3700, 3500, 36000, 3400, 3500, 4000, 3500, 3700, 4000, 4800, 4400, 3600, 4300, 3500};

  int total = stream(gold).sum();
  int reais = total / 10000;

  @GetMapping("/")
  public String home(Model model)
  {
    List<HomeController> goldData = new ArrayList<>();
    List<HomeController> total;
    List<HomeController> reais;

    model.addAttribute("soma", total);
    model.addAttribute("reais", "R$" + reais);
    model.addAttribute("goldData", goldData);

    return "home";
  }
}

Solution

  • In this case, because days is just an integer sequence, you don't need to pass it to the model. Instead, you can use Thymeleaf's iteration status tracker variables. In this case, the variable count (which starts at 1) does what you need:

    <table>
        <thead>
            <tr>
                <th> Dia </th>
                <th> Ouros </th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="gold,iterStat : ${golds}">
                <td th:text="${iterStat.count}"></td>
                <td th:text="${gold}"></td>
            </tr>
        </tbody>
    </table>
    

    You can see the iterStat.count value in the first cell of each row.

    In my case, I placed the values in a standard th:text attribute. You can use your alternative syntax [[...]] if you prefer.


    If you didn't have a simple integer sequence for days I would recommend two possible alternatives:

    1 - Use iterStat.index - which starts at zero (unlike count which starts at 1) and use that value as the array index value - for example, ${days[iterStat.index]}.

    2 - Re-design your two separate arrays into a simple JavaBean containing the two fields you need (a day value and a gold value). Then create a list of these objects. Then iterating the list in Thymeleaf becomes straightforward and you would not need any iterStat variables.


    Update

    Some additional notes on alternative option 2:

    Assume you have a very basic POJO (plain old Java object) with 2 fields:

    public static class GoldData {
        private int day;
        private int gold;
        // getters and setters not shown, but are needed.
    }
    

    Then you would build a list of such objects:

    List<GoldData> goldData = new ArrayList<>();
    

    And populate it so that each goldData object contains one pair of your data which is currently stored in your arrays.

    Then add that to your model, instead of 2 (or more) arrays:

    model.addAttribute("goldData", goldData);
    

    Then your HTML table can use that object and refer to its fields directly:

    <tr th:each="goldDataItem : ${goldData}">
        <td th:text="${goldDataItem.day}"></td>
        <td th:text="${goldDataItem.gold}"></td>
    </tr>
    

    Now, there is no need for any iteration tracking counters/variables.

    More generally, if you don't need to store your data in Java arrays, objects can be much easier/flexible to work with.


    Update 2

    In your edit to your question, you simply moved your two arrays into a separate class. That is not what my example does.

    In my example class I got rid of the arrays entirely.

    Ideally you would not have those two arrays in the first place. But for simplicity, let's assume you do start with those 2 arrays.

    To create one of my GoldData objects, you can do this:

    GoldData gd = new GoldData();
    gd.setDay(days[0]);
    gd.setGold(gold[0]);
    

    (I'm ignoring the improvement that could be made by adding appropriate constructors, just for this demo.)

    Now, that creates one GoldData object, containing one day value and one gold value.

    You add that into the List as follows:

    List<GoldData> goldData = new ArrayList<>();
    goldData.add(gd);
    

    Now, imagine iterating over every item in your two arrays simultaneously, and creating 45 of these objects and adding each one to your list.

    See if you can use the above notes to build your List so it contains these objects. You will see there are no arrays involved in the end result, anywhere.


    Final Update

    Here is the GoldData class, but this time I added the getters and setters. I also added constructors, this time:

    public static class GoldData {
    
        private int day;
        private int gold;
    
        public GoldData() {
        }
    
        public GoldData(int day, int gold) {
            this.day = day;
            this.gold = gold;
        }
    
        public int getDay() {
            return day;
        }
    
        public void setDay(int day) {
            this.day = day;
        }
    
        public int getGold() {
            return gold;
        }
    
        public void setGold(int gold) {
            this.gold = gold;
        }
    
    }
    

    And here is the code which takes your array data and loads it into a list of GoldData objects:

    // Here is your array data, from the question:
    int[] days = IntStream.range(1, 46).toArray();
    int[] golds = {15000, 12000, 11000, 9000, 7200, 16400, 6600, 5400, 7000, 6300, 4700, 5400, 10600, 7000, 4400, 4500, 4400, 4400, 4400, 6300, 3400, 3600, 5100, 3600, 3700, 3800, 5700, 3700, 3500, 16700, 3600, 3700, 3500, 36000, 3400, 3500, 4000, 3500, 3700, 4000, 4800, 4400, 3600, 4300, 3500};
    
    // Here is what we actually want to send to the Thymeleaf template, instead of your arrays:
    List<GoldData> goldDataList = new ArrayList<>();
    
    // Here is how we transfer your array data into the above list:
    for (int i = 0; i < days.length; i++) {
        goldDataList.add(new GoldData(days[i], golds[i]));
    }
    
    model.addAttribute("goldDataList", goldDataList);
    

    Here is the body of the Thymeleaf template's table, which uses goldDataList:

    <tbody>
        <tr th:each="goldData : ${goldDataList}">
            <td th:text="${goldData.day}"></td>
            <td th:text="${goldData.gold}"></td>
        </tr>
    </tbody>
    

    Note how there are no arrays anywhere in this code, except for your two original arrays from your question.