前面我们已经完成了 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 个评论
求更新!