这节我们用vue2来实现todoMVC,主要功能有:添加和删除事件、筛选、显示未完成数量、清空、全选等等。
因为是对vue的练习,主要是处理一些逻辑和渲染,就不写html和css了,可以在github主页里copy一下。
静态展示
我们的最终目标是实现全部的功能,待办事项都是自己添加的,需要数据的交互。在这里我们首先尝试一下静态展示写好的待办事项。
首先当然是编辑列表里要展示的事项啦,数据写在data里,每个事件都有他自己的标识(id)、内容(title)、完成情况(completed)。
其次是在template中找出展示这些事件的结构标签,和数据进行绑定。这里物品们先只处理展示部分,编辑和删除的部分下面再处理。
<script>
export default {
data() {
return {
todos: [
{
id: 123,
title: "测试内容1",
completed: false
},
{
id: 456,
title: "测试内容2",
completed: true
}
]
};
}
}
</script>
<ul class="todo-list">
<!-- These are here just to show the structure of the list items -->
<!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
<li
v-for="todo in todos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<!-- 上面配置基础的结构 -->
<div class="view">
<!-- 做切换操作的,使用v-moudle进行双向绑定 -->
<input class="toggle" type="checkbox" v-model="todo.completed" />
<!-- 任务标题,使用v-text进行绑定或者使用插值 -->
<label v-text="todo.title"></label>
<button class="destroy"></button>
</div>
<input class="edit" value="Create a TodoMVC template" />
</li>
</ul>
顶部操作
现在我们来处理顶部全选按钮,点击切换所有选项状态。这个很简单,找到对应的结构标签绑定点击事件,然后在methods里写一下这个方法就好了,注意要遍历所有事件。
methods: {
toggleAll(e) {
this.todos.forEach(todo => todo.completed = e.target.checked)
}
}
<input id="toggle-all" class="toggle-all" type="checkbox" @click="toggleAll" />
底部操作
下面我们来做一下底部的切换功能,展示已完成项/未完成项。
首先找到该功能的模块,也就是<ul class="filters">
,可以看到三个按钮是对应三个不同的hash值的。当我们点击按钮时,hash值会随之更新,页面显示的事件选项也会更新(也就是根据hash值过滤要显示的事件)。
因此我们要对hash进行监听并根据不同的hash进行处理。在hash值发生改变时,及时更新visibility,并使用计算属性来进行过滤,筛选出符合条件的数据。
最后再加一点细节,比如点击按钮时显示类名选中、刷新页面后保持原始状态、显示待办事件个数
<script>
export default {
data() {
return {
todos: [
{
id: 123,
title: "测试内容1",
completed: false
},
{
id: 456,
title: "测试内容2",
completed: true
},
],
// 筛选标准,默认全部可见
visibility: 'all'
};
},
computed: { // 计算属性,在数据有改变时才触发
filteredTodos () { // filter是过滤器,返回符合条件的元素
switch(this.visibility) {
case 'all':
return this.todos
case 'active':
return this.todos.filter(todo => !todo.completed)
case 'completed':
return this.todos.filter(todo => todo.completed)
}
},
// 待办任务数量
remining () {
return this.todos.filter(todo => !todo.completed).length
}
}
,
methods: {
toggleAll(e) {
this.todos.forEach(todo => (todo.completed = e.target.checked));
},
//根据监听到的hash进行处理
onHashChange() {
// 去掉hash中多余的符号
const hash = window.location.hash.replace('#/', '')
// 根据不同的hash进行不同的操作
if(['all', 'active', 'completed'].includes(hash)) {
this.visibility = hash
} else {
window.location.hash = ''
this.visibility = 'all'
}
}
},
// 在元素挂载完毕后,在mounted钩子中对hash进行监听并调用事件处理
mounted() {
window.addEventListener('hashchange', this.onHashChange)
}
};
</script>
<template>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus />
</header>
<!-- This section should be hidden by default and shown when there are todos -->
<section class="main">
<!-- 顶部按钮点击切换全部事件状态 -->
<input id="toggle-all" class="toggle-all" type="checkbox" @click="toggleAll" />
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<!-- These are here just to show the structure of the list items -->
<!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
<!-- 配置基础的结构,展示筛选后的列表 -->
<li v-for="todo in filteredTodos" :key="todo.id" :class="{ completed: todo.completed }">
<div class="view">
<!-- 做切换操作的,使用v-moudle进行双向绑定 -->
<input class="toggle" type="checkbox" v-model="todo.completed" />
<!-- 任务标题,使用v-text进行绑定或者使用插值 -->
<label v-text="todo.title"></label>
<button class="destroy"></button>
</div>
<input class="edit" value="Create a TodoMVC template" />
</li>
</ul>
</section>
<!-- This footer should be hidden by default and shown when there are todos -->
<footer class="footer">
<!-- This should be `0 items left` by default -->
<span class="todo-count">
<!-- 显示剩余任务数量 -->
<strong>{{ remining }}</strong> item left
</span>
<!-- Remove this if you don't implement routing -->
<!-- 过滤功能,需要根据hash来进行过滤 -->
<ul class="filters">
<li>
<!-- 根据visiblity来设置选中状态 -->
<a :class="{ selected: visibility === 'all' }" href="#/all">All</a>
</li>
<li>
<a :class="{ selected: visibility === 'active' }" href="#/active">Active</a>
</li>
<li>
<a :class="{ selected: visibility === 'completed' }" href="#/completed">Completed</a>
</li>
</ul>
<!-- Hidden if no completed items are left ↓ -->
<button class="clear-completed">Clear completed</button>
</footer>
</section>
</template>
添加和删除
添加任务就要在输入框绑定回车事件,并写好相应的函数,根据输入的内容生成新对象
<script>
export default {
data() {
return {
todos: [
{
id: 123,
title: "测试内容1",
completed: false
},
{
id: 456,
title: "测试内容2",
completed: true
},
],
// 筛选标准,默认全部可见
visibility: 'all'
};
},
computed: { // 计算属性,在数据有改变时才触发
filteredTodos () { // filter是过滤器,返回符合条件的元素
switch(this.visibility) {
case 'all':
return this.todos
case 'active':
return this.todos.filter(todo => !todo.completed)
case 'completed':
return this.todos.filter(todo => todo.completed)
}
},
// 待办任务数量
remining () {
return this.todos.filter(todo => !todo.completed).length
}
},
methods: {
toggleAll(e) {
this.todos.forEach(todo => (todo.completed = e.target.checked));
},
//根据监听到的hash进行处理
onHashChange() {
// 去掉hash中多余的符号
const hash = window.location.hash.replace('#/', '')
// 根据不同的hash进行不同的操作
if(['all', 'active', 'completed'].includes(hash)) {
this.visibility = hash
} else {
window.location.hash = ''
this.visibility = 'all'
}
},
addTodo (e) {
const title = e.target.value.trim() // 获取输入框的内容并去空格
if (!title) {
return
}
this.todos.push({
id: Date.now(), // 把时间戳作为id
title,
completed: false
})
e.target.value = '' // 清空输入框的内容
},
removeTodo (todo) {
this.todos.splice(this.todos.indexOf(todo), 1) // 根据索引删除一个元素
},
clearCompleted () {
this.todos = this.todos.filter(todo => !todo.completed) // 过滤出未完成的元素
}
},
// 在元素挂载完毕后,在mounted钩子中对hash进行监听并调用事件处理
mounted() {
window.addEventListener('hashchange', this.onHashChange)
}
};
</script>
<template>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<!-- 回车添加新任务 -->
<input class="new-todo" @keyup.enter="addTodo" placeholder="What needs to be done?" autofocus />
</header>
<!-- This section should be hidden by default and shown when there are todos -->
<section class="main">
<!-- 点击顶部按钮切换全部事件状态 -->
<input id="toggle-all" class="toggle-all" type="checkbox" @click="toggleAll" />
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<!-- These are here just to show the structure of the list items -->
<!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
<!-- 配置基础的结构,展示筛选后的列表 -->
<li v-for="todo in filteredTodos" :key="todo.id" :class="{ completed: todo.completed }">
<div class="view">
<!-- 做切换操作的,使用v-moudle进行双向绑定 -->
<input class="toggle" type="checkbox" v-model="todo.completed" />
<!-- 任务标题,使用v-text进行绑定或者使用插值 -->
<label v-text="todo.title"></label>
<!-- 删除按钮,点击触发,记得传入当前todo -->
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input class="edit" value="Create a TodoMVC template" />
</li>
</ul>
</section>
<!-- This footer should be hidden by default and shown when there are todos -->
<footer class="footer" v-show="todos.length">
<!-- This should be `0 items left` by default -->
<span class="todo-count">
<!-- 显示剩余任务数量,注意复数细节 -->
<strong>{{ remining }} {{ remining > 1 ? 'items' : 'item' }}</strong> left
</span>
<!-- Remove this if you don't implement routing -->
<!-- 过滤功能,需要根据hash来进行过滤 -->
<ul class="filters">
<li>
<!-- 根据visiblity来设置选中状态 -->
<a :class="{ selected: visibility === 'all' }" href="#/all">All</a>
</li>
<li>
<a :class="{ selected: visibility === 'active' }" href="#/active">Active</a>
</li>
<li>
<a :class="{ selected: visibility === 'completed' }" href="#/completed">Completed</a>
</li>
</ul>
<!-- Hidden if no completed items are left ↓ -->
<button class="clear-completed" @click="clearCompleted">Clear completed</button>
</footer>
</section>
</template>
<style>
@import "https://unpkg.com/todomvc-app-css@2.4.1/index.css";
</style>
最后呈现出的结果就是这样啦: