In this article, I extend further my earlier article, Creating a fullstack React/Material-UI DataGrid frontend data grid connected to a Java SpringBoot REST GET API backend with axios. Hopefully, you will get a more in-depth knowledge of the many facets of Java when you reach the end of this article!

For this app, we will continue using a Java SpringBoot backend providing REST service (refer to my earlier article above) but we will not be using a database. Instead we will upload Excel files or CSV files from the client (using Java upload facility) containing daily sales and no of customers data from which we will calculate the monthly average using the very handy Java Collectors class, which provides reduction operations, such as accumulating elements into collections, summarizing elements according to various criteria, such as mean/averages, binary operations and group by functions.

For the frontend, I use pure HTML pages with asynchronous requests using fetch(), ajax() and getJSON(). I make the frontend more interesting visually, using a grid called jsGrid (which is free) to display the data in tabular fashion, and also I add charting capabilities with Apex charts (there is a free version as well of this) to view this monthly average data visually.

To develop the above app, I am using a Windows 10 machine running Java JDK 15 and Eclipse 2020/2021 IDE. The Java SpringBoot REST service backend runs on Tomcat 8.0 on a separate remote server which runs Windows Enterprise Server 2012. I compile the app on my local machine to a war file and upload it to the server with the Windows Remote Connection tool to be run on Tomcat.

I will show you how to develop both the frontend and backend.

(Note on my earlier articles— For a node.js-ExpressJS-MongoDB (through mongoose) stack REST API backend with CRUD functionality accessed via a React-Data-Grid and Material-UI/DataGrid, please check out my article, Creating a CRUD node.js REST service backend with node.js, Express and MongoDB (mongoose) and a React-Data-Grid / Material-UI DataGrid frontend to access the service.

The above article is further extended with Deploying a Node.js server-side backend CRUD REST service to Heroku connected to a cloud-based MongoDB Atlas database which shows how to deploy the node.js app created to Heroku cloud hosting and also to use the cloud-based MongoDB Atlas for public deployment.

For a Python-Django REST API stack implementing HTTP GET, check out my other article, Creating a Python Django REST Service backend with Postgres database.

An Android/Google Location API article, Creating a Realtime Handphones Locations Tracker For Android Devices with Google Location API and Google Maps).

The Data File

The format of the data found in the Excel (or CSV) files is as follows:

Fig 1 : Excel file input data

The three parameters are sales date, no of unique customers sold to and the total sales for that day. Many Excel files will be loaded to the server over time on a daily basis. The app will only read and show statistics (in the table and graph) for the latest Excel file based on the file date modified.

The CSV model class

The CSV model class mirrors the format or structure of the Excel or CSV file as shown below:

CSV.java:
package default;

import java.time.LocalDateTime;

import javax.persistence.*;

public class CSV {


@Id
@GeneratedValue
private Integer id;
private LocalDateTime salesDate;
private Integer noOfCustomers;
private Double salesValue;

//constructor
public CSV () {
this.salesDate = null;
this.noOfCustomers = 0;
this.salesValue = 0.0;

public LocalDateTime getSalesDate() {
return salesDate;
}

public void setSalesDate(LocalDateTime salesDate) {
this.salesDate = salesDate;
}

public Integer getNoOfCustomers() {
return noOfCustomers;
}

public Integer setNoOfCustomers(Integer noOfCustomers) {
this.noOfCustomers = noOfCustomers;
}

public Double getSalesValue() {
return salesValue;
}

public Double setSalesValue(Double salesValue) {
this.salesValue= salesValue;
}

public String toString() {
return “CSV Class”;
}

}

The Backend

Here, we do not use a database. Instead we will upload Excel files or CSV files from the client side, which contains the data which we want to interpret, and store it to a location on the server. Our REST controller will read the most recent uploaded file, based on the date created, and display it in tabular fashion using our jsGrid component and which we can view visually using Apex charts.

HTML File To Upload Excel/CSV Files from the Client

Here is the HTML client-side code to upload the Excel or CSV (comma-separated-values) file on the client to the server

upload.html:

<!DOCTYPE html>

<html>

<head>

<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8" />

<title>Upload Excel/CSV File</title>

<style>

.fileLoading {

visibility:hidden;

}

#myProgress {

visibility: hidden;

margin: 0 0 0 800px;

width: 10%;

background-color: lightgrey;

}

