TaskApp with Dynamic Bootstrap Modals

In this post you’ll learn how to add dynamic modals to our TaskApp from the previous post using Javascript. This time you’ll create the project from scratch with Spring Initializr.

Optional: You learn how to add you project to a GitHub repository for version control. For that create an account.

Find a working version of the finished App on Heroku(it may take a while to load) and the Source Code here. Feel free to use the code on Github for practice. The Frontend was designed by Bootstrap and can be found here. Provided under MIT License.

Development Environment:

IDE: IntelliJ, Eclipse
Install: MySQL Workbench (How to install the mySQL Workbench)
Deployment: Get a free Heroku Account
Java: Install Java on your computer and follow the installation instructions
Version Control: Install Git on your computer. Get a GitHub Account. Generate an ssh-key on your computer.

 

Generate project with Spring Initializr:
Get the dependencies: JPA, Web, mySQL, PostgreSQL, Thymeleaf. Download, unpack and import the project into an IDE of your choice. I’ll use IntelliJ.

The Folder Structure will look like this:

For the Frontend download the Admin Dashboard from startbootstrap. Unpack the content and add it to your folder structure like this:

Next, copy this into your .gitignore
This is more extensive than necessary for this project but you may use it for future projects, too.

/target/
!.mvn/wrapper/maven-wrapper.jar

######################
# STS
######################
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

######################
# Node
######################
/node/
node_tmp/
node_modules/
npm-debug.log.*

######################
# SASS
######################
.sass-cache/

######################
# Eclipse
######################
*.pydevproject
.project
.metadata
tmp/
tmp/**/*
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
.factorypath
/src/main/resources/rebel.xml

