在上一篇文章中我们把todos这个实例的数据模型进行了简单的分析, 有关于数据模型的操作也都知道了。接着我们来看剩下的两个view的模型,以及它们对页面的操作。
首先要分析下,这个俩view是用来干嘛的。按照自己的想法,一个页面上的操作,直接用一个view来搞定不就行了吗,为何要用两个呢?
我觉得这就是新手和老手的主要区别之一,喜欢在一个方法里面搞定一切,随着时间的推移,再逐渐重构,让代码变得灵活可扩展。但既然我们拿到一个成熟的代码,就应该吸取其中的精华。
我觉得这里面的精华就是,将数据的展示和对数据的操作进行分离,也就是现在代码里面TodoView和AppView。前者的作用是展示数据模型中的数据到界面,并对数据本身进行管理;后者是对整体的一个控制,如所有数据的显示(调用TodoView),添加一个任务、统计多少完成任务等。
有了上面的分析,让我们来一起看下代码:
.. code:: javascript
// 首先是创建一个全局的Todo的collection对象
var Todos = new TodoList;
// 先来看TodoView,作用是控制任务列表
var TodoView = Backbone.View.extend({
//下面这个标签的作用是,把template模板中获取到的html代码放到这标签中。
tagName: "li",
// 获取一个任务条目的模板
template: _.template($('#item-template').html()),
// 为每一个任务条目绑定事件
events: {
"click .check" : "toggleDone",
"dblclick label.todo-content" : "edit",
"click span.todo-destroy" : "clear",
"keypress .todo-input" : "updateOnEnter",
"blur .todo-input" : "close"
},
//在初始化设置了todoview和todo的以一对一引用,这里我们可以把todoview看作是todo在界面的映射。
initialize: function() {
_.bindAll(this, 'render', 'close', 'remove');
this.model.bind('change', this.render);
this.model.bind('destroy', this.remove); //这个remove是view的中的方法,用来清除页面中的dom
},
// 渲染todo中的数据到 item-template 中,然后返回对自己的引用this
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.input = this.$('.todo-input');
return this;
},
// 控制任务完成或者未完成
toggleDone: function() {
this.model.toggle();
},
// 修改任务条目的样式
edit: function() {
$(this.el).addClass("editing");
this.input.focus();
},
// 关闭编辑界面,并把修改内容同步到界面
close: function() {
this.model.save({content: this.input.val()}); //会触发change事件
$(this.el).removeClass("editing");
},
// 按下回车之后,关闭编辑界面
updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
},
// 移除对应条目,以及对应的数据对象
clear: function() {
this.model.clear();
}
});
//再来看AppView,功能是显示所有任务列表,显示整体的列表状态(如:完成多少,未完成多少)
//以及任务的添加。主要是整体上的一个控制
var AppView = Backbone.View.extend({
//绑定页面上主要的DOM节点
el: $("#todoapp"),
// 在底部显示的统计数据模板
statsTemplate: _.template($('#stats-template').html()),
// 绑定dom节点上的事件
events: {
"keypress #new-todo": "createOnEnter",
"keyup #new-todo": "showTooltip",
"click .todo-clear a": "clearCompleted",
"click .mark-all-done": "toggleAllComplete"
},
//在初始化过程中,绑定事件到Todos上,当任务列表改变时会触发对应的事件。最后把存在localStorage中的数据取出来。
initialize: function() {
//下面这个是underscore库中的方法,用来绑定方法到目前的这个对象中,是为了在以后运行环境中调用当前对象的时候能够找到对象中的这些方法。
_.bindAll(this, 'addOne', 'addAll', 'render', 'toggleAllComplete');
this.input = this.$("#new-todo");
this.allCheckbox = this.$(".mark-all-done")[0];
Todos.bind('add', this.addOne);
Todos.bind('reset', this.addAll);
Todos.bind('all', this.render);
Todos.fetch();
},
// 更改当前任务列表的状态
render: function() {
var done = Todos.done().length;
var remaining = Todos.remaining().length;
this.$('#todo-stats').html(this.statsTemplate({
total: Todos.length,
done: done,
remaining: remaining
}));
//根据剩余多少未完成确定标记全部完成的checkbox的显示
this.allCheckbox.checked = !remaining;
},
//添加一个任务到页面id为todo-list的div/ul中
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
},
// 把Todos中的所有数据渲染到页面,页面加载的时候用到
addAll: function() {
Todos.each(this.addOne);
},
//生成一个新Todo的所有属性的字典
newAttributes: function() {
return {
content: this.input.val(),
order: Todos.nextOrder(),
done: false
};
},
//创建一个任务的方法,使用backbone.collection的create方法。将数据保存到localStorage,这是一个html5的js库。需要浏览器支持html5才能用。
createOnEnter: function(e) {
if (e.keyCode != 13) return;
Todos.create(this.newAttributes()); //创建一个对象之后会在backbone中动态调用Todos的add方法,该方法已绑定addOne。
this.input.val('');
},
// 去掉所有已经完成的任务
clearCompleted: function() {
_.each(Todos.done(), function(todo){ todo.clear(); });
return false;
},
//用户输入新任务的时候提示,延时1秒钟
//处理逻辑是:首先获取隐藏的提示节点的引用,然后获取用户输入的值,
//先判断是否有设置显示的延时,如果有则删除,然后再次设置,因为这个事件是按键的keyup时发生的,所以该方法会被连续调用。
showTooltip: function(e) {
var tooltip = this.$(".ui-tooltip-top");
var val = this.input.val();
tooltip.fadeOut();
if (this.tooltipTimeout) clearTimeout(this.tooltipTimeout);
if (val == '' || val == this.input.attr('placeholder')) return;
var show = function(){ tooltip.show().fadeIn(); };
this.tooltipTimeout = _.delay(show, 1000);
},
//处理页面点击标记全部完成按钮
//处理逻辑:如果标记全部按钮已选,则所有都完成,如果未选,则所有的都未完成。
toggleAllComplete: function () {
var done = this.allCheckbox.checked;
Todos.each(function (todo) { todo.save({'done': done}); });
}
});
通过上面的代码,以及其中的注释,我们应该认识了其中的各个函数的作用。但是有一点没有说到的是template这个东西。
在前几篇的view介绍中我们已经认识过了简单的模板使用,以及变量参数的传递:
.. code:: html
<script type="text/template" id="search_template">
<label><%= search_label %></label>
<input type="text" id="search_input" />
<input type="button" id="search_button" value="Search" />
</script>
既然能定义变量,那么就能使用语法,如同django模板,那来看下带有语法的模板,也是上面的两个view用到的模板,我想这个是很好理解的。
<script type="text/template" id="item-template">
<div class="todo <%= done ? 'done' : '' %>">
<div class="display">
<input class="check" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
<label class="todo-content"><%= content %></label>
<span class="todo-destroy"></span>
</div>
<div class="edit">
<input class="todo-input" type="text" value="<%= content %>" />
</div>
</div>
</script>
<script type="text/template" id="stats-template">
<% if (total) { %>
<span class="todo-count">
<span class="number"><%= remaining %></span>
<span class="word"><%= remaining == 1 ? 'item' : 'items' %></span> left.
</span>
<% } %>
<% if (done) { %>
<span class="todo-clear">
<a href="#">
Clear <span class="number-done"><%= done %></span>
completed <span class="word-done"><%= done == 1 ? 'item' : 'items' %></span>
</a>
</span>
<% } %>
</script>
简单的语法,上面的那个对应TodoView。
这一篇文章就先到此为止,文章中我们了解到在todos这个实例中,view的使用,以及具体的TodoView和AppView中各个函数的作用,这意味着所有的肉和菜都已经放到你碗里了,下面就是如何吃下去的问题了。
下一篇我们一起来学习todos的整个流程。
- from the5fire.com微信公众号:Python程序员杂谈