Back in the day when I started working with Java, things were tough, quite tough actually. For my first job out of university, I was working on a large real-time inventory management app built using Java Struts framework with a MySql db backend. I remember how tough it was and how much time it took to just setup things and get it going. Now with Spring Initializr, it’s much easier to setup and get started. I have been working with Spring Boot a lot lately and thought it would be a good idea to build something with in my spare time. This ended being my weekend activity and in this post, I will be talking about my weekend hack which was a Java Spring Boot & Vanilla Javascript solution. The entire solution of this post is online and fully open-source on Github. You can find a link to it at the end of this post.

Java spring boot & vanilla Javascript

A simple Java spring boot app that’s fully open source and is available on Github. I will be honest, my primary goal of this exercise was to brush up on Java myself. For the last few years, I have done a lot of iOS/Swift and NodeJS as opposed to Java. While I remember the Java syntax like the back of my hand, I thought I would practice a bit on some of the new things, like Lombok or CompletableFuture etc.

Spring Boot backend

This was the easy part. It was not that hard at all. I remember starting Java development back in 2007 with the Struts framework and how hard it was. Now with Spring initializr, it’s so easy to get started with building a Spring boot app.

Start coding the app

Generate a Spring boot project from here at Spring Initializr. On that url click on Add Dependencies and add Spring Boot Dev Tools, Lombok and Spring Web and click generate. This will generate a project that will be downloaded on your machine.

Unzip the contents of the downloaded file and import the project in your IDE. One example is using IntelliJ, with IntelliJ, click open project in IntelliJ and import the folder from the zip file generated. The folder structure should look like,

Dataset

I got the dataset for Covid case numbers from the NSW government website. It is the Covid cases data set my likely source of infection.

Write your controller

Controllers are an entry point into your application. Let’s create a REST controller and call it ProcessDataController which will have 3 endpoints.

@RestController
@RequestMapping("data")
public class ProcessDataController {

    @Autowired
    private CSVParser csvParser;

    @GetMapping("/raw")
    public Dataset parseToJson() {
        return csvParser.parseDataset("cnfrm_case_table4_location_likely_source.csv");
    }

    @GetMapping("/byPostcode/{postcode}")
    public Dataset casesByPostcode(@PathVariable("postcode") Integer postcode) {
        return csvParser.byPostcode(postcode);
    }

    @GetMapping("/byPostcode/{postcode}/aggregate")
    public List<CasesByDate> aggregateCasesByPcode(@PathVariable("postcode") Integer postcode) {
        return csvParser.caseAggregateByInf(postcode);
    }
}

Notice the AutoWired CSVParser class? we will look at that next.

Code to parse the data

In summary, the Java code parses the contents of the csv files and stores them in a data structure. Let’s look at the Here’s what the Java code looks like,

First up is the one to one mapping of a row in the csv file to a Java POJO.

@NoArgsConstructor
@Getter
@Setter
public class CovidCase {
            private Date notification_date;
            private Integer postcode;
            private String likely_source_of_infection;
            private String lhd_2010_code;
            private String lhd_2010_name;
            private Integer lga_code19;
            private String lga_name19;