# External tool builders
.externalToolBuilders/**

# Locally stored "Eclipse launch configurations"
*.launch

# CDT-specific
.cproject

# PDT-specific
.buildpath

######################
# Intellij
######################
.idea/
*.iml
*.iws
*.ipr
*.ids
*.orig

######################
# Visual Studio Code
######################
.vscode/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/

######################
# Maven
######################
/log/
/target/

######################
# Gradle
######################
.gradle/
/build/

######################
# Package Files
######################
*.jar
*.war
*.ear
*.db

######################
# Windows
######################
# Windows image file caches
Thumbs.db

# Folder config file
Desktop.ini

######################
# Mac OSX
######################
.DS_Store
.svn

# Thumbnails
._*

# Files that might appear on external disk
.Spotlight-V100
.Trashes

######################
# Directories
######################
/bin/
/deploy/

######################
# Logs
######################
*.log

######################
# Others
######################
*.class
*.*~
*~
.merge_file*

######################
# Gradle Wrapper
######################
!gradle/wrapper/gradle-wrapper.jar

######################
# Maven Wrapper
######################
!.mvn/wrapper/maven-wrapper.jar

To connect your project to GitHub:
Create an empty new repository on GitHub.  Navigate to your repository.
Under “clone or download” copy the ssh-link. Make sure that your have your ssh -key on GitHub. If not follow this video.
In IntelliJ, under VCS: click on “Enable Version Control Integration…“, choose Git and enter your username and password for GitHub.

Version Control in IntelliJ

application.properties and data.sql:

Connects our app to a Database “taskapp_with_modal_db”. The default port is 8080. With the current cofigurations the app will insert the data from data.sql (code below, create file in same directory as application.properties) and create a new DB after every restart of the app (it will drop the old DB if it exists).

spring.datasource.url=jdbc:mysql://localhost/taskapp_with_modal_db?useSSL=false

#place your password and username from your MySQL Workbench
spring.datasource.username=root
spring.datasource.password=root

spring.jpa.show-sql=true
#init data after every restart
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=create
spring.datasource.initialization-mode=always

## Hibernate Logging
logging.level.org.hibernate.SQL= DEBUG
INSERT INTO tasks (task_id, content, status, title) VALUES
  ( 1000, '(1)Generate a new project with Spring Initializr using Thymeleaf, MySQl, Web, and JPA', 'CLOSED', 'Setup a new project'),
  ( 1001, '(2)Create Folders for your model, repositories, controllers and services', 'REOPENED', 'Folder Structure'),
  ( 1002, '(3)Write a Task POJO with the following properties: title, status of type Status(Enum), content', 'OPEN', 'Task Entity'),
  ( 1003, '(4)Write an Enum Class for Status containing OPEN, CLOSED, REOPENED', 'OPEN', 'Status Enum'),
  ( 1004, '(5)Create a Service Interface and its Implementation', 'OPEN', 'Service Layer'),
  ( 1005, '(6)Complete the Application.properties file and create a new schema in your sql workbench', 'CLOSED', 'Databasa and Application Properties'),
  ( 1006, '(7)Write a Crud Repository for your Task entity', 'OPEN', 'Repository'),
  ( 1007, '(8)Write a TaskController', 'REOPENED', 'Controller');

Create a new Schema “taskapp_with_modal_db” in your mySQL Workbench. Change password and username in the application.properties file
Once you have the data.sql file, you will be asked to “Configure data source“.

Create a new data source as described in the picture below. Enter your password and username from your mySQL Workbench and the name of the Databse (taskapp_with_modal_db). Be sure to test the connection before closing the window.

Creating the Backend:

Create a new folder “model” and the files “Task.java” and “Status.java”. Each Task will have one Status.

Task.java:

@Entity
@Table(name = "tasks")
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "task_id")
    private Long id;

    @NotEmpty
    private String title;

    @Lob
    @NotEmpty
    @Type(type = "org.hibernate.type.TextType") //heroku config
    private String content;

    //status may not be null
    @NotNull
    @Enumerated(value = EnumType.STRING)
    private Status status;

    public Task() {
        this.status = Status.OPEN;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    public void closeTask(){
        this.status = Status.CLOSED;
    }

    public void reopenTask(){
        this.status = Status.REOPENED;
    }
}

Status.java

public enum Status {
    OPEN("open"),
    CLOSED("closed"),
    REOPENED("reopened");

    private String typeOfStatus;

    Status(String typeOfStatus){
        this.typeOfStatus = typeOfStatus;
    }

    public String getTypeOfStatus() {
        return typeOfStatus;
    }

    public static Stream<Status> stream() {
        return Stream.of(Status.values());
    }
}

Create a new folder “repository” and the file TaskRepository.java:

@Repository
public interface TaskRepository extends CrudRepository<Task, Long>{
}

For the Service Layer create a new folder “service” and its files “TaskService.java” and “TaskServiceImpl.java”.

TaskService.java:

@Service
public interface TaskService {
    /**
     * GET all tasks from DB
     * @return all tasks from Database
     */
    Set<Task> getTasks();

    /**
     * finds a task by its ID
     * @param taskId    Database ID of task
     * @return          task
     */
    Task findById(Long taskId);

    /**
     * creates new Task and saves it in Database
     * @param taskDetails   field values
     * @return              new Task
     */
    Task createTask(Task taskDetails);

    /**
     * updates Task from Database with field values in taskDetails
     * @param taskId        Database ID of task
     * @param taskDetails   field values
     * @return              updated Task
     */
    Task updateTask(Long taskId, Task taskDetails);

    /**
     * deletes Task from Database
     * @param taskId    Database ID of task
     */
    void deleteTask(Long taskId);

    /**
     * Sets Task.Status to CLOSED
     * @param taskId    Database ID of task
     */
    void closeTask(Long taskId);

    /**
     * Sets Task.Status to REOPENED
     * @param taskId    Database ID of task
     */
    void reopenTask(Long taskId);

    /**
     * Filters all Tasks by Status
     * @param status    Enum: OPEN;CLOSED;REOPENED
     * @return          Set of Tasks
     */
    Set<Task> getTasksByStatus(Status status);

}

TaskServiceImpl.java

@Service
public class TaskServiceImpl implements TaskService{

    @Autowired
    private TaskRepository taskRepository;

    @Override
    public Set<Task> getTasks() {
        Set<Task> taskSet = new HashSet<>();
        taskRepository.findAll().iterator().forEachRemaining(taskSet::add);
        return taskSet;
    }

    @Override
    public Task findById(Long taskId) {
        Optional<Task> taskOptional = taskRepository.findById(taskId);
        if(!taskOptional.isPresent()){
            throw new RuntimeException("Task Not Found!");
        }
        return taskOptional.get();
    }

    @Override
    public Task createTask(Task taskDetails) {
        Task newTask = taskRepository.save(taskDetails);
        return newTask;
    }

