简明 Vue.js 教程(5)

前面我们已经完成了 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定义了两个计算属性remainingallDoneremaining是只读的,唯一的函数在读取时使用。该函数调用了前面定义的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 项目有三种交互:

  1. 点击选择框,将其标记为完成状态,更新其completed属性值,并且要设置其父组件的 class 为completed
  2. 双击标签进入编辑模式,为其添加.editing
  3. 鼠标滑过是显示删除按钮(.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类。在过滤状态下,一个项目被修改,它应该同步更新。例如,如果过滤器是“活动的”而该项目被“完成”,那么它应该被隐藏。注意在每次加载时,持久化活动过滤器。

One Response

  1. Reckful 2016年8月13日

Leave a Reply