            public CovidCase(String notification_date,
                             Integer postcode,
                             String likely_source_of_infection,
                             String lhd_2010_code,
                             String lhd_2010_name,
                             Integer lga_code19,
                             String lga_name19) {

                this.postcode = postcode;
                this.likely_source_of_infection = likely_source_of_infection;
                this.lhd_2010_code = lhd_2010_code;
                this.lhd_2010_name = lhd_2010_name;
                this.lga_code19 = lga_code19;
                this.lga_name19 = lga_name19;
                try {

                    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                    this.notification_date = dateFormat.parse(notification_date);
                } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
    @Autowired
    ResourceLoader resourceLoader;

    public CSVParserImpl() {
        super();
        this.parseDataset(defaultFilename);
    }

    @Override
    public Dataset parseDataset(String filePath) {
        List<CovidCase> cases = new ArrayList<>();
        try {
            InputStream is = getClass().getClassLoader().getResourceAsStream(filePath);
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            //skip the first line
            String headerStr = reader.readLine();
            String line = null;
            while((line = reader.readLine()) != null) {
                cases.add(new CovidCase(line.split(",")));
            }
            Collections.sort(cases, new Comparator<CovidCase>() {
                @Override
                public int compare(CovidCase o1, CovidCase o2) {
                    if(o1.getNotification_date() == null || o2.getNotification_date() == null){
                        return 0;
                    }
                    return o1.getNotification_date().compareTo(o2.getNotification_date());
                }
            });
            dataCache.setCovidCases(cases);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        return dataCache;
    }
    @Override
    public List<CasesByDate> caseAggregateByInf(Integer postcode) {
        DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
        Map<String, CasesByDate> caseMap = new HashMap<>();
        for(CovidCase c: dataCache.getCovidCases()) {
            if((c.getPostcode() != null) && c.getPostcode().equals(postcode)) {
                String pattern = c.getLikely_source_of_infection()
                        .trim()
                        .toLowerCase(Locale.ROOT) + ":" + df.format(c.getNotification_date());
                CasesByDate caseByDate = caseMap.get(pattern);
                if (caseByDate == null) {
                    caseByDate = new CasesByDate();
                    caseByDate.setDate(c.getNotification_date());
                    caseByDate.setLhdName(c.getLhd_2010_name());
                    caseByDate.setSource(c.getLikely_source_of_infection());
                }
                caseByDate.setCount(caseByDate.getCount() + 1);
                caseMap.put(pattern, caseByDate);
            }
        }
        List<CasesByDate> cases = caseMap.values().stream().collect(Collectors.toList());
        cases.sort(new Comparator<CasesByDate>() {
            @Override
            public int compare(CasesByDate o1, CasesByDate o2) {
                return o2.getDate().compareTo(o1.getDate());
            }
        });
        return cases;
    }

In the method above, the first step is to parse the data from the csv file and storing it in the Java class. Next ups is the method caseAggregateByInf which gets all the data based on the postcode.

The front-end

The front-end has been kept relatively simple since, the required functionality is not a lot, we can solve it all using vanilla Javascript.

Page and Javascript

First, A simple HTML page with a few divs, an input field anda button.

    <body>
        <div class="localCovidReport">
            <h2>Cases near you</h2>
            <div style="display: flex; align-items: center ;flex-direction: column;">
    
                <input style="height: 35px;"
                        id="inPostcode" type="text" placeholder="Enter your postcode" />
                <p><button class="loadBtn" onclick="getAggregate()" > Load </button> </p>
            </div>
            <hr style="width: 100%;" />
            <!--<button class="loadBtn" onclick="toggleDetails()">Toggle Details</button> -->
            <div id="tableHeading"></div>
            <div id="summaryCards" style="display: flex; flex-direction: row; justify-content: center; flex-wrap: wrap; max-width: 1200px;" >
            </div>
        </div>
    </body>

Above we have a very simple HTML page which has a few divs and a button that calls some Javascript. The Javascript is in the index.js file and let us take a look at the index.js file.

async function getAggregate() {
    const postcode = document.getElementById("inPostcode").value;
    const url = `/data/byPostcode/${postcode}/aggregate`;
    let cases = await fetch(url)
    .then(response => response.json())
    .then(data => data);
    renderAggregate(cases);
}
function renderAggregate(data) {
    const reducer = (a, b) => a + b.count;
    let casesCount = data.reduce(reducer, 0);
    let header = document.createElement("h3");
    header.innerHTML = `Total cases: ${casesCount}`;
    const tableHeader = document.getElementById("tableHeading");
    tableHeader.innerHTML = "";
    tableHeader.appendChild(header);

    const dateMap = new Map();
    data.forEach(dt => {
        let casesObj = dateMap.get(dt.date);
        if(casesObj === null || casesObj === undefined) {
            casesObj = {};
            casesObj.dataPoints = [];
            casesObj.date = dt.date;
            casesObj.total = 0;
        }
        casesObj.total += dt.count;
        casesObj.dataPoints.push(dt);
        dateMap.set(dt.date, casesObj);
    });
    const parent = document.getElementById("summaryCards");
    parent.innerHTML = "";
    //we should have a map of case numbers by date
    for (const [key, value] of dateMap) {
      const card = document.createElement("div");
      card.className = "resultCard";

      const summaryDiv = document.createElement("div");
      summaryDiv.style.textAlign = "center";
      summaryDiv.innerHTML = `<p> <b>${value.total}</b> case(s) on
                              <b>${formatDate(key)}</b></p>
                               <hr />`;
      card.appendChild(summaryDiv);
      const dataPointsDiv = document.createElement("div");
      value.dataPoints.forEach(dp => {
        const detailDiv = document.createElement("div");
        detailDiv.innerHTML = `<p> <b>${dp.count}</b> cases are
                                <b><i>${dp.source}</i> </b> </p>
                                <hr />`;
        card.appendChild(detailDiv);
      });
      parent.appendChild(card);
    }
}

Solution explanation

Once the user clicks the button, it calls the getAggregate method in the index.js file.

const postcode = document.getElementById("inPostcode").value;
const url = `/data/byPostcode/${postcode}/aggregate`;
let cases = await fetch(url)
.then(response => response.json())
.then(data => data);
renderAggregate(cases);

Remember the get aggregate function is async, hence we can wait for the fetch operation to complete and get the data. If you remember, earlier we defined a ProcessDataController, annotated it with @RequestMapping(‘data’) and added a method

@GetMapping("/byPostcode/{postcode}/aggregate")
public List<CasesByDate> aggregateCasesByPcode(@PathVariable("postcode") Integer postcode) {
return csvParser.caseAggregateByInf(postcode);
}

The Javascript function will get the cases data by postcode by invoking the code above. Once the data is retrieved, the renderAggregate method is called which will create a div element and set the innerHTML to render the covid cases data.

const card = document.createElement("div");
card.className = "resultCard"; const summaryDiv = document.createElement("div");
summaryDiv.style.textAlign = "center";
summaryDiv.innerHTML = `<p> <b>${value.total}</b> case(s) on
<b>${formatDate(key)}</b></p>
<hr />`;
card.appendChild(summaryDiv);

What do you think of the above? It is not that hard is it? Maybe it has a steeper learning curve then when using a framework, but once you get the initial understanding, it’s not that hard. It is just a lot more work i.e. more code to write. Hence I agree had a framework such as Angular or Reactjs be used here, there will be a lot less code.

To read more about the Java code and the vanilla Javascript used, please have a look at the README or look at the source code in the repository. Now let’s focus on to the main aspects of this post, the AWS side of things.

Conclusion

You can find the complete source code as well as instructions on how to run it on this Github.

https://github.com/cptdanko/localCovidCases

At this stage this only shows data for the Covid cases in New South Wales, Australia. Feel free to modify it and add the logic to show for another Australian state or for that matter any other part of the world.

Get updates?

If you find any of my posts useful and want to support me, you can buy me a coffee :)

https://www.buymeacoffee.com/bhumansoni

Or you can  buying or even try one of my apps on the App Store. 

https://mydaytodo.com/apps/

In addition the above, have a look at a few of the other posts,
How to create radio buttons using vanilla Javascript
https://mydaytodo.com/vanilla-javascript-create-radio-buttons/

How to build a Javascript frontend for Java Spring Boot backend 
https://mydaytodo.com/java-spring-boot-vanilla-javascript-solution/

Or have a look at some useful Javascript tutorials below
https://mydaytodo.com/category/javascript/

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published.

Ads Blocker Image Powered by Code Help Pro

Ads Blocker Detected!!!

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

Powered By
Best Wordpress Adblock Detecting Plugin | CHP Adblock