    @Override
    public Task updateTask(Long taskId, Task taskDetails) {
        Task task = findById(taskId);
        task.setTitle(taskDetails.getTitle());
        task.setContent(taskDetails.getContent());
        task.setStatus(taskDetails.getStatus());
        taskRepository.save(task);
        return task;
    }

    @Override
    public void deleteTask(Long taskId) {
        taskRepository.delete(findById(taskId));
    }

    @Override
    public void closeTask(Long taskId) {
        Task task = findById(taskId);
        task.closeTask();
        taskRepository.save(task);
    }

    @Override
    public void reopenTask(Long taskId) {
        Task task = findById(taskId);
        task.reopenTask();
        taskRepository.save(task);
    }

    @Override
    public Set<Task> getTasksByStatus(Status status) {
        Set<Task> allTasks = getTasks();
        Set<Task> filteredTasks = new HashSet<>();

        for(Task t : allTasks){
            if(t.getStatus().equals(status)){filteredTasks.add(t);}
        }

        return filteredTasks;
    }
}

Next, create a folder “controller” and the file TaskController.java:

@Controller
public class TaskController {

    @Autowired
    private TaskService taskService;

    /**
     * GET all tasks from Database
     * @return template view for all tasks
     */
    @RequestMapping(value = {"/tasks", "/"}, method=RequestMethod.GET)
    public String dashboard(Model model) {
        //display all Tasks
        Set<Task> tasks = taskService.getTasks();
        model.addAttribute("tasks", tasks);

        Set<Status> statusList = new HashSet<>();
        Status.stream().forEach(statusList::add);
        model.addAttribute("statusList", statusList);

        return "index";
    }

    /**
     * Shows Tasks by Status
     * @param model         contains TaskObject
     * @param taskStatus    may have the values "open/closed/reopened"
     * @return              Set of Tasks with specific status
     */
    @RequestMapping(value = "/{status}", method=RequestMethod.GET)
    public String displayByStatus(Model model, @PathVariable("status") String taskStatus) {

        if(taskStatus.equals(Status.OPEN.getTypeOfStatus())){
            model.addAttribute("tasks", taskService.getTasksByStatus(Status.OPEN));
        } else if(taskStatus.equals(Status.CLOSED.getTypeOfStatus())){
            model.addAttribute("tasks", taskService.getTasksByStatus(Status.CLOSED));
        } else if(taskStatus.equals(Status.REOPENED.getTypeOfStatus())){
            model.addAttribute("tasks", taskService.getTasksByStatus(Status.REOPENED));
        }

        Set<Status> statusList = new HashSet<>();
        Status.stream().forEach(statusList::add);
        model.addAttribute("statusList", statusList);

        return "index";
    }

    /**
     * handles Status Changes
     * @param taskId        Task Id
     * @param action        may contain "close/open/reopen"
     * @param request       helps redirect to previous site
     * @return              redirection
     */
    @RequestMapping(value = "/task/{id}/{action}", method=RequestMethod.GET)
    public String handleStatus(@PathVariable("id") Long taskId,
                               @PathVariable("action") String action,
                               HttpServletRequest request){
        Task task = taskService.findById(taskId);

        if (action.equals("close")) {
            if(task.getStatus() == Status.OPEN) {taskService.closeTask(taskId);}
            if(task.getStatus() == Status.REOPENED) {taskService.closeTask(taskId);}
        }
        if(action.equals("reopen") && task.getStatus() == Status.CLOSED) {taskService.reopenTask(taskId);}

        String referer = request.getHeader("Referer");
        return "redirect:"+ referer;
    }

    /**
     * Save NEW Task in Database
     * @param taskDetails   field values
     * @return              redirect to Dashboard
     */
    @RequestMapping(path = "/task/create", method = RequestMethod.POST)
    public String createTask(Task taskDetails) {
        Task newTask = taskService.createTask(taskDetails);
        return "redirect:/";
    }

    /**
     * updates Task in DB wirh field Values from EDIT Modal
     * @param taskDetails   Task Object with field Values from EDIT Modal
     * @return              redirect to dashboard
     */
    @RequestMapping(path = "/update", method = RequestMethod.POST)
    public String updateTaskWithModal(Task taskDetails) {
        taskService.updateTask(taskDetails.getId(), taskDetails);
        return "redirect:/";
    }

