前面我们已经了解了有关 Vue.js 的基础知识,本章我们开始创建一个经典的 todo 程序。我们选择 todomvc 作为页面模板。虽然 Vue.js 官方已经提供了类似的示例,但是现在,我们自己在 todomvc 模板基础上,自己使用 Vue.js 实现相关功能。
todomvc 原始模板可以在 github 下载;具体实现要求则可以在这里找到。首先我们使用 git clone 下来模板。todomvc 使用 npm 管理依赖。npm 是 nodejs 的包管理工具,类似于 Java 的 maven 之类。安装好 nodejs 后,就可以直接使用 npm。进入到 close 下来的 todomvc-app-template 文件夹,运行 npm install 命令即可。如果你不想安装 nodejs,本文末尾有完整的模板打包文件,提供给初学者使用。
准备好模板文件之后,在浏览器打开 todomvc-app-template 中的 index.html,可以看到运行界面(点击这里运行)。
这只是一个静态模板,没有任何动态的功能。我们的目标,就是按照todomvc应用规范,使用 Vue.js 实现这些需求。
todomvc 的需求比较简单:页面上有一个输入框,每次输入内容,按下回车将新增一行;用户也可以选择删除一行,或者双击编辑等。因此我们的思路是,维护一个 todolist 数组,将视图与这个数组进行绑定。这一点看起来与之前我们写的代码非常类似,事实也的确如此。
按照todomvc应用规范,我们自己的 JavaScript 代码必须写在 js/app.js 中。于是,我们修改这个文件如下:
(function (window) { 'use strict'; // Your starting point. Enjoy the ride! new Vue({ el: '.todoapp', data: { todolist: [] } }); })(window);
查看 index.html 页面,整个应用位于>section class="todoapp"<
中,因此Vue
对象的el
属性设置为.todoapp
。目前我们只需要一个data
,即todolist
数组。同时,todomvc应用规范要求,当todolist
为空时,#main
和#footer
应该隐藏。所以,我们修改 index.html 页面相关代码如下:
... <section class="todoapp"> <!-- This section should be hidden by default and shown when there are todos --> <section class="main" v-show="todolist.length"> ... </section> <!-- This footer should hidden by default and shown when there are todos --> <footer class="footer" v-show="todolist.length"> ... </footer> </section> ...
为了将#main
和#footer
与todolist
的长度进行绑定,我们使用了v-show
指令。v-show
指令控制被绑定的元素是否要显示。我们将其绑定到todolist.length
,由于todolist.length
默认值为 0,JavaScript 认为是false
,因此不会显示#main
和#footer
。当我们给todolist
添加元素之后,todolist.length
不为 0,#main
和#footer
将会显示。到目前为止还挺顺利的!
todomvc应用规范要求,使用页面上方的输入框创建新的 todo。页面加载完毕后,该输入框需要自动获得焦点;当输入完毕按下回车键,新增一个 todo 项目,同时清空输入框。输入的内容需要首先使用trim()
函数过滤掉两侧空白字符。
这对我们来说基本不是问题,因为在前面的章节,我们已经完成了类似的工作,只需要简单地重新添加一下就可以了。至于页面加载完毕自动获取焦点,只需要给<input>
标签添加autofocus
属性即可——这是 HTML5 新增属性,正是为了达到类似的目的。
为了新增 todo,我们需要为我们的模型增加一个newTodo
数据,其类型暂时只需是字符串:
new Vue({ el: '.todoapp', data: { todolist: [], newTodo: '' }, methods: { addTodo: function () { if (this.newTodo) { var todo = this.newTodo.trim(); this.todolist.push(todo); this.newTodo = ''; } } } });
然后修改 HTML 中的.header
和.main
两部分如下:
<header class="header"> <h1>todos</h1> <input class="new-todo" placeholder="What needs to be done?" autofocus v-model="newTodo" @keyup.enter="addTodo"> </header> <!-- This section should be hidden by default and shown when there are todos --> <section class="main" v-show="todolist.length"> <input class="toggle-all" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul class="todo-list"> <!-- These are here just to show the structure of the list items --> <!-- List items should get the class `editing` when editing and `completed` when marked as completed --> <li v-for="todo in todolist"> <div class="view"> <input class="toggle" type="checkbox"> <label>{{todo}}</label> <button class="destroy"></button> </div> <input class="edit" value="Rule the web"> </li> </ul> </section>
首先,我们在<input>
标签添加了v-model
属性,将<input>
绑定到数据模型newTodo
;然后为<input>
添加了键盘事件@keyup.enter
,用于监听回车事件。这部分代码与之前的示例非常相似,这里不再赘述。
这部分代码的执行点击这里运行。
最后再按照todomvc应用规范对比下我们实现了什么:
没有 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
类。在过滤状态下,一个项目被修改,它应该同步更新。例如,如果过滤器是“活动的”而该项目被“完成”,那么它应该被隐藏。注意在每次加载时,持久化活动过滤器。
附件