编辑todo

编辑 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 --


0 条评论 / 0 人参与