利用 BackboneJS 更好的组织 jQuery 应用的架构

在构建高交互度的Web应用程序方面,JavaScript发挥出越来越重要的作用。如Backbone.js,Marionette.js,Ember.js 和Angular.js 这些库和框架,在流行度和功能方面快速成长。对于建立复杂和强大的浏览器应用这件事这些库和框架使之变得容易了。它们帮助我们迎来了一个单页应用时代SPAs使网页上的交互性和实用性达到了前所未有的程度

但为了达到互动的目的,并不是每个Web应用程序或网站上的每个页面都需要通过SPA框架来创建很多应用程序更适合于小规模的功能。对于这些页面,需要有一种高层次的交互性,同时又不需要客户端路由和其他的SPA功能属性。随着小页面在功能需求方面的减少,在创建页面时,将适当的框架和库的列表减少也顺理成章。例如,如果只是添加表单验证,你并不需要一个完整的MVC框架。要是不需要一个完整的SPA框架,而且有很好的理由的话,许多开发者编写代码时只会用到jQuery。而为了创建进一步的交互动作可以很容易地通过 plug-ins 和add-ons扩展jQuery提供的交互性

在建造大型的jQuery应用时,所面临的困难之一是保持代码有条理。它很容易迷失于世界末日的一棵圣诞树——一个深层嵌套的回调函数列表,这个列表看起来似乎掌控着每一个功能和交互。事实上,对于代码的组织或结构jQuery提供任何指导

好消息是,你不需要在jQuery和结构良好的代码之间做出选择,后者由SPA库和框架帮你书写。借助Backbone.js提供的结构良好创建模块,你可以把基于jQuery应用的简单性和交互性结合起来,以便创建同样具有很好代码组织结构的高互动性的网页

什么是Backbone.js

Backbone.js是一个帮助开发者在Web应用程序中向Javascript代码添加架构的组件库。来自BackboneJS.org的描述是这样的:

“Backbone.js通过提供带有键值绑定的模型和定制的事件,带有枚举功能的丰富的集合API,带有声明式事件处理的视图,而且将它所有这些东西通过一个RESTful JSON接口连接到你现有的API上面,来向Web应用程序提供架构

Backbone.js包含了提供不同组特定功能集合的构造块,包括:

  • Model:包含由键值对组成的属性的对象。Model封装了应用程序中的数据以及同数据相关的行为。
  • Collection:是一个模型的集合。Collection可以让相关模型的集合共同工作并且作为一个整体被管理。通用的集合特性诸如新增和移除,迭代,过滤等都有提供。
  • View:用于管理和操作DOM的通用方法集合与配置,并且显示来自Model和Collection的数据。View提供了使用jQuery DOM 事件的一种结构良好的方法,它们可以很容易的对HTML模板以及更多的其它东西进行渲染。
  • Event:它是对面向Javascript对象的观察者设计模式的一个小巧但强大的实现。事件将Backbone.js应用粘合在一起。Backbone.js中的每一个对象都包含事件系统,它让每一个Backbone.js对象都能触发事件并能得到处理。
  • Router和History:使用URL哈希片段或者HTML5的pushState技术来管理浏览器历史的对象集合。Router和History对象允许对一个应用程序的对象打上书签或者链接,重载页面的时候精确的回到上一次最后留下的样子。    

尽管Backbone.js有其它小的部分,但如果需要定制它们的时候,理解这些很重要。不过一个高层次观察对于大多数应用程序来说,这六个对象是最重要的

在  这些 Backbone.js 单个部分之外,从模式与架构的视角有一些关于如何称呼它的争论。  显然Backbone.js符合Model-View-* (MV*)模式家族。  毕竟它提供了一组对象,帮助你组织模型和视图。  但这并不完全符合MVC模式,或MVVM模式,或MVP,或别的什么。 称  Backbone.js为MV *家族的一个成员是最简单的。在此基础上    人们叫它什么并没有多大  差异   

建立联系人列表

许多应用类型需要为他人存储和查询联系人信息,比如一个CRM系统,一个时间追踪应用,账单或者其他的一些东西。尽管联系人应用可能还没有最令人激动的特性集合,但它提供了一个使用高级交互性的很好的例子,但不需要成为SPA。

一个简单的联系人列表仅仅需要少量的特性:

  • 联系人列表
  • 添加联系人
  • 编辑联系人
  • 删除联系人
  • 联系人数据
它也需要与这些特性相配的数据,包括:
  • 姓名
  • 图片URL
  • 邮件地址
  • 电话号码
  • 注释
