0%

【Vue.js 學習筆記】03. 製作一個 Todo List 來小試身手吧

前言

解了一些 Vue.js 基本的概念,那麼就繼續往下做一些簡單的應用吧!

本節內容包含下述子章節:

  1. 套用版型及建立代辦事項列表的資料
  2. 刪除陣列上的特定資料
  3. 製作頁籤分類的功能
  4. 雙擊修改資料內容
  5. 刪除項目補充說明


先來看看成品長怎樣


套用版型及建立代辦事項列表的資料

作業模板:傳送門

1. 新增變數與函式

  • newTodo:字串,用於新增新的代辦事項內容
  • todos:陣列,紀錄代辦事項,裡面為 key / value ,包含 idtitlecompleted (完成狀況)
  • addTodomethods function,當點擊新增按鈕時,會被觸發的函式。

2. 輸入框與變數雙向綁定

 將變數 newTodo 使用 v-modelinput 建立雙向綁定。

<input type="text" class="form-control" placeholder="準備要做的任務" v-model="newTodo">

3. 按鈕點擊事件與函式綁定

 將 addTodobutton 使用 v-on:click / @click 進行綁定。

<input type="text" class="form-control" placeholder="準備要做的任務" v-model="newTodo">
    <div class="input-group-append">
      <button class="btn btn-primary" type="button" v-on:click="addTodo">新增</button>
    </div>

4. 將 todos 顯示在 UI 上

 這邊打算用 label 來顯示文字,且因每筆代辦事項之前,想會加入一個核選方塊,因此使用一個區塊標籤 div

 包覆住 labelcheckbox,並在 <li> 標籤上使用 v-for='item in todos' 進行綁定。

<li class="list-group-item" v-for="item in todos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" id="a1">

            <label class="form-check-label" for="a1">
              Cras justo odio
            </label>
        </div>

        <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>

5. 將上述綁定的項目加到 vue 原始碼

 在 data 裡面新增 newTodotodos ,還有新增一個 methods 放入要新增代辦事項的 addTodofunction

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [],   // 紀錄代辦事項有哪些內容
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {

    }
  }
});
</script>

6. 在 todos 裡面先試寫一份內容

todos 陣列裡,新增一個物件 { } ,物件裡面需定義

  • id ,這個 id 是要讓 checkbox 能夠跟代辦事項內容作對應的。
  • title,代辦事項的內容。
  • completed,代表完成的狀態。
<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {

    }
  }
});
</script>

7. 將取出的代辦事項內容呈現在 UI 上

 將 item.title 使用兩個大括弧 Mustache 語法包覆在 label 標籤中顯示於 UI 上。

<li class="list-group-item" v-for="item in todos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" id="a1">

            <label class="form-check-label" for="a1">
                {{ item.title }}
            </label>
        </div>

        <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>

8. 將每筆代辦事項的 label 和 checkbox 綁定

  • 將所取出的 itemidcheckbox 使用 v-bind 進行綁定,可使用縮寫 :id 來表示
  • 將所取出的 itemidlabel 使用 v-bind 進行綁定,可使用縮寫 :for 來表示
<li class="list-group-item" v-for="item in todos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" :id="item.id">

            <label class="form-check-label" :for="item.id">
                {{ item.title }}
            </label>
        </div>

        <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>

9. 將 checkbox 狀態與 item 的完成狀況進行綁定

 將 item.completedcheckbox 使用 v-model 建立雙向綁定。

<li class="list-group-item" v-for="item in todos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

            <label class="form-check-label" :for="item.id">
                {{ item.title }}
            </label>
        </div>

        <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>

10. 實做 addTodo 函式,將 input 新增的內容加入 todos 的列表裡面

  • 先宣告一個變數 value 儲存 newTodo 的內容
  • 宣告一個變數 timestamp 做為 itemid,做為 titleid 使用。這邊使用時間 Date.now 轉為數字的一種格式,

  再透過 Math.floor 轉為正整數。

  • 接下來要將 todos內容,使用 array.push 將新的 Object 加入 todos 陣列尾端。
  • 最後記得清空輸入欄, this.newTodo = '';
<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo;
        var timestamp = Math.floor(Date.now());

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    }
  }
});
</script>

11. 支援 enter 新增

 除了點擊新增的按鈕外,也可以透過按鍵行為把資料內容新增上去,使用 @keyup.enter="addTodo" ,讓輸入欄可以

 支援按 enter 提交。

<input type="text" class="form-control" placeholder="準備要做的任務" v-model="newTodo" @keyup.enter="addTodo">