#myBar {

width: 1%;

height: 30px;

background-color: blue;

}

.column {

float: left;

width: 50%; /*for two images — 50%*/

padding: 5px;

}

/* Clear floats after image containers */

.row::after {

content: “”;

clear: both;

display: table;

}

</style>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>

<script type=”text/javascript” src=”./jsgrid/jsgrid.min.js”></script>

</head>

<body>

<div style=”text-align:center; font-size:16pt; color:#ffffff;”>Upload The File</div>

<form id= “uploadExcel” name=”uploadExcel” method=”POST” enctype=”multipart/form-data”>

<table>

<tr><td colspan=”2" style=”color:grey;”><span style=”border-bottom: 1px solid grey;”>Upload File:.csv .xlsm</span>&nbsp;&nbsp;<input type=”text” id=”txtFileName” name=”txtFileName” style=”visibility:hidden; ”></td><td></td></tr>

<tr><td colspan=”2" style=”height:3px;”><div id=”fileLoader” class=”fileLoading”><img src=”file.png” height=”30" width=”30">&nbsp;&nbsp;<input type=”text” id=”txtFileName1" name=”txtFileName1" readonly>

<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src=”greenline.png” width=50% height=”5">&nbsp;&nbsp;<span style=”color:grey;font-size:16pt;”>x</span></div></td></tr>

<tr><td></td><td align=”right”><input type=”button” value=”Upload” onclick=”uploadFile();”></td><td><input type=”button” onclick=”window.open(‘uploadExcel.html?name=’ + document.getElementById(‘inputUserName’).value, ‘_self’)” value=”Upload”></td></tr>

</table>

<input type=”file” id=”fileOpenButton” name=”fileOpenButton” accept=”.xlsx, .xlsm, .csv” style=”font-size:16pt;” onchange=”registerFile()” >

</form>

<br/><br/><br/>

<div id=”myProgress”>

<div id=”myBar”>1%</div>

</div>

<script>

var i = 0;

function move() {

if (i == 0) {

i = 1;

var elem = document.getElementById(“myBar”);

var width = 10;

var id = setInterval(frame, 10);

function frame() {

if (width >= 100) {

clearInterval(id);

i = 0;

} else {

width++;

elem.style.width = width + “%”;

elem.innerHTML = width + “%”;

} }

}}

</script>

<script>

