Search code examples
javaspringspring-bootthymeleaf

Thyemleaf nested iteration triggers org.thymeleaf.exceptions.TemplateInputException


I'm trying to iterate through a list of objects and generate a div class="card-deck" every 4 objects and a nested div class="card" for every object.

This is the code that generates the exception on line 234

UPDATE: Note: line 234 is mentioned in html and had the <!-- Error-Line 234 --> due to a missing ) at ${#numbers.sequence(0,3}

    <div class="card-deck" th:each="qr: ${objects}" th:if="${qr.tableid}%4==0"> <!-- Iterate every 4 objects -->

    <!--syntax error missed clossing ) at ${#numbers.sequence(0,3) triggered the exception here -->
    <div class="card" th:each="i : ${#numbers.sequence(0,3)} ">   <!-- Error-Line 234 -->


        <!-- Some More Code -->
        <img th:src="${qr.qrcodestaticpath}" class="card-img-top" alt="...">
        <div class="card-body">
            <h5 class="card-title" align="center" th:text="'Table '+${qr.tableid}"></h5>
            <p class="card-text" align="center" th:text="'Random Generated QR Code'"></p>
            <h6 align="center" th:text=" ${qr.qrcodestring}"></h6>

        </div>
    </div>
  </div>

org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/qrcodes.html]" - line 234, col 10)

I've already been on these topics

and gone through this documentation

and still can't figure a proper way of doing it , without triggering an exception

UPDATE: Exception is fixed, the logic i'm trying implementing doesn't have the desired outcome:

Outcome of the above snippet:

enter image description here

Imagine there are 8 tables, table1, table2 ... table8 , i'm trying to generate a <div class="card-deck" ... for every 4 or 5 tables. Due to <div class="card" th:each="i : ${#numbers.sequence(0,3)} "> I get the 4 same tables.

  • qr.tableid are the table numbers, 1 to x

For purposes of demonstration take a look at this java snippet

int numOfObjects=11;
    for(int i=0 ;i<numOfObjects;i++)
    {
        if(i%4==0)
        {
           System.out.println();
           System.out.print("Deck:");
        }
          System.out.print("Card"+(i+1)+" ");    
     }

Output:

enter image description here

This is my Controller

@GetMapping("/qrcodes")
      public String greetingForm(Model model) {

        List<QrObject> qr =qrRepo.findAll();
        int numOfobj= qr.size();
        int decks;

        if(numOfobj % 4==0)
            decks = numOfobj / 4 ;
        else
            decks = (numOfobj / 4) +1 ;

        int posa_periseuoun = numOfobj % 4 ;
        model.addAttribute("objects", qr);
        model.addAttribute("decks",decks);
        model.addAttribute("cards",posa_periseuoun);
        model.addAttribute("size", numOfobj);
        return "qrcodes";
      }

Solution

  • Here is an approach which I think represents what you are trying to do.

    End Result

    Jumping to the end result, this will display the following text in a browser:

    Deck: Card1 Card2 Card3 Card4
    Deck: Card5 Card6 Card7 Card8
    Deck: Card9 Card10 Card11 
    

    More usefully, the HTML is as follows:

    <div class="card-deck">
        <span>Deck: </span>
        <span class="card">Card1 </span>
        <span class="card">Card2 </span>
        <span class="card">Card3 </span>
        <span class="card">Card4 </span>
    </div>
    <div class="card-deck">
        <span>Deck: </span>
        <span class="card">Card5 </span>
        <span class="card">Card6 </span>
        <span class="card">Card7 </span>
        <span class="card">Card8 </span>
    </div>
    <div class="card-deck">
        <span>Deck: </span>
        <span class="card">Card9 </span>
        <span class="card">Card10 </span>
        <span class="card">Card11 </span>
     </div>
    

    The Java Objects

    The Deck:

    public class Deck {
    
        private final String deckName;
        private final List<Card> cards = new ArrayList();
    
        public Deck(String deckName) {
            this.deckName = deckName;
        }
    
        public List<Card> getCards() {
            return cards;
        }
    
        public String getDeckName() {
            return deckName;
        }
    
    }
    

    The Card:

    public class Card {
    
        private final String cardName;
    
        public Card(String cardName) {
            this.cardName = cardName;
        }
    
        public String getCardName() {
            return cardName;
        }
    
    }
    

    Assembling the Decks:

    Map<String, Object> model = new HashMap();
    
    // this is equivalent to your findAll()...
    List<Card> allCards = new ArrayList();
    for (int i = 1; i<= 11; i++) {
        allCards.add(new Card("Card" + i));
    }
    
    int maxCardsPerDeck = 4;        
    List<Deck> decks = new ArrayList();
    
    Deck deck;
    List<Card> deckCards = new ArrayList();
    int cardCount = 0;
    for (Card card : allCards) {
        cardCount++;
        deckCards.add(card);
        if (cardCount % maxCardsPerDeck == 0 ||
                cardCount == allCards.size()) {
            deck = new Deck("Deck");
            deck.getCards().addAll(deckCards);
            decks.add(deck);
            deckCards.clear();
        }
    }
    
    model.put("decks", decks);
    

    The above code is fairly crude - it could probably be refined. But the point is: it assembles a collection of decks, with each deck containing no more than the allowed maximum of cards (4 in this example).

    The Thymeleaf

    <div class="card-deck"
         th:each="deck: ${decks}">
        <span th:text="${deck.deckName + ': '}">
        </span>
        <span class="card"
              th:each="card: ${deck.cards}"
              th:text="${card.cardName + ' '}">
        </span>
    </div>
    

    I used <span>s here, just so things are aligned. You can use whatever you need, and provide the CSS styling you need also.