Ember.js实战

978-7-115-39001-1
作者: 【挪】Joachim Haagen Skeie(乔基姆•哈根•斯基)
译者: 卢俊祥
编辑: 杨海玲

图书目录:

详情

Ember.js是现代JavaScript MVC框架代表,本书深入介绍了这一框架的方方面面知识。全书分为三个部分。第一部分引导读者对Ember.js有个概括性认识,并掌握其基础知识点,此部分包含两个示例程序;第二部分涉及Ember.js实战的几个关注点,如Ember Data、自定义组件以及测试;第三部分讨论Ember.js的高级主题,如认证、运行循环和打包部署。

图书摘要

版权信息

书名:Ember.js实战

ISBN:978-7-115-39001-1

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [挪]Joachim Haagen Skeie(乔基姆•哈根•斯基)

译    卢俊祥

责任编辑 杨海玲

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

反盗版热线:(010)81055315


Ember.js号称是“雄心勃勃”的JavaScript MVC框架、现代JavaScript MVC框架的一个代表,是构造如单页面应用等现代Web应用程序的新型Web端开发框架。本书深入介绍了这一框架的方方面面。

全书分为三个部分。第一部分为基础内容,共4章,引导读者对Ember.js有个概括性认识,并掌握其基础知识点,此部分包含两个示例程序——注意事项应用程序与博客应用程序;第二部分包括第5章到第8章,涉及Ember.js实战的几个关注点,如Ember Data、自定义组件以及测试;第三部分为第9章到第11章,讨论Ember.js的高级主题,如认证、运行循环和打包部署。

本书对于Web开发者掌握Ember.js非常有价值。阅读本书需要读者具备一定的JavaScript开发经验。


卢俊祥 译者,书迷;关注Web技术趋势,热衷App开发、Web开发、数据分析、架构设计以及各类编程语言;陈氏太极拳五十六式爱好者;佛禅人生,缘散缘聚。


至完稿时,本书是最全面且最结合实战的Ember书籍,学习本书是有一定挑战性的。在历经打击和困难的写作过程中,我也获益颇多。

感谢Manning团队出版这本Ember.js书籍,让我们有机会开启一段Ember学习之旅。我还要特别感谢策划编辑Susanna Kline容忍我一次次延期以及在Skype上无数次地发问,并始终耐心给予反馈,帮助我提高。同时,感谢文字编辑团队——Lianna Wlasiuk与Rosalie Donlon、Sharon Wilkey、Teresa Wilson——他们修订了全书大量拼写及语法错误。感谢也要送给校对员Melody Dolab、排版员Marija Tudor,以及项目经理Mary Piergies和Kevin Sullivan。

审稿人确保了各个阶段目标的实现,我要对他们的工作表示感谢,感谢Benoît Benedetti、Chetan Shenoy、Dineth Mendis、Jean-Christopher Remy、Leo Cassarani、Marius Butuc、Michael Angelo、Oren Zeev-Ben-Mordehai、Philippe Charrière、Richard Harriman以及Rob MacEachern。最后,感谢技术审校Deepak Vohra在送印前的认真复审。

我还要向我美丽的妻子Lene、两个总给我惊喜的孩子Nicolas和Aurora致以特别而崇高的谢意!Lene的支持和理解对我的写作是如此重要,要知道,写作得占用大量晚上和周末的空暇时间。当把时间优先花在其他事情上的时候,家庭带给你的安全感和幸福感至关重要。


Ember.js是一个最具雄心的JavaScript Web应用框架。随着v1.0.0正式版的发布,经过不到两年的发展,API已经稳定了下来,项目也有序推进,并很快又推出了v1.1.0和v1.2.0两个版本。

构建一个庞大而有雄心的Web应用是个挑战。Ember.js的应运而生是因为创建者们希望开发出一种框架,能够简化并标准化Web应用开发方式。本书的出发点是通过实例来讲解Ember.js的特性及精彩之处。

本书内容分为以下三个部分。

第一部分通过简单、独立的例子介绍Ember.js核心特性以及应用这些特性应具备的条件。

第二部分会结合案例展开阐述,并介绍后续大多数章节将用到的Montric库。这一部分会深入探讨 Web 应用开发的难点:如何与服务器端高效交互、编写自定义组件以及测试 Ember.js应用。

第三部分将进一步深入高级Ember.js主题,并讨论其他服务和工具,为应用开发提供便利,并加深读者对Ember.js的理解。

本书立足于帮助读者成为一名熟练而高效的Ember.js开发者。有赖于读者的背景,使用如Ember.js这样的框架开发JavaScript应用程序有可能要面对较大困难。本书帮助读者快速掌握Ember.js概念并熟悉Ember.js技术及应用结构,本书适合Ember.js新手和专业开发者阅读。

作为前提条件,本书假定读者已经熟练掌握JavaScript语言,并对jQuery相关知识有一定了解。

本书排版约定如下。

本书包含了许多代码片段和源代码。第一部分的源代码可以通过本书GitHub页获取,或者到Manning出版社网站www.manning.com/Ember.jsinAction下载zip格式的源代码压缩包。第二部分和第三部分的内容以Montric源代码为基础,Montric源代码可以通过GitHub获取。

由于本书案例是实际的项目,本书写成时项目代码应该已经发生了变化。考虑到这一点,本书使用以下链接给出写作时的代码版本。

尽管要保持案例的真实有效,但我还是会努力通过文字来阐述Ember.js。虽然在独立而优秀的案例中使用Montric源代码可能会偶尔给读者带来挑战,但也让本书内容更深入。此外,书中案例的更新变化也能够让读者了解Ember.js的发展历程。

购买本书的读者可以免费访问Manning出版社维护的专用论坛,在论坛里读者可以评论本书、提出技术问题并得到作者和其他开发者的帮助。要访问论坛并订阅信息请访问www.manning.com/Ember.jsinAction。该页面提供了注册后如何访问论坛的指引、各种帮助信息以及论坛行为准则。

Manning以尽责的态度提供一个读者间、读者与作者间互动的空间。Manning无法承诺作者的参与程度,其对论坛的贡献基于自愿而免费的原则。我们建议你尽量向作者提一些富有挑战性的问题,以保持作者的热情!

只要书籍得以出版,你就可以通过出版社网站访问作者在线论坛以及早期的讨论归档内容。

Joachim Haagen Skeie是一位自由职业者,其供职于自己的公司Haagen Software AS。致力于开发Montric(一款开源的应用程序性能监控工具)和Conticious(一款开源的主要用于Ember.js富互联网应用的CMS API)的同时,他还是一名独立咨询师、Ember.js和RaspberryPi课程讲师。Montric和Conticious的前端部分基于Ember.js技术,后端使用Java技术。

Joachim Haagen Skeie从2006年开始,就从事各种规模的Web应用开发工作,主要使用Java和Ember.js技术。他和妻子、孩子一起居住在挪威首都奥斯陆。


本书封面图画的标题是“巴黎的戏剧导演”。这幅图出自19世纪法国出版的西尔万·马雷夏尔(Sylvain Maréchal)地域服饰习俗四卷本摘要。其每幅图画皆由手工精心绘制和着色而成。丰富多样的马雷夏尔作品集让我们强烈感受到200年前世界上城镇与地域在人文上的巨大差异。那时候人们生活封闭,所操语种、方言各不相同。无论是在街道还是乡野,通过各式穿戴,就能够很容易地识别人们的职业、身份,以及他们来自何方。

自那时起着装发生了变化,同时那个年代丰富的地域多样性也逐渐褪去。现在要分辨出不同大陆的居民已非常困难,更不用说城镇或地域的区别了。也许我们已用文化多样性来装扮个人生活的多样化——当然是从更加多样而快节奏的科技生活的角度。

有时候从某个角度很难描述一本计算机书籍,Manning参考两个世纪前地域生活的丰富多样性来设计图书封面,借此表达对计算机领域开创精神的赞誉,并通过马雷夏尔的图画将时光带回到历史。


