To-Do Calendar - Day19 實作新增、修改、刪除功能
接著來實作原子習慣的新增、修改、刪除功能,因為寫法大同小異,就集中一篇來說明。
其實我沒看過天龍八部
原子習慣新增、修改、刪除功能
- 開發流程:
- 根據前端傳來的 op 參數判斷要執行新增、修改或刪除原子習慣,並將結果並返回前端
- 最後實作前端呼叫對應的 API,然後顯示更新後返回的結果
設計 RESTful API
實作原子習慣新增、刪除、修改功能 點「+」新增原子習慣 點「筆」編輯原子習慣 點「垃圾桶」刪除原子習慣
addHabit API
Request Body 的 value 帶的是要新增的原子習慣參數。
- Request URL
PATCH http://localhost:8080/users/{userId}/habits
- Request Body
{ "op":"add", "path":"/habitList", "value":{"name": "12點前就寢", "checkColor": "pink"} }
- Response Body
{ "id": "628fd7820f99c13e6d6d95fe", "userId": "user01", "habitList":[ {"habitId": "habit01", "name": "吃早餐", "checkColor": "red", "createdTime": 1653405213436, "lastModifiedTime":1653405213436}, {"habitId": "habit02", "name": "喝水2000cc", "checkColor": "yellow", "createdTime": 1653405213436, "lastModifiedTime":1653405213436}, {"habitId": "habit03", "name": "寫技術部落格", "checkColor": "green", "createdTime": 1653405213436, "lastModifiedTime":1653405213436}, {"habitId": "36ced75352e74206a9a3b55eeda6b240", "name": "12點前就寢", "checkColor": "pink", "lastModifiedTime":1653405213436} ], "createdTime": 1653405213436, "lastModifiedTime": 1654445557493 }
updateHabit API
Request Body 的 value 帶的是要修改的 habitId 與原子習慣參數。
- Request URL
PATCH http://localhost:8080/users/{userId}/habits
- Request Body
{ "op":"replace", "path":"/habitList", "value":{"habitId": "36ced75352e74206a9a3b55eeda6b240", "name": "12點前就寢", "checkColor": "purple"} }
- Response Body
{ "id": "628fd7820f99c13e6d6d95fe", "userId": "user01", "habitList":[ {"habitId": "habit01", "name": "吃早餐", "checkColor": "red", "createdTime": 1653405213436, "lastModifiedTime":1653405213436}, {"habitId": "habit02", "name": "喝水2000cc", "checkColor": "yellow", "createdTime": 1653405213436, "lastModifiedTime":1653405213436}, {"habitId": "habit03", "name": "寫技術部落格", "checkColor": "green", "createdTime": 1653405213436, "lastModifiedTime":1653405213436}, {"habitId": "36ced75352e74206a9a3b55eeda6b240", "name": "12點前就寢", "checkColor": "purple", "lastModifiedTime":1653405213436} ], "createdTime": 1653405213436, "lastModifiedTime": 1654445557493 }
deleteHabit API
Request Body 的 value 帶的是要刪除的 habitId 參數。
- Request URL
PATCH http://localhost:8080/users/{userId}/habits
- Request Body
{ "op":"remove", "path":"/habitList", "value":{"habitId": "36ced75352e74206a9a3b55eeda6b240"} }
- Response Body
{ "id": "628fd7820f99c13e6d6d95fe", "userId": "user01", "habitList":[ {"habitId": "habit01", "name": "吃早餐", "checkColor": "red", "createdTime": 1653405213436, "lastModifiedTime":1653405213436}, {"habitId": "habit02", "name": "喝水2000cc", "checkColor": "yellow", "createdTime": 1653405213436, "lastModifiedTime":1653405213436}, {"habitId": "habit03", "name": "寫技術部落格", "checkColor": "green", "createdTime": 1653405213436, "lastModifiedTime":1653405213436} ], "createdTime": 1653405213436, "lastModifiedTime": 1654497890163 }
實作 Controller 層
- 在 HabitsController class 新增 patchHabit 方法,返回類型為
ResponseEntity<Habits>
- 在方法上加上
@PatchMapping
註解,表示前端要使用 PATCH 方法來請求 API - 在
@PatchMapping
註解括號中指定 url 路徑,並使用@PathVariable
註解去取得 url 路徑的值 - 使用
@RequestBody
去接住前端傳來的參數,並使用@Valid
註解讓寫在 PatchRequest class 的驗證請求參數的註解生效 - 接著實作 patchHabit 方法(call habitsService 的 patchHabit 方法)
HabitsController class
實作 Service 層
- 在 HabitsService interface 宣告 patchHabit 方法
- 接著再到 HabitsServiceImpl class,實作 patchHabit 方法
- 依 patchRequest 的 op 參數的值決定要 call habitsDao 的哪個方法
HabitsService interface HabitsServiceImpl class
如果是 update 操作,可以在前面先檢查指定修改的原子習慣是否存在,存在才呼叫 dao 去執行,不存在則回傳 404 Not Found 的資訊給前端。
實作 Dao 層
- 在 HabitsDao interface 宣告 addHabit 方法、replaceHabit 方法和 removeHabit 方法
- 實作之前可先在 Query Console 測試 MongoDB 原生語法
- 接著再回到 HabitsDaoImpl class,實作 addHabit 方法、replaceHabit 方法和 removeHabit 方法
- addHabit 方法中會先檢查指定的 habits Document 是否存在,如果尚未存在則以查詢條件建立一個,然後才去執行更新語法(使用前才會去建立使用者的 habits Document)
Query Console(新增原子習慣) Query Console(修改原子習慣) Query Console(刪除原子習慣) HabitsDao interface HabitsDaoImpl class(addHabit) HabitsDaoImpl class(replaceHabit) HabitsDaoImpl class(removeHabit)
- 接著運行 Spring Boot 程式,使用 API Tester 測試一下效果
addHabit API
updateHabit API
deleteHabit API
實作前端串接 API
- 在 src/axios/index.js 新增 apiHabitAdd 方法、apiHabitUpdate 方法和 apiHabitDelete 方法,然後再 export 出去給外面的組件 import
export const apiHabitAdd = (userId, name, checkColor) => instance.patch(`/users/${userId}/habits`, { op: "add", path: "/habitList", value: { name: name, checkColor: checkColor, }, }); export const apiHabitUpdate = (userId, habitId, name, checkColor) => instance.patch(`/users/${userId}/habits`, { op: "replace", path: "/habitList", value: { habitId:habitId, name: name, checkColor: checkColor, }, }); export const apiHabitDelete = (userId, habitId) => instance.patch(`/users/${userId}/habits`, { op: "remove", path: "/habitList", value: { habitId:habitId, }, });
- 在 habitTracker.vue 中為各個事件去呼叫對應的 API,最後顯示更新後的結果
- pickedHabit 為目前顯示的是哪個原子習慣追蹤的位置標示(淺綠背景色),如果使用者點選其他原子習慣就會選顯示其原子習慣追蹤,預設為原子習慣列表的第一筆,若原子習慣列表尚未有資料則不顯示
- 建立的新原子習慣會長在原子習慣列表最下方,pickedHabit 也會指到新原子習慣的位置
- 刪除原子習慣後,pickedHabit 會改指回預設的第一筆
sendCreateHabit(createItem) { let self = this; apiHabitAdd(self.$store.state.userId, createItem.name, createItem.checkColor).then((res) => { self.habitList = res.data.habitList; if (this.habitList.length > 0) { this.pickedHabit = this.habitList[this.habitList.length-1]; self.pickedDays = []; } $("#habitCreateModal").modal("hide"); }); }, sendEditHabit(editItem) { let self = this; apiHabitUpdate(self.$store.state.userId, editItem.habitId, editItem.name, editItem.checkColor).then((res) => { self.habitList = res.data.habitList; self.pickedHabit = editItem; $("#habitEditModal").modal("hide"); }); }, sendDeleteHabit(deleteItem) { let self = this; apiHabitDelete(self.$store.state.userId, deleteItem.habitId).then((res) => { self.habitList = res.data.habitList; $("#deleteModal").modal("hide"); if (self.habitList.length > 0) { self.pickedHabit = self.habitList[0]; self.sendQueryHabitTracker(self.pickedHabit); } }); },
初次運行結果
發現在編輯 modal 編輯原子習慣時,後面列表的原子習慣名稱和圈選顏色會跟著改變,取消後也不會回復 ∑( ̄□ ̄;)

還有刪除 modal 在消失途中會冒出第一筆的資料…
解決方式
- 在開啟 modal 前,先複製(深拷貝) pickedHabit,再丟進子組件中
<habit-edit-modal :pickedHabit="copyHabit" @submitEvent="sendEditHabit" ></habit-edit-modal> <delete-modal :pickedItem="copyHabit" :modalTitle="modalTitle" @submitEvent="sendDeleteHabit" ></delete-modal>
openHabitEditModal(item) { let self = this; self.pickedHabit = item; self.copyHabit = JSON.parse(JSON.stringify(self.pickedHabit)); $("#habitEditModal").modal({ backdrop: "static", keyboard: false }); }, openDeleteModal(item) { let self = this; self.pickedHabit = item; self.copyHabit = JSON.parse(JSON.stringify(self.pickedHabit)); $("#deleteModal").modal({ backdrop: "static", keyboard: false }); },
再次運行結果
新增原子習慣 修改原子習慣 刪除原子習慣