This article is a record of the first attempt to create a web application from scratch. I have not been able to explain the technology because there are some parts that I do not understand, but since I have posted the flow from design to implementation and the source code, I hope that it will be a little reference for other beginners.
A web application that records the start time of work, displays an alert after 50 minutes, and records the end time of work.
Project tree
.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── jp
│ │ │ └── co
│ │ │ └── anyplus
│ │ │ ├── Entity
│ │ │ │ └── TasksEntity.java
│ │ │ ├── Repository
│ │ │ │ └── TasksRepository.java
│ │ │ ├── ServletInitializer.java
│ │ │ ├── TaskTrackerApplication.java
│ │ │ ├── controller
│ │ │ │ └── IndexController.java
│ │ │ ├── form
│ │ │ │ └── TaskForm.java
│ │ │ └── service
│ │ │ ├── TaskCancelService.java
│ │ │ └── TaskRegisterService.java
│ │ ├── resources
│ │ │ ├── application.properties
│ │ │ ├── static
│ │ │ │ ├── css
│ │ │ │ │ ├── bootstrap-grid.css
│ │ │ │ │ ├── bootstrap-grid.css.map
│ │ │ │ │ ├── bootstrap-grid.min.css
│ │ │ │ │ ├── bootstrap-grid.min.css.map
│ │ │ │ │ ├── bootstrap-reboot.css
│ │ │ │ │ ├── bootstrap-reboot.css.map
│ │ │ │ │ ├── bootstrap-reboot.min.css
│ │ │ │ │ ├── bootstrap-reboot.min.css.map
│ │ │ │ │ ├── bootstrap.css
│ │ │ │ │ ├── bootstrap.css.map
│ │ │ │ │ ├── bootstrap.min.css
│ │ │ │ │ ├── bootstrap.min.css.map
│ │ │ │ │ └── jQuery.countdownTimer.css
│ │ │ │ ├── images
│ │ │ │ └── js
│ │ │ │ ├── bootstrap.bundle.js
│ │ │ │ ├── bootstrap.bundle.js.map
│ │ │ │ ├── bootstrap.bundle.min.js
│ │ │ │ ├── bootstrap.bundle.min.js.map
│ │ │ │ ├── bootstrap.js
│ │ │ │ ├── bootstrap.js.map
│ │ │ │ ├── bootstrap.min.js
│ │ │ │ ├── bootstrap.min.js.map
│ │ │ │ ├── import.js
│ │ │ │ ├── jQuery.countdownTimer.js
│ │ │ │ ├── jQuery.countdownTimer.min.js
│ │ │ │ ├── jQuery.countdownTimer.min.js.map
│ │ │ │ ├── jquery-3.4.1.min.js
│ │ │ │ ├── localisation
│ │ │ │ └── view-index.js
│ │ │ └── templates
│ │ │ └── index.html
I made the function as simple as possible, and made it on the premise that it can be made in one day for the time being.
// The result display has been postponed this time. I want to implement it in the future.
The sequence diagram is as follows.
Create only one table because you only need to store the tasks you have performed.
** Task table **
column | Description |
---|---|
task_id | Sequence value |
task_name | Task name |
task_category | Category of work. |
task_starttime | Work start time |
task_endtime | Work end time |
userid | USER ID. Because there is no user functionsystem Assuming that only can be entered |
DB We prepared the DB for this WEB application and made the table as follows.
DDL
CREATE TABLE public.tasks (
task_id bigserial NOT NULL,
task_name varchar(200) NULL,
task_category varchar(200) NULL,
task_starttime timestamp NULL,
task_endtime timestamp NULL,
userid varchar(20) NULL
);
Create a project. This time, I made it from the Spring Starter project as follows.
The Maven repository set above is as follows.
--SpringBootDevTool: A convenient development tool that automatically compiles and restarts when Java source changes are triggered. --SpringDataJpa: A convenient library for DB operations. It does automatic type conversion between Java <-> DB. --PostgreSQL Driver: Driver for accessing PostgreSQL --Thymeleaf: Template engine. Variables described by attribute values are automatically replaced with values --SpringWebStarter: A definition set that includes all the settings and library dependencies required for a web application.
After creating the project, right-click on the project> Execute> Maven Install to install the repository file defined in pom.xml.
In this procedure, pom.xml is automatically generated in Spring Starter project, When changing the version or adding the library manually, it seems better to edit pom.xml directly and perform Maven Install or Maven Clean and Install. You can find the Maven repository from this site.
Next, open the application.properties file and set the DB connection settings.
application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/[Database name]
spring.datasource.username=[Database user]
spring.datasource.password=[Database password]
spring.datasource.driver-class-name=org.postgresql.Driver
Run it to see if the project is ready without any problems. (Right click on the project> Run> Spring Boot application)
Confirm that the following message is output to the console log and proceed to the next.
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s):-(Abbreviation)-
jp.co.anyplus.TaskTrackerApplication : Started [Application name] in [Numerical value] seconds (JVM running for [Numerical value])
First, put in the required libraries. There was jQuery as an extension of JavaScript, Bootstrap for layout, and Countdown Timer for timer, so I used this.
CountdownTimer (https://github.com/harshen/jQuery-countdownTimer/) A jQuery library that has all the functions of date and time countup and countdown. It was perfect for this app because it has all the functions of layout specification and Start / Stop.
html I made only one screen this time.
The layout is decided by Bootstrap.
For the timer, the display area is determined by <span id =" timerView ">
, and the display location is specified for the parts of the Coundown Timer.
The default CSS of CoundownTimer is a little unsatisfactory, so I am editing it, but I will omit it here.
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/css/jQuery.countdownTimer.css" />
<title>TaskTracker</title>
</head>
<body>
<main class="container">
<h1>Task Tracker</h1>
<p>Record the task.</p>
<div class="container text-center">
<div id="taskMainContainer" class="jumbotron col-md-8 mx-auto">
<form id="TaskForm" th:object="${taskForm}">
<div class="row">
<div class="col display-4"><span id="timerView">50:00</span></div>
</div>
<div class="row mt-3 justify-content-md-center">
<div class="col-md-4 "><button id="startButton" type="button" class="btn btn-block btn-success" disabled>Start</button></div>
<div class="col-md-4 "><button id="stopButton" type="button" class="btn btn-block btn-danger" disabled>Stop</button></div>
</div>
<div class="row mt-3 justify-content-md-center">
<div class="col-md-4 p-1"><input th:field="*{category}" type="text" class="form-control" placeholder="category"></div>
<div class="col-md-8 p-1"><input th:field="*{taskName}" type="text" class="form-control" placeholder="task"></div>
<div><input th:field="*{taskId}" type="text" class="form-control" hidden></div>
</div>
</form>
</div>
</div>
</main>
<script src="/js/import.js"></script>
<script src="/js/view-index.js"></script>
</body>
</html>
JavaScript I made import.js for importing the library and view-index.js for describing screen processing.
import.js
/**
* import
**/
document.write('<script type="text/javascript" src="/js/jquery-3.4.1.min.js"></script>');
document.write('<script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script>');
view-index.js
/*--------------------------------
* index.JS for html
--------------------------------*/
document.write('<script type="text/javascript" src="/js/jQuery.countdownTimer.js"></script>');
$(function(){
$("#timerView").countdowntimer({
minutes :50,
seconds :00,
displayFormat : "MS",
size : "xl",
timeUp : taskFinish
});
$("#timerView").countdowntimer("stop", "stop");
stopbtnOn();
});
/*--------------------------------
*Start task
--------------------------------*/
$('#startButton').on('click', taskStart);
function taskStart(){
startbtnOn();
$("#timerView").countdowntimer("stop", "start");
var form = $('#TaskForm').serializeArray();
var formdata = {};
jQuery.each(form, function(i, e) {
formdata[e.name] = e.value;
});
$.ajax({
url:'/',
type:'POST',
contentType : 'application/json; charset=utf-8',
data: JSON.stringify(formdata)
}).done( (data) => {
console.log("success");
console.log(data);
$('#taskId').val(data);
}).fail( (data) => {
console.log("fail");
console.log(data);
});
}
/*--------------------------------
*Stop task
--------------------------------*/
$('#stopButton').on('click', taskStop);
function taskStop(){
stopbtnOn();
$("#timerView").countdowntimer("stop", "stop");
var form = $('#TaskForm').serializeArray();
var formdata = {};
jQuery.each(form, function(i, e) {
formdata[e.name] = e.value;
});
$.ajax({
url:'/stop',
type:'POST',
contentType : 'application/json; charset=utf-8',
data: JSON.stringify(formdata)
}).done( (data) => {
console.log("success");
console.log(data);
}).fail( (data) => {
console.log("fail");
console.log(data);
});
}
/*--------------------------------
*Task completed
--------------------------------*/
function taskFinish(){
setTimeout(() => {
taskStop();
alert("Finish!!");
$("#timerView").countdowntimer("stop", "stop");
stopbtnOn();
}, 1000);
}
/*--------------------------------
*Button control: When the start button is ON
--------------------------------*/
function startbtnOn(){
$('#startButton').prop("disabled", true);
$('#stopButton').prop("disabled", false);
}
/*--------------------------------
*Button control: When the stop button is ON
--------------------------------*/
function stopbtnOn(){
$('#startButton').prop("disabled", false);
$('#stopButton').prop("disabled", true);
}
First, create the TasksEntity class for storing table data and the TasksRepository class for operating the table created in the DB.
TasksEntity.java
package jp.co.anyplus.Entity;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="tasks")
public class TasksEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="task_id")
private Long taskId;
@Column(name="task_name")
private String taskName;
@Column(name="task_category")
private String taskCategory;
@Column(name="task_starttime")
private Timestamp taskStartTime;
@Column(name="task_endtime")
private Timestamp taskEndTime;
@Column(name="userid")
private String userid;
public Long getTaskId() {
return taskId;
}
public void setTaskId(Long taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskCategory() {
return taskCategory;
}
public void setTaskCategory(String taskCategory) {
this.taskCategory = taskCategory;
}
public Timestamp getTaskStartTime() {
return taskStartTime;
}
public void setTaskStartTime(Timestamp taskStartTime) {
this.taskStartTime = taskStartTime;
}
public Timestamp getTaskEndTime() {
return taskEndTime;
}
public void setTaskEndTime(Timestamp taskEndTime) {
this.taskEndTime = taskEndTime;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
}
TasksRepository.java
package jp.co.anyplus.Repository;
import java.sql.Timestamp;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import jp.co.anyplus.Entity.TasksEntity;
@Repository
public interface TasksRepository extends JpaRepository<TasksEntity, Long> {
@Query(value="select current_timestamp", nativeQuery = true)
public Timestamp getCurrentTime();
@Query(value="select * from tasks where task_id = :taskId"
, nativeQuery = true)
public TasksEntity getByTaskId(@Param("taskId")Long taskId);
}
Since JpaRepository inherited by TasksRepository.java can use the function of Spring Data JPA (reference article), It seems that the content of this query could be automatically generated from the method name without writing SQL.
Create around the Controller layer to flow from View to Model. IndexController.java of Cotroller class, TaskRegisterService.java and TaskCancelService.java of Service class, Screen <-> We have prepared a total of 4 classes of TaskForm.java for passing task information between servers.
IndexController.java
//Package and import statements omitted
@Controller
public class IndexController {
@Autowired
TaskRegisterService registerService;
@Autowired
TaskCancelService cancelService;
/**
*Initial display processing
*
* @param mav ModelAndView
* @return Initial display information
*/
@GetMapping("/")
public ModelAndView init(ModelAndView mav) {
TaskForm taskForm = new TaskForm();
mav.addObject("taskForm", taskForm);
mav.setViewName("index.html");
return mav;
}
/**
*Task registration process
*
* @param model Model
* @param taskForm Task information Form
* @return Newly assigned task ID
*/
@PostMapping("/")
@ResponseBody
public String taskRegister(Model model, @RequestBody TaskForm taskForm) {
try {
TasksEntity regTask = registerService.registerTask(taskForm);
return regTask.getTaskId().toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
*Task end processing
*
* @param model Model
* @param taskForm Task information Form
* @return processing message
*/
@PostMapping("/stop")
@ResponseBody
public String taskEnd(Model model, @RequestBody TaskForm taskForm) {
try {
cancelService.endTask(taskForm);
} catch(RuntimeException e) {
e.printStackTrace();
return "error";
}
return "task canceling success";
}
}
I haven't come to understand the @ResponseBody, @ PostMapping / @GetMapping, and Model classes used to communicate between the screen and the server. If you understand, I would like to review the source and make a Qiita article.
TaskRegisterService.java
//Package and import statements omitted
@Service
public class TaskRegisterService {
@Autowired
TasksRepository tasksRep;
@Transactional(rollbackOn = Exception.class)
public TasksEntity registerTask(TaskForm taskForm) throws RuntimeException {
Timestamp currenttime = tasksRep.getCurrentTime();
TasksEntity taskEntity = new TasksEntity();
taskEntity.setTaskName(taskForm.getTaskName());
taskEntity.setTaskCategory(taskForm.getCategory());
taskEntity.setTaskStartTime(currenttime);
taskEntity.setUserid("system");
try {
TasksEntity regTask = tasksRep.saveAndFlush(taskEntity);
return regTask;
} catch (RuntimeException e) {
//log: Registration failed.
throw e;
}
}
}
TaskCancelService.java
//Package and import statements omitted
@Service
public class TaskCancelService {
@Autowired
TasksRepository tasksRep;
@Transactional(rollbackOn = Exception.class)
public void endTask(TaskForm taskForm) throws RuntimeException {
Timestamp currenttime = tasksRep.getCurrentTime();
TasksEntity taskEntity = tasksRep.getByTaskId(Long.parseLong(taskForm.getTaskId()));
taskEntity.setTaskId(Long.parseLong(taskForm.getTaskId()));
taskEntity.setTaskEndTime(currenttime);
try {
tasksRep.saveAndFlush(taskEntity);
} catch (RuntimeException e) {
//log: Update failed.
throw e;
}
}
}
TaskForm.java
//Package and import statements omitted
@SuppressWarnings("serial")
public class TaskForm implements java.io.Serializable {
private String taskId;
private String category;
private String taskName;
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
}
Recommended Posts