運用已學到的基礎與進階指令,試著寫出一個 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
宣告為空陣列即可