    /**
     * @ResponseBody: object returned is automatically serialized
     * into JSON and passed back into the HttpResponse object
     * (Source: https://www.baeldung.com/spring-request-response-body)
     *
     * @param taskId    taskId
     * @return      Task from DB
     */
    @RequestMapping(path = "/findTask/{id}", method = RequestMethod.GET)
    @ResponseBody
    public Task findTask(@PathVariable("id") long taskId){
        return taskService.findById(taskId);
    }


    /**
     * Deletes Task from Database
     * @param taskId    TaskId
     * @return          redirect to Dashboard
     */
    @RequestMapping(path = "/task/{id}/delete", method = RequestMethod.GET)
    public String deleteTask(@PathVariable("id") long taskId, HttpServletRequest request) {
        taskService.deleteTask(taskId);
        return "redirect:/";
    }

}

This concludes the Backend. For the Frontend copy the following into your index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:include="fragments/header"></th:block>

<body id="page-top">

    <!-- Page Wrapper -->
    <div id="wrapper">

        <th:block th:include="fragments/sidebarLeft"></th:block>

        <!-- Content Wrapper -->
        <div id="content-wrapper" class="d-flex flex-column">

            <!-- Main Content -->
            <div id="content">

            <th:block th:include="fragments/navbar"></th:block>

            <!-- Begin Page Content -->
            <div class="container-fluid">

                <div class="row">
                    <div class="col">
                        <div style="margin-bottom: 20px;">
                            <button class="btn btn-primary newBtn">
                                <i class="fas fa-plus-circle"></i>  New Task
                            </button>
                        </div>
                    </div>
                </div>
                <!-- Content Row -->

                <div class="row">
                    <!-- List of Tasks-->
                    <th:block th:include="tasks/tasks"></th:block>
                </div>
                <div class="row">
                    <th:block th:include="tasks/editModal"></th:block>
                </div>
                <div class="row">
                    <th:block th:include="tasks/newModal"></th:block>
                </div>
            </div>
            <!-- /.container-fluid -->

            </div>
            <!-- End of Main Content -->

            <th:block th:include="fragments/footer"></th:block>

        </div>
        <!-- End of Content Wrapper -->

    </div>
    <!-- End of Page Wrapper -->

    <!-- Scroll to Top Button-->
    <a class="scroll-to-top rounded" href="#page-top">
        <i class="fas fa-angle-up"></i>
    </a>

    <th:block th:include="fragments/scripts"></th:block>

</body>

</html>

In the resources>templates>fragments folder create the following files:

header.html

<head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>TaskApp</title>

    <!-- Custom fonts for this template-->
    <link th:href="@{/vendor/fontawesome-free/css/all.min.css}" rel="stylesheet" type="text/css">
    <link th:href="@{https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i}" rel="stylesheet">

    <!-- Custom styles for this template-->
    <link th:href="@{/css/sb-admin-2.min.css}" rel="stylesheet">

</head>

navbar.html

<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
    <!-- Sidebar Toggle (Topbar) -->
    <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
        <i class="fa fa-bars"></i>
    </button>

    <!-- Topbar Navbar -->
    <ul class="navbar-nav ml-auto"></ul>

</nav>

sidebarLeft.html

<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">

    <!-- Sidebar - Brand -->
    <a class="sidebar-brand d-flex align-items-center justify-content-center" th:href="@{/}">
        <div class="sidebar-brand-text mx-3">TaskApp</div>
    </a>

    <!-- Divider -->
    <hr class="sidebar-divider my-0">

    <!-- Nav Item - Dashboard -->
    <li class="nav-item">
        <a class="nav-link" th:href="@{/}">
            <i class="fas fa-fw fa-tachometer-alt"></i>
            <span>Dashboard</span></a>
    </li>

    <!-- Divider -->
    <hr class="sidebar-divider">


    <!-- Heading -->
    <div class="sidebar-heading">
        Tasks by Status
    </div>

    <!-- Nav Item - Charts -->
    <li class="nav-item">
        <a class="nav-link" th:href="@{/{status}(status='open')}">
            <i class="fas fa-fw fa-chart-area"></i>
            <span>OPEN</span></a>
    </li>

    <!-- Nav Item - Charts -->
    <li class="nav-item">
        <a class="nav-link" th:href="@{/{status}(status='reopened')}">
            <i class="fas fa-fw fa-chart-area"></i>
            <span>REOPENED</span></a>
    </li>

    <!-- Nav Item - Tables -->
    <li class="nav-item">
        <a class="nav-link" th:href="@{/{status}(status='closed')}">
            <i class="fas fa-fw fa-table"></i>
            <span>CLOSED</span></a>
    </li>

    <!-- Divider -->
    <hr class="sidebar-divider d-none d-md-block">

    <!-- Sidebar Toggler (Sidebar) -->
    <div class="text-center d-none d-md-inline">
        <button class="rounded-circle border-0" id="sidebarToggle"></button>
    </div>