在学习Backbone.js过程中使用的简单联系人列表仅仅需要处理一个单独的邮件地址和电话号码。在真正的应用中,你会想要允许多个电话号码和邮箱地址,但Backbone.js没有内建的嵌套或者分层的模型。这让处理多值或者嵌套对象和集合变得有些更具挑战性,尽管通过插件添加这项功能并不困难。一个简单的邮件地址和电话号码可以满足这个应用。

这个应用的布局相对简单,可以如图片1图片2的样子。只需要一个列表视图而且有一个表单来添加和编辑联系人。

利用 BackboneJS 更好的组织 jQuery 应用的架构_第1张图片

图片 1: 这是添加联系人表单

利用 BackboneJS 更好的组织 jQuery 应用的架构_第2张图片


图片2: 这是联系人列表例子。

当你点击添加按钮,表单将显现,并允许你添加一个新的联系人。点击保存将联系人信息发送到服务器。点击取消清除表单并隐藏表单。

在联系人列表中,点击编辑按钮可以显示同添加过程一样的样子,但是表单里将有存在的联系人信息。点击保存并发送编辑信息给服务器。保存还是用最新的信息来更新联系人列表。取消将抛弃任何改变并不会更新列表。

一个API服务器

Backbone.js为数据提供一个如REST的API,并能和服务器端很好的交互。这个在很多语言中都能很容易的安装,包括Ruby on Rails、NodeJS、ASP.NET MVC(包括WebAPI)、Python Django和各种PHP框架等等。最重要的方面不是服务器使用的技术架构。当然,其重点是如REST的API并能提供各种资源。

Backbone.js是在Rails的世界中开发的,所以它的API使用起来如一个Rails生成的API。Backbone.js工作使用的每一个资源应该对应一个在Web服务器上的API,这个API可以接受和返回JSON,而且符合Rails URL和HTTP的约定动作。

在某个联系人列表里,其API可能如下:

  • GET /contacts:返回所有联系人
  • POST /contacts: 从表单数据中创建一个新的联系人,其数据用JSON格式
  • GET /contacts/:id: 通过参数id得到指定联系人
  • PUT /contacts/:id: 通过得到表单传来的JSON格式数据来更新一个存在的联系人。通过id参数来标识更新的联系人
  • DELETE /contacts/:id: 通过指定id来删除联系人

这篇文章中构建联系人应用是一个技术而且对于服务器框架来说是未知的,直到服务器符合了基本API结构。至于你怎样才能实现服务器框架则完全取决于你。

新增表单

在为这个app准备的javascript中首先要做的,是处理新增按钮的点击。添加一个标准的jQuery DOMReady 回调函数,以确保DOM已经准备好并且可用了。然后,在DOMReady回调函数里面,添加一个找到#add元素的选择器还有对点击事件的侦听。在事件参数上调用preventDefault以确保按钮的点击不会传到服务器。

01 $(function(){
02  
03   $("#add").click(function(e){
04     e.preventDefault();
05     showAddForm();
06   });
07  
08   // other code will go here ...
09  
10 });

在事件的回调函数里面制造了一个对showAddForm的调用,以展示添加表单。这个函数找到#add-edit-form元素,并且使用动画来展示它。

1 function showAddForm(){
2   var form = $("#add-edit-form");
3   form.slideDown();
4 }
接下来,为#save按钮的点击事件添加一个事件处理器。这个按钮存在于#add-edit-form里面,但它也可以通过ID被选择器找到。在点击处理器中,从表单中所有的输入元素中抓取数据并且将其发送到服务器以创建一个新的contact。当这些都做好以后,就用新的contact信息更新contact的列表,就像列表1所展示的那样。 

当向服务的传送成功时,它会调用updateList和clearForm函数,它们会做你所希望做的事情( 简洁起见,这些断码从略)。

不需要完成这个简单应用的整个功能,你就能看明白现在代码能够很快的变成一揽子可用的功能挂在上面。这种结构松散的代码被相当典型的用在使用jQuery的小型页面上。几乎每一个Javascript和jQuery开发者都写过类似的代码,而这一风格由于其能让一些小型的任务得以完成,因此会持续流行下去。

编写结构良好的jQuery代码是可能的,当然,即使你使用了提供那种架构的框架或者库也可以。  那会变得像是你在重复制造轮子,所以你应该专注于如何让业务运作起来。不要在构建基础架构上花时间,就使用模型,集合,视图和其他Backbone.js能够提供的类型,将你的应用程序迁移到这样一个更好的架构上,然后一步一个脚印。

从jQuery到Backbone.js:迁移

