# 27. TodoList 頁面模組 3 - 修改

這個小節主要說明「修改事項」模組。

# 修改事項的處理細節

修改事項主要會有這幾項功能:

  • 雙擊 <label> 可編輯內容。
  • 寫入:鍵盤按下 Enter 會儲存編輯後的結果
  • 取消:鍵盤按下 Esc 或滑鼠在輸入框外點擊 (blur),可取消編輯。

這裡的修改事項內容,和上面修改完成狀態的寫法不一樣:

  • 修改狀態:修改後直接 commit 到 store 裡面。
  • 修改內容:會先判斷要寫入或取消,才去改 store 裡面的資料。

# 修改 template

首先,我們可以依據想要的功能修改 template 的結構,然後再去 JS 寫邏輯:

  • label 綁定雙擊事件,使得 "TodoListItem" 顯示 input 輸入框,以供使用者編輯資料。
  • 建立一個新的 input 輸入框,並帶有以下屬性和值:
    • v-model.trim="edit" :雙向綁定 data 中的 edit ,存放輸入框的內容並去除首尾空白。
    • v-if="edit !== null" :如果 edit 不是 null,就會顯示這個輸入框。
    • v-focus :畫面出現這個輸入框時,滑鼠游標會自動 focus。
    • @keyup.enter="submitHandler" :按下 Enter 寫入內容。
    • @keyup.esc="cancelHandler" :按下 Esc 取消編輯。
    • @blur="cancelHandler" :滑鼠點擊輸入框以外的地方取消編輯。
  • 編輯事項的時候,我們會以一個 input 輸入框取代原有的 input+label+button 結構。我們可以用一個 <template> 標籤將 input+label+button 包起來,一來不會多一層 DOM,二來便於使用 v-else 一起判斷是否要呈現到畫面上。
//- components/TodoListItem/template.html

<div class="todoListItem">
  <input
    type="text"
    class="edit"
    v-model.trim="edit"
    v-if="edit !== null"
    v-focus
    @keyup.enter="submitHandler"
    @keyup.esc="cancelHandler"
    @blur="cancelHandler"
  />
  <template v-else>
    <input type="checkbox" class="toggle" v-model="complete" />
    <label @dblclick="editHandler">{{ todo.content }}</label>
    <button class="destroy" @click="destroyHandler"></button>
  </template>
</div>

# 雙擊編輯內容

  • 建立 edit 暫存輸入框裡的資料,並預設值為 null
    • 如果預設值為空字串,且輸入框的顯示條件為 v-if="edit"。在編輯過程中將輸入框的內容清空時,輸入框會因為 v-if="edit" 的判斷而切換回來。
  • editHandler 被觸發後,將 store 中 todo 的內容傳給 edit
  • 此時 edit 的值不是 null,畫面上會將此事項改為輸入框,並帶入事項內容以供編輯。
//- components/TodoListItem/index.vue

// 不相關的其他內容省略
export default {
  data(){
    return {
      edit: null
    }
  },
  computed: {
    todo(){
      return this.$store.state.todos[this.index]
    },
  },
  methods: {
    editHandler(){
      this.edit = this.todo.content
    },
}

# 取消編輯內容

取消編輯比較單純,只要讓 this.edit 的值是 null,輸入框就會因 v-if 的判斷而切回 input+label+button

//- components/TodoListItem/index.vue

cancelHandler(){
  this.edit = null
}

由於 label 是拿 store 裡面 todos 的值,而這個值沒有被改動,因此畫面切換回來的時候,內容會維持原樣。

# 寫入編輯內容

  • 判斷是不是空字串
    • 如果是空字串,我們就刪除這筆資料。
  • 更新資料內容
    • 如果不是空字串,就將 edit 的值 commit 到 store 裡面。
  • 清除 this.edit 的值
    • 最後要將 this.edit 的值恢復成 null,畫面才會切回來顯示事項。
//- components/TodoListItem/index.vue

submitHandler(){
  if(!this.edit) return this.destroyHandler()
  this.$store.commit("UPDATE_TODO", {
    index: this.index,
    data: {
      content: this.edit,
      complete: this.todo.complete
    }
  })
  this.cancelHandler()
},

# 改寫 UPDATE_TODO

前面資料觀測的單元有提到,如果直接對陣列中的資料作修改,Vue 無法觸發 Observer 通知資料變動,畫面就不會即時更新。因此 Vuex store 裡面的 UPDATE_TODO 要改寫:

//- store/index.js

  mutations: {
    UPDATE_TODO(state, { index, data }) {
      // state.todos[index] = data
      state.todos[index].complete = data.complete
      state.todos[index].content = data.content
      LS.save(state.todos)
    },
  },