什么是backbone
backbone不是脊椎骨,而是帮助开发重量级的javascript应用的框架。
主要提供了3个东西:1、models(模型) 2、collections(集合) 3、views(视图)
backbone.js文件本身很小,压缩后只有5.3KB,作为一个框架级别的核心JS文件,这个数字很可怕。
除此之外,这个JS还必须依赖于另一个JS文件:underscore.js(包含许多工具方法,集合操作,js模板等等)。
简介
用Backbone.Model表示应用中所有数据,models中的数据可以创建、校验、销毁和保存到服务端。
当models中值被改变时自动触发一个"change"事件、所有用于展示models数据的views都会侦听到这个事件,然后进行重新渲染。
Backbone.Collection和我们平时接触的JAVA集合类相似,具有增加元素,删除元素,获取长度,排序,比较等一系列工具方法,说白了就是一个保存models的集合类。
Backbone.View中可以绑定dom el和客户端事件。页面中的html就是通过views的render方法渲染出来的,当新建一个view的时候通过要传进一个model作为数据,例如:
- var view = new EmployeeView({model:employee});
var view = new EmployeeView({model:employee});
也就是说model就是以这种方式和view进行关联的。
特点
创建models或者views的语法:extends,相当于类继承
models的创建,销毁,校验等一系列改变都会触发相应的事件
示例
需求:用backbone.js和jquery实现一个可编辑的员工信息表格。
功能:1、录入员工信息。2、删除员工信息。3、双击表格可对员工信息进行修改。4、能对员工信息进行有效性校验。5、能对员工信息进行持久化。
设计:
用Employee类(继承自Backbone.Model)表示员工信息,包含ID、姓名、性别、年龄和职位字段。
- window.Employee = Backbone.Model.extend({
-
- validate:function(attrs){
- for(var key in attrs){
- if(attrs[key] == ''){
- return key + "不能为空";
- }
- if(key == 'age' && isNaN(attrs.age)){
- return "年龄必须是数字";
- }
- }
- }
- });
window.Employee = Backbone.Model.extend({
// 模型值校验
validate:function(attrs){
for(var key in attrs){
if(attrs[key] == ''){
return key + "不能为空";
}
if(key == 'age' && isNaN(attrs.age)){
return "年龄必须是数字";
}
}
}
});
声明Employee类之后就可以新增一个Employee的示例对象了:
- var employee = new Employee();
var employee = new Employee();
Employee类中不必声明ID、姓名等业务字段。当需要给employee设置这些信息时候,只需要调用
- employee.set({'id':1,'name':'Jason'});
employee.set({'id':1,'name':'Jason'});
当然,如果需要对employee的信息进行校验,需要给Employee类配置一个validate方法,这个方法的参数attrs就是set进去的json数据。这样,当employee里面的数据每次发生改变的时候都会先调用这个validate方法。
Model类定义好之后就可以开始定义集合类了,在集合类里面可以对里面的每个Model进行增加,删除等一系列操作,还可以调用fetch方法从server端获取集合的初始值。
- window.EmployeeList = Backbone.Collection.extend({
- model : Employee,
-
- localStorage: new Store("employees"),
-
- });
- window.Employees = new EmployeeList();
window.EmployeeList = Backbone.Collection.extend({
model : Employee,
// 持久化到本地数据库
localStorage: new Store("employees"),
});
window.Employees = new EmployeeList();
设置 localStorage属性后Employees里面的数据自动会同步保存到本地数据库里面,每当调用Employees.fetch()后又会从localStorage里面恢复数据。
View类主要负责一切和界面相关的工作,比如绑定html模板,绑定界面元素的事件,初始的渲染,模型值改变后的重新渲染和界面元素的销毁等:
- window.EmployeeView = Backbone.View.extend({
- tagName : 'tr',
- template : _.template($('#item-template').html()),
- events : {
- "dblclick td" : "edit",
- "blur input,select" : "close",
- "click .del" : "clear",
- },
- initialize : function(){
-
- this.model.bind('change', this.render, this);
-
- this.model.bind('destroy', this.remove, this);
- },
- setText : function(){
- var model = this.model;
- this.input = $(this.el).find('input,select');
- this.input.each(function(){
- var input = $(this);
- input.val(model.get(input.attr("name")));
- });
- },
- close: function(e) {
- var input = $(e.currentTarget);
- var obj = {};
- obj[input.attr('name')] = input.val();
- this.model.save(obj);
- $(e.currentTarget).parent().parent().removeClass("editing");
- },
- edit : function(e){
-
- $(e.currentTarget).addClass('editing').find('input,select').focus();
- },
- render: function() {
- $(this.el).html(this.template(this.model.toJSON()));
-
- this.setText();
- return this;
- },
- remove: function() {
- $(this.el).remove();
- },
- clear: function() {
- this.model.destroy();
- }
- });
window.EmployeeView = Backbone.View.extend({
tagName : 'tr',
template : _.template($('#item-template').html()),
events : {
"dblclick td" : "edit",
"blur input,select" : "close",
"click .del" : "clear",
},
initialize : function(){
// 每次更新模型后重新渲染
this.model.bind('change', this.render, this);
// 每次删除模型之后自动移除UI
this.model.bind('destroy', this.remove, this);
},
setText : function(){
var model = this.model;
this.input = $(this.el).find('input,select');
this.input.each(function(){
var input = $(this);
input.val(model.get(input.attr("name")));
});
},
close: function(e) {
var input = $(e.currentTarget);
var obj = {};
obj[input.attr('name')] = input.val();
this.model.save(obj);
$(e.currentTarget).parent().parent().removeClass("editing");
},
edit : function(e){
// 给td加上editing样式
$(e.currentTarget).addClass('editing').find('input,select').focus();
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
// 把每个单元格的值赋予隐藏的输入框
this.setText();
return this;
},
remove: function() {
$(this.el).remove();
},
clear: function() {
this.model.destroy();
}
});
这个类里面的代码比较多,但主要和界面的渲染有关。一个EmployeeView对象对应table里面的一个tr元素。每次new一个EmployeeView对象的时候都会先调用initialize方法,这个方法里面绑定的事件确保了tr元素对应的model值每次发生改变或者被删除时都会同步到界面。也就是说当每次操作界面对数据进行修改后都是先把当前的变更保存到view绑定的model对象里面,然后model里面的事件机制会自动触发一个"change"事件对界面进行修改。
template中使用的方法_.template($('#item-template').html())是前面提到的underscore.js中提供一个工具方法,可以通过界面的HTML模板和一个JSON生成动态的HTML,说白了就是把JSON里面的值填充到HTML模板中对应的占位符里面去,牛X的是HTML模板里面支持一些常用的逻辑表达式如if,else,foreach等:
- <script type="text/template" id="item-template">
- <td><%= eid %></td>
- <td class="username">
- <div class="display"><%= username %></div>
- <div class="edit"><input class="username" name="username"></input></div>
- </td>
- <td class="sex">
- <div class="display"><%= sex=="1" ? "女":"男" %></div>
- <div class="edit">
- <select name="sex" class="sex" style="width:45px">
- <option value="0">男</option><option value="1">女</option>
- </select>
- </div>
- </td>
- <td class="age">
- <div class="display"><%= age %></div>
- <div class="edit">
- <input class="age" name="age"></input>
- </div>
- </td>
- <td class="position">
- <div class="display"><%= position %></div>
- <div class="edit">
- <input class="position" name="position"></input>
- </div>
- </td>
- <td>
- <a href="#" class="del">删除</a>
- </td>
- </script>
<script type="text/template" id="item-template">
<td><%= eid %></td>
<td class="username">
<div class="display"><%= username %></div>
<div class="edit"><input class="username" name="username"></input></div>
</td>
<td class="sex">
<div class="display"><%= sex=="1" ? "女":"男" %></div>
<div class="edit">
<select name="sex" class="sex" style="width:45px">
<option value="0">男</option><option value="1">女</option>
</select>
</div>
</td>
<td class="age">
<div class="display"><%= age %></div>
<div class="edit">
<input class="age" name="age"></input>
</div>
</td>
<td class="position">
<div class="display"><%= position %></div>
<div class="edit">
<input class="position" name="position"></input>
</div>
</td>
<td>
<a href="#" class="del">删除</a>
</td>
</script>
setText方法主要负责把model里面的数据设置到每个tr里面的隐藏输入域里面。
close方法被绑定到了input和select元素的blur事件中。当用户对单元格数据进行修改后都会把鼠标点击到界面其他地方然后输入框会自动隐藏并且把修改的数据显示在表格上面。close方法首先从当前被编辑的元素中拿到最新值,然后封装成一个对象,调用model的save方法后首先执行model的validate方法,如果校验通过则保存到本地存储并触发"change"事件。
最后还需要一个主界面View,这个View主要绑定了界面中的录入表单的“增加”按钮事件,Employees的相关事件以及页面初始化时从本地存储中恢复数据:
- window.AppView = Backbone.View.extend({
- el : $("#app"),
- events : {
- "click .#add-btn" : "createOnEnter"
- },
-
- initialize: function() {
- Employees.bind('add', this.addOne, this);
-
- Employees.bind('reset', this.addAll, this);
- Employees.fetch();
- },
- createOnEnter : function(e) {
- var employee = new Employee();
- var attr = {};
- $('#emp-form input,#emp-form select').each(function(){
- var input = $(this);
- attr[input.attr('name')] = input.val();
- });
- employee.bind('error',function(model,error){
- alert(error);
- });
-
- if(employee.set(attr)){
- Employees.create(employee);
- }
- },
- addOne : function(employee){
- employee.set({"eid":employee.get("eid")||Employees.length});
- employee.bind('error',function(model,error){
- alert(error);
- });
- var view = new EmployeeView({model:employee});
- $(".emp-table tbody").append(view.render().el);
- },
- addAll : function(){
- Employees.each(this.addOne);
- }
- });
window.AppView = Backbone.View.extend({
el : $("#app"),
events : {
"click .#add-btn" : "createOnEnter"
},
// 绑定collection的相关事件
initialize: function() {
Employees.bind('add', this.addOne, this);
// 调用fetch的时候触发reset
Employees.bind('reset', this.addAll, this);
Employees.fetch();
},
createOnEnter : function(e) {
var employee = new Employee();
var attr = {};
$('#emp-form input,#emp-form select').each(function(){
var input = $(this);
attr[input.attr('name')] = input.val();
});
employee.bind('error',function(model,error){
alert(error);
});
// set方法中会自动调用model的validate方法进行校验,如果不通过则返回false
if(employee.set(attr)){
Employees.create(employee);
}
},
addOne : function(employee){
employee.set({"eid":employee.get("eid")||Employees.length});
employee.bind('error',function(model,error){
alert(error);
});
var view = new EmployeeView({model:employee});
$(".emp-table tbody").append(view.render().el);
},
addAll : function(){
Employees.each(this.addOne);
}
});
initialize方法中绑定了Employees的add和reset事件,也就是说每当往Employees中添加一个model的时候都会调用AppView的addOne方法,这个方法主要绑定了model的error事件以及把EmployeeView生成的html插入到界面中的合适位置。
OK,万事俱备,只欠启动,整个应用的初始化方法就是AppView的initialize方法,因此只需要新建一个AppView就可以了:
- window.App = new AppView();
window.App = new AppView();
整个示例的JS代码很少,加上注释只有100行左右,感兴趣的可以下载看看。由于示例使用到了本地存储,所以不要用IE运行示例,你懂的。