"th:text/each" not displaying data (Spring Boot/Thymeleaf)

Viewed 677

This application is supposed to take info on the corona virus from a webpage and output it into a nice table on our own webpage.

All table headers are there but the columns themselves are empty.

Thymeleaf doesn't display data passed to it in model despite data being passed correctly in the controller. I would appreciate any input, thank you.

LocationStats.java (Edited)

package io.javabrains.coronavirustracker.models;

public class LocationStats
{
    private String state;
    private String country;
    private int latestTotalCases;

    public String getState() { return this.state; }

    public void setState(String state) { this.state = state; }

    public String getCountry() { return this.country; }

    public void setCountry(String country) { this.country = country; }

    public int getLatestTotalCases() { return this.latestTotalCases; }

    public void setLatestTotalCases(int latestTotalCases) { this.latestTotalCases = latestTotalCases; }

    @Override
    public String toString()
    {
        return "LocationStats{" + "state='" + state + '\'' + ", country='" + country + '\'' + ", latestTotalCases=" + latestTotalCases + '}';
    }
}

CoronaVirusDataService.java

package io.javabrains.coronavirustracker.services;

import io.javabrains.coronavirustracker.models.LocationStats;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;

@Service
public class CoronaVirusDataService
{
    private static String VIRUS_DATA_URL = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv";
    private List<LocationStats> allStats = new ArrayList<>();

    public List<LocationStats> getAllStats() { return allStats; }

    @PostConstruct
    @Scheduled(cron = "* * 1 * * *")
    public void fetchVirusData() throws IOException, InterruptedException
    {
        List<LocationStats> newStats = new ArrayList<>();
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(VIRUS_DATA_URL)).build();
        HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(httpResponse.body()); // Prints all data as expected

        StringReader csvBodyReader = new StringReader(httpResponse.body());
        Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(csvBodyReader);

        for(CSVRecord record : records)
        {
            LocationStats locationStat = new LocationStats();

            locationStat.setCountry(record.get("Country/Region"));
            locationStat.setState(record.get("Province/State"));
            locationStat.setLatestTotalCases(Integer.parseInt(record.get(record.size() - 1)));
            newStats.add(locationStat);
            
            System.out.println(locationStat); // Prints data as expected
        }

        this.allStats = newStats;
    }
}

HomeController.java

package io.javabrains.coronavirustracker.controllers;

import io.javabrains.coronavirustracker.models.LocationStats;
import io.javabrains.coronavirustracker.services.CoronaVirusDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
public class HomeController
{
    @Autowired
    CoronaVirusDataService coronaVirusDataService;

    @GetMapping("/")
    public String home(Model model)
    {
        List<LocationStats> allStats = coronaVirusDataService.getAllStats();
        int testNumber = 999; // For test attribute

        model.addAttribute("locationStats", allStats); // Actual attribute
        model.addAttribute("testInt", testNumber); // Test attribute

        return "home";
    }
}

home.html (Edited)

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<head>
    <title>Corona Virus Tracker Application</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>

<body>

<h1 th:text="${testInt}">-1</h1>

<table>
    <tr>
        <th>State</th>
        <th>Country</th>
        <th>Total Cases</th>

    </tr>
    <tr th:each="locationStat : ${locationStats}">
        <td th:text="${locationStat.state}"></td>
        <td th:text="${locationStat.country}"></td>
        <td th:text="${locationStat.latestTotalCases}">0</td>
    </tr>
</table>

</body>

</html>

CoronavirusTrackerApplication.java

package io.javabrains.coronavirustracker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class CoronavirusTrackerApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(CoronavirusTrackerApplication.class, args);
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.javabrains</groupId>
    <artifactId>coronavirus-tracker</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>coronavirus-tracker</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>14</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-csv</artifactId>
            <version>1.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>14</source>
                    <target>14</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Webpage's current state

1 Answers

Thymeleaf can only access data through getters. Add getters to your LocationStats class.

To clarify:

when you write in Thymeleaf:

<td th:text="${locationStat.state}"></td>

the .state in Thymeleaf is under the hood using the getter defined in your class to retrieve state. No getter = no thymeleaf representation.

Related