在这一点上直接跳转到Backbone.js上是很有诱惑力的,但那可能会给大多数开发者帮倒忙。从头开始是很容易的。真正的挑战在于处理现有的代码并将其移植到其它的东西上。

迁移到Backbone.js的方式要做两件事:

  • 表明不必用一种全都要或者全都不要的重写来将Backbone.js加入到现有的项目中。
  • 表明使用jQuery自身与使用Backbone.js来构造jQuery这两种方式的相同和不同之处。

使用原生jQuery代码构建的Add表单可以一步一个脚印按部就班的移植到Backone.js。而在那发生之前,你需要在你的项目中配置Backbone.js。 

安装Backbone

Backbone.js技术仅有一个依赖包:Underscore.js。Underscore.js如蝙蝠侠身上的腰带:一个不可缺少的工具集合,实用工具,还有一些玩具,这些都可以让JavaScript更容易的工作。同时它也有其它语言的一些特性,有些特性是浏览器中未来JavaScript版本才有的功能。Underscore.js和Backbone.js类似,都是由Jeremy Ashkenas创建,并由GitHub中的DocumentCloud组织维护。

如果你想要用Backbone.为了实现组织操作DOM和交互的代码,你也需要一个DOM操作库;jQuery是自然(而且最流行)让人想到的一个不错的选择。

最近,Backbone.js支持了JavaScript文档格式JSON。所有的浏览器都很自然的支持JSON格式。如果你需要支持老的或是指定的不支持JSON的浏览器,你需要从Douglas Crockford 引用JSON2.js库,这是JSON标准的创建者。

将所有这些下载的文件放入你工程的/js目录下。在HTML中脚本引用如下:

<script src="/js/json2.js"></script>
<script src="/js/jquery.js"></script>
<script src="/js/underscore.js"></script>
<script src="/js/backbone.js"></script>

注意顺序也很重要。Backbone.js必须在jQuery和Underscore.js之后。如果你这部分不正确,Backbone.js不会正常工作。

一旦在你的工程中拥有Backbone.js需要的文件以及在你的HTML中得到引用,你就可以开始你的奇妙之旅。

Backbone.View 与 Add Form

Backbone.View是将Backbone.js引入现有应用的最简单的方法。作为它的核心,Backbone.View只不过是用于组织jQuery 代码的一些简单的约定。当然它允许的不仅仅是这些,但从组织jQuery 代码开始,进行下去会很容易。

几乎Backbone.js中的任何东西,都需要调用基本类型的扩展,而这个基本类型就是你想要处理的类型。如果你熟悉Java,这个方法名听起来会很熟悉,如果你熟悉C#,这就相当于":"与一个基类。Backbone.js中的扩展方法就是继承的实现。

var AddForm = Backbone.View.extend({
  // add configuration, methods and behavior here
});

通过从一个基本的backbone.js类型扩展,你可以创建你自己的专有类型,用它做你的应用需要做的事情。在这个案例中,你创建的是一个名为AddForm的新视图类型。它表现并操作#addformDOM元素这与之前原生jQuery作的是相同的表单元素

在这个视图 和 }定义之内,你可以添加你的视图的具体配置,方法,和行为。有许多配置选项和方法,出乎你的利益,Backbone.View可以识别并使用它们。不过大多数你所写的代码将是自己设计的

渲染 #addform 输入字段

添加到Backbone.View的最常见的函数是render函数。基础的Backbone.View内置有这个方法,但它只是返回了视图的实例,并没有做任何别的事情。它实际上是一种无操作方法。因为这个核心方法的存在,任何应用或框架都可以在View实例中,调用render方法。

要想给你的AddForm添加一个render方法,只需声明这个方法,再加上一个 ":" ,将方法名字与函数相隔离。这是JavaScript的对象的语法它也是用来定义backbone.js中类型的语法

var AddForm = Backbone.View.extend({

  render: function(){
    var rawTemplate = $("#add-form-template").html();
    var compiledTemplate = _.template(rawTemplate);
    var renderedTemplate = compiledTemplate();
    this.$el.html(renderedTemplate);
    return this;
  }

});

这个视图仅仅只有五行代码,但是每一行都很精彩。

第一行将一个DOM元素的HTML内容加载到一个变量rawTemplate这是一个HTML模板,与服务器端HTML模板意义相同。它是一个HTML标签的集合,带或不带数据和行为的特殊标记,它可用于生成原始HTML。在之后的某个时刻,你会把这个模板添加到HTML网页

