iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 25
0
Modern Web

30天從零撰寫 Kotlin 語言並應用於 Spring Boot 開發系列 第 25

[Day 25] 遠征 Kotlin × Spring Boot 介紹 Template Engine (2)

  • 分享至 

  • xImage
  •  

昨日我們介紹了 Thymeleaf 樣板引擎,並已經完成 Template 環境建置與初始頁面,接下來我們嘗試利用樣板引擎來製作一個待辦清單 TodoList,我們直接進入實際實作步驟與介紹:

  1. 設定環境設定 application.yml

    spring:
      h2: # 設定 H2 資料庫相關配置
        console:
          enabled: true
          path: /h2-console
      datasource: # 設定資料庫相關配置
        url: jdbc:h2:file:./src/main/resources/data/ironman;AUTO_SERVER=true
        username: sa
        password: Ironman0924!
      jpa: # 設定 JPA 相關配置
        hibernate:
          ddl-auto: update
        database-platform: H2
        show-sql: true
        generate-ddl: false
      thymeleaf:
        cache: false    # 關閉 Cache
        encoding: UTF-8 # 編碼設定
        mode: HTML5     # 模式
        suffix: .html   # 檔案副檔名
        prefix: classpath:/templates/ # 檔案儲存位置
    
  2. 建立 Todo Entity,映射 Todo 資料表與欄位,此部份我們將 id 改為 UUID 來進行自動編號,並且利用資料庫自動新增資料建立時間 createTime 與資料修改時間 updateTime

    @Entity
    @Table
    @EntityListeners(AuditingEntityListener::class)
    @EnableJpaAuditing
    data class Todo(
            @Id val id: UUID = UUID.randomUUID(),
            val task: String = "",
    
            var status: Int = 0,
    
            @CreatedDate
            @Column(updatable = false, nullable = false)
            val createTime: Date = Date(),
    
            @LastModifiedDate
            @Column(nullable = false)
            val updateTime: Date = Date()
    )
    
  3. 建立 Todo DAO,建立DAO物件處理資料庫溝通

    interface TodoDao :  JpaRepository<Todo, Long>, JpaSpecificationExecutor<Todo> {
    
        /**
         * 查詢符合 Id 條件的資料
         */
        fun findById(id: UUID): Todo?
    }
    
  4. 建立 Service Interface-TodoService,預計會使用四種動作(取得資料、建立資料、更新狀態、刪除資料)

    interface TodoService {
    
        /**
         * 取得所有 Todo 資料
         */
        fun getTodos(): Iterable<Todo>
    
        /**
         * 建立 Todo 資料
         */
        fun createTodo(todo: Todo): Todo
    
        /**
         * 更新 Todo 狀態
         */
        fun updateTodoStatus(id: String): Boolean
    
        /**
         * 刪除 Todo 資料
         */
        fun deleteTodo(id: String): Boolean
    }
    
  5. 實作 Service - TodoServiceImpl

    @Service
    class TodoServiceImpl(@Autowired val todoDao: TodoDao) : TodoService {
        override fun getTodos(): Iterable<Todo> = todoDao.findAll()
    
        override fun createTodo(todo: Todo): Todo = todoDao.save(todo)
    
        override fun updateTodoStatus(id: String): Boolean = todoDao.findById(UUID.fromString(id)).run {
            return try {
                this?.let {
                    if (it.status == 1) it.status = 0 else it.status = 1
                    todoDao.save(it)
                }
                true
            } catch (exception: Exception) {
                false
            }
        }
    
        override fun deleteTodo(id: String): Boolean = todoDao.findById(UUID.fromString(id)).run {
            return try {
                this?.let { todoDao.delete(it) }
                true
            } catch (exception: Exception) {
                false
            }
        }
    }
    
  6. 建立 Controller Interface - HomeController,建立資料(createTodo)會使用到 @ModelAttribute Annotation接收來自前端表單資料,而修改狀態資料(updateTodoStatus)與刪除資料(deleteTodo)則預計利用呼叫 API 方式進行動作,故須加上 @ResponseBody 標註:

    interface HomeController {
    
        @GetMapping("/todos")
        fun getTodos(model: Model) : String
    
        @PostMapping("/todos")
        fun createTodo(@ModelAttribute todo: Todo) : String
    
        @PutMapping("/todos/{id}")
        @ResponseBody
        fun updateTodoStatus(@PathVariable id: String)
    
        @DeleteMapping("/todos/{id}")
        @ResponseBody
        fun deleteTodo(@PathVariable id: String)
    }
    
  7. 實作 Controller,建立資料(createTodo)完成後要記得轉向取得資料頁面,會使用到 redirect 轉向方法

    @Controller
    class HomeControllerImpl(@Autowired val todoService: TodoService): HomeController {
        override fun getTodos(model: Model): String {
            model.addAttribute("todolist", todoService.getTodos());
            model.addAttribute("todoObject", Todo())
            return "home"
        }
    
        override fun createTodo(todo: Todo): String {
            todoService.createTodo(todo)
    
            return "redirect:/todos"
        }
    
        override fun updateTodoStatus(id: String) {
            todoService.updateTodoStatus(id)
        }
    
        override fun deleteTodo(id: String) {
           todoService.deleteTodo(id)
        }
    
    }
    
  8. 在 resource / templates 資料夾建立 home.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
      <meta charset="UTF-8">
      <link rel="stylesheet" th:href="@{/style.css}">
      <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
      <title>Todo List</title>
    </head>
    <body>
    <div class="container">
      <h2>待辦事項清單</h2>
      <form class="inputBox" th:action="@{/todos}" method="post" th:object="${todoObject}">
        <input type="text" id="input" placeholder="新增項目" th:field="*{task}">
        <button type="submit" class="submit">加入</button>
      </form>
      <ul th:each="todo: ${todolist}">
        <li th:class="${todo.status} == 1 ? 'checked': '' " th:onclick="updateTodoStatus([[${todo.id}]])">
          <span th:text="${todo.task}"></span>
          <span class="close" th:onclick="deleteTodo([[${todo.id}]])">x</span>
        </li>
      </ul>
    </div>
    <script>
    
      const deleteTodo = (id) => {
        fetch('todos/'+ id, {
              method: 'delete',
        }).then((response) => {
          if (response.status === 200) {
              location.reload();
          }
        })
      };
    
      const updateTodoStatus = (id) => {
        fetch('todos/' + id, {
          method: 'put',
        }).then((response) => {
          if (response.status === 200) {
            location.reload();
          }
        })
      };
    </script>
    </body>
    </html>
    
  9. 在 resource / static 建立 style.css

    body{
      background-color: #d5827b;
      font-family: arial,
      "Microsoft JhengHei","微軟正黑體",
      sans-serif !important;
    }
    
    body h2{
      color: white;
    }
    
    .container {
      width: 50%;
      margin: 10% auto;
    }
    
    ul {
      padding: 0px;
    }
    
    ul li {
      cursor: pointer;
      position: relative;
      padding: 12px 8px 12px 40px;
      background: #eee;
      font-size: 18px;
      transition: 0.2s;
      box-shadow: 0px 10px 15px #666;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      list-style: none;
    }
    
    ul li:nth-child(odd) {
      background: #f9f9f9;
    }
    
    ul li:hover {
      background: #ddd;
    }
    
    h2 {
      font-size: 32px;
      font-weight: 700;
    }
    
    ul li.checked {
      background: #7a534f;
      color: #fff;
      text-decoration: line-through;
      border: 0.5px solid black;
    }
    
    ul li.checked::before {
      content: '';
      position: absolute;
      border-color: #fff;
      border-style: solid;
      border-width: 0 2px 2px 0;
      top: 10px;
      left: 16px;
      transform: rotate(45deg);
      height: 15px;
      width: 7px;
    }
    
    .close {
      position: absolute;
      right: 0;
      top: 0;
      padding: 12px 16px 12px 16px;
    }
    
    .close:hover {
      background-color: #f44336;
      color: white;
    }
    
    .inputBox {
      background-color: #e0e2c6;
      padding: 30px 40px;
      color: #5C4319;
      text-align: center;
      box-shadow: 0px 10px 15px #666;
    }
    
    .inputBox:after {
      content: "";
      display: table;
      clear: both;
    }
    
    input {
      margin: 0;
      border: none;
      border-radius: 0;
      width: 75%;
      padding: 10px;
      float: left;
      font-size: 16px;
    }
    
    .submit {
      padding: 8px;
      background: #79b786;
      color: #fbfffd;;
      float: left;
      text-align: center;
      font-size: 16px;
      cursor: pointer;
      transition: 0.3s;
      border-radius: 0;
      margin-left: 10px;
    }
    
    .submit:hover {
      background-color: #bbb;
    }
    
  10. 最後執行專案,即可看到 Todo List:
    https://ithelp.ithome.com.tw/upload/images/20201004/20121179uy0ZW9Kwb5.png

此文章有提供範例程式碼在 Github 供大家參考


上一篇
[Day 24] 遠征 Kotlin × Spring Boot 介紹 Template Engine (1)
下一篇
[Day 26] 遠征 Kotlin × Spring Boot 部署網站到 Heroku
系列文
30天從零撰寫 Kotlin 語言並應用於 Spring Boot 開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言