前面我们已经完成了 todo 的几个状态。下面我们开始针对这些状态添加一些便捷操作。这也是 todomvc 应用规范所要求的。
在输入框左边有一个箭头,这是一个 checkbox。当点击这个箭头时,所有 todo 都应该设置为完成状态;再次点击则将所有 todo 恢复为未完成状态。需要注意的是,这个 checkbox 的选择与下面 todo 的操作息息相关:当一个个将所有 todo 手动完成后,该 checkbox 自动变成勾选状态;当去除任意一个 todo 的完成状态时,该 checkbox 自动去除勾选状态。这种灵活的变化暗示我们,将 checkbox 与一个模型绑定,可能是一个好主意。
现在我们的模型只有一个todolist,保存所有 todo。但是我们还需要各种状态下的 todo,例如,所有完成状态的 todo。对此,我们不应该再维护一个数组——这是不方便的,而是应该返回todolist的子数组,也就是对其进行过滤之后再返回。
为了实现这种过滤,我们添加一个名为filters的对象。该对象包含三个函数:all()返回全部 todo;active()返回活动状态的 todo;completed()返回完成状态的 todo:
var filters = {
all: function (todos) {
return todos;
},
active: function (todos) {
return todos.filter(function (todo) {
return !todo.completed;
});
},
completed: function (todos) {
return todos.filter(function (todo) {
return todo.completed;
});
}
}; 我们定义好了各种过滤器,这意味着我们有了过滤之后的数据。下面就要让 Vue 的数据与这些过滤器进行关联,同时绑定到页面的组件。找到 index.html 中的<input type="checkbox">标签。该标签需要绑定到一个布尔型变量:当它被选中时,todoList中每个 todo 的completed属性都被设置为true;当它取消选中时,todoList中每个 todo 的completed属性都被设置为false。我们将其与所有活动的 todo 列数组关联起来:如果所有活动的 todo 数组长度为 0,也就是没有活动的 todo,checkbox 将被选中。现在,我们没有这么个数组,所以要构建一个。前面我们说到,我们不希望再维护一个额外的数组,Vue 提供了一个计算属性,正好可以满足我们的需求。
所谓“计算属性”,顾名思义,就是计算得来的属性;也就是说,这个属性不是真实存在的数据,而是由实际数据计算得来的一种“虚拟”属性。虽然不大准确,但是我们可以这么理解计算属性。Vue 的计算属性使用computed表示:
new Vue({
el: '.todoapp',
data: {
todolist: [],
newTodo: ''
},
computed: {
remaining: function () {
return filters.active(this.todolist).length;
},
allDone: {
get: function () {
return this.remaining === 0;
},
set: function (value) {
this.todolist.forEach(function (todo) {
todo.completed = value;
});
}
}
},
methods: {
...
}
...
}); 以上代码,我们使用computed定义了两个计算属性remaining和allDone。remaining是只读的,唯一的函数在读取时使用。该函数调用了前面定义的filters中的active过滤器,返回其length属性,即活动的 todo 数组的长度。allDone是可读可写的,使用get定义读取,set定义写入。get函数直接返回前面定义的计算属性remaining的长度是否为 0,如果为 0,说明没有“剩余”活动的 todo,checkbox 应该被选中;set函数则遍历todoList,将每一个 todo 的completed属性均设置为value,这个value当然就是 checkbox 传递过来的是否选中的布尔值。
有了以上基础,我们也可以实现清理完成的功能。当我们点击右下角的 Clear completed 按钮时,需要删除所有已完成的 todo。当没有已完成的 todo 时,该按钮不需要显示,因此我们需要将该按钮修改如下:
<button class="clear-completed"
@click="removeCompleted"
v-show="todolist.length > remaining">Clear completed</button> 使用@click设置点击按钮时的回调函数;v-show指令则表明当todolist.length > remaining时,该按钮才会显示出来。按照我们之前的定义,remaining是未完成的 todo 数组长度,当总数大于未完成数时,说明有已完成的 todo,因此显示按钮。值得注意的是,虽然remaining是一个计算属性,但是我们像普通属性一样使用它,与普通属性没有什么区别。
接下来是removeCompleted回调函数,该函数应该在methods中定义:
...
methods: {
...
removeCompleted: function () {
this.todolist = filters.active(this.todolist);
}
},
... 函数实现很简单,我们用过滤器去除所有活动的 todo,将其赋值给todolist。由于todolist时我们保持的全部数据的数组,所以相当于将所有已完成的 todo 删除掉了。
最后,我们来完成剩余数提示。我们只需要将remaining的值显示出来即可。需要注意的是,remaining后面的 item 需要根据单复数变更形式。这意味着我们需要再添加一个计算属性:
computed: {
...
remainingText: function () {
if (this.remaining === 0 || this.remaining > 1) {
return this.remaining + ' items';
} else {
return this.remaining + ' item';
}
},
...
}, 然后将这个计算属性与<strong>标签进行绑定。由于我们需要设置<strong>的文本内容,因此使用v-text即可:
<span class="todo-count"><strong v-text="remainingText"></strong> left</span>
这样,我们便完成了大部分工作,现在页面运行如这里所示。
最后,我们照例要看一下我们已经完成了哪些功能:
没有 todo 时
没有 todo 的时候,#main和#footer应当隐藏。
新增 todo
在应用上方的输入框按下回车新增 todo。当页面加载完毕后,输入框应该获得焦点,这可以使用autofocus属性实现。按下回车创建 todo,将其追加到 todo 列表最后,同时清空输入框。确保对输入值调用.trim(),并且在创建新 todo 之前检查是否为空。
标记所有为完成
该checkbox将所有 todo 设置为与自己相同的状态。在点击“Clear completed”按钮之后清空所选状态。当一个 todo 项目被选择或反选是,“Mark all as complete”应该同步更新。例如,当所有 todo 都被勾选时,它也应该是选择状态。
项目
一个 todo 项目有三种交互:
点击选择框,将其标记为完成状态,更新其completed属性值,并且要设置其父组件的 class 为completed双击标签进入编辑模式,为其添加.editing类鼠标滑过是显示删除按钮(.destroy)
编辑
进入编辑模式时,其它组件将被隐藏,只显示包含 todo 标题的输入框,这个输入框应该获得焦点(.focus())。失去焦点或者按下回车结束编辑,移除.editing类。确保对输入值调用.trim(),并且在创建新 todo 之前检查是否为空。如果为空,该 todo 应该被销毁。如果在编辑时点击了Esc,应该退出编辑状态,丢弃所有修改。
计数器
以复数形式显示活动的 todo 数。该数值需要使用<strong>标签。并且要保证复数形式是正确的:0 items,1 item,2 items。例如,2 items left
Clear completed 按钮
点击按钮移除已完成 todo。如果没有已完成 todo,隐藏该按钮。
持久化
应用应该能够将 todo 动态持久化到 localStorage。如果框架自身能够处理持久化数据(例如 Backbone.sync),使用框架提供的即可。否则,使用 vanilla localStorage。如果可能的话,为每个 todo 定义 id、title、completed。localStorage 使用如下名称格式:todos-[framework]。编辑模式不应该被持久化。
路由
所有实现都需要路由。如果框架支持路由,使用内置的路由机制。否则,使用 /assets 文件夹中的 Flatiron Director 路由库。需要实现下列路由:#/ (全部 - 默认);#/active 和 #/completed (也可以使用 #!/)。路由变化时,todu 列表应该在模型级别进行过滤,过滤器链接应该添加selected类。在过滤状态下,一个项目被修改,它应该同步更新。例如,如果过滤器是“活动的”而该项目被“完成”,那么它应该被隐藏。注意在每次加载时,持久化活动过滤器。
1 个评论
求更新!