To-Do Calendar - Day19 實作新增、修改、刪除功能

接著來實作原子習慣的新增、修改、刪除功能,因為寫法大同小異,就集中一篇來說明。

image

其實我沒看過天龍八部


原子習慣新增、修改、刪除功能

  • 開發流程:
    • 根據前端傳來的 op 參數判斷要執行新增、修改或刪除原子習慣,並將結果並返回前端
    • 最後實作前端呼叫對應的 API,然後顯示更新後返回的結果

設計 RESTful API

image

實作原子習慣新增、刪除、修改功能

image

點「+」新增原子習慣

image

點「筆」編輯原子習慣

image

點「垃圾桶」刪除原子習慣


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 方法)

image

HabitsController class


實作 Service 層

  • 在 HabitsService interface 宣告 patchHabit 方法
  • 接著再到 HabitsServiceImpl class,實作 patchHabit 方法
    • 依 patchRequest 的 op 參數的值決定要 call habitsDao 的哪個方法

image

HabitsService interface

image

HabitsServiceImpl class

Info
更嚴謹的寫法
如果是 update 操作,可以在前面先檢查指定修改的原子習慣是否存在,存在才呼叫 dao 去執行,不存在則回傳 404 Not Found 的資訊給前端。

實作 Dao 層

  • 在 HabitsDao interface 宣告 addHabit 方法、replaceHabit 方法和 removeHabit 方法
  • 實作之前可先在 Query Console 測試 MongoDB 原生語法
  • 接著再回到 HabitsDaoImpl class,實作 addHabit 方法、replaceHabit 方法和 removeHabit 方法
    • addHabit 方法中會先檢查指定的 habits Document 是否存在,如果尚未存在則以查詢條件建立一個,然後才去執行更新語法(使用前才會去建立使用者的 habits Document)

image

Query Console(新增原子習慣)

image

Query Console(修改原子習慣)

image

Query Console(刪除原子習慣)

image

HabitsDao interface

image

HabitsDaoImpl class(addHabit)

image

HabitsDaoImpl class(replaceHabit)

image

HabitsDaoImpl class(removeHabit)

  • 接著運行 Spring Boot 程式,使用 API Tester 測試一下效果
    image

    addHabit API

    image

    updateHabit API

    image

    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 編輯原子習慣時,後面列表的原子習慣名稱和圈選顏色會跟著改變,取消後也不會回復 ∑( ̄□ ̄;)

image

還有刪除 modal 在消失途中會冒出第一筆的資料…

image

解決方式

  • 在開啟 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 });
      },
    

再次運行結果

image

新增原子習慣

image

修改原子習慣

image

刪除原子習慣



延伸閱讀

參考資料

comments

comments powered by Disqus