12. 避免加入空的任務

 建議可以針對輸入的內容去刪除前後的空格,在變數 value = this.newTodo; 後面加上 .trim()

 並判斷是否為空字串,避免加入空字串,加上 if 的判斷式。

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    }
  }
});
</script>


刪除陣列上的特定資料

1.新增函式並與刪除按鈕綁定

 接著我們要在能夠在 todos 代辦事項列表的右方能夠刪除資料,找到程式碼 button 的區塊,

 利用 @click="removeTodo" 綁定。

<li class="list-group-item" v-for="item in todos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

            <label class="form-check-label" :for="item.id">
                {{ item.title }}
            </label>
        </div>

        <button type="button" class="close ml-auto" @click="removeTodo" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>

 綁定按鈕後,在 vuemethods 下方建立所需函式 removeTodo

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function () {

    }
  }
});
</script>

2. 實做 removeTodo 函式

 當我們點擊刪除時,還無法得知是按了哪一個項目的刪除,所以在點擊按鈕後必須把相關的參數傳回來。
 這邊我們可以調整

<li> 裡面的 v-for ,將 v-for 調整為 v-for="(item, key) in todos" ,這邊的 key 是陣列的索引位置。

<li class="list-group-item" v-for="(item, key) in todos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

            <label class="form-check-label" :for="item.id">
                {{ item.title }}
            </label>
        </div>

        <button type="button" class="close ml-auto" @click="removeTodo" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>

在傳送參數的同時會將 key 傳到 button 所綁定的 removeTodo 函式中,所以程式碼須調整為 @click="removeTodo(key)"

<li class="list-group-item" v-for="(item, key) in todos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

            <label class="form-check-label" :for="item.id">
                {{ item.title }}
            </label>
        </div>

        <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>

而函式 removeTodo function 中也包含了 key ,在移除陣列中的值可以使用 array.splice 刪除陣列中某一筆資料,但其指定

方式是使用 index ,也就是我們這邊的變數 key ,而 (key, 1) 中的 1 是代表要刪除幾筆資料的意思。

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function (key) {
      this.todos.splice(key, 1)
    }
  }
});
</script>


製作頁籤分類的功能

刪除線效果

在顯示任務名稱的 label 標籤上添加 class 的動態切換,也就是 :class="{'completed': item.completed}" ,物件中的

'completed' 放的是要套用的 class 名稱, item.completed 則是條件。

<li class="list-group-item" v-for="(item, key) in todos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

            <label class="form-check-label" :class="{'completed': item.completed }" :for="item.id">
                {{ item.title }}
            </label>
        </div>

        <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>

頁籤切換功能

頁籤功能分成兩部份來實做,一是上方頁籤切換,二是下方資料切換。

先實做上方頁簽切換的部份

1. 新增變數

 先在 data 裡面新增一個變數 visibility ,此變數用來紀錄目前使用者正在查看的頁簽,並將該值初始化為 allItem

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
    visibility: 'allItem'
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function (key) {
      this.todos.splice(key, 1)
    }
  }
});
</script>

新增三個變數 allItemprocessingdone,分別對應到 card-header 的三個分頁:全部、進行中、已完成。



2. 頁簽渲染

 為了突顯目前正在查看的頁簽,會將該頁簽進行渲染。在各個頁籤加上 class 的動態切換,當 visibility 指向該頁時,

 就加上 active class 的渲染效果。例如 :class="{'active': visibility == 'allItem'}" ,物件中的 'active' 放的是

class 的名稱,冒號 : 後方是條件,當 visibility 指向全部時,所以這邊的 == 後面就要放剛才所宣告的變數 allItem

<div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'allItem'}" href="#">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " :class="{'active': visibility == 'processing'}" href="#">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'done'}" href="#">已完成</a>
        </li>
      </ul>
    </div>


3. 頁簽切換效果

 為實做為點擊切換的效果,因此當點擊頁籤時需將 visibility 指向自己,例如: @click="visibility = 'allItem'"

 一旦 visibility 指向自己,:class 的判斷式就會為真,因此就會為該頁簽加上渲染效果。

<div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'allItem'}" @click="visibility = 'allItem'" href="#">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " :class="{'active': visibility == 'processing'}" @click="visibility = 'processing'" href="#">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'done'}" @click="visibility = 'done'" href="#">已完成</a>
        </li>
      </ul>
    </div>


下方資料切換

