利用BackboneJS更好组织jQuery应用的架构(二)
在HTML页面中,添加一个ID为main的div标签:
<divid="main"></div>
回到JavaScript代码,在这个视图渲染之后,立即用jQuery选择这个元素。然后获得这个视图的$el并将其填充到div。
varaddForm=newAddForm();
addForm.render();
$("#main").html(addForm.$el);
这个代码替换了showAddForm函数的内容,是通过jQuery代码在应用启动的时候实现的。
functionshowAddForm(){
varaddForm=newAddForm();
addForm.render();
$("#main").html(addForm.$el);
}
做完这些,你将看到如图3所示的表单。
Clickforalargerversionofthisimage.
图3:Addform已由Backbone.View实例渲染。通过这个步骤,Backbone.js的第一部分被集成到jQuery应用之中了。不过到目前为止,Backbone.js的唯一功能就是渲染并显示视图。下一步我们将取出老的#save点击处理程序,将其分为两个部分:视图中的事件处理部分,以及Backbone.Model中的保存联系人的代码部分。
DOM事件与Backbone.View
为了将由View呈现出来的表单里的数据传送到服务器中,你需要以下三步:
处理save按钮的点击。
从表单的input中获取数据。通常来说,使用表单input向服务器传递信息,是非常简单的方法。
发送数据到服务器上,并处理相应的响应请求。
处理按钮点击事件
第一步,先要从表单的input中获取数据。对于这些数据,也是将依靠Backbone.View声明事件进行处理。事件配置是一个通过jQuery事件设置的on函数来创建所需要的事件处理工作的集合。这就意味着你能使用任何有效的jQuery事件和有效的jQuery选择器去定义你的View事件。在此情况下,你只是需要去注意Save按钮即可。这个按钮的单击事件被定义在View中,就像:
varAddForm=Backbone.View.extend({
//configuretheDOMevents
events:{
"click#save":"saveClicked"
},
//handlethe"click"ofthe"#save"button
saveClicked:function(e){
}
});
这里有几个问题被标注了。
事件的配置是一种对对象字面量的赋值,其事件声明在左,方法名称在右。
事件声明包括要处理的事件的名称,以及可选的跟着附带的jQuery选择器。当为事件处理器设置一个选择器的时候,明白该处理器在视图实体的DOM元素的范围之内是很重要的。:这就是视图的$el。你不能选择这个视图实体之外的元素,就算你设置了一个CSSID也不行。
事件配置的右手边包含了用来在事件被触发时使用的回调函数的名字。这个名字必须是视图实体或者原型中可用的方法名。由于这个视图在事件处理器中定义了一个saveClicked方法,一个saveClicked方法就会被添加到视图的定义中。
saveClicked方法会处理实际的事件。它从http://api.jquery.com/category/events/接收你将处理器所绑定到的事件。这个函数包含处理点击的代码以及据此以产生回应。
获取用户输入到表单中的数据同运行一个jQuery选择器获取.val()一样容易。事实上,实际你所要做的只有一处不同。
从表单获取名称所代表数据的jQuery代码在早先已经展示过了:
$("#name").val()
在应用程序中使用Backbone.View所要做的工作同这一个一样简单,就只要使用一个CSSID(它在整个DOM中被假定是唯一的),偏离你预期而没有找到值的机会很小很小。但这暴露出了在其它常用场景中的一个问题。那就是input使用的是一个name属性,而不是id:
<inputname="name">
对于那个input使用的选择器大概是像下面这样:
$("input[name='name']").val()
如果在DOM中有超过一个元素使用了同一个name,它就会返回多个DOM项,并且很有可能给你错误的值。Backbone.View使用一种很小的选择器附加语法来解决了这个问题:
this.$("input[name='name']").val()
你注意到这其中的不同了么?就是在选择器的前面加上了this.。这一个小改变为Backbone.View做了一些非常重要的事情:它使用了同对事件配置的范围进行限制一样的方式,限制了视图$el的选择器的范围。
使用this.$而不是$运行一个jQuery选择器,就能确保你从视图或者界面上的其它地方获得一个结果(或者没有结果,如果没有被找到的话),而它们中的每一个都拥有一个匹配该选择器的input元素,这个视图实体只返回在它控制之下的那一个的数据。这里假设视图的$el只包含一个匹配该选择器的元素。如果超过了一个,所有的都会被返回。
通过提供在一个视图中将this.$(...)作为选择器的用法,构造一个包含来自表单的数据的对象字面量就变得简单起来.
saveClicked:function(e){
//stopthebrowserfromsubmittingtheform
e.preventDefault();
//storethedatainanobject
varformData={
name:this.$("#name").val(),
url:this.$("#url").val(),
email:this.$("#email").val(),
phone:this.$("#phone").val(),
notes:this.$("#notes").val(),
};
//…dosomethingwiththedata
}
这里并没有什么值得一提的代码。你只是阻止了浏览器自己来提交表单,然后将数据收集到一个对象字面量中。从这儿开始,你就需要使用这些数据来做一些事情,比如将它发送到服务器以存入某种数据库,还有就是在应用的联系人列表中展示新的联系人。
用Backbone.js的Model保存数据
在这个代码的原始jQuery版本中,数据是通过使用jQueryajax调用推送到服务器的(文档在http://api.jquery.com/jQuery.ajax/)。这个代码的Backbone.js版本很有效的做了同样的事情。但是不是直接调用$.ajax,而是隐藏在Backbone.Model中。
一个Backbone.Model代表着应用中的模型,或者浏览器中的实体。它是一个数据结构,至少包含这样几种行为,加载,修改,保存,以及删除数据,它们通过使用服务器的API实现。
Model与View的定义很相似。开始你需要扩展Backbone.Model并且将结果赋给一个变量。这就创建了一个新的对象,它可以在需要的时候实例化,而且每个实例包含自己的数据。Backbone.Model实例常常与服务端的模型或者数据库记录相关,即使并非总是如此。
定义一个Contact模型
下一步,你需要创建一个Contact模型给这个应用程序使用,传递一个对象名字给扩展方法:
varContact=Backbone.Model.extend({});
在Model声明内部,可以提供选项和其它配置。例如,给模型增加一个urlRoot属性并设置值为/contacts。
varContact=Backbone.Model.extend({
urlRoot:"/contacts"
});
Backbone.Model实现了一种名为活动记录(ActiveRecord)的模式(不要与Ruby中Rails的ActiveRecord或ActiveModel混淆了)。这个模式的观点是,一个实体或者模型应该知道它相对于数据存储的状态。对于Backbone.Model来说,数据存储就是服务器的API。urlRoot属性告诉模型,使用服务器API的哪个端点,这使得模型可以执行它自己的持久化操作。
保存新的Contact
回到add表单的saveClicked方法,创建一个Contact模型的新实例。然后调用模型上的.save方法,并将formData作为第一个参数传递给这个方法。save方法还具有一个对象字面量(译注:objectliteral类似字符串常量的对象定义)的第二个参数。这个参数可以包含任何save方法所需要的配置,其中包括了jQuery.ajax调用的回调函数。
saveClicked:function(e){
//…
//createanewcontactandsaveit
//usingtheformdata
varcontact=newContact();
contact.save(formData,{
//waitforaresponsefromtheserver
wait:true,
//whenthesaveissuccessful,
//updatethelistofcontacts
//andcleartheform
success:function(contact){
updateList(contact);
clearForm();
}
});
}
在urlRoot设置为"/contacts"时,一个新的模型就将在这个端点保存到服务器的API。模型实例知道它是一个新的模型,因为模型或者数据中没有.id。它执行HTTP"POST"方法将数据回送到服务器,就像这个应用的原始的jQueryAJAX调用所做的那样。它还使用了AJAX调用和其它行为变化的参数选项。
wait选项告诉Backbone.js在继续之前先等待服务器响应。默认条件下,这个参数值是false。这就是说对.save的调用将会立刻激发success回调,并且将完全忽略服务器的响应。
success回调与前面jQueryAJAX的success回调很相似。主要的一点不同是,第一个参数不是一个从服务器返回的原始对象字面量(译注:rawobjectliteral即类似字符串常量的对象定义)。代替它的是模型的实例。
在这里,updateList函数需要升级与Backbone.Model共事,而不是与一个原始对象字面量。为了将这里简化,可以引入Backbone.Collection来管理Contact对象列表。
和Backbone.Collection有关的东西一览
Backbone.Collection是像你在使用其它语言和框架时所期望的那样一个集合。你可以添加和删除成员,对它们进行排序,从里面找到一个符合查询标准的特定子集,迭代输出所有的成员,当然还有更多的操作可以做。在Backbone中,Collection是Model实体的一个特定的集合,并且这些实体都是同一个类型的。那些实体直接默认的就只是Backbone.Model实体。但是也可以简单的让Collection工作于一个特殊类型的模型。
对于Model的加载和运作,Collection也为此通过服务器API提供了一些方便的特性。例如你可以调用一个方法加载一整个集合。也有保存一整个集合的简单方法,尽管在写下这篇文章的时候这还没有内置到Collection中。
定义一个通讯的集合
当你有一个通讯的地址,而且你现在需要管理这一系列通讯地址,你可以创建一个ContactCollection并按如下方式指定他使用的通信模块:
varContactCollection=Backbone.Collection.extend({
urlRoot:"/contacts",
model:Contact
});
现在你可以为这个用户的集合创建实例,并且添加或者删除或者对这个实例内融做其他的一些操作。
注意这里创建这个类型的相同的模式,就像View和Model:通过在基本类型上调用.extend方法,并传递一个文本对象用于配置这个类型。
指定model属性:配置的属性是为了告诉这个集合的实例将要和什么模块一起工作。任何时候你添加或者从服务器上获取的一个模块,如果这个模块不是这个类型,他将会被转化到这个类型。
展现联系人列表
在你用刚刚添加的联系人更新联系人列表之前,最好让列表在浏览器中展示一下。这样一来,当你添加了一个新的联系人时,你就马上可以看到了。为此,你将需要一个ContractListView。它有同AddForm类似的渲染方法,但这个渲染将会是在视图引用的集合中的一次循环的每一个模型中。
<scripttype="text/template"id="contact-list-template">
<li>
<ahref="mailto:<%=email%>"><%=name%></a><br>
<ahref="<%=url%>"><%=url%></a><br>
Phone:<%=phone%><br>
Notes:<br>
<%=notes%>
</li>
</script>
列表2展示了完整的ContractListView。注意这个视图并没有扩展自一个特殊的或者是与众不同的视图类型。同AddForm一样,它扩展自Backbone.View。它是Backbone.js应用程序中所有视图都要继承的基础视图类型,不论它们的目的和用途。
其次,在tagName这个视图中有一个新的选项。当Backbone.View被实例化时,它创建了一个封装了的DOM元素,并且立即把它放置到视图的$el中。这就是即使是在视图被渲染之前,视图里面也总能有一个DOM元素能起作用的原因。它默认创建的DOM元素是一个“<div>”。而这可以通过制定视图上的tagName属性,改成任何你想要使用的标签。这本文的场景中,视图正在渲染一个联系人列表,因而就需要使用一个ul列表来渲染。
代码的主要部分同AddForm的渲染是一样的。其中一个主要的不同之处是模板的渲染已经被放置到了视图的集合上一个迭代循环上面。模板会在方法的最开始执行一次编译,而不是在每一次迭代时都重新对模板进行一次编译。然后针对数据中的每一条联系人预编译好的模板都会执行到。
在每一个联系人都被渲染以后,结果会推送到一个Array中。一旦每一条数据的渲染完成,结果会被放到视图实体的$el中。jQuery能足够聪明的识别这个数组,并恰当的将它们追加到html中。
使用ContractListView
ContractListView的使用同AddForm一样。你创建了一个实体,传给它任何需要的选项,然后就渲染它。一旦它被渲染好了,你就可以将视图的$el放置到DOM中,得以使其可以被人看见。
由于ContractViewList应该被用来显示联系人,并且由于有了一个集合附加到它的上面,你将会想要将一个ContractListView传给视图实体。
为了展示的目的,这里预先用一些示例数据填充ContractListView实体,所以硬编码了构造器中的集合,如清单3所示。
注意当你创建ContractListView实体时,你传入了一个数组字面量,数组中有三个单独的对象。每一个对象都包含一条联系人数据,带有id,名字等等。数组中的这三个对象被解析和处理成了联系人实体,从而创建了拥有三个联系人的ContractListView实体。
使用构造器参数中的集合属性,联系人集合被传到视图中。像Model,View将会识别到集合的存在,并且恰当的将其附加到视图中以供使用。集合被用来视图的渲染方法中来产生所需的列表。
当ContractListView视图被渲染,它会产生一个带有三个<li>项的<ul>——对应列表中的每一个联系人。
渲染完以后,视图的$el被塞进DOM中的"#contract-list",像用户展示这个列表。
响应新的联系人
对于ContractListView你需要做的最后一件事情是处理向集合中加入的新的联系人。这可以通过视图中来自集合自身的事件绑定做到。而此处没有简单的事件配置对象。这个事件处理器必须在视图的实例化方法中添加——这个方法在对象被构建之后就会被执行——如清单4所示。
.listenTo方法是绑定来自Backbone.js对象的事件的两种方式其中之一。另外一个选择是.on方法,你可能对它更加的熟悉。差别在于事件触发对象和事件订阅对象之间的关系的处理方式上。随着View快速活动的倾向——即将它们创建、展示并且销毁——同模型相比,一般最好还是在View上面使用.listenTo方法,View自身会在视图关闭的时候清理掉事件处理器。
在实例化方法中,来自集合的“添加”方法会得到处理,将视图的“contractAdded”方法设置为一个回调。不论何时有新的模型被添加到传入视图的集合中,“添加”事件就会被触发。如果在这个事件发生时正好有一个ContractListView实体在旁边,“contractAdded”方法就会处理这个事件,并重新渲染列表。
随着ContractCollection和ContractView就位,你就可以回头来处理将联系人添加到应用程序中的流程,并且将列表更新。
添加时更新联系人列表
回头来看看来自Addform调用来保存新联系人的成功回调。在这个方法中,有一次对updateList方法的调用,这个方法将来自表单的数据手动添加到DOM中。而随着ContractCollection和ContractView的就位,这段代码就不必需要了。所有需要发生的就是联系人会被添加到集合中,而不是手动的将新的模型传递进去。
让次发生的最简单办法就是将updateList方法整个删除掉,并且稍微改变一下AddForm的工作方式。ContractCollection的.create方法可以派得上用场,而不是直接去new一个联系人出来。这个方法使用同模型的保存方法一样基础的方式。而除了创建和保存模型之外,它也直接将模型添加到集合中,发起“添加”事件。
修改AddForm的savaClicked方法以使它调用集合的create方法。
//AddForm
saveClicked:function(e){
//…
//createanewcontactandsaveit
//usingtheformdata
this.collection.create(formData,{
wait:true,
success:function(contact){
clearForm();
}
});
}
成功的回调不会再召唤出updateList方法。由于新的联系人被立即添加到集合中,ContractListView将会接收到”添加“事件并且将重新渲染自身,以展示新的联系人。
varcontacts=newContactCollection(/*...*/);
//...
varaddForm=newAddForm({
collection:contacts
});
//...
varcontactList=newContactListView({
collection:contacts
});
现在你就可以通过添加表单增加新的联系人,而它们将自动的在联系人列表中展示出来了。
除了重组jQuery代码之外
现在你可以添加新的联系人而它们会自动在列表中显示了。但更加重要的是,你已经有了一个具有良好结构的代码基础。应用程序中各司其责的部分已经被拉开,并且经过精心的编排后共同创造出所需的功能。而最重要的是,你加入这些东西以后并没有浪费掉任何在编写的jQuery代码上的投入。这并不是一个非此即彼的选择。你可以挑挑拣拣,这在Backbone中很容易实现,并依此推动应用程序向前发展。
Backbone也还可以为你做更多的事情。随着附加需求的引入,应用程序可能会成长到非常的庞大。如果你需要转出内容,例如,你可能想利用Backbone.Router以让人们直接连接到一个指定的应用状态或者加上书签。立足在这一点上,你正在放眼于SPA领域,并且打开了选择和机遇的一个新世界。
然而在额外介绍SPA时,Backbone.js开始显现出它同更加完整的框架相比存在的一些相关的缺陷。例如,Backbone.js并没有提供盒子之外的创建具有分层结构数据源的方法。这并不是很难做到,但是它需要你自己写代码或者使用额外的插件来处理它。另外,Backbone.js趋向于需要太多的在应用程序之间和对象之间复制和粘贴的几乎没有做任何改变的成段代码。Backbone.View的渲染函数就是这样一个典型的例子。如果你不加注意的话,你就会以一遍又一遍的编写重复的渲染方法收场。
样板的问题和功能的确实是一把双刃剑。这让你可以自由的在没有任何旧有方式干扰的情况下,对Backbone.js的使用加以发挥。但它并不要求你编写比一些其它的框架更多的代码。幸运的是,Backbone.js的社区已经非常的庞大,并且几乎涉及Backbone.js每一个可能的问题和场景。如果还没有成百的话,那就有成打的Backbone.js插件和附加功能,提供Backbone.js核心并没有的特性和功能,来帮助你减少需要编写的代码的数量。
其他资源
学习Backbone.js及其功能特性有大量的诱人资源可用,全部这些从书本到博客和视频都有。这里有一些我喜欢并且要推荐给大家的资源。
书籍
Backbone.js基础
http://shop.oreilly.com/product/0636920025344.do
AddyOsmani通过采用合作的方式,借助于Github和社区贡献写了一本讨论Backbone基础的书籍。它是对Backbone.js和其围绕它的开发生态环境做的一个全面的介绍。它包含你需要知道的所有核心的知识,入门和运行,以及像我在本文中所展示的在简单的应用程序之上演进。如果你对Addy在Javascript世界中所做的工作,那你就需要去了解一下。
Backbone.js教程
https://leanpub.com/backbonetutorials
ThomasDavis在他的Backbone.js教程博客上收集了一系列小知识。他吸取了社区的建议和信息,并把这些信息汇总到一起制作了一本免费的电子书。这本电子书采用了简易的blog文章大小的章节,覆盖了Backbone.js发展的方方面面。
创建Backbone.js插件
http://BackbonePlugins.com
这本电子书描述了如何——更重要的是为何——创建对于Backbone.js合适的摘要和项目。这本书会指导你创建Backbone.js插件的方法和原因。它提供了学习的第一手资料、最好的练习以及创建复杂项目和框架的细节部分。这一切都是建立在真实的Backbone.js实践当中,这些实践有成功有失败,他们来源于许多工程项目,以及在这些工程项目上建立的插件和框架。
原文:http://www.linuxeden.com/html/news/20131112/145443_2.html