$(“#fileOpenButton”).css(‘opacity’,’0');

$(“#falseFileOpenButton”).click(function(e){

e.preventDefault();

$(“#fileOpenButton”).trigger(‘click’);

});

function registerFile() {

document.getElementById(“txtFileName”).value = document.getElementById(“fileOpenButton”).files[0].name;

document.getElementById(“txtFileName1”).value = document.getElementById(“fileOpenButton”).files[0].name;

if(document.getElementById(“txtFileName”).value.substring(document.getElementById(“txtFileName”).value.length — 3 , document.getElementById(“txtFileName”).value.length) == ‘csv’ ||

document.getElementById(“txtFileName”).value.substring(document.getElementById(“txtFileName”).value.length — 4 , document.getElementById(“txtFileName”).value.length) == ‘xlsm’) {

} else {

alert(“File must be in xlsx or csv format.”);

}

}

function beforeSubmit() {

if(document.getElementById(“txtFileName”).value.substring(document.getElementById(“txtFileName”).value.length — 3 , document.getElementById(“txtFileName”).value.length) == ‘csv’ ||

document.getElementById(“txtFileName”).value.substring(document.getElementById(“txtFileName”).value.length — 4 , document.getElementById(“txtFileName”).value.length) == ‘xlsx’) {

move();

document.forms[0].action = “/storeUploadedFile”; //call Spring controller

document.forms[0].submit();

} else {

alert(“File must be in xlsx or csv format.”);

}

}

async function uploadFile() {

try {

$( ‘.fileLoading’ ).css(‘visibility’, ‘visible’);

let formData = new FormData();

formData.append(“fileOpenButton”, fileOpenButton.files[0]);

formData.append(“txtFileName”, document.getElementById(“txtFileName”).value);

await fetch(‘/storeUploadedFile’, { //call Spring controller upload function

method: “POST”,

body: formData

});

await new Promise(r => setTimeout(r, 1500)); //delay loop — leaves some extra time for process file upload to finish

$( ‘.fileLoading’ ).css(‘visibility’, ‘visible’);

} catch(err) {

alert(document.getElementById(“txtFileName”).value + ‘. There was an error uploading this file.’);

}

}

</script>

</body>

</html>

Basically, this HTML file allows you to select a file using the familiar File Open Windows Explorer interface dialog, and then calls the upload function of the Spring Controller to submit the file using the PUT method and displays the filename and a progress bar after the file has been sent. Further details are explained in the controller section below.

The Spring Controller (.java)

This is the most important portion of the program! The Spring MVC controller code contains all the Java functions to actually upload the Excel or CSV file and to read the data contained in that file (readCSV())and translate it into a JSON array list to be returned which can then be read, interpreted and displayed by both the jsGrid and Apex chart components on the client side. Here is the Spring controller code:

Controller.java:

package default;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Base64;

import java.util.HashSet;

import java.util.Iterator;

import java.util.List;

import java.util.Map;

import java.util.Properties;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import javax.mail.Authenticator;

import javax.mail.PasswordAuthentication;

import javax.mail.Session;

import javax.servlet.ServletException;

import javax.servlet.http.Part;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.IOException;

import java.time.LocalDateTime;

import java.time.ZoneId;

import java.time.format.DateTimeFormatter;

import java.io.InputStream;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.attribute.BasicFileAttributes;

import org.apache.poi.ss.usermodel.Cell;

import org.apache.poi.ss.usermodel.CellType;

import org.apache.poi.ss.usermodel.Row;

import org.apache.poi.ss.usermodel.Sheet;

import org.apache.poi.ss.usermodel.Workbook;

import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.MediaType;

import org.springframework.stereotype.Controller;

import org.springframework.ui.ModelMap;

import org.springframework.util.FileCopyUtils;

import org.springframework.web.bind.annotation.CrossOrigin;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.multipart.MultipartFile;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.demo.model.ApexData;

import com.demo.model.CSV;

import com.demo.service.CSVService;

import com.opencsv.CSVReader;

import com.opencsv.exceptions.CsvException;

import java.util.Set;

import java.util.stream.Collectors;

import org.apache.log4j.Logger;

@Controller
@RequestMapping(value= “/”,
method={RequestMethod.POST,RequestMethod.GET}
)
@CrossOrigin(origins = “*”)
public class UserController {

static final Logger log = Logger.getLogger(UserController.class);

@Autowired
UserService service;


@Autowired
CSVService csvservice;

private ArrayList<CSV> listCSV = new ArrayList<CSV>();
private ArrayList<CSV> listCSV1 = new ArrayList<CSV>();


// upload file multipart settings
private static final int MEMORY_THRESHOLD = 1024 * 1024 * 3; // 3MB
private static final int MAX_FILE_SIZE = 1024 * 1024 * 40; // 40MB
private static final int MAX_REQUEST_SIZE = 1024 * 1024 * 50; // 50MB

@RequestMapping(value= “/storeUploadedFile”, method= {RequestMethod.POST, RequestMethod.GET})
public String storeUploadedFile(HttpServletRequest request, HttpServletResponse response, @RequestParam(“fileOpenButton”) MultipartFile uploadedFile, @RequestParam(“txtFileName”) String fileName, ModelMap modelMap) throws IOException, CsvException, ServletException {

modelMap.addAttribute(“fileOpenButton”, uploadedFile);

//create Assets subfolder in current directory if not exist
String path = System.getProperty(“user.dir”) + “/Assets”;
log.info(“System.getProperty(\”user.dir\”) : “ + System.getProperty(“user.dir”));

//check if Assets subfolder exists first
File tmpDir = new File(path);
boolean folderExists = tmpDir.exists();


if (!folderExists) {
//Creating a File object
File file = new File(path);

//Creating the directory
boolean bool = file.mkdir();

if(bool){
log.info(“Assets subfolder created successfully”);
}else{
log.info(“Assets subfolder already exists.”);
}
} else {
log.info(“Couldn’t create Assets subfolder. It already exists?”);
}

Part part = request.getPart(“fileOpenButton”);

InputStream fileContent = part.getInputStream();

//Copy file first to Assets folder then do the comparison
Path dest = Paths.get(System.getProperty(“user.dir”) + “\\Assets\\” + part.getSubmittedFileName());
FileCopyUtils.copy(part.getInputStream(), Files.newOutputStream(dest));

return (“uploadExcel.html”);
}


//Extract the contents of the latest Excel CSV files for display in grid and chart
@ResponseBody
@CrossOrigin(origins = “*”)
@RequestMapping(value= “/readLatestFile”, method= RequestMethod.GET)
public List<CSV> readLatestFile() throws IOException, CsvException {

//First build the ListArray with all the files in the Assets folder
listCSV.clear();
listCSV1.clear();
File dir = new File(System.getProperty(“user.dir”) + “\\Assets\\”);
File[] directoryListing = dir.listFiles();

if (directoryListing != null) {

for (File child : directoryListing) {
CSV rowCSV = new CSV();
rowCSV.setFilename(child.getName());
BasicFileAttributes attr = Files.readAttributes(child.toPath(), BasicFileAttributes.class);
LocalDateTime fileModifiedDateTime = LocalDateTime.ofInstant(attr.lastModifiedTime().toInstant(), ZoneId.systemDefault());
rowCSV.setUpdateDateTime(fileModifiedDateTime);
listCSV.add(rowCSV);
}
}

listCSV.sort((CSV csv1, CSV csv2) -> csv1.getUpdateDateTime().compareTo(csv2.getUpdateDateTime()));

String fileName = System.getProperty(“user.dir”) + “\\Assets\\” + listCSV.get(listCSV.size() — 1).getFilename();


//DETERMINE IF FILENAME IS CSV OR XLSX AND GO TO THE APPROPRIATE LOOP


//XLSX LOOP
if (fileName.substring(fileName.length() — 4, fileName.length()).equalsIgnoreCase(“xlsx”)) {

int i = 1; //don’t include headers at the first row
int j = 0;
LocalDateTime todayDateTime = LocalDateTime.now();
StringBuilder cellcontent = new StringBuilder();

cellcontent.insert(0, “”);

try {

FileInputStream excelFile = new FileInputStream(new File(fileName));
Workbook workbook = new XSSFWorkbook(excelFile);
Sheet datatypeSheet = workbook.getSheetAt(0);
Iterator<Row> iterator = datatypeSheet.iterator();

while (iterator.hasNext()) {

i++; j=0;
Row currentRow = iterator.next();
if (i==2) {
currentRow = iterator.next();
}
Iterator<Cell> cellIterator = currentRow.iterator();
CSV newRow = new CSV();

while (cellIterator.hasNext()) {

j++;
cellcontent.setLength(0);
Cell currentCell = cellIterator.next();
if (currentCell.getCellTypeEnum() == CellType.STRING) {
cellcontent = cellcontent.append(currentCell.getStringCellValue());
} else if (currentCell.getCellTypeEnum() == CellType.NUMERIC) {
cellcontent = cellcontent.append(currentCell.getNumericCellValue());
}

if (j == 1) { //first column cell — sales date and time
newRow.setSalesDateTime(currentCell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
} elseif (j == 2) { //second column cell — number of customers
newRow.setNoOfCustomers(Integer.valueOf(cellcontent.toString()).intValue());
} elseif (j == 3) { //second column cell — sales value
newRow.setSalesAmount(Double.valueOf(cellcontent.toString()).intValue());
}

}

listCSV1.add(newRow);

}

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
log.info(“IOException : “ + e.getMessage());
}


} // if file extension == .xlsm


//CSV LOOP
if (fileName.substring(fileName.length() — 3, fileName.length()).equalsIgnoreCase(“csv”)) {

try (CSVReader reader = new CSVReader(new FileReader(fileName))) {


List<String[]> r = reader.readAll();


//start with 1 not 0 because we do not want to include the header row
for (int i = 1; i < r.size(); i++) {
String[] result = Arrays.toString(r.get(i)).split(“\t”); // use ‘\t’ for tab delimited instead of ‘,’

CSV newRow = new CSV();

//opening square bracket -remove
newRow.setFilename(result[0].toString().trim().replace(“[“, “”));

//sales date and time
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“d/M/yyyy H:mm”);
LocalDateTime dateTime = LocalDateTime.parse(result[1].toString(), formatter);
newRow.setSalesDate(dateTime);

//no of customers
newRow.setNoOfCustomers((Integer) Integer.parseInt(result[2].toString().trim().length()==0?”0":result[2].toString().trim()));

//sales
newRow.setSales((double) Double.valueOf(result[3].toString()).doubleValue());

//closing square bracket
newRow.setTicketNo(result[4].toString().trim().replace(“]”, “”));

listCSV1.add(newRow);

}

}

}

return listCSV1;
}

//read Excel data and perform calculations
@ResponseBody
@CrossOrigin(origins = “*”)
@RequestMapping(value= “/calculate”, method= RequestMethod.GET)
public ArrayList<ApexData> calculate(HttpServletRequest request, HttpServletResponse response, @RequestParam(“select_job_type”) String jobType) throws IOException, CsvException {

ArrayList<ApexData> listApexData = new ArrayList<ApexData>();

List<CSV> listGraphCSV = new ArrayList<CSV>();
listGraphCSV.clear();
listGraphCSV = getCSVs();

int counter = 0;

// MAPPED AVERAGE SALES BY MONTH
Map<String, Double> o = listGraphCSV.stream().collect(Collectors.groupingBy(CSV::getSalesValue, Collectors.averagingDouble((CSV::getSalesValue))));
Console.log(“o AVG SALES MONTHLY : \n” + o);

// MAPPED AVERAGE NO OF CUSTOMERS BY MONTH
Map<String, Integer> r = listGraphCSV.stream().collect(Collectors.groupingBy(CSV::getNoOfCustomers, Collectors.averagingInt((CSV::getNoOfCustomers ))));
Console.log(“r AVG NO OF CUST MONTHLY : \n” + r);

//APEX CHART COORDS
listApexData.clear();
for (Map.Entry<String, Double> entry : o.entrySet()) {
ApexData graphCoords = new ApexData();
graphCoords.setX(Integer.parseInt(entry.getKey())); //x co-ord
graphCoords.setY(entry.getValue()); //y co-ord
listApexData.add(graphCoords);
}

//sort coordinates list according to x (month)
listApexData.sort((o1, o2)
-> o1.getX().compareTo(
o2.getX()));

return listApexData;
}

}

Explanation

Let’s go through the main bits of the controller code.

storeUploadedFile() function -

The storeUploadedFile() functionality — the uploaded file is retrieved at the server side using the HTTPRequest getPart() function which gets the Part of the uploaded file named, sent in the request to be saved on the server in the location specified by the programmer. In the HTML client sending the file, the form must of type : multipart/form-data, and so this must be inserted in the <form> tag using the enctype parameter. Note that the upload settings at the top of the controller code defining the maximum file size and file portions are important to ensure the file data being transmitted is of the correct size. The function accepts the uploaded file as a MultipartFile object.The file uploaded Part is converted to InputStream and copied to the Assets server folder location using file copy utilities tool. For the first time, it checks if the folder exists and if not, it creates it. The Java New (non-blocking) Input/Output (nio) and Input/Output (io) library is used to handle the system file management functionalities as described.

readLatestFile() function -

As you can see, all uploaded Excel or CSV files are stored in the Assets folder on the server. The Excel or CSV file selection to be read will be the one with the latest date modified (hence, the most recent). Data will be extracted from this file using Java and returned from the function as a JSON array to the calling function on the client — this is handled in the readLatestFile() controller function above.

A File[] array is created to hold a list of all the uploaded files in the Asset folder of the server so that their modified dates can be compared with each other, and the latest file is selected.

The BasicFileAttributes class is used to compare file properties and the LastModifiedTime property is used to get the file’s modified date and time. The sort method is used to sort all the files in the list by modified date and time, and the file with the latest modified date and time is chosen using the compareTo() method in the portion of the code snippet shown below:

listCSV.sort((CSV csv1, CSV csv2) -> csv1.getUpdateDateTime().compareTo(csv2.getUpdateDateTime()));

The filename extension is then extracted with Java string manipulation, obtaining the portion of the string after the “.” of the filename. Based on whether it has an “xlsx” (Excel) extension or a “csv” (comma-separated values text file) extension, two separate programming loops will handle the appropriate matching file and extract its contents. The contents are extracted row-by-row. The Apache POI free library is used to handle manipulations for the Excel file using Workbook and Sheet, and row extraction uses the Row class. The OpenCSV free library is used when the file is a CSV text file. The OpenCSV’s CSVReader is used to read each line of the CSV text file and the List<String[]> stores the contents of the CSV file on a row-by-row basis.

The split(“\t”) syntax is used to extract each cell or column component of each row and afterwards, hence using a tab separator. The replace() method is used to replace the opening and closing square brackets found in each row of the CSV file with an empty string. The iterator and cellIterator is used to iterate or loop between rows, and for each row, between cells of each column.

All the data per row, for each cell in the Excel or CSV file, is returned as a JSON array in the last line of the function by using the @ResponseBody annotation and the @Controller annotation at the beginning of the controller class. The HTML client file, readCSVFile.html, reads the JSON array returning all the rows in the Excel/CSV file and populates the jsGrid datagrid component.

calculate() function-

The last function, calculate(), makes use of the very interesting Java Collectors reducing function which allows you to perform operations on them such as averaging, max, min, binary operators, group by and so on. For this application, we will use the average reducing function to aggregate the daily sales data and group them by monthly average in order to get a month-by-month view.

In this calculate() function, our input is the list containing all the converted rows from the latest Excel or CSV file we have just read. The calculate() function performs the averaging function for each month and outputs the data in x,y coordinate form for the ApexData class for our Apex chart which simply stores the coordinates for our chart in {x,y} JSON format. For the chart coordinates, x represents the month number (1 to 12, being January to December) and y represents the computed average value for the month.

ApexData.java:
package default;

import java.util.Comparator;

public class ApexData {

private Integer x; //sales month
private Double y;//average sales value for that month

//constructor

public ApexData() {

this.x = 0;
this.y = 0.0;

}

public Integer getX() {

return x;

}

public void setX(Integer x) {

this.x = x;

}

public Double getY() {

return y;

}

public void setY(Double y) {

this.y = y;

}

public static Comparator<ApexData> monthComparator = new Comparator<ApexData>() {

@Override

public int compare(ApexData jc1, ApexData jc2) {

return (jc2.getX() < jc1.getX() ? -1 :

(jc2.getX() == jc1.getX() ? 0 : 1));

}

};

public String toString() {

return “ApexData class”;

}

}

We need a Comparator utility function in ApexData in order to sort out the month and its corresponding data, in order, from 1 (=Jan) to 12 (Dec) for the year.

We use the Java Collectors class based on AveragingInt and AveragingDouble criteria to get the monthly averages for number of customers (which is an integer value) and monthly average sales (a double or currency/float value with 2 decimal places). The HTML file, showCharts.html, retrieves the Apex chart coordinates, returned as a JSON array, and draws the chart.

For the ApexData, we test from the browser that the JSON is coming out correctly:

Fig 2: JSON array returned from calculate()

The Frontend

With the backend coding done, we now proceed to do the frontend code. This basically just involves writing asynchronous HTML files which calls the Spring controller and retrieves the JSON array or string returned.

The first HTML file involves the jsGrid data grid component to show tabular data of the latest Excel or CSV file uploaded into the default server folder (\Assets).

showChart.html:

<!DOCTYPE html>
<html>

<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8" />

<title>Java App Demo</title>

<link rel=”stylesheet” href=”https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src=”https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src=”https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src=”https://cdn.jsdelivr.net/npm/apexcharts"></script>

<link type=”text/css” rel=”stylesheet” href=”./jsgrid/jsgrid.css” />
<link type=”text/css” rel=”stylesheet” href=”./jsgrid/jsgrid-theme.css” />
<script type=”text/javascript” src=”./jsgrid/jsgrid.min.js”></script>

<head>

<style>

</style>

<script>

var renmin = null;

var data = [];

var array1 = [];

var array2 = [];

var array3 = [];

var actualArray = [];

var averageArray = [];

var masterArray = [];

var myData = “”;

var myData1 = “”;

var myData2 = “”;

/* APEX CHART */
function chart1(a) {
var options = {
chart: {
toolbar: {
show: false
},
type: ‘line’,
background: ‘#2c3842’,
height: “184%”,
width: “97%”,,
opacity: 1.0,
},
fill: {
opacity: 1.0, //0.35,
background: ‘#2c3842’, //THIS COLOR IS THE OUTER AREA BACKGROUND FILL COLOR
},
title: {
text: ‘SALES CHART’,
align: ‘center’,
style: {
fontSize: ‘18px’,
fontWeight: ‘bold’,
color: ‘#ffffff’,
},
},
legend: {
show: false,
fontSize: ‘18px’,
color: ‘#ffffff’,
fontWeight: ‘bold’,
},
xaxis: {
labels: {
style: {
colors: ‘#ffffff’,
fontSize: ‘15px’,
fontWeight: ‘normal’,
},
},
type : ‘category’,
},
yaxis: {
min: 0,
tickAmount: 5,
forceNiceScale: true,
labels: {
style: {
colors: ‘#ffffff’,
fontSize: ‘14px’,
fontWeight: ‘normal’,
},
formatter: function (value) {
return parseFloat(value.toFixed(1));
},
},
},
grid: {
show: true,
borderColor: ‘#ffffff’,
row : {
colors: ‘#ffffff’
},
column : {
colors: ‘#ffffff’
},
},
colors: [“#15ebeb”, “#52eb34”],
fill: {
colors: [‘#ffffff’],
opacity: 1.0, //0.35,
},
grid: {
row: {
colors: [‘#2c3842’] // THESE COLORS DETERMINE THE INNER AREA BACKGROUND FILL
},
column: {
colors: [‘#2c3842’]
}
},

series: [],

noData: {
text: ‘Loading…’ //loading…
}
}

var chart = new ApexCharts(document.querySelector(“#chart”), options);

var url = ‘/calculate’;

$.getJSON(url, function() {

})

.done(function(response) {

for(let i = 0; i < response.length; i++) {
actualArray.name = “Actual”;
actualArray.push({x: response[i].x, y: parseFloat(response[i].y.toFixed(2))});

}

chart.updateSeries([ {name: ‘Actual, data: actualArray }]);

})
.fail(function() {
//fail code here
})
.always(function() {
//code here is always run regardless
});

chart.render();

}

</script>

</head>

<body>

<div id=”chart”>

</div>

</script>

</body>

</html>

The other HTML file, showTable.html, involves reading the Excel or CSV file data which has been processed on a monthly basis, which the Spring controller returns in the form of Apex chart coordinates (the ApexData class) as a JSON array. This JSON array is fed directly to the jsGrid in its controller function.

showTable.html:

<!DOCTYPE html>
<html>

<head>

<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8" />

<title>Java App Tutorial</title>

<style>

</style>

<link rel=”stylesheet” href=”https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link type=”text/css” rel=”stylesheet” href=”jsgrid.css” />
<link type=”text/css” rel=”stylesheet” href=”jsgrid-theme.css” />

<script src=”https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src=”https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>

<script type=”text/javascript” src=”./jsgrid/jsgrid.min.js”></script>

</head>

<body>

<div id=”jsGrid” style=”height:83%; width: 80%; left:240px; color:#000000; border: 0.1px solid #e9e9e9; border-radius:10px;”></div>

<script>

$(“#jsGrid”).jsGrid({
width: “84%”,
height: “640px”,

filtering: false,
control: true,
heading: true,
inserting: false,
editing: true,
sorting: true,
paging: true,
autoload: true,
selecting: true,
pageSize: 20,
pageButtonCount: 5,
pagerContainer: null,



controller: {
loadData: function(filter) {
return $.ajax({
type: “GET”,
url: “/readLatestFile”,
data: item
});
},


insertItem: function(item) {
return $.ajax({
type: “POST”,
url: “/add”,
data: item
});
},

updateItem: function(item) {
return $.ajax({
type: “PUT”,
url: “/update”,
data: item
});
},

deleteItem: function(item) {
return $.ajax({
type: “DELETE”,
url: “/delete”,
data: item
});
},
},

fields: [

{ title: “Sales Date”, name: “salesDate”, type: “text”, width: 250, align: “center” },
{ title: “No of customers”, name: “noOfCustomers”, type: “number”, width: 250 },
{ title: “Sales (USD)”, name: “salesValue”, type: “number”, width: 250 }
{ type: “control”, width: 70}
]

});

</script>

</body>

</html>

Compiling to WAR and deploying to the Tomcat web server on remote server machine

The final step is to compile the app to a WAR file and deploy it to the Tomcat web server which has been installed on the remote Windows Server 2012 computer. We can compile on Eclipse using the Maven Install option which will output the WAR file which will be generated in the target folder in Package Explorer.

We then copy the war file and paste it to the server using Remote Connection Manager in Tomcat’s webapp folder, and then run Tomcat to deploy the application.

.

--

--

Ezani

38+ years as a programmer. Started with VB, now doing Java, SpringBoot, Android, React, ASP and Python. Running my own software company at mysoftware2u.com