第二行将模板编译进一个JavaScript函数。在这个案例中,Underscore.js被用作模板引擎,因为backbone.js依赖于Underscore.js,所以它在你的应用程序中已经是可用的。调用_template方法,并传入原始HTML模板,这样就将HTML编译进一个函数。这个函数之后就可以从这个模板生成最终的HTML输出

第三行运行编译的模板,并从模板生成HTML输出。在本案例中,该方法被调用时没有带参数。可是如果模板中需要数据,就可以传入一个JavaScript对象。这个对象属性的名字与值被用来填塞模板,你会在后面看到这一点。

第四行取到渲染后的HTML,并通过视图的$el属性将其放进视图。 $el属性是一个jQuery选择对象,代表这个视图实例所管理的DOM元素。每一个视图实例都默认有一个$el属性,它是一个<div>元素(虽然这可以很容易的改变)。当处理视图实例的时候,所有的DOM控制都是通过视图的 $el 实现的——包括读取数据,写数据到DOM,更新CSS类,动画,以及其它用jQuery可以做的工作。视图对特定的DOM元素及其子元素,是一个有效的行为封装。

这个方法的最后一行返回视图实例。这使得你在渲染视图以后,将方法与属性访问链接到一行之中。

将要渲染的模板

AddFormrender方法中,用jQuery选择器引用了一个HTML模板。然后#add-form-template 的DOM元素内容被取出来送给Underscore.jstemplate方法。为了让这一切工作,你需要给DOM元素赋以正确的ID和内容。不过你无需让这个DOM元素显示在页面上。你只需要让它作为一个模板以供渲染。这其中有几个方法,最简单的方法可能不太显眼:用一个<script>标签。

给你的HTML页面添加一个<script>标签,将它的ID设为add-form-template。通常情况下,在浏览器看到一个script标签的时候,它会试着将这个标签解释为JavaScript脚本。这不会是你希望的,因为你创建的模板并不包含可执行的脚本。替代的,它包含的是一个HTML模板。为了阻止浏览器执行脚本的尝试,给这个标签增加一个type属性,将它设置为javascript以外的类型,或者任何浏览器可以识别的类型。最常用的typetext/html-template或者类似的一些类型。

<script id="add-form-template" type="text/html-template">
</script>
只要将type设置成浏览器不能识别的类型,实际设置成什么并不重要。不过,最佳实践方法是将type设置成某种类型,使得另一个开发人员可以识别出它是一个模板。
 

使用<script> 标签是由于两个原因:
  •  HTML 标准还尚未具有一个 <template>标签
  •  HTML 模板是在视图中隐藏的,但它仍然需要可被获取使用

给template使用 script 标签的目的在于第二个原因。你需要HTML模板可以被JavaScript代码访问到,但是你不又希望未渲染的模板显示在浏览器的可视区域,让用户能看到它。用一个script标签再加上一个自定义的属性将能满足这两个需求。Script标签永远不会显示给用户,但是赋之以一个ID却可以使他能被jQuery所选择。提供一个type的额外需求,是将script将本用作它用的副作用,但这是一个可以接收(而且并不显著)的代价。

给模板填上标准的HTML标记,这样就可以创建增加一个联系人所需要的输入表单。

<script id="add-form-template" type="text/html">
  <label>Name: </label>
  <input type="text" id="name"><br>
  <label>Photo URL: </label>
  <input type="text" id="url"><br>
  <label>Email: </label>
  <input type="text" id="email"><br>
  <label>Phone: </label>
  <input type="text" id="phone"><br>
  <label>Notes: </label>
  <input type="text" id="notes"><br>
  <button id="save">Save</button>
</script>

只要模板和AddForm视图同时具备,就可以渲染表单,并将它放到DOM中,这样用户就可以增添图像了。

查看视图

有一个视图定义是很棒的,但是或许它没啥用,除非它完成了实例化,渲染,以及被放到DOM中让用户可以看到内容并与之交互。

就像其它的JavaScript对象,Java, C#,以及其它的语言,Backbone.js对象由new关键字实例化。只要将新对象实例赋给一个命名变量,你就可以根据需要调用视图上的方法,就像这样。

var addForm = new AddForm();

手上一旦有了一个view实例,你就可以调用它的render方法,将HTML模板渲染到视图自身之中。

addForm.render();

如果回头看看render方法最后一行代码,你会想起来这个方法并没有返回HTML。而且,它返回的是视图本身。渲染的HTML被放在视图的$el中,也就是视图管理的DOM元素。这个属性可以在view实例上直接获得,而且它也是你用来将视图的HTML加到DOM的工具。

在 HTML页面中,添加一个 ID 为 main的 div标签:

<div id="main"></div>