1. 動態過濾陣列內容

 因為我們不希望修改原始 todos 中的資料,因此實做時會希望回傳一個新過濾後的陣列。且因為我們希望在不同頁籤會

 回傳不同的陣列,因此我們會在 methods 下方新增 computed 並實作名為 filteredTodos 的函式。

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
    visibility: 'allItem'
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function (key) {
      this.todos.splice(key, 1)
    }
  },
  computed:{
    filteredTodos: function () {
      return this.todos;
    }
  }
});
</script>


2. 使用回傳陣列取代原始陣列

 使用 filteredTodos 取代掉先 v-for 中所使用的 todos

PS. 如果為確認 filteredTodosv-for 的搭配是否正常運作,可以在 filteredTodos 中直接回傳 todos 做為測試。

<li class="list-group-item" v-for="(item, key) in filteredTodos">
    <div class="d-flex">
        <div class="form-check">

            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

            <label class="form-check-label" :class="{'completed': item.completed }" :for="item.id">
                {{ item.title }}
            </label>
        </div>

        <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
</li>


3. 實作 filteredTodos 函數

 依照 visibility 的內容回不同的過濾結果。實作如下,課程影片中老師是用 forEach 的方式,

 不過也可以用 filter 的做法。

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
    visibility: 'allItem'
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function (key) {
      this.todos.splice(key, 1)
    }
  },
  computed:{
    filteredTodos: function () {
      if (this.visibility == 'allItem') {
          return this.todos;

      } else if (this.visibility == 'processing') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (!item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;

        } else if (this.visibility == 'done') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;
        }
    }
  }
});
</script>


雙擊修改資料內容

1. 在 UI 上實做輸入框架

 準備好一個 input text 元件,並與目前顯示內容的元件 - 也就是剛剛包住 label 與 checkbox 的 div - 放置在同一層。

<ul class="list-group list-group-flush text-left">
  <li class="list-group-item" v-for="(item, key) in filteredTodos">
      <div class="d-flex">
          <div class="form-check">

              <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

              <label class="form-check-label" :class="{'completed': item.completed }" :for="item.id">
                  {{ item.title }}
              </label>
          </div>

          <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
              <span aria-hidden="true">&times;</span>
          </button>
      </div>

      <input type="text" class="form-control">
  </li>

  <!-- <li class="list-group-item">
    <input type="text" class="form-control">
  </li> -->
</ul>

2. 雙擊事件

 這邊希望點擊代辦事件兩下後,可以進入修入模式。因此我們在 <li> 標籤建立一個雙擊事件, @dblclick="editTodo(item)"

<ul class="list-group list-group-flush text-left">
  <li class="list-group-item" v-for="(item, key) in filteredTodos" @dblclick="editTodo(item)">
      <div class="d-flex">
          <div class="form-check">

              <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

              <label class="form-check-label" :class="{'completed': item.completed }" :for="item.id">
                  {{ item.title }}
              </label>
          </div>

          <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
              <span aria-hidden="true">&times;</span>
          </button>
      </div>

      <input type="text" class="form-control">
  </li>

  <!-- <li class="list-group-item">
    <input type="text" class="form-control">
  </li> -->
</ul>

3. 實做 editTodo 函數

 將剛才新增的雙擊事件 editTodo function 加入 methods ,並將 function 裡的 item 另外存起來,目的是要讓我們知道

 哪一筆資料是目前正在編輯中的,所以在資料的部分需要做一些調整,我們在 data 裡面新宣告變數 cacheTodo ( 用於:暫

 時存放todo的地方)、 cacheTitle (用於:編輯標題時預存的地方)。

 新增完後回到 editTodo function ,將 this.cacheTodo 指向我們傳入的 item ,並且把預存的 this.cacheTitle 指向剛才

 我們傳入的 item.title

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
    cacheTodo: {},
    cacheTitle: '',
    visibility: 'allItem',
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function (key) {
      this.todos.splice(key, 1)
    },
    editTodo: function (item) {
      console.log(item);
      this.cacheTodo = item;
      this.cacheTitle = item.title;
    }
  },
  computed:{
    filteredTodos: function () {
      if (this.visibility == 'allItem') {
          return this.todos;

      } else if (this.visibility == 'processing') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (!item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;

        } else if (this.visibility == 'done') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;
        }
    }
  }
});
</script>

4. 顯示編輯框

 畫面中我們原本的 todos 項目與 input 修改框,兩者只會出現其中一個,所以我們要在剛剛包住 labelcheckbox