</ul>

scripts.html

This snippet controls what kind of data will be loaded into the modals. The Edit Modal will display the data of a single Task. The New Task Modal will have the initial values of status: OPEN.

<!-- Bootstrap core JavaScript-->
<script th:src="@{/vendor/jquery/jquery.min.js}"></script>
<script th:src="@{/vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>

<!-- Core plugin JavaScript-->
<script th:src="@{/vendor/jquery-easing/jquery.easing.min.js}"></script>

<!-- Custom scripts for all pages-->
<script th:src="@{/js/sb-admin-2.min.js}"></script>

<!-- EDIT modal-->
<script>
    $(document).ready(function(){
        $('.dropdown .eBtn').on('click', function(event){
            //prevents redirection to blank page with JSON-Task-Object
            event.preventDefault();
            var href = $(this).attr('href');
            $.get(href, function(task){
                $('.myForm #id').val(task.id);
                $('.myForm #title').val(task.title);
                $('.myForm #content').val(task.content);
                $('.myForm #status').val(task.status);
            });
            $('.myForm #editModal').modal();
        });
    });
</script>

<!--NEW Task Modal-->
<script>
    $(document).ready(function(){
        $('.newBtn').on('click', function(event){
            $('.newTaskForm #id').val('');
            $('.newTaskForm #title').val('');
            $('.newTaskForm #content').val('');
            $('.newTaskForm #status').val('OPEN');
            $('.newTaskForm #newTaskModal').modal();
        });
    });
</script>

In templates>tasks create the following files:

editModal.html

<div class="col-xl-8 col-lg-7">
    <div class="myForm">
        <form th:action="@{/update}" th:method="post">
        <div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="editModalLabel" aria-hidden="true">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title" id="exampleModalLabel">Edit</h5>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">
                        <div class="form-group">
                            <input  type="hidden" value="" class="form-control" id="id" name="id"/>
                        </div>
                        <div class="form-group">
                            <label for="title">Title:</label>
                            <input value="" type="text" class="form-control" id="title" name="title"/>
                        </div>
                        <div class="form-group">
                            <label for="status">Status:</label>
                            <select id="status" class="form-control" name="status">
                                <option th:each="status : ${statusList}"
                                        th:text="${status}" th:value="${status}">
                                </option>
                            </select>
                        </div>
                        <div class="form-group">
                            <label for="content">Content:</label>
                            <textarea value="" class="form-control" name="content"
                                      id="content" rows="5">
                            </textarea>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                        <input type="submit" class="btn btn-primary" value="save"/>
                    </div>
                </div>
            </div>
        </div>
        </form>
    </div>
</div>

newModal.html

<div class="col-xl-8 col-lg-7">
    <div class="newTaskForm">
        <form th:action="@{/task/create}" th:method="post">
            <div class="modal fade" id="newTaskModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
                <div class="modal-dialog" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="exampleModalLabel">Edit</h5>
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                <span aria-hidden="true">&times;</span>
                            </button>
                        </div>
                        <div class="modal-body">
                            <div class="form-group">
                                <input  type="hidden" value="" class="form-control" id="id" name="id"/>
                            </div>
                            <div class="form-group">
                                <label for="title">Title:</label>
                                <input value="" type="text" class="form-control" id="title" name="title"/>
                            </div>
                            <div class="form-group">
                                <label for="status">Status:</label>
                                <select id="status" class="form-control" name="status">
                                    <option th:each="status : ${statusList}"
                                            th:text="${status}" th:value="${status}">
                                    </option>
                                </select>
                            </div>
                            <div class="form-group">
                                <label for="content">Content:</label>
                                <textarea value="" class="form-control" name="content"
                                          id="content" rows="5">
                            </textarea>
                            </div>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                            <input type="submit" class="btn btn-primary" value="save"/>
                        </div>
                    </div>
                </div>
            </div>
        </form>
    </div>