回到 JavaScript 代码,在这个视图渲染之后,立即用jQuery选择这个元素。然后获得这个视图的 $el 并将其填充到 div。

var addForm = new AddForm();
addForm.render();

$("#main").html(addForm.$el);

这个代码替换了showAddForm 函数的内容,是通过jQuery 代码在应用启动的时候实现的。

function showAddForm(){
  var addForm = new AddForm();
  addForm.render();

  $("#main").html(addForm.$el);
}

做完这些,你将看到如 图3 所示的表单。

利用 BackboneJS 更好的组织 jQuery 应用的架构_第3张图片 
图 3:  Add form 已由 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 中,就像:

var AddForm = Backbone.View.extend({

  // configure the DOM events
  events: {
    "click #save": "saveClicked"
  },

  // handle the "click" of the "#save" button
  saveClicked: function(e){
  }

});

这里有几个问题被标注了。

事件的配置是一种对对象字面量的赋值,其事件声明在左,方法名称在右。

事件声明包括要处理的事件的名称,以及可选的跟着附带的jQuery选择器。当为事件处理器设置一个选择器的时候,明白该处理器在视图实体的DOM元素的范围之内是很重要的。:这就是视图的$el。你不能选择这个视图实体之外的元素,就算你设置了一个CSS ID也不行。

事件配置的右手边包含了用来在事件被触发时使用的回调函数的名字。这个名字必须是视图实体或者原型中可用的方法名。由于这个视图在事件处理器中定义了一个saveClicked方法,   一个saveClicked方法就会被添加到视图的定义中。 

saveClicked方法会处理实际的事件。它从http://api.jquery.com/category/events/接收你将处理器所绑定到的事件。这个函数包含处理点击的代码以及据此以产生回应。

获取用户输入到表单中的数据同运行一个jQuery选择器获取.val()一样容易。事实上,实际你所要做的只有一处不同。

从表单获取名称所代表数据的jQuery代码在早先已经展示过了:

1 $("#name").val()
 在应用程序中使用Backbone.View所要做的工作同这一个一样简单,就只要使用一个CSS ID(它在整个DOM中被假定是唯一的),偏离你预期而没有找到值的机会很小很小。但这暴露出了在其它常用场景中的一个问题。那就是input使用的是一个name属性,而不是id:
1 <input name="name">

对于那个input使用的选择器大概是像下面这样:

1 $("input[name='name']").val()

如果在DOM中有超过一个元素使用了同一个name,它就会返回多个DOM项,并且很有可能给你错误的值。Backbone.View使用一种很小的选择器附加语法来解决了这个问题:

1 this.$("input[name='name']").val()

你注意到这其中的不同了么?就是在选择器的前面加上了this.。这一个小改变为Backbone.View做了一些非常重要的事情:它使用了同对事件配置的范围进行限制一样的方式,限制了视图$el的选择器的范围。

使用this.$而不是$运行一个jQuery选择器,就能确保你从视图或者界面上的其它地方获得一个结果(或者没有结果,如果没有被找到的话),而它们中的每一个都拥有一个匹配该选择器的input元素,这个视图实体只返回在它控制之下的那一个的数据。这里假设视图的$el只包含一个匹配该选择器的元素。如果超过了一个,所有的都会被返回。   

通过提供在一个视图中将this.$(...)作为选择器的用法,构造一个包含来自表单的数据的对象字面量就变得简单起来.

01 saveClicked: function(e){
02   // stop the browser from submitting the form
03   e.preventDefault();
04  
05   // store the data in an object
06   var formData = {
07     name: this.$("#name").val(),
08     url: this.$("#url").val(),
09     email: this.$("#email").val(),
10     phone: this.$("#phone").val(),
11     notes: this.$("#notes").val(),
12   };
13  
14   // … do something with the data
15 }
 这里并没有什么值得一提的代码。你只是阻止了浏览器自己来提交表单,然后将数据收集到一个对象字面量中。从这儿开始,你就需要使用这些数据来做一些事情,比如将它发送到服务器以存入某种数据库,还有就是在应用的联系人列表中展示新的联系人。  

用Backbone.js的Model保存数据