div 裡加入判斷式, v-if="item.id !== cacheTodo.id" ,此段意指:假設 item.id 不等於 cacheTodo.id 就讓它顯示出

 來。


 相反的在 input 加入判斷式 v-if="item.id === cacheTodo.id" ,假設 inputitem.id 等於 cacheTodo.id 就會顯示

input ,並且把原本資料內容隱藏起來。

<ul class="list-group list-group-flush text-left">
  <li class="list-group-item" v-for="(item, key) in filteredTodos" @dblclick="editTodo(item)">
      <div class="d-flex" v-if="item.id !== cacheTodo.id">
          <div class="form-check">

              <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

              <label class="form-check-label" :class="{'completed': item.completed }" :for="item.id">
                  {{ item.title }}
              </label>
          </div>

          <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
              <span aria-hidden="true">&times;</span>
          </button>
      </div>

      <input type="text" class="form-control" v-if="item.id === cacheTodo.id">
  </li>

  <!-- <li class="list-group-item">
    <input type="text" class="form-control">
  </li> -->
</ul>

5. 將相關事件加入 input

  • 使用 v-model 綁定 textcacheTitle 紀錄編輯中的內容。

  • 使用 @keyup.esc="cancelEdit()" 取消編輯,並回到 methods 裡面新增 cancelEdit function

  並且將空物件指向給 this.cacheTodo 即為: this.cacheTodo = {};

因此當使用者按下 ESC 就會把預存的資料清空,清空後達成 v-if="item.id !== cache.id" ,因此會顯示原本的 div ,並把 input 隱藏起來


  • 使用 @keyup.enter="doneEdit(item)" 將編輯內容存進去,並回到 methods 裡面新增 doneEdit function,將 item

  title 指向剛才預存的標題。另外預存的標題 this.cacheTitle 清空,最後將 this.cacheTodo = {}; 替換回來。

doneEdit(item) 這邊,因為在編輯時會更改 cacheTitle 原本預存的資料,所以把 this.cacheTitle 指向給 item.title ,把原本的 title 更改為使用者想更改的 title ,只有變動 titleidcompleted 不會變動,之後把 this.cacheTitle 的值清空,因為不需要了已經把他丟給原本的 item.title ,並且也把 this.cacheTodo 清空,這樣才會達到 v-if="item.id !== cache.id" ,把原本的 div 顯示出來


<ul class="list-group list-group-flush text-left">
  <li class="list-group-item" v-for="(item, key) in filteredTodos" @dblclick="editTodo(item)">
      <div class="d-flex" v-if="item.id !== cacheTodo.id">
          <div class="form-check">

              <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

              <label class="form-check-label" :class="{'completed': item.completed }" :for="item.id">
                  {{ item.title }}
              </label>
          </div>

          <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
              <span aria-hidden="true">&times;</span>
          </button>
      </div>

      <input type="text" class="form-control" v-if="item.id === cacheTodo.id" v-model="cacheTitle" @keyup.esc="cancelEdit()" @keyup.enter="doneEdit(item)">
  </li>

  <!-- <li class="list-group-item">
    <input type="text" class="form-control">
  </li> -->
</ul>
<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
    cacheTodo: {},
    cacheTitle: '',
    visibility: 'allItem',
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function (key) {
      this.todos.splice(key, 1)
    },
    editTodo: function (item) {
      console.log(item);
      this.cacheTodo = item;
      this.cacheTitle = item.title;
    },
    cancelEdit: function () {
      this.cacheTodo = {};
    },
    doneEdit: function (item) {
      item.title = this.cacheTitle;
      this.cacheTitle = ''; //預存的標題清空
      this.cacheTodo = {};
    }
  },
  computed:{
    filteredTodos: function () {
      if (this.visibility == 'allItem') {
          return this.todos;

      } else if (this.visibility == 'processing') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (!item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;

        } else if (this.visibility == 'done') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;
        }
    }
  }
});
</script>


刪除項目補充說明

之前實做刪除時,是採用傳入陣列 index、並刪除所傳入 index 元素,但在 filtered 後的陣列中內容元素所在 index 與之

在原始陣列中的 index 有異。如果直接刪除所傳入 index 會出現誤刪的情況,因此改利用傳入 id 反查該筆資料在原始陣列

中正確的位置,然後在進行刪除。


課程中在實做時方法有二

  • 一是使用 array.forEach ,當遇到 id 同時就紀錄下目前的 index,最後在刪除所紀錄下的 index

首先在 removeTodo function 裡先宣告 var newIndex = ''; 等於一個空的值,並且透過 forEach 的方式來確保我們取得的是

