编辑 todo 的功能略微有点复杂,我们一点点来分解。
首先根据之前的分析,Vue 很容易知道我们想要编辑的是哪一个 todo,只要把当前的 todo 传给绑定的方法即可。我们实现的功能是双击当前的 todo 进入编辑状态,我们就可以在后边的输入框编辑这个 todo,因此需要给元素绑定一个双击监听事件。还有一点需要注意,我们有一个取消编辑的功能,假设用户将 todo 编辑到了一半又不想编辑了,想回到原来的 todo 内容,我们该怎么办?为了解决这个问题,我们可以在 data 返回的对象添加一个属性,让它来暂存编辑前的 todo 状态,如果用户取消了编辑,就让已修改的 todo 退回到之前的状态:
var app = new Vue({
el: '#todo-app',
data: function () {
return {
todos: [],
newTodoTitle: '',
editedTodo: null // 用于暂存编辑前的 todo 状态
}
},
})
另一个问题是后面那个编辑框十分烦人,因为无论我们是否在编辑状态,这个框始终出现。应该根据当前的 todo 是否处于编辑状态来决定它的出现与否,只有当用户双击 todo 进入编辑状态时才出现,那么怎么知道这个 todo 是否是处于当前状态呢?
我们之前在 data 返回的对象里增加了一个 editedTodo 属性,并且把它的初始值设为 null,这个属性的用途是用来暂存编辑前的 todo 状态的,即当用户编辑某个 todo 时,这个 editedTodo 就会被设置为当前 todo 未被编辑前的值。换句换说,如果 editedTodo 为 null,则一定说明这个 todo 不在编辑状态。所以我们可以根据 editedTodo 的值来判断。
<ul>
<li v-for='todo in todos' :key='todo.id'>
...
<input type="text"
value="编辑 todo..."
v-if="editedTodo !==null"/>
</li>
</ul>
然后我们为双击 todo 添加一个 editTodo 方法,这个方法把编辑前的 todo 状态暂存到 editedTodo。双击事件为 dblclick:
<li v-for='todo in todos' :key='todo.id'>
<span :class="{finished: todo.finished}"
@dblclick="editTodo(todo)">{{ todo.title }}</span>
...
</li>
<script>
let id = 0; // 用于 id 生成
var app = new Vue({
...
methods: {
...
editTodo: function (todo) {
this.editedTodo = {id: todo.id, title: todo.title}
}
},
})
</script>
特别注意这里我们使用了
this.editedTodo = {id: todo.id, title: todo.title, finished: todo.finished}
而不是简单的 this.editedTodo=todo 进行复制,因为这样做的话仅仅只是将对 todo 的引用存到 this.editedTodo,这样的话任何对 todo 的修改都会反映到 editedTodo 上,为了防止这种情况,我们要为 editedTodo 创建一个全新的对象。
打开浏览器刷新,多创建几条 todo(一定要多创建几条),然后双击 todo 的标题,你会发现...
好吧,这并不符合我们的预期,我们希望双击哪条todo,哪条 todo 对应的编辑框弹出来,而不是所有的都弹出来。仔细分析一下我们的代码,我们根据 this.editedTodo !==null 来决定是否显示编辑框,而当某条 todo 被编辑时,this.editedTodo !==null 不再成立,所以所有编辑框都出现了,怎么解决这个问题呢?
只有在被编辑的 todo 的 id 和被暂存的 editedTodo 的 id 相等时,才表示这条 todo 在编辑,而其它的 todo 的 id 和 editedTodo 的 id 都是不相等的,所以我们可以加一个判断:
<ul>
<li v-for='todo in todos' :key='todo.id'>
<span :class="{finished: todo.finished}">{{ todo.title }}</span>
...
<input type="button" value="删除" @click="removeTodo">
<input type="text" value="编辑 todo..."
v-if="editedTodo!==null && editedTodo.id===todo.id"/>
</li>
</ul>
要注意 Vue 允许我们在指令中写入任何合法的 javascript 表达式,Vue 会自动对其求值。
然后我们将编辑框的值和 todo 的 title 值双向绑定,那么 todo 的 title 就会跟着编辑框输入的值来变化了,我们也不用担心用户改变了 todo 的 title 值,因为我们已经把编辑前的 todo 的状态暂存到了 editedTodo,想反悔可以随时还原。表单绑定用 v-model:
<li v-for='todo in todos' :key='todo.id'>
...
<input type="text" value="编辑 todo..."
v-if="editedTodo!==null && editedTodo.id===todo.id"
v-model="todo.title"/>
</li>
然后就是用户敲击回车,编辑完成,这里我们给编辑框绑定了一个 keyup 方法监听键盘事件,enter 是修饰符,表示这个键盘事件是按下回车,此时会调用 editDone 方法。用户按下回车后因为 todo.title 的值本身就是随着编辑框输入的值变化的,所以我们基本不用做什么事情,如果用户编辑已经完成,暂存的 todo 就不再需要了,我们可以简单地把 editedTodo 还原成 null,这样编辑框的 v-if 判断就会失效,编辑框自动隐藏,完美!
<li v-for='todo in todos' :key='todo.id'>
...
<input type="text" value="编辑 todo..."
v-if="editedTodo!==null && editedTodo.id===todo.id"
v-model="todo.title"
@keyup.enter="editDone(todo)"/>
</li>
<script>
let id = 0; // 用于 id 生成
var app = new Vue({
...
methods: {
...
editDone: function (todo) {
this.editedTodo = null
}
},
})
</script>
取消怎么办呢?取消就是把已经编辑修改的 todo 标题还原,我们的原始信息存在 editedTodo,取出来即可。用户按键盘的 ESC 键进行取消编辑,为此绑定一个键盘事件,和 enter 类似,用 esc 修饰该事件,表示按下的是 ESC 键,然后调用 cancelEdit 方法,该方法将 todo 还原成编辑前的状态:
<li v-for='todo in todos' :key='todo.id'>
...
<input type="text" value="编辑 todo..."
v-if="editedTodo!==null && editedTodo.id===todo.id"
v-model="todo.title"
@keyup.enter="editDone(todo)"
@keyup.esc="cancelEdit(todo)"/>
</li>
<script>
let id = 0; // 用于 id 生成
var app = new Vue({
...
methods: {
...
cancelEdit: function (todo) {
todo.title = this.editedTodo.title;
this.editedTodo = null
}
},
})
</script>
注意要编辑框聚焦后按 ESC 才有效。
同样因为用户编辑已经取消,todo 状态已经还原,暂存的 todo 也不再需要了,我们可以简单地把 editedTodo 还原成 null,这样 v-if 判断就会失效,编辑框自动隐藏。
练习
我们应用的体验有一点点不好的地方,如果用户把编辑的内容清空然后按回车确认修改,这时一条空的 todo 就保存了。我们认为用户清空内容就是不想要这条 todo 了,毕竟现实一条空的 todo 没有意义,所以我们应该删除掉这条 todo,实现这个需求。(hint:我们之前实现了 removeTodo 方法,就是做这个的。学会复用代码而不是重复造轮子。)
另外一个不爽的地方就是双击 todo 后弹出编辑框,焦点并没有自动转到编辑框,只有手动点击编辑哭后我们才能编辑内容。这个需求的实现设计到 Vue 的自定义指令功能,我们在下一节来实现。
-- EOF --
再次感谢CyberFork和楼主的耐心.
我在CyberFork的基础上,给每一条todo都加了一个newTitle,所以可以同时编辑几条.
否则的话我觉得应该要防止一条编辑了既没有按esc又没有按enter就跑去下一条
这章编辑todo还有一个体验不好的地方就是如果是已完成的todo双击点击也会出现编辑框,所以还应该在editTodo方法前面判断这个todo是不是已完成:
editDone: function(todo){
if (todo.title.length === 0){
this.deleteTodo(todo)
}
this.editedTodo = null
}
editTodo: function(todo){
if (todo.completed===true){
return
}
this.editedTodo = {id: todo.id, title: todo.title}
}
博主,感谢您的教程。
我现在使用了一种新方式来编辑更新todo:
1. 首先在li中使span双击调用进入该条的编辑模式@dblclick='editTodo(todo)'
在editTodo(todo)中将todo.editingTodo = true 设置
根据todo.editingTodo 条件渲染隐藏span,显示编辑的text input。
2. 之后在input中输入数据,使用v-model='editedTodo'绑定数据到editedTodo中,最后如果回车是空数据则不更新,如果不是空数据则更新数据todo.title = this.editedTodo。
3. 这样就不需要像您教程一样额外使用一个this.editedTodo = {id: todo.id, title: todo.title}啦
嗯!不错的实现方式,学习了!
v-if='editedTodo!==null && editedTodo.id===todo.id'
上面好像跑不通...
v-if='!(editedTodo !== null && editedTodo.id === todo.id)'
前面标记完成是:
completed
后面变成:
finished
练习那一处有一个问题 就是在editDone()中, 调用removeTodo()方法,
this的指向发生了变化, 直接调用removeTodo. this指向我们定义的vue,
在editDone中调用removetodo的话, this指向了vue.methods
于是我在removeTodo这个方法中, 将this.todos修改为了 app.todos, 请教一下这两种写法有什么区别和影响吗
事实证明是我用错了方法
我应该直接再editDone()中调用 this.removeTodo(),
由于对vue不熟悉, 我调用了this.$option.methods.removeTodo(),
导致了这个错误