</div>

tasks.html

<div class="col-xl-11 col-lg-11">

    <div th:if="${#lists.isEmpty(tasks)}" class="card shadow mb-4">
        <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
            <h6 class="m-0 font-weight-bold text-primary">&nbsp;&nbsp;</h6>
        </div>
        <!-- Card Body -->
        <div class="card-body">
            <p>Your Task List is empty.</p>
        </div>
    </div>

    <div th:each = "task : ${tasks}" class="card shadow mb-4">
        <!-- Card Header - Dropdown -->
        <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">

            <!-- Info Button for OPEN Tasks-->
            <div th:if="${task.status == T(com.example.taskapp.model.Status).OPEN}" class="btn btn-primary btn-icon-split">
                    <span class="icon text-white-50">
                      OPEN
                    </span>
                <span class="text" th:text="${task.title}"></span>
            </div>

            <!-- Info Button for CLOSED Tasks-->
            <div th:if="${task.status == T(com.example.taskapp.model.Status).CLOSED}" class="btn btn-success btn-icon-split">
                    <span class="icon text-white-50">
                      CLOSED
                    </span>
                <span class="text" th:text="${task.title}"></span>
            </div>

            <!-- Info Button for REOPENED Tasks-->
            <div th:if="${task.status == T(com.example.taskapp.model.Status).REOPENED}" class="btn btn-warning btn-icon-split">
                    <span class="icon text-white-50">
                      REOPENED
                    </span>
                <span class="text" th:text="${task.title}"></span>
            </div>

            <h6 class="m-0 font-weight-bold text-primary">&nbsp;&nbsp;</h6>
            <!-- Dropdown -->
            <div class="dropdown no-arrow">
                <a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                    <i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
                </a>
                <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" aria-labelledby="dropdownMenuLink">
                    <div class="dropdown-header">Actions:</div>

                    <div th:if="${task.status == T(com.example.taskapp.model.Status).OPEN}">
                        <a class="dropdown-item" th:href="@{/task/{id}/{action}(id=${task.id}, action='close')}">CLOSE</a>
                    </div>
                    <div th:if="${task.status == T(com.example.taskapp.model.Status).CLOSED}">
                        <a class="dropdown-item" th:href="@{/task/{id}/{action}(id=${task.id}, action='reopen')}">REOPEN</a>
                    </div>
                    <div th:if="${task.status == T(com.example.taskapp.model.Status).REOPENED}">
                        <a class="dropdown-item" th:href="@{/task/{id}/{action}(id=${task.id}, action='close')}">CLOSE</a>
                    </div>

                    <div class="dropdown-divider"></div>
                    <!--<a class="dropdown-item" th:href="@{/task/{id}/edit(id=${task.id})}"><i class="far fa-edit"></i> Edit</a>-->
                    <a class="dropdown-item" th:href="@{/task/{id}/delete(id=${task.id})}"><i class="fas fa-trash-alt"></i> Delete</a>
                    <a class="dropdown-item eBtn" th:href="@{findTask/{id}(id=${task.id})}"><i class="fas fa-pencil-alt"></i>  Edit</a>
                </div>
            </div>
        </div>
        <!-- Card Body -->
        <div class="card-body">
            <p th:text="${task.content}"></p>
        </div>
    </div>
</div>

This concludes the Frontend. Run the app with “mvn:spring-boot:run” in your terminal.

Deploy your App to Heroku:

application.properties.file: Delete all and place the following

## Heroku Properties
spring.datasource.url=${JDBC_DATABSE_URL}
spring.datasource.username=${JDBC_DATABSE_USERNAME}
spring.datasource.password=${JDBC_DATABSE_PASSWORD}

spring.jpa.show-sql = false
spring.jpa.generate.ddl = true
spring.jpa.hibernate.ddl-auto = create
        
spring.datasource.initialization-mode=always

Next open the command line runner and navigate to your application folder

(1) enter: “heroku login” and provide your email and password (you need to have an account on Heroku by now)
(2) then: “git init” and “git add .” which will add all your files to the version control
(3) git commit -m “your commit message”
(4) heroku create “name of your app”
(5) git push heroku master
(6) heroku open – will open your deployed app in your browser

Leave a Reply