運用已學到的基礎與進階指令,試著寫出一個 todo-list 待辦事項清單吧!
先附上成品 Demo,以下會一步步說明該如何實作一個 todo-list。
基本架構:輸入欄及待辦事項列表
HTML 部分
- 任務輸入欄:
v-model="newTodo" - 輸入按鈕:
@click="addTodo" - 渲染每項任務的
<li>:v-for="items in todos",todos是一個陣列,用來儲存所有待辦事項 <li>裡面的 checkbox:checkbox 要用v-bind綁定todos中每個物件的id特性,:id="item.id"<li>裡面的 label:要指向跟 checkbox 同樣的id,所以要寫成:for="item.id",<label>中間用雙花括號包住item.title- 任務的完成狀態:在 checkbox 使用
v-model="item.completed" - 加上按鍵功能:為了讓按下 Enter 也能產生跟點擊一樣的效果,在 input 上綁定按鍵事件,
@keyup.enter="addTodo"
Vue 的 data 部分
1 | newTodo:'', // 一個字串,對應到任務輸入欄 |
Vue 的 methods 部分 (把新事項加到列表)
1 | methods: { |
輸入欄防呆
依照上面的設定可以順利新增任務了,但是如果沒在 <input> 打入文字也會送得出去,所以需要增加一些防止空白的效果。
把 addTodo() 裡面的程式碼稍作修改:
1 | addTodo: function() { |
刪除待辦事項
刪除功能必須仰賴待辦事項在陣列中的索引值,才能辨認使用者要刪除的是哪一筆資料,所以要將索引值設參數傳入刪除按鈕綁定的事件中去處理。
- 將原本
<li>設定的v-for值改為(item, key) in todos,key代表待辦事項的索引值 - 在
<li>中的刪除按鈕綁定事件:@click="removeTodo(key)" - 到
methods增加removeTodo():1
2
3
4removeTodo: function(key) {
this.todos.splice(key,1);
// 括號中的 1 代表從該索引值起,刪除 1 筆資料
}
為已完成事項加上刪除線
寫一個 CSS 樣式,內容是在文字上加上刪除線。
由於 chceckbox 上已經綁定了 v-model="tem.completed" 可以雙向修改 data 資料中 completed 的值,所以接下來只要在 <label> 上切換 class 即可。
在 <label> 上設定::class="{ 'completed': item.completed }"。
製作「全部」、「進行中」、「已完成」頁籤切換,及過濾相對應的待辦事項
頁籤切換
在 data 新增一個特性,這個特性會去感應使用者點擊的是哪一個頁籤,這邊將該特性取名作 visibility,且它的值可以先預設成第一頁頁籤的名稱。
在 HTML 頁籤結構的 <a> 上使用切換 class 的指令::class="{'active': visibility == '頁籤名稱'}",各個頁籤 <a> 都要加這一行,名稱記得替換。頁籤名稱可以依據各頁代表的意義來取。
接著在頁籤 <a> 上綁定點擊事件:@click="visibility = '頁籤名稱'"。
頁籤過濾
切換頁籤的功能做好後,繼續做頁面內容的替換(過濾)。
剛剛我們呈現在畫面上的都是從 todos 陣列撈出來的資料 (原始資料),但其實畫面上應該呈現的是過濾後的資料。
所以要在 computed 裡面新增方法,運用 todos 進行資料的過濾。
在 methods 同層新增一個 compute 物件,並宣告一個新方法(這邊取名為 filteredTodos),用來執行過濾功能。
1 | compute:{ |
把 <li> 原本寫的 v-for = "(item, key) in todos" 的 todos 改為 filteredTodos,可以這樣寫是因為 filteredTodos 回傳的是一個陣列。(in 後面可以接陣列或物件)
刪除功能的修正
加入了頁籤換頁功能後,原本的刪除功能會失靈,這是因為不同頁籤就是內容不同的陣列,因此其他頁籤的待辦事項索引值會跟在原始陣列 todos 不同,透過修正 removeTodo() 的程式碼,去比對在不同頁籤中的待辦事項 id 是否相同,如果 id 相同,就回傳它在原始陣列 todos 的索引值,統一從 todos 裡刪除,這樣才不會刪錯。
1 | removeTodo: function(todo) { |
原本 removeTodo() 是傳入 key 當參數,要改成 todo,todo 代表所點選的項目;刪除按鈕綁定的事件 @click="removeTodo(key)",要把參數 key 改成 item。
用變數存取 this 的必要性
若在 forEach 中的 callback 函式內使用 this 來存取 data 中的屬性,就會發生讀取不到的問題,為了保險起見還是會宣告個 vm 變數,以確保存取的屬性是 Vue 實例中的 data 內的屬性。
雙擊修改待辦事項內容
<li> 的雙擊事件
在設定 v-for 的 <li> 上,再綁一個雙擊事件的監聽:@dblclick="editTodo(item)"
data 預存要編輯的任務
在 data 新增兩個特性,用來預存要編輯的物件及該物件的文字:
1 | cacheTodo: {}, |
在 methods 增加 editTodo() 方法
1 | editTodo: function(item){ |
添加編輯任務的輸入欄
把一個 <input> 寫在待辦列表 <li> 裡面的最下方(刪除按鈕的下面)。
這個 <input> 就是拿來編輯待辦事項用的,<input> 跟待辦事項兩者不會同時顯示,所以要在 <li> 的下一層(包住所有表單元素的 <div>)設定判斷是否要渲染一個待辦事項出來。
- 在
<li>下判斷:1
2v-if="item.id !== cacheTodo.id"
<!-- 這一行的意思是:只顯示沒有被雙擊的項目 --> - 在
<input>上設定的判斷則相反:1
2v-if="item.id == cacheTodo.id"
<!-- 項目被雙擊時被 input 取代 -->
關於渲染的邏輯v-if 條件為 true 時會把內容的 DOM 渲染出來,條件為 false 時會把 DOM 移掉。
- 雙擊
<li>時,產生cacheTodo.id,讓item.id == cacheTodo.id為true,所以<input>就會渲染出來 。 - 沒有雙點擊
<li>時, 不會產生cacheTodo.id,讓item.id !== cacheTodo.id為true,所以待辦事項就會渲染出來。
<input> 欄位中顯示被雙擊的任務文字
在 <input> 上設定:v-model="cacheTitle"。
按下 Esc 鍵時取消編輯
1 | <!-- HTML input --> |
1 | // JS methods |
項目修改完成
按下 Enter 鍵就用新文字替換掉舊的,在 <input> 上設定:@keyup.enter="doneEdit(item)"。
到 methods 去新增方法:
1 | doneEdit: function(item){ |
關於將暫存資料清空
將 doneEdit 方法中的 this.cacheTitle = ''; 刪除也不會影響功能,是因為在 editTodo 方法中已經用 this.cacheTitle = item.title; 先把 this.cacheTitle 的內容用當下這筆事項的內容取代了,當編輯不同筆事項時,this.cacheTitle 中的值都會被使用者正在編輯的事項內容給取代。
如果沒有 this.cacheTitle = '' ; 編輯完成後 cacheTitle 的值就會固定成改變後的值,而不是原來預設在 data 裡的空值。雖然 editTodo 方法中已經寫好了替換編輯內容的效果,但假設有要再用 cacheTitle 做其他資料的處理,可能就會受影響,所以保險起見把它的值歸零。
未完成任務數目與清除所有任務
未完成任務數目
- 在
computed新增方法,目的是計算todos.completed為false的數量 - 用
filter()過濾todos - 取得一個未完成任務組成的陣列
- 用
compute方法return這個陣列的長度 - 在 HTML 中用雙花括號插入
compute 方法名
清除所有任務
- 在「清除所有任務」
<a>上綁定事件監聽 - 在
methods新增方法,把todos宣告為空陣列即可