2006年起,我已经以某种方式进行Web应用开发。我开始为挪威最大的零售商开发Web应用,先是使用JavaServer Pages(JSP)技术,之后换成JavaServer Faces(JSF)。当时这些技术很不错,并能让使用者达到使用目的。在那时(Ajax流行之前),HTTP的请求-响应周期要求将大多数处理逻辑放在服务器端,服务器端在每次请求中传递所有标记、脚本和样式表给浏览器。

虽然以服务器端方式开发Web应用也能行之有效,但状态问题总是绕不开的。由于服务器端要求记住所有登录用户,管理状态很快就成为一个棘手而占用大量内存的任务。要如何处理用户打开多个标签页并在彼此间进行切换?跨多个(虚拟)机器延展服务的时候如何持久化会话数据?如果在服务器端存储用户状态,那又如何以一致方式方便地进行水平扩展?处理起来真不容易。

当我开始从事开源项目Montric(那时叫EurekaJ),我很快决定,如果想要在不借助独立的会话缓存的情况下水平扩展应用,我就得掌握一个具备良好前景和流行度的JavaScript框架。

我评估了多个框架,并用Cappuccino(www.cappuccino-project.org)和SproutCore(http://sproutcore.com)这两个框架搭建了原型。虽然Cappuccino的工具比较完整,而且提供了更细致好看的用户界面,我还是选择了一个SproutCore,因为它可以让我使用既有技术积累,它还承诺可以跟第三方库方便结合。SproutCore提供了更强大的视图,其组件方式可以最终组建出一个完整功能的Web应用。以组件为基础,使用服务器端框架,这些特性让我对SproutCore倍感亲切。但经历了最初的喜悦之后,我发现将第三方库集成到SproutCore并不简单。

随着SproutCore开发团队被收购以及该框架放慢了发展脚步,SproutCore社区开始发生了变化,SproutCore 2.0版进展顺利,但老版本与新版本间的裂痕却在扩大,最终,SproutCore产生了一个新的分支——Ember.js。

Ember.js依托能够提供优良Web体验的技术而打造,它能够帮助开发者使用既有技能组合开发JavaScript应用程序。Ember.js并不抽象或隐藏JavaScript、HTML或者CSS的实现细节,反而与时俱进地充分利用这些技术。

不用说,我肯定是跟随Ember.js,并且决定用Ember.js重写EurekaJ的前端部分。在此期间,我把项目改名为Montric(http://montric.no)。从那时起,我就一直使用Ember.js。在v1.0.0预览版发布期间,Ember.js社区的发展也经历了起浮。那时频繁调整API,感觉每周都在重新考虑和审视概念,但随着问题被逐步解决,思路也越来越明晰。在预览版时就决定写一本全面介绍Ember.js的图书,但对于这个想法我确实还有顾虑。

在我写作过程中,Ember.js v1.2.0也发布了,API变得稳定了,整个项目健康发展。今天,Ember.js已经成为一个了不起的框架,能够帮助你创建极具挑战的Web应用。


JavaScript MVC框架Ember.js用于组织大型Web应用代码结构。与其他流行的JavaScript应用框架相比,其具有更完整的MVC模式特征,并包含创建新一代Web应用所需特性。它自信满满,严格依赖约定优于配置的设计范式来构造应用程序。

由于包含大量特性及应用约定,Ember.js的学习曲线比较陡峭。本书第一部分包含4章内容,帮你尽快找到Ember.js开发的感觉,并确保你从一开始就能有所成。

前两章重点介绍Ember.js常用核心特性。第3章介绍Ember路由器,第4章聚焦于为Ember程序员遴选的模板库Handlebars.js。


本章涵盖的内容

本章介绍Ember.js应用框架,以及Ember.js生态系统的大量特性和技术。大多数主题将在后续章节具体展开。你还将快速了解Ember.js应用的轮廓及其优势。

同时,本章也会介绍如何构建Ember.js应用,其间将涉及Ember.js框架的不同方面。如果一开始你不太理解某些代码,请别担心!所有的这些开发代码都会在后面一步步具体展开。

如果你已习惯开发服务器端技术驱动的Web应用,掌握Ember.js可不是轻松的事情。本章代码示例和记事本应用将涉及构建Ember.js应用的各种概念。

Ember.js的结构以一系列基础库为基础。书中各章开始处都会提供一张图,展示各个基础库并高亮显示各章涉及的内容。本章就会接触到许多Ember.js基础库,如图1-1所示。

图1-1 Ember.js内部结构

像《纽约时报》网站或苹果公司网站这样的内容服务网站以传统的HTTP请求-响应生命周期为基础,在服务器端渲染大部分的HTML、CSS和JavaScript代码。如图1-2左半部分所示,对于每个请求,服务器端都会生成网页标记全新而完整的复本。

图1-2 Ember.js框架支持各种Web应用

另一种技术是富互联网应用(RIA),诸如Google地图、Trello以及某种程度上的GitHub。这些网站的目标就是重新定义应用类型,在客户端渲染大部分内容,以与原生安装应用竞争。如图 1-2 右半部分所示,应用在第一次请求发生时,服务器端做出响应,将完整的应用(HTML、CSS以及JavaScript)一次性传送给客户端。对于随后请求将只返回显示下一页面所需的数据。

两种技术的优缺点展示在图示的两端。左边描述的页面很容易被服务器端缓存,但其依赖请求-响应生命周期模式,而且为了响应用户动作必须整页刷新。

图示右半部分拥有典型的富用户界面,提供了更好的用户体验,并与大家所熟知的原生应用相似,但实现起来也更复杂,需要浏览器软件提供更多的计算能力、新特性以及稳定性。

单页面应用(SPA)越来越流行,因为RIA——尤其是SPA——更像是原生安装应用,其具有更加响应式的用户界面、少量或局部的页面刷新。在这个领域,Ember.js的目标是成为Web应用开发者的最佳框架解决方案,并将Web应用效果发挥到极致。例如,Ember.js非常适合请求长时用户会话、需要富用户界面以及基于标准Web技术等各类场景。

如果打算创建图示右半部分风格的应用,那么,Ember.js正是为此打造的。Ember.js还有助于思索如何构建应用。它提供了创建丰富Web应用程序的强大工具,让你的创意发挥到极致,同时提供一系列丰富特性以构建真正雄心勃勃的Web应用程序。

在开发Ember.js应用程序之前,先来讨论一下为什么我们一开始就选择象Ember.js这样的框架,以及Ember.js提出要解决的问题。

从20世纪90年代中期引入万维网(WWW或W3),到21世纪00年代中期Ajax出现之前,大多数的网站本质上是静态网站。服务器端通过一个HTTP响应来应答所有HTTP页面请求,该响应包含了显示一个完整页面所需的全部HTML、CSS和JavaScript代码,如图1-3左半部分所示。

图1-3 早期Web结构(左边)与Ajax Promise模式(右边)的比较

虽然许多网站仍然如图1-3左半部分所示那样,依赖于整页刷新的方式,但越来越多的开发者都在创建动态内容。今天,用户希望在体验网站时能够做到页面不要刷新。

随着异步调用功能的引入,其提供了为每个响应发送特定内容的能力。客户端有专门接受这个响应的JavaScript代码,并会替换网站中所有相关的HTML元素内容,如图1-3右半部分所示。这看起来很好,但这种方式存在一个很大的问题。

在服务器端实现一个服务是很容易的事情:给出一个元素类型,呈现元素的新内容,并以原子级的完整方式返回给浏览器。如果这就是富Web应用用户所需,那就没那么多麻烦了。但问题是用户极少一次只更新一个元素。

比如,当你浏览在线商店的时候,会搜索商品项并将其添加到个人的购物车中。但添加一项商品到购物车时,你理所当然希望商品数量以及购物车商品总金额也相应更新。这样你才知道购物车里的商品总数以及总金额到底有多少。

由于服务器端在每个Ajax响应中应该明确包含哪些元素的规则很难定义,大多数服务器端框架都直接发送整个页面给客户端。同时,客户端知道应该用哪个元素替换/互换对应的HTML元素。

如你猜到的,这种方式效率很低,其意味着增加了大量客户端发送到服务器端的HTTP请求,而这正是Ember.js的用武之地。作为一名开发者,你或许理解图1-3所示模型的问题所在,服务器端为页面上单个元素返回更新过的标记,而要更新多个元素,你就需要采取以下的某种方式。

第一种方式增加了HTTP调用服务器端的次数;第二种方式需要你在客户端和服务器端同时管理用户状态。因此,这大大增加了HTTP请求的次数,导致服务器端负担加重,但又无法减少服务器端处理每个请求的工作量。别误解了,这种模型下,通过判断元素标识符来替代元素,以及挑选出服务器端所返回完整标记中的特定元素,是可以支持局部页面更新的。但如你所想,这种模式同时浪费了服务器端和客户端的资源。图1-4说明了这种结构。

图1-4 服务器端框架结构

理想情况下,我们希望只在初始时服务器端将完整应用一次性传送给客户端。当整个应用加载后,客户端只需提交数据请求。伴随这种想法让我们进入Ember.js使用的模型。

以往,大多数网站忽视在服务器端与客户端间传递标记,而更关注数据传递。这正是Ember.js擅长的领域,如图1-5所示。

图1-5 现代Web应用模型

在图1-5中,用户在初次请求发出后一次性接收完整的网站。这将导致两件事情发生:增加初次加载的时间,但也意味着随后每次用户操作时性能得到提升。

实际上,图1-5所示的模型与可追溯到20世纪70年代的传统客户端/服务器端模型类似,但有两个重要的区别:初始请求充当客户端应用程序高度可行且可定制的分发渠道,同时确保所有客户端遵循一套通用的 Web 标准(HTML、CSS、JavaScript和其他)。

随着客户端/服务器端模型提出,涉及用户交互的业务逻辑、GUI(图形用户界面),以及执行逻辑已从服务器端转移到了客户端来处理。对特定部署而言这种转移会带来一定的安全问题,但在通常情况下,只要服务器端控制着所请求数据的访问权,安全事宜就可以委托给所属的服务器。随着客户端和服务器端职责的清晰分离,客户端和服务器端就可以各司其职——分别关注用户界面和数据处理。

现在你理解了通过Ember.js创建的Web应用程序的类型,接下来将进入到Ember.js内部。

Ember.js起源于SproutCore框架的第二个版本。在SproutCore 2.0开发期间,SproutCore团队成员已经清楚地认识到,如果想要创建满足目标广泛的Web应用程序的需要,并且体积还保持小巧的易用框架,SproutCore框架的底层结构就需要有个根本改变。

SproutCore简介

SproutCore是一个用高度面向组件编程模型开发出来的框架。SproutCore的大多数概念都是从Apple的Cocoa借鉴来的,而Apple也使用SproutCore 来构建它的一些Web应用(MobileMe和iCloud)。同时,Apple还贡献了大量代码给SproutCore项目。2011年11月,Facebook得到了该项目团队并负责维护SproutCore。

最后,核心团队的部分成员决定从SproutCore分离出来,创建一个新的框架来实现这些改变。

Ember.js借鉴了SproutCore大量的底层结构和设计。但SproutCore为了创建桌面风格的应用程序,通过对开发者隐藏大部分实现细节来费力提供端到端解决方案,与此不同,Ember.js力求让开发者明白HTML和CSS才是开发栈里的核心。

Ember.js的优势在于能够让你以一致而可靠的模式组织JavaScript源代码,同时还保持着HTML与CSS代码的易见性。此外,不强制依赖特定工具来开发、构建及装配应用程序,给开发者更多的选择控制权来组织开发过程。在装配及打包应用程序时,有许多可靠的工具供选择。第11章将介绍一些有效的打包选项。

你迫不及待地想开始Ember.js编码了?但在创建你的第一个Ember.js应用程序之前,还是先来了解下Ember.js及其应用结构吧。

按照Ember.js官网上的说法,Ember.js是一个帮助你构建“雄心勃勃”Web应用的框架。“雄心勃勃”这个词对不同的人可能有不同理解,但有一点是众所周知的,Ember.js的目标是挑战Web开发的极限,同时确保源代码的结构化和健壮性。

Ember.js将应用结构封装为逻辑抽象层,并强制尽可能采用面向对象开发模式,以达成其目标。其内置支持以下核心特性。

将以上特性与强大而优良的MVC架构结合起来,你就获得了一个众望所归的Ember.js框架。

如果你曾经花费了大量时间通过服务器端生成标记及JavaScript代码来开发Web应用程序,Ember.js——一个全新亮相的JavaScript框架——其应用结构完全不同于旧有做法。

Ember.js包含了完整的MVC实现,MVC架构强化了控制器层和视图层。随着本章内容的推进,我们将涉及更多的MVC实现细节。

注意


第5章介绍的Ember Data,将充当Ember.js的模型层。

构建Ember.js应用程序时,开发者会对应用按一致而结构化的原则进行划分。可以花点时间考虑下放置应用逻辑的最佳位置。尽管这种方式要求在编码之前先仔细思考,但却能保证产品最终具有更好的结构,也就意味着程序易于维护。

大多数情况下,你将遵循Ember.js的指导原则和约定惯例,但有些情况下还需要花一些时间采取特别的方式来实现更复杂的应用功能。

如图1-6所示,Ember.js在标准MVC模型的各层之上引入了额外的概念,本书前5章会介绍这些概念。

图1-6 Ember.js结构及如何匹配MVC模式

记住这张图,我们来具体了解一下每个MVC组件。

1.模型与Ember Data

在图1-6的底部,Ember.js通过Ember Data来简化应用程序,Ember Data提供了创建富Web应用所需的大量数据-模型特性,其描绘了一种跟服务器端通信的可行实现方式。其他库也具备这种功能,你可以编写或引入你自己的客户端-服务器端通信层。Ember Data将在第5章中详细介绍,并在第6章介绍如何整合你自己的数据层。

模型层通常以半严格模式指定的方式来保存应用数据。模型层负责服务器端通信以及模型特有任务如数据转换。视图可以通过控制器绑定界面组件到模型对象属性。

Ember Data在模型层发挥作用,用来定义模型对象和客户端到服务器端的API,以及Ember.js与服务器端的传送协议(jQuery、XHR、WebSockets及其他)。

2.控制器与Ember路由器

在模型层之上是控制器层。控制器的主要作用是担当模型与视图之间的纽带。Ember.js附带了几个自定义控制器,最主要的是Ember.ObjectControllerEmber.ArrayController这两个控制器。通常,当控制器描述单一对象时(如选择一条事项)使用ObjectController;而在控制器描述项目数组时(如列出当前用户所有有效事项)使用ArrayController

在此之上,Ember.js通过路由器把应用程序分割为清晰界定的逻辑状态。每个路由可以有多个子路由,使用路由器在应用程序的不同状态间导航。

Ember路由器同时也是Ember.js用于更新应用程序URL以及监听URL变化的机制。使用Ember路由器的时候,将以类似状态图的层级结构来模型化所有应用状态。第3章将涉及Ember路由器的内容。

3.视图与Handlebars.js

视图层负责绘制界面元素。视图通常不保存自身永久状态,但也有极少例外。默认情况下,Ember.js中的每个视图都有一个对应的控制器作为其上下文。视图通过控制器获取数据,默认情况下,使用这个控制器来处理任何对该视图进行的用户操作。

同样是在默认情况下,Ember.js使用Handlebars.js作为其模板引擎。所以,大多数Ember.js应用程序通过Handlebars.js模板来定义用户界面。一个视图使用一个模板来渲染。第4章会介绍Handlebars.js和模板。

Handlebars.js

Handlebars.js基于Mustache,包括JavaScript在内的许多编程语言中都能看到无逻辑模板库Mustache的应用。Handlebars.js在Mustache之上添加了逻辑表达式(if、if-else、each等)。这样,随着可以将模板绑定到视图与控制器的属性上,开发者就能够为Ember.js应用构建逻辑清晰且可定制的结构化模板。

Ember.js附带了支持HTML5基本元素的默认视图,在处理简单元素时这些视图是非常合适的。而在构建Web应用复杂元素时,无论是扩展还是结合标准Ember.js视图,都能够很容易地创建出属于自己的自定义视图。

现在你了解了Ember.js应用程序结构,接下来开始编写你的第一个Ember.js应用。

记事本应用大约有200行程序代码(包括模板和JavaScript代码)以及130行CSS代码。你完全可以在Windows、Mac以及Linux等各种操作系统上使用你喜爱的编辑器来开发并运行这个应用。

提示


我使用JetBrains WebStorm来编写JavaScript应用,但这对你来说不是必需的。

你将通过编写一个简单的记事本Web管理应用来一探Ember.js。该应用功能如下。

该应用设计轮廓如图1-7所示。

图1-7 记事本应用的设计及布局

开始之前请下载以下各个库。取决于Ember.js当前版本,所需各个库的版本可能会有所不同。

可选项:从头开始或者从GitHub获取代码

从头开始。

(1)在硬盘里创建目录,用以存储所有应用文件。

(2)目录结构如下所示。

从GitHub获取代码:如果想要获取打包整理好的源代码,请通过 GitHub 仓库的链接https://github.com/joachimhs/Ember.js-in-Action-Source/tree/master/chapter1下载或复制,该内容是第2章结束之时的源代码。

设置完毕,请打开index.html文件。

在index.html中加载应用依赖的各个程序文件,如代码清单1-1所示。

代码清单1-1 在index.html文件里加载依赖文件

这段程序足以作为开发记事本应用的起点。

模板的放置位置

简单起见,可以在index.html中放置所有的应用模板。这将简化设置操作,并便于开始新的Ember.js应用。一旦应用规模增长了,通常需要通过构造工具提取模板到独立的文件中,构造工具将在第11章介绍。

在大多数用于生产的 Ember.js 应用程序中,代码清单 1-1 中的代码就是将始终放在index.html文件里的所有代码。这可能跟以往的Web开发方式不尽相同,如我接触Ember.js之前的经历。除非开发者特别指定,默认情况下Ember.js应用程序将把内容放置在HTML文档的<body>标签里。

这段代码里并没有什么特别的。文档一开始定义了doctype类型,之后定义HTML元素——先是定义标准的<head>元素。在<head>元素中,设置页面标题,并加载Twitter Bootstrap CSS和应用所需的自定义CSS。<head>标签里的<script>元素加载应用程序依赖的各个脚本,最后一个<script>标签加载记事本应用程序的实现代码,在本章剩余章节中我们将实现它。

本节将通过基本Web应用布局来创建记事本应用程序的第一部分内容。

注意


不管是自己编写还是从GitHub链接(https://github.com/joachimhs/Ember.js-in-Action-Source/blob/master/chapter1/notes/js/app/app1.js)下载代码,这里的代码文件都命名为app1.js。

Ember.js应用程序首先需要一个命名空间来容纳它。对于记事本应用程序,使用Notes作为命名空间。

创建命名空间之后,需要创建一个路由器,以获悉应用程序的结构。路由器不是必须的,但如本书所述,路由器能够极大简化并管理整个应用程序的结构化工作。你可以把路由器想象成一种黏合剂,用来恰当地处理应用程序并将各部分功能联系在一起。

让记事本应用程序以空白网站形式运行起来的最简单代码如下所示:

这行代码通过Ember.Application.create()创建Notes命名空间。任何应用实现代码都包含在这个命名空间里。这将有效分隔实现代码与引入的第三方库乃至包含它的其他JavaScript文件。但空白网站很无趣,接下来添加些显示内容。

当前Ember.js创建了4个具有默认行为的对象,这些对象跟我们的应用程序密切相关:

这时你还不用具体了解这4个对象。但你必须知道可以覆写它们并包含自定义行为。

要想在页面呈现些文字,需要用自定义标记覆写默认的application模板。在index.html文件里的<head>标签里添加一个<script>标签。<script>标签的类型设置为“text/x-handlebars”,同时必须包括你的模板的名称(id),如代码清单1-2所示。

代码清单1-2 覆写application模板

在浏览器中打开index.html文件,将显示“Hello Notes Application!”,如图1-8所示。

图1-8 渲染application模板

运行程序

虽然可以通过拖放index.html到浏览器的方式来运行记事本应用,但我还是推荐使用一个合适的Web服务器来运行它。你可以使用你最熟悉的Web服务器。如果你打算使用一个轻量级的小型Web服务器,你可以考虑asdf 这个Ruby gem,或者是使用一段简单的Python脚本。

如果已安装Ruby

(1)在终端窗口(Mac或Linux)或命令行窗口(Windows)输入gem install asdf命令来安装asdf;

(2)安装完成后,在当前目录的终端或命令行窗口执行asdf –port 8080

(3)gem启动后,在浏览器打开链接http://localhost:8088/index.html,运行应用程序。

如果已安装Python

(1)在终端或命令行窗口执行python-m SimpleHTTPServer 8088命令;

(2)命令执行并启动后,在浏览器打开链接http://localhost:8088/index.html,运行应用程序。

你已经能够在屏幕上显示一些文字了,接下来继续设置记事本应用程序的剩余部分。在继续之前,我们先来思考一下应用程序可以有哪些状态(路由)。

但首先删除在代码清单1-2中添加的application模板。对于本章剩余内容,我们不需要复写默认的application模板,因此删除它,我们继续前进。

回头看看图1-7,你会发现记事本应用程序可以有两种逻辑状态——应用窗口左边的事项列表呈现一种状态,所选事项右边的内容呈现第二种状态。此外,所选事项内容的状态依赖于左边列表的选择。基于此,你可以将应用分为两个路由设置。一个是初始路由notes,一旦用户选择了该路由,应用就转换到第二个路由notes.note

Ember路由器及其工作原理将在第3章介绍。现在,给app.js文件添加代码清单1-3所示的路由定义。

代码清单1-3 定义应用程序的路由器

以上代码在Notes.Router类里创建应用程序路由的映射。这个路由器里有两个路由。一个名称为notes,对应URL“/”;另一个名称为note,是notes路由的子路由。拥有子路由的路由在Ember.js中被称为resource(资源),而子路由被称为route(路由)。

资源和路由以其父路由名称和自身名称的组合作为完全限定名称。例如,列表中note路由被称为notes.note路由。这个规则同样适用于控制器、视图以及模板。根据所定义的路由,Ember.js创建了以下默认对象实现:

此外,每个应用程序的路由都与一个关联URL实现双向访问绑定,这也意味着路由能够如约响应URL的变化,而在状态之间转换时同时以编程方式修改URL。路由的概念刚开始容易让人迷惑,但请放心,我们会在第3章彻底讨论它。

注意


Ember.js创建了上述列表中每个对象的默认实现,你只需覆写需要修改的内容。因此,记事本应用程序并不用实现所有列出的类。

现在已定义了应用程序的每个路由,接下来还需要通知应用程序每个路由可使用哪些数据。代码清单1-4所示的程序定义了notesnotes.note路由。

代码清单1-4 定义应用程序路由

这段代码引入了几个新概念。最明显的是每个路由都扩展自Ember.Route。接下来,通过model()函数通知每个路由可使用哪些有效数据。在这里,我们先不讨论model()函数代码的作用。

通过 Ember Data,通知notes 路由将所有注册事项传入NotesController;类似的,通知notes.note路由将所选事项传入NotesNoteController。此外,我们使用了Local Storage Adapter来与Ember Data协同工作,也就是说,创建的事项会存储在本地浏览器里,应用可用其实现跨站更新。现在你可能不太理解Ember Data,没关系,我们会在第5章详细解释它。

接下来为应用添加一些真实内容。

notes路由里,可以包含一个输入文本字段以及一个按钮,这样用户就可以为应用添加新的事项。在文本字段和按钮的下方,将列出所有注册事项。

前面已经定义了路由,现在来添加一个新的notes模板。代码清单1-5在index.html中添加文本字段和按钮。

代码清单1-5 添加模板、输入字段和按钮

我们在idnotes<div>元素里放置了notes模板的内容,这样可以确保应用正确的CSS样式到notes列表上。在<div>元素内,添加了文本字段和按钮。现在,程序尚未具备太多的功能,因为还未告知Ember.js在文本字段输入内容或用户点击按钮时应该如何动作。

先前为了在应用中添加文字,在代码清单1-2中编写了application模板的自定义实现。由于后续不再需要用到这段文字,所以请删除前面创建的application模板。依托Ember.js的标准应用模板就能够为记事本应用程序提供很好的支持。

注意


任何时候,只要Ember.js请求一个尚未定义好的模板,其都会使用默认实现,默认实现只包含了一个{{outlet}}表达式。

最后要实现的是用户在文本字段中输入新事项名称并点击按钮时,需创建该事项并将其保存到浏览器的本地存储当中。

要实现该功能,需要绑定文本字段的内容到NotesController的一个变量上,并添加一个动作,当点击按钮时,在NotesController中触发该动作。Ember.js自动创建了一个默认的NotesController,但要实现具体的动作,就需要覆写它。在app.js文件中添加代码清单1-6所示的实现。

代码清单1-6 创建NotesController


该代码清单包含了不少处理逻辑。首先,创建名为Notes.NotesController的控制器。由于控制器里包含了事项列表,因此,其扩展自Ember.ArrayController比较合适。

接下来,在控制器里定义newNoteName属性,用来绑定输入文本字段。在这里也可以省略这个定义,Ember.js在用户第一次输入文本字段内容时自动创建该属性,但我更喜欢明确指出模板会使用该属性。这只是个人偏好,你的习惯可能有所不同。

createNewNote动作的含义很清楚了。

要在用户界面上添加事项,还得修改notes模板。首先需要初始化Ember Data。将代码清单1-7中的代码添加到app.js中。

代码清单1-7 初始化Ember Data

现在,应用程序已设置好可以通过Ember Data来使用浏览器本地存储了,你还可以将文本字段值以及按钮动作绑定到Notes.NotesController。代码清单1-8所示的代码修改了index.html文件里的notes模板。

代码清单1-8 添加绑定功能

现在就可以为应用程序添加新事项了,我们的应用还需要能够列出所有的事项。要实现此功能,请编辑notes模板,如代码清单1-9所示。

代码清单1-9 创建事项列表

即使此时你还不太理解 Ember.js,但添加的代码已足够直观。你使用 Handlebars.js 表达式{{#each}}来迭代Notes.NotesController中的每条事项,并打印每条事项名称。我们还是使用Twitter Bootstrap来设置界面样式。加载修改过的index.html,效果如图1-9所示。

图1-9 修改过的记事本应用程序主页面index.html

到目前为止,也许你是经历了好一番周折,才在屏幕上显示出事项列表。但你很快就会发现,这些周折是有回报的。接下来,你将实现两个应用功能中的另一个:选择列表中的一条事项,转换到notes.note路由,并查看每条事项的内容。

记事本应用程序都具备输入事项内容的能力,本节最后面将实现该功能。

注意


本节完整源代码可以通过 GitHub 链接https://github.com/joachimhs/Ember.js-in-Action-Source/blob/master/chapter1/notes/js/app/app2.js下载,相关文件为index2.html页面文件和app2.js JavaScript文件,如果是手动输入代码,别忘了按此设置正确的文件名。本节示例使用Ember.js 1.0.0,因此相应使用{{#linkTo}}辅助器。对于新版本的Ember.js,这个辅助器已改名为{{#link-to}}。如果你使用1.0.0以上的新版本Ember.js,Ember.js将提示你{{#linkTo}}已被废弃。

代码清单1-10 接每条事项到notes.note路由

当用户在Ember.js应用程序中导航时,在{{linkTo}}表达式中包含{{name}}是最通用的做法,可以帮助用户从一个路由转换到另一个路由上。{{linkTo}}表达式有1~2个属性:第一个是目标路由的名称;第二个指定{{linkTo}}表达式注入目标路由的上下文。

这段代码在点击某条事项名称的情况下, 将用户从NotesRoute转换到NotesNoteRoute,并将所选事项传给NotesNoteRoute。

列表中每条事项的名称都是一个链接。重新载入应用,然后点击选择一条事项,之后可以发现应用的URL随之更新,以反映当前浏览的事项(如图1-10所示)。

图1-10 列表中的事项是个链接,当选择某条事项时,浏览器的链接将相应更新

现在可以查看并选择事项了,我们还希望在列表右边显示所选事项的内容。

要显示所需事项,需要创建notes/note模板。但在创建之前,需要添加一个{{outlet}}表达式到模板中,以通知notes模板在哪里渲染其子路由。Notes模板修改如代码清单1-11所示。

代码清单1-11 往notes模板中添加一个outlet

在通知notes模板在哪里渲染notes.note路由之后,可以添加显示所选事项的模板。在index.html文件中创建新模板,idnotes/note,代码如代码清单1-12所示。

代码清单1-12 添加notes.note模板

虽然只添加了一小块代码,以允许用户选择某条事项并查看其内容,但现在的应用程序已可以提供以下功能给用户。

图1-11所示为更新后的应用效果。

图1-11 更新后的应用效果

在添加事项删除操作之前,还得先解决两个问题。

要解决第一个问题,我们在{{linkTo}}表达式上使用Twitter Bootstrap的CSS样式及一个附加的CSS类名,如代码清单1-13所示。

代码清单1-13 高亮所选事项

这里有一些微小调整:移除了<div>标签,然后添加一个CSS类名到{{linkTo}}表达式,这样就足以成功用蓝色高亮所选事项。不管你是点击事项或是直接通过URL进入notes.note路由,注意观察相应变化。

接下来,通过在notes/note模板中添加一个修改按钮来解决第二个问题,代码如代码清单1-14所示。

代码清单1-14 在notes/note模板中添加按钮

一旦按钮就位,接下来就在Notes.NotesNoteController添加相应的事项修改动作。到目前为止,即使尚未覆写Ember.js创建的默认NotesNote控制器,你的程序也已相当不错了。在app.js文件里添加代码清单1-15所示的修改功能的程序。

代码清单1-15 添加NotesNoteController来修改事项内容

应用程序现在看起来如图1-12所示。请注意所选事项高亮显示在图的左边,URL随所选事项会相应更新,右边文本区域的下方有一个修改按钮。

图1-12 应用演示了事项被选中,并保存事项内容的修改

现在来完成记事本应用程序的最后一部分功能:删除事项。

本节将完成记事本应用程序的第三个也是最后一部分功能。

注意


本节JavaScript代码文件名为app3.js ,或者你也可以通过GitHub链接下载源代码:https://github.com/joachimhs/Ember.js-in-Action-Source/blob/master/chapter1/notes/js/app/app3.js

要删除事项,需要为界面左边列表中的所选事项添加一个删除按钮。当用户点击该按钮时,应用程序会弹出一个模式面板,在删除所选事项之前请用户确认。一旦用户确认删除,该事项就从Notes.NotesControllercontent属性里移除,同时设置selectedNote属性为null。我们用Twitter Bootstrap实现一个模式面板,并添加到应用程序。同时还要在Notes.NotesController中添加一些新动作。

首先在notes模板中添加删除按钮,代码如代码清单1-16所示。

代码清单1-16 在notes模板中添加删除按钮

在用户界面添加好按钮后,接下来为Notes.NotesController添加新的doDeleteNote动作。这时候,传递this给doDeleteNote动作,通知该动作用户希望删除哪一条事项。控制器修改代码如代码清单1-17所示。

代码清单1-17 在NotesController里添加doDeleteNote动作

doDeleteNote动作接受一个参数。由于之前在{{action}}表达式的第三个参数里传入了打算删除的事项,Ember.js将确保传入该对象到动作中。此时,尚未得到用户的确认,因此还不能真正删除事项。在显示确认信息给用户之前,先临时保存用户打算删除的事项。之后,显示模式面板,接下来你就会创建它。

由于用HTML代码渲染Bootstrap模式面板有点儿复杂,且有可能在应用程序的多处地方重用它,因此,我们将创建一个新模板来渲染模式面板。在index.html文件中创建名为confirmDialog的新模板,如代码清单1-18所示。

代码清单1-18 为模式面板创建新模板

一旦掌握了 Bootstrap 标记,实现模式面板就很简单,但在这里处理起来却有点复杂。面板包含了标题区域、主体区域以及页脚区域。对记事本应用程序而言,需给模式面板添加文本信息以提示用户确认是否删除事项,并要提醒用户操作不可回退。在页脚区域添加两个按钮:一个用来取消删除操作,另一个执行事项删除。取消按钮调用控制器的doCancelDelete动作;删除按钮调用控制器的doConfirmDelete动作。

要显示模式面板,只需添加一行代码来通知notes模板在哪里渲染confirmDialog新模板。通过{{partial}}来构造该行代码,如代码清单1-19所示。

代码清单1-19 渲染confirmDialog新模板

{{partial}}表达式找出名称匹配其第一个参数的模板,之后将该模板渲染进DOM中。

最后的任务是在Notes.NotesController中实现doCancelDeletedoConfirm Delete动作,修改控制器的代码,如代码清单1-20所示。

代码清单1-20 实现doCancelDeletedoConfirmDelete动作

我们来分析一下这段代码,首先实现了doCancelDelete动作,处理逻辑很简单:重置控制器的noteForDeletion属性为null,然后隐藏模式面板。

doConfirmDelete动作则更复杂。在重置控制器的noteForDeletion属性之前,从该属性中获取想要删除的事项。接下来,确认控制器有一个实际要删除事项的引用。一旦得到确认,就通知Ember Data从存储器中删除该记录。这仅是标记事项为删除状态,要执行真正的删除操作,需要对该事项对象调用save()方法,该方法执行完毕,事项即从浏览器本地存储中删除,并从页面的事项列表中移除。

在关闭模式面板及完成doConfirmDelete动作之前,需要考虑这样一个场景:如果用户删除一条正在查看的事项会发生什么事情?有以下两种选择。

在本应用中,我认为第二种选择更合适。

可以发现控制器的第二行添加了一个needs属性。这种方式用来告知Ember.js将来某个时刻需要访问Notes.NotesNoteController的实例。有了该属性,以后就能够通过controllers.notesNote属性来访问Notes.NotesNoteController。这将允许你比较删除事项与用户正在查看事项的id属性(可能的话)。如果属性匹配,则通过transitionToRoute()函数将用户转换到notes路。

试一下删除事项功能,在浏览器中重新加载整个应用程序,并尝试删除一条事项(如图1-13所示)。

图1-13 完成后的应用,显示删除模式面板

现在,我们完成了本章记事本应用程序的相关内容。随着第2章对Ember.js核心概念的深入研究,你还将继续完善记事本应用程序。

本章快速浏览了Ember.js的基础构建模块,并介绍了Ember.js应用程序的核心概念。通过本章的学习,我希望你对Ember.js框架有了一个较好了解,同时理解其存在价值及其为Web开发者提供的便利。

作为Ember.js的介绍性章节,本章用一个简单Web应用的开发过程来引导你,过程中触及了框架的重要方面。开发记事本应用程序的目的是为了通过并不复杂的代码,尽可能多地将Ember.js基本特性展示在你面前。Ember.js的学习曲线比较陡峭,但它却能够给Web开发者带来莫大实惠,本章的小试牛刀,已隐约要引爆现代框架Ember.js的巨大威力……

下一章里,将复用并扩展本章编写的代码,以彻底理解Ember.js的核心特性。

http://emberjs.com/


本章涵盖的内容

本章将在第1章代码的基础上详细阐述Ember.js框架中最具特色的知识点。Ember.js最关键的设计目标之一就是提供完整、合理的默认实现以避免开发者必须自己创建大量样板代码。Ember.js通过默认设置来满足大多数Web应用的需要,并允许开发者在合适之处轻松覆写这些默认设置。有了这些完备选择,我们就可以大大降低编写各种Web应用程序的难度,从此不用过多纠结于数据如何从A传到B,也不用再老想着如何以清晰而高效的方式更新HTML元素,同时,还能够方便地集成第三方JavaScript框架。

如果你接触过诸如Objective-C、Adobe Flex以及JavaFX这些具备绑定功能的其他编程环境,那么在应用中运用观察者模式、约束变量以及数据自动同步等方法对你而言应该再熟悉不过了。否则,你得忘掉旧有编程习惯并接受Ember.js基本概念,因为这些概念将彻底改变你编写程序的思维方式。改变编程习惯以适应松耦合及异步编程思维方式,应该是学习高效运用Ember.js的最大难点。

图2-1列出了本章涉及的Ember.js功能模块——ember-application、ember-views以及Handlebars.js。

图2-1 本章涉及的Ember.js知识点

本章将进一步完善第1章创建的记事本应用程序。 更新代码放在index4.html和app4.js文件中。

注意


你可以自己编写源代码,或通过GitHub获取源代码:https://github.com/joachimhs/Ember.js-in-Action-Source/blob/master/chapter1/notes/js/app/app4.js

我们先从绑定这个核心特性入手,它是整个Ember.js框架的基础。

Ember.js框架基于相关联的几个特性来整合框架并提供其他特性。掌握绑定、计算属性及观察者模式的工作方式,是Ember.js程序员的必备技能。

最常见的任务可能就是不断重复编写Web应用代码请求服务器端数据,解析响应信息并调用控制器,确保视图在数据改变时同步更新。

然后,当用户使用这些数据的时候,还得在某种程度上同时更新浏览器缓存及视图数据,这样才能确保浏览器缓存数据与页面展示数据保持一致。

你可能已经为成百上千的用户案例编写过代码,但是大多数的Web应用程序仍缺乏一个网站级的底层结构以一种清晰而一致的方式来处理各种交互,这就导致开发者为每个应用、应用各层重复造轮子。通常的实现如图2-2所示。

图2-2 通常的数据同步实现

该模型假设你已经思考过要如何组织应用程序,并且已经在应用程序中实现了一个MVC结构。Ember.js的MVC模型与你以往开发Web应用时惯用的MVC模型稍有不同,但不用害怕,第3章将详细讲解Ember.js的MVC模型。

图2-2所示模型存在一个问题,开发者得自己实现一个结构来确保保存到服务器端的数据与展示给用户的数据是一致的。此外,还需为列出的6个步骤(步骤3~8)逐一编写代码。考虑一下各种边界情况。

这些只是需要在应用各个环节都要判别的几个例子。如果你打算在开发中实现通常的数据同步解决方案,那真是了不起!现在,你了解了在视图与控制器、模型与模型、客户端与服务器端之间同步应用数据的困难所在。而Ember.js凭其完整而健壮的绑定机制与MVC实现,特别适合在此类场景中发挥直接作用。Ember.js还提供了完整持久层Ember Data,我们将在第5章讨论它。

最简单的形式是,通过绑定方式告知应用程序“当变量A改变时,请确保同步更新变量B”。Ember.js的绑定可以是单向或双向的,两者的工作方式相同,但双向绑定无论哪个变量发生改变,都会在两个变量间保持同步。Ember.js中最常用的绑定类型可能就属双向绑定了,因为它是Ember.js的默认绑定结构,此外,编写客户端应用时也最可能需要双向绑定。

可以调用Ember.Binding.twoWayEmber.Binding.oneWay函数来明确声明一个绑定;在创建单向绑定时需要这么做。而大多数情况下,我们在对象属性声明里通过Binding后缀关键字来创建双向绑定。Ember.js的构造足够聪明,现实中很少出现需要手动实例化绑定对象的情况。因此,在第1章开发的记事本应用程序里,并不需要手动创建绑定。

然而,假设你打算在Notes.NotesController上跟踪我们选择了哪条事项,可以绑定一个属性(如selectedNote)到Notes.NotesController 的模型对象上。代码清单2-1修改了NotesController。

代码清单2-1 通过绑定同步两个变量

如果重新加载应用程序并打开浏览器控制台,将看到如图2-3所示的结果。

图2-3 控制台日志

Ember.js会输出应用程序使用的Ember.js、Handlebars.js以及jQuery版本号。当Ember.js实例化控制器和路由,Ember.js会把它们放进一个叫容器(container)的结构。你可以请求容器查找NotesController实例并检查selectedNote属性值。在控制台输入以下命令并回车,图2-4显示了结果。

图2-4 请求容器获取selectedNote属性值

selectedNote属性返回空值。这是预期结果,因为此时尚未选择事项。现在,选择一条事项并再次执行上条命令,图2-5显示了结果。

图2-5 选择一条事项,并请求容器获取selectedNote属性值

现在可以通过NotesControllerselectedNote属性获取所选事项了。注意,还可以通过调用get('selectedNote.id')获取所选事项的id属性。通过点记法可以深入对象继承链查找及更新属性值

尽管只在代码清单2-1中添加了一条语句,但Ember.js却帮你实现了以下特性。

接下来,我们添加一些代码行,以了解通过自动更新模板将数据绑定到视图的方式。

Ember.js默认使用Handlebars.js模板引擎。Ember Handlebars实现的一个关键点是无论何时将模板与底层数据联系起来,Ember.js都会在应用各层之间创建双向绑定。在第1章记事本应用程序开发过程中你已经了解了相关工作机制。

思考一下代码清单2-2里的notes/note模板代码。

代码清单2-2 重访notes/note模板

这个示例中包含了两种绑定,第一种是通过Handlebars表达式实现模板绑定;第二种是通过Binding关键字在自定义视图上实现属性绑定,与代码清单2-1类似。

重点关注一下Handlebars表达式{{name}}。即使是模板中的一个简单表达式,也蕴藏了大量实现细节。notes/note模板注入了对应的支持控制器上下文。这样,将数据填入模板的就是控制器NotesNoteController

在Ember.js内部实现里,这样将操作NotesNoteControllermodel属性。这可能看上去有点奇怪,但{{name}}{{model.name}}的速记法,{{model.name}}反过来又是{{controller.model.name}}的速`记法。实际上,你可以在模板里使用其中任何一种表达式打印事项名称。Ember Handlebars实现里的优雅之处在于无论属性何时发生改变,模板内容都会相应更新,Ember.js会确保视图同步并自动更新。例如,如果你在控制台改变了事项名称,Ember.js设置的观察者将确保视图及时更新。可以运行记事本应用并选择一条事项,然后在控制台运行以下命令试试看:

你将看到左边的事项列表和所选事项顶部信息里的事项名称已改变。如图2-6所示。

图2-6 在控制台改变事项名称

如果只想显示所选事项给用户,可以使用Handlebars的if辅助器。{{#if model}}语句确保控制器的模型属性在非nullundefined的情况下,才执行if辅助器里`的代码。通过少量的代码,Ember.js就可以实现原先需要手动为各个视图编码的功能。

在结束自动更新模板的讨论之前,我们再来做件事情:通过Ember Data创建全新的Note对象。在控制台执行以下命令:

首先通过容器,使用store:main关键字获取Ember Data存储器。之后,通过createRecord函数,传入事项id和名称创建一条新事项。当执行这条命令时,请注意,新事项即添加在事项列表的底部。

这时很容易想象,如果要在记事本应用与真实的服务器端应用之间同步记事本数据,会发生些什么。更新有效事项的数量、切换所选事项,甚至改变所选事项的内容,这些操作都能够通过服务器端的推送请求来发起。你只需编写少量的语句,就可以实现大批应用功能,完成所有这些任务的同时还可以保持应用程序结构的合理性。

Ember.js默认的模板引擎Handlebars拥有集成到Ember.js的大量特性,第4章会详细介绍Handlebars。

你可能想知道如何处理实际数据与显示数据不匹配的情况。类似的,如果要显示的、或者依赖if/each辅助器的数据比较复杂,你该怎么做?这些场景正是计算属性的用武之地。

计算属性是一个函数,其返回一个从其他变量或表达式(也可以是其他计算属性)获取的值。计算属性与普通JavaScript函数之间的区别在于,Ember.js将计算属性看作其真正的属性。因此,就可以在计算属性上调用get()set()等`方法,以及绑定/观察它们(观察者概念在本章稍后介绍)。通常,在模型对象中定义计算属性,并在控制器和视图中使用它。

目前的记事本应用程序还没用上计算属性,但如果你想增强应用程序功能,在界面左边的事项列表中显示每条事项前20个字符的内容,那么,就请忘掉使用jQuery选择器以及在视图某处注入/替代信息的方式,现在可以通过Ember.js的计算属性来实现。

接下来在Notes.Note类中创建一个名为introduction的计算属性,用来返回每条事项前20个字符的内容。修改Notes.Note模型类,如代码清单2-3所示。

代码清单2-3 创建introduction计算属性

Ember.js在这里实现了大量功能。首先,Ember.js能够智能感知对计算属性返回值进行计算的时机和频率。只有用到计算属性,才会计算其返回值。这对性能而言非常有利,因为应用程序不用浪费时间计算那些有可能从不在界面上显示的大量属性。

了解如何将一个函数定义为计算属性的方式,将有助于了解对计算属性进行计算的第二层意思。property('value')意思是“无论this对象的value属性何时改变,都对计算属性的返回值进行重新计算”。因此,当在文本区域字段输入内容到事项的value属性,可以看到界面立即发生了更新,以反映这种变化。

到目前,尚未将introduction计算属性添加到模板中去,我们将用它来预览每条事项。代码清单2-4扩展了notes模板,以在事项列表显示value属性前20个字符的内容。

代码清单2-4 在notes模板中添加introduction计算属性

我们只在模板中添加了一行代码。如果当前Note模型的introduction属性非null,则其内容长度大于0,那么就打印新行,之后输出introduction属`性自身内容。修改后的记事本应用程序如图2-7所示。

图2-7 事项名称包含了显示introduction属性值的附加行

前面提到可以将计算属性当作setter来用。但如何设置一个从其他关联属性获取的值呢?在代码清单2-5中,一个名为Notes.Duration的对象拥有一个durationSeconds属性。尽管对后台服务而言以秒为单位存储时长是有意义的事情,但对于用户来讲,看到秒数就显得很不直观。因此,我们应该将秒数转换成以冒号隔开的“时:分:秒”格式的字符串。

代码清单2-5 将计算属性当作setter

要注意的第一件事就是计算属性函数现在包含了两个参数:keyvalue。你可以通过检测这两个参数来判断函数是作为getter还是setter来调用。根据请求,对于value参数,null可能有意义也可能无意义,因此不能用是否为null来判断。你希望输入有效格式时才更改durationSeconds属性,因此,先将输入值各部分分解到一个数组中,然后验证输入值。如果输入合法,开始将HH:MM:SS格式字符串转换成秒数,之后用新值更新对象durationSeconds属性。`

计算属性函数的第二部分是getter,如你预料的,与setter部分相反。其首先获取durationSeconds属性值,之后生成durationString并`返回。

你大概已经想到了,通过简单地绑定到文本区域字段元素,以这种方式使用计算属性来填充输入字段是相当简单的。Ember.js只需关注将秒数自动格式化为易读的时长,而反过来当用户更改文本区域字段中的时长时也一样。

前面还提及计算属性可以通过观察者来计算它的值,但你还未能一睹Ember.js观察者模式的庐山真面目,接下来就来了解它。

从概念上讲,单向绑定包含一个观察者与一个setter,双向绑定包含两个观察者与两个setter。观察者在不同语言和框架中有不同的称谓和实现。在Ember.js里,一个观察者就是一个JavaScript函数,无论其观察的变量何时更新,都会触发该函数的调用。在绑定较难实现或希望在某个值发生改变时执行某个任务的场景中,比较适合使用观察者模式。

要实现一个观察者,请使用.addObserver()方法,或者使用内联的observes()方法后缀。代码清单2-6展示了观察者的一种使用方式。基于控制器的content数组的项数,启动并停止计时器。

代码清单2-6 观察控制器内容长度并控制计时器

观察者contentObserver是一个普通的JavaScript方法,其首先获取控制器的content数组。如果存在content数组项且计时器未启动,则创建一个新的计时器,时间间隔设为15 000 ms。计时器里将遍历每个数组项,并通过自定义的reload()方法来重新加载数组项数据。如果content数组为空,则停止已有计时器。

要让contentObserver函数成为一个观察者,我们添加了内联的observes()方法,并添加被观察属性。

可以使用替代的addObserver()方法来重构上面的观察者。函数的主体代码部分都是一样的,但声明稍有不同,如代码清单2-7所示。

代码清单2-7 通过.addObserver方法创建观察者

虽然这是一种创建观察者的可能方式,但我发现如代码清单2-6的内联方式更清晰并更具可读性。我也习惯为观察者函数添加Observer后缀,当然这不是必须的。

有时你可能想观察数组项的属性,在记事本应用里,Notes.NotesController有一个Ember对象组成的content数组,该对象有两个属性:namevalue。为了观察每个对象的name属性,可以使用@each来遍历被观察属性,如代码清单2-8所示。

代码清单2-8 通过@each观察数组项的改变

有了Ember.js对象模型,本章的所有功能才可能得以实现,接下来具体了解Ember.js对象模型。

Ember.js扩展了JavaScript默认对象类的定义,以构建一个更强大的对象模型。此外,Ember.js还支持基于混入类的方式,在模块与模块之间、应用与应用之间共享代码。

你可能想了解Ember.js是怎样知道某个属性发生改变的,以及它何时触发观察者函数和绑定对象。同时你可能还注意到,Ember.js总是要求使get()set()方法来获取或修改Ember.Object子类对象的属性。当在一个属性上调用set()方法,Ember.js就会检查更新值与对象原有属性值是否不同,如果不同,Ember.js就会触发绑定对象、观察者函数或者计算属性函数。

尽管刚接触Ember.js时使用get()set()方法看起来可能有点不习惯,但这却是确保Ember.js统一智能处理观察者、绑定、计算属性以及DOM操作的重要机制。实际上,使用get()set()方法是Ember.js解决涉及多个DOM元素更新及绑定性能问题的重要基础。

创建自定义Ember.js对象通常有两种方式,要么通过extend()方法扩展其他Ember.js对象并添加自定义功能,要么用create()方法创建一个Ember.js对象实例。无论选择哪种方式,应用程序中创建的每个对象都以某种方式扩展自Ember.Object类,Ember.Object是基础类,其确保Ember.js能够提供本书涉及的所有功能。

想像一下,不通过扩展DS.Model来实现Notes.Note模型对象,绕开Ember Data,自己实现一个Notes.Note模型,如代码清单2-9所示。

代码清单2-9 创建Notes.Note对象

不像使用Ember.Object.create()创建一个匿名Note实例,这里通过扩展Ember.Object类创建了一个显式的Notes.Note类。注意以下两点。

要创建Notes.Note类的新实例,请使用create()方法:

这时你可能会认为,我们仍看不到这种方式优于匿名Ember.Object.create()实现的地方。但是,对于Ember.js应用中用到的所有数据类型和对象而言,显式定义类通常是个好主意。即使这么做需要更多的代码,但你清晰地展示了实例化一个对象的意图,并且可以明确分隔各个业务模型对象。最终代码可读性强、更易于维护,而且容易测试。

明确定义应用程序对象还使得在正确的地方添加观察者、绑定以及计算属性变得容易,并确保应用快速应变。考虑这样一个场景,原先后台应用为每个Note对象的value属性提供了纯文本实现方式,以将属性值编码成Markdown格式,但现在要编码成HTML格式了。如代码清单2-10所示,如果在应用程序中显式定义了Notes.Note,就很容易添加这个功能。

代码清单2-10 添加计算属性,将Markdown格式转换为HTML格式

一旦成功实现了Notes.convertFromMarkdownToHtml函数,接下来就可以在应用视图模板中将原先使用value的方式更改为使用新增的计算属性htmlValue,这个改变显然易如反掌;简单更改视图Handlebars模板(见代码清单2-2)的代码如下:

这样,记事本应用程序就改好了。接下来具体了解Ember.js MVC模式下各层之间如何同步数据。

本章前面我们看到了一个数据同步模型,其可以确保数据始终在客户端和服务器端之间保持同步(见图2-2)。在这个模型中,应用程序8个步骤中就有6个需要显式跟踪并关注应用程序内部状态。反观Ember.js框架如何使用绑定、控制器以及清晰的模型层来尽可能多地自动化样板代码,孰优孰劣就很清楚了。图2-8展示了一个改进后的概念模型。

图2-8 Ember.js数据同步实现

Ember.js方式的步骤减少了,这是因为我们把更多的样板代码留给了Ember.js框架,而你仍完全掌控着应用的数据流。与前面模型相比,主要的差异在于Ember.js代码方式以尽可能接近“源头”(source)的方式明确表示各种操作,也就是在合适之处通知Ember.js,以合适方式将应用程序各层联系在一起。

如你所见,Ember.js提供了合理的默认实现方式,同时,只要有其他方式更适合特定使用场景,就可以用该方式覆写这些默认实现。这种特性将不断帮助你实现目标——编写雄心勃勃的Web应用程序,打造属于未来的强大Web应用。

本章介绍了一些你可能不太熟悉的Ember.js新概念,以及它不同以往的处理方式。

我们讲述了绑定功能,以确保应用程序各层间数据更新与同步。同时还介绍了自动更新模板,该特性有助于提升开发效率,并使得用户界面总能及时反映模型对象里的数据变化。

拥有了创建、修改及删除事项的能力之后,紧接着我们添加计算属性以增强应用UI效果。接下来还了解了观察者角色,你在记事本应用中创建了一个观察者以观察数组里的属性变化。

之后,我们阐述了Ember.js对象模型,并讨论如何在标准Ember.js对象或自己应用的自定义对象基础上创建复杂对象。

最后,对Ember.js数据同步实现、本章开头通常的服务器端应用实现进行了比较。

前面多次提及模型-视图-控制器(MVC)模式,但我们尚未深入了解Ember.js如何帮助开发者创建真正MVC架构的Web应用程序,下一章就来讨论这个主题。

前面的原文阐述有误。演示的命令应该是Notes.__container__.lookup('controller:notes').
get('selectedNote')Notes.__container__.lookup('controller:notes').get
('selectedNote.id'),这才能够跟文字上下文匹配起来。

原文应该有误,实际应该不是SelectedNoteController.model,而是SelectedNote


相关图书

HTML+CSS+JavaScript完全自学教程
HTML+CSS+JavaScript完全自学教程
JavaScript面向对象编程指南(第3版)
JavaScript面向对象编程指南(第3版)
JavaScript全栈开发
JavaScript全栈开发
HTML CSS JavaScript入门经典 第3版
HTML CSS JavaScript入门经典 第3版
HTML+CSS+JavaScript网页制作 从入门到精通
HTML+CSS+JavaScript网页制作 从入门到精通
JavaScript重难点实例精讲
JavaScript重难点实例精讲

相关文章

相关课程