在这个代码的原始jQuery 版本中,数据是通过使用jQuery ajax 调用推送到服务器的(文档在http://api.jquery.com/jQuery.ajax/)。这个代码的Backbone.js版本很有效的做了同样的事情。但是不是直接调用$.ajax ,而是隐藏在Backbone.Model中。 

一个Backbone.Model代表着应用中的模型,或者浏览器中的实体。它是一个数据结构,至少包含这样几种行为,加载,修改,保存,以及删除数据,它们通过使用服务器的API实现

Model View的定义很相似。开始你需要扩展Backbone.Model 并且将结果赋给一个变量。这就创建了一个新的对象,它可以在需要的时候实例化,而且每个实例包含自己的数据。Backbone.Model 实例常常与服务端的模型或者数据库记录相关,即使并非总是如此。

定义一个Contact 模型

下一步,你需要创建一个Contact 模型给这个应用程序使用,传递一个对象名字给扩展方法:

var Contact = Backbone.Model.extend({});

在 Model 声明内部,可以提供选项和其它配置。例如,给模型增加一个urlRoot 属性并设置值为 /contacts

var Contact = Backbone.Model.extend({
  urlRoot: "/contacts"
});

Backbone.Model实现了一种名为活动记录(Active Record)的模式 (不要与 Ruby中 Rails的 ActiveRecord ActiveModel 混淆了)。这个模式的观点是,一个实体或者模型应该知道它相对于数据存储的状态。对于Backbone.Model来说,数据存储就是服务器的API。urlRoot 属性告诉模型,使用服务器API的哪个端点,这使得模型可以执行它自己的持久化操作。

保存新的 Contact

回到add 表单的saveClicked 方法,创建一个Contact 模型的新实例。然后调用模型上的 .save方法,并将 formData  作为第一个参数传递给这个方法。save 方法还具有一个对象字面量(译注: object literal 类似字符串常量的对象定义)的第二个参数。这个参数可以包含任何save 方法所需要的配置,其中包括了 jQuery .ajax调用的回调函数。

saveClicked: function(e){

  // … 

  // create a new contact and save it
  // using the form data
  var contact = new Contact();
  contact.save(formData, {

    // wait for a response from the server
    wait: true,

    // when the save is successful,
    // update the list of contacts
    // and clear the form
    success: function(contact){
      updateList(contact); 
      clearForm(); 
    }

  });
}

在 urlRoot 设置为 "/contacts"时,一个新的模型就将在这个端点保存到服务器的API。模型实例知道它是一个新的模型,因为模型或者数据中没有 .id 。它执行HTTP "POST" 方法将数据回送到服务器,就像这个应用的原始的 jQuery AJAX 调用所做的那样。它还使用了AJAX调用和其它行为变化的参数选项。

wait 选项告诉 Backbone.js 在继续之前先等待服务器响应。默认条件下,这个参数值是 false。这就是说对 .save的调用将会立刻激发 success 回调,并且将完全忽略服务器的响应。

success 回调与前面 jQuery AJAX 的 success 回调很相似。主要的一点不同是,第一个参数不是一个从服务器返回的原始对象字面量( 译注:raw object literal 即类似字符串常量的对象定义)。代替它的是模型的实例。

在这里,updateList 函数需要升级与 Backbone.Model 共事,而不是与一个原始对象字面量。为了将这里简化,可以引入 Backbone.Collection 来管理Contact 对象列表。

和Backbone.Collection有关的东西一览

Backbone.Collection是像你在使用其它语言和框架时所期望的那样一个集合。你可以添加和删除成员,对它们进行排序,从里面找到一个符合查询标准的特定子集,迭代输出所有的成员,当然还有更多的操作可以做。在Backbone中,Collection是Model实体的一个特定的集合,并且这些实体都是同一个类型的。那些实体直接默认的就只是Backbone.Model实体。但是也可以简单的让Collection工作于一个特殊类型的模型。   

对于Model的加载和运作,Collection也为此通过服务器API提供了一些方便的特性。例如你可以调用一个方法加载一整个集合。也有保存一整个集合的简单方法,尽管在写下这篇文章的时候这还没有内置到Collection中。 

定义一个通讯的集合

当你有一个通讯的地址,而且你现在需要管理这一系列通讯地址,你可以创建一个ContactCollection并按如下方式指定他使用的通信模块:

var ContactCollection = Backbone.Collection.extend({
  urlRoot: "/contacts",
  model: Contact
});

现在你可以为这个用户的集合创建实例,并且添加或者删除或者对这个实例内融做其他的一些操作。

注意这里创建这个类型的相同的模式,就像View和Model:通过在基本类型上调用.extend方法,并传递一个文本对象用于配置这个类型。

指定model属性:配置的属性是为了告诉这个集合的实例将要和什么模块一起工作。任何时候你添加或者从服务器上获取的一个模块,如果这个模块不是这个类型,他将会被转化到这个类型。

展现联系人列表

在你用刚刚添加的联系人更新联系人列表之前,最好让列表在浏览器中展示一下。这样一来,当你添加了一个新的联系人时,你就马上可以看到了。为此,你将需要一个ContractListView。它有同AddForm类似的渲染方法,但这个渲染将会是在视图引用的集合中的一次循环的每一个模型中。

1 <script type="text/template" id="contact-list-template">
2   <li>
3     <a href="mailto:<%= email %>"><%= name %></a><br>
4     <a href="<%= url %>"><%= url %></a><br>
5     Phone: <%= phone %><br>
6     Notes:<br>
7     <%= notes %>
8   </li>
9 </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方法。

01 // AddForm
02 saveClicked: function(e){
03   // …
04  
05   // create a new contact and save it
06   // using the form data
07   this.collection.create(formData, {
08     wait: true,
09     success: function(contact){
10       clearForm();
11     }
12  
13   });
14 }
成功的回调不会再召唤出updateList方法。由于新的联系人被立即添加到集合中,ContractListView将会接收到”添加“事件并且将重新渲染自身,以展示新的联系人。
01 var contacts = new ContactCollection(/* ... */);
02  
03 // ...
04 var addForm = new AddForm({
05   collection: contacts
06 });
07  
08  
09 // ...
10 var contactList = new ContactListView({
11   collection: contacts
12 });
现在你就可以通过添加表单增加新的联系人,而它们将自动的在联系人列表中展示出来了。

除了重组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

Addy Osmani通过采用合作的方式,借助于Github和社区贡献写了一本讨论Backbone基础的书籍。它是对Backbone.js和其围绕它的开发生态环境做的一个全面的介绍。 它包含你需要知道的所有核心的知识,入门和运行,以及像我在本文中所展示的在简单的应用程序之上演进。如果你对Addy在Javascript世界中所做的工作,那你就需要去了解一下。  


Backbone.js 教程 
https://leanpub.com/backbonetutorials 
Thomas Davis 在他的Backbone.js 教程博客上收集了一系列小知识。他吸取了社区的建议和信息,并把这些信息汇总到一起制作了一本免费的电子书。这本电子书采用了简易的blog 文章大小的章节,覆盖了Backbone.js 发展的方方面面。 


创建Backbone.js 插件 
http://BackbonePlugins.com 
这本电子书描述了如何——更重要的是为何——创建对于Backbone.js 合适的摘要和项目。这本书会指导你创建Backbone.js 插件的方法和原因。它提供了学习的第一手资料、最好的练习以及创建复杂项目和框架的细节部分。这一切都是建立在真实的Backbone.js 实践当中,这些实践有成功有失败,他们来源于许多工程项目,以及在这些工程项目上建立的插件和框架。 

屏幕录像

玩转Backbone.js

http://tekpub.com/products/backbone

这是Rob Conery对学习如何使用Backbone.js构建一个SPA的过程的遍历。循着这条历程,他展示了许许多多通常都会遇到的陷阱和问题,以及如何解决它们。在这个系列结束的时候我也做了下展示,来介绍我的MarionetteJS应用程序框架。

BackboneRail.com

http://backbonerails.com/

Brian Mann有一系列的Backbone.js屏幕录像,主要涵盖了大型Backbone.js应用程序的伸缩性及管理。他还有大量的免费作品和收费作品,涵盖了从SPA历史,到构建可伸缩系统以及更多的所有东西。

Backbone.js上手

http://pragprog.com/screencasts/v-dback/hands-on-backbone-js

我的学习Backbone.js的系列屏幕录像。在这里面使用的Backbone.js版本已经过时了,但是其核心概念里面都有,而且这些内容仍然有用。如果你是一个靠视觉的学习者并且想要跟着示范一步一步做,那这就很适合你。  

应用程序框架

来自Backbone.js社区最棒的贡献之一就属应用程序框架了。有大量这样的框架提供用于构建更大型的伸缩式应用程序框架的附加特性和功能。这些大多数都是很流行的,而我则强烈建议你看看它们,如果你计划在使用其实现几个简单的对象之外还要用Backbone.js做任何其它更多事情的话。

MarionetteJS

http://marionettejs.com/

原来以Backbone.Marionette而闻名,MarionetteJS是一个面向Backbone.js复合应用程序架构,也在我自己的数十个客户端应用程序工作中被使用,还被全世界的开发者用来构建了数以百计的其它系统。它提供了一个特殊化的View抽象层(ItemView,CollectionView, CompositeView),提供了使用一个Layout来构筑视图内部的能力,一个应用程序引导器,一个简单的模块系统,已经更多的东西。 

LayoutManager

http://layoutmanager.org/

LayoutManager用于在Backbone中组装布局和视图的逻辑基础设施。Tim Branyen —— 他是Backbone.js项目的骨干贡献人员——构建了一个基于约定来快速创建应用程序布局和内部视图架构的框架。它在浏览器和服务器端的NodeJS中都能工作,在其使用场景中提供了许多灵活性。

ChaplinJS

http://chaplinjs.org/

ChaplinJS提供了基于Backbone构建Javascript应用程序的架构指引。ChaplinJS通过提供一种其特性在设计模式和最佳实践中都得到了良好验证的轻量且灵活的架构来解决Backbone的局限。它是通过在其实现中使用Coffeescript来做到的,而它仍然可以在Javascript中被使用。Chaplin的控制器和路由器管理值得一看究竟,即使你现在正使用着另外一个应用程序框架。 

Thorax

http://thoraxjs.org/

Thorax是一个信心满满,久经考验的用于构建大型伸缩式web应用程序的Backbone+Handlebar框架。 Thorax的生命开始于Wal-Mart实验室,用于支持这个实验室在移动领域的尝试。其同Handlerbar在模板渲染上的紧密集成提供了一种风格独特的构建Backbone.js应用程序的方式,看上去比大多数Backbone.js框架更像是真正的MVC。

Aura

http://addyosmani.github.io/aura/

Aura是一个基于Backbone.js解耦的,事件驱动的架构,用于开发基于小组件的应用程序。一开始只是一个Backbone的应用程序框架,由于其也有同其它MV*库协同工作的能力,Aura开启了其新的生命。Aura采用基于小组件开发的方式,而其应用程序就使用那些组件组装而成。它集成了大量Javascript的社区知识,还有多年的大型可伸缩Javascript系统的扩展经验。

其它插件和附件

还有数以百计的涵盖了从内嵌模型架构,到特殊的服务器API的其它插件可供使用。想要得到更多Backbone.js社区所提供的东西的完整列表,请访问Backbone.js的关于其扩展,插件和资源的维基页面。  

Derick Bailey

列表1: 点击保存按钮并将数据传送到服务器.
01 $("#save").click(function(e){
02   var formData = {
03     name: $("#name").val(),
04     photo: $("#photo").val(),
05     email: $("#email").val(),
06     phone: $("#phone").val(),
07     notes: $("#notes").val()
08   };
09  
10   $.ajax({
11     url: "/contacts",
12     type: "POST",
13     dataType: "JSON",
14     data: formData,
15     success: function(){
16       updateList(formData);
17       clearForm();
18     }
19   });
20 });

列表2: 定义了一个列出联系人的视图

01 var ContactListView = Backbone.View.extend({
02   template: "#contact-list-template",
03  
04   // tell this view it is rendering a list
05   tagName: "ul",
06  
07   // render the list of contacts by iterating
08   // over the view's collection, and rendering
09   // for each of the models in the collection.
10   // add the results to an array and stuff the
11   // array in to the view's $el when it's done.
12   render: function(){
13     var results = [];
14  
15     var compiledTemplate = _.template(this.template);
16  
17     this.collection.each(function(contact){
18       var html = compiledTemplate(contact.toJSON());
19       results.push(html);
20     });
21  
22     this.$el.html(results);
23  
24     return this;
25   }
26  
27 });
列表3: 显示联系人列表视图 
01 var contacts = new ContactCollection([
02   {
03     id: 1,
04     name: "Derick Bailey",
05     email: "[email protected]",
06     url: "http://mutedsolutions.com",
07     notes: "just some guy"
08   }, {
09     id: 2,
10     name: "Ian Bailey",
11     email: "[email protected]",
12     url: "",
13     notes: "the most awesome little man, ever"
14   }, {
15     id: 3,
16     name: "Ashelia Bailey",
17     email: "[email protected]",
18     url: "http://example.com/ashe",
19     notes: "the best ballerina in the universe"
20   }
21 ]);
22  
23 var contactList = new ContactListView({
24   collection: contacts
25 });
26  
27 $("#contact-list").html(contactList.render().$el);
列表4: 新增一个联系人时,显示一个视图.
01 var ContactListView = Backbone.View.extend({
02  
03   // the initialize method executes just
04   // after the view is instantiated
05   initialize: function(){
06     this.listenTo(this.collection, "add"this.contactAdded);
07   },
08  
09   // handle a contact being added
10   contactAdded: function(contact){
11     this.render();
12   },
13  
14   // ... existing methods and config
15 });


你可能感兴趣的:(jquery,on,Ruby,web服务器,Rails,Web应用,应用)