相同的值,這邊原本傳入的是 key ,我們把它改為是 todo 本身,在 html 程式碼 button 也需要調整,

@click="removeTodo(key)" 原本傳入的是 key ,改為 item 本身,變成 @click="removeTodo(item)"


再回到 removeTodo function ,宣告 var vm = thisvm.todos.forEach(function(item, key){}) ,這邊的 key 是等

一下我們要使用的正確位置,而 item 用來比對所有的 id 有沒有和 todoid 是一樣的。


再來 forEach 裡面加入判式 if (todo.id === item.id) {newIndex = key;} 並且將 newIndex 替換至

this.todos.splice(key, 1)key ,所以變成 this.todos.splice(newIndex, 1)


這邊跑 for 迴圈的目的是為了確認我們要刪除的物件 todo 跟所有物件的 id 是要相符合的,如果式符合的狀態就把 key

索引位置取出來,並且放到要刪除的位置上

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
    cacheTodo: {},
    cacheTitle: '',
    visibility: 'allItem',
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function (todo) {
      var newIndex = '';
      var vm = this;
      vm.todos.forEach(function (item, key) {
        if (todo.id === item.id) {
          newIndex = key;
        }
      })
      this.todos.splice(newIndex, 1)
    },
    editTodo: function (item) {
      console.log(item);
      this.cacheTodo = item;
      this.cacheTitle = item.title;
    },
    cancelEdit: function () {
      this.cacheTodo = {};
    },
    doneEdit: function (item) {
      item.title = this.cacheTitle;
      this.cacheTitle = ''; //預存的標題清空
      this.cacheTodo = {};
    }
  },
  computed:{
    filteredTodos: function () {
      if (this.visibility == 'allItem') {
          return this.todos;

      } else if (this.visibility == 'processing') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (!item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;

        } else if (this.visibility == 'done') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;
        }
    }
  }
});
</script>
<ul class="list-group list-group-flush text-left">
  <li class="list-group-item" v-for="(item, key) in filteredTodos" @dblclick="editTodo(item)">
      <div class="d-flex" v-if="item.id !== cacheTodo.id">
          <div class="form-check">

              <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">

              <label class="form-check-label" :class="{'completed': item.completed }" :for="item.id">
                  {{ item.title }}
              </label>
          </div>

          <button type="button" class="close ml-auto" @click="removeTodo(item)" aria-label="Close">
              <span aria-hidden="true">&times;</span>
          </button>
      </div>

      <input type="text" class="form-control" v-if="item.id === cacheTodo.id" v-model="cacheTitle" @keyup.esc="cancelEdit()">
  </li>

  <!-- <li class="list-group-item">
    <input type="text" class="form-control">
  </li> -->
</ul>


  • 第二種方法使用 findIndex

    findIndex 是比較簡單找到索引的方式,它會把回傳為 true 的索引位置存到前方的變數 newIndex

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo: '', // 新增代辦事項
    todos: [     // 紀錄代辦事項有哪些內容
      {
        id: '123',
        title: '妳好',
        completed: false
      }
    ],
    cacheTodo: {},
    cacheTitle: '',
    visibility: 'allItem',
  },
  methods: {     //新增代辦事項到 list
    addTodo: function () {
        var value = this.newTodo.trim();
        var timestamp = Math.floor(Date.now());

        if (!value) {
          return;
        }

        this.todos.push({
            id: timestamp,
            title: value,
            completed: false
        });

        this.newTodo = '';
    },
    removeTodo: function (todo) {
      // var newIndex = '';
      var vm = this;
      var newIndex = vm.todos.findIndex(function (item, key) {
        return todo.id === item.id;
      })

      this.todos.splice(key, 1)
    },
    editTodo: function (item) {
      console.log(item);
      this.cacheTodo = item;
      this.cacheTitle = item.title;
    },
    cancelEdit: function () {
      this.cacheTodo = {};
    },
    doneEdit: function (item) {
      item.title = this.cacheTitle;
      this.cacheTitle = ''; //預存的標題清空
      this.cacheTodo = {};
    }
  },
  computed:{
    filteredTodos: function () {
      if (this.visibility == 'allItem') {
          return this.todos;

      } else if (this.visibility == 'processing') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (!item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;

        } else if (this.visibility == 'done') {
          var newTodos = [];

          this.todos.forEach(function (item) {

              if (item.completed) {
                newTodos.push(item);
              }

          })
          return newTodos;
        }
    }
  }
});
</script>