《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

本节书摘来自异步社区《Ember.js实战》一书中的第1章,第1.4节,作者:【挪】Joachim Haagen Skeie(乔基姆•哈根•斯基)著,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.4 第一个Ember.js应用程序:记事本应用

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

提示
 我使用JetBrains WebStorm来编写JavaScript应用,但这对你来说不是必需的。
你将通过编写一个简单的记事本Web管理应用来一探Ember.js。该应用功能如下。

  • 添加新事项——应用提供了用户添加事项的专用区域。
  • 选择、查看及编辑事项——界面左边显示事项列表。用户一次选择一个事项,并可在右边查看、编辑内容。
  • 删除已有事项——用户可以删除所选事项。

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

  • Ember.js 1.0.0
  • Handlebars.js 1.0.0
  • jQuery 1.1x
  • Twitter Bootstrap CSS
  • Twitter Bootstrap Modal
  • Ember Data 1.x Beta版
  • Ember Data Local Storage Adapter

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

从头开始。

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

1.4.1 记事本应用开发起步
在index.html中加载应用依赖的各个程序文件,如代码清单1-1所示。

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

模板的放置位置

简单起见,可以在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 >标签加载记事本应用程序的实现代码,在本章剩余章节中我们将实现它。

1.4.2 创建命名空间与路由器
本节将通过基本Web应用布局来创建记事本应用程序的第一部分内容。

注意
 不管是自己编写还是从GitHub链接下载代码,这里的代码文件都命名为app1.js。
Ember.js应用程序首先需要一个命名空间来容纳它。对于记事本应用程序,使用Notes作为命名空间。

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

  • application路由;
  • application控制器;
  • application视图;
  • application模板。

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

运行程序

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

如果已安装Ruby

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

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

(3)gem启动后,在浏览器打开链接,运行应用程序。

如果已安装Python

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

(2)命令执行并启动后,在浏览器打开链接,运行应用程序。
你已经能够在屏幕上显示一些文字了,接下来继续设置记事本应用程序的剩余部分。在继续之前,我们先来思考一下应用程序可以有哪些状态(路由)。

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

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

  • Notes.NotesRoute
  • Notes.NotesController
  • Notes.NotesView
  • notes模板
  • Notes.NotesNoteRoute
  • Notes.NotesNoteController
  • Notes.NotesNoteView
  • notes/note模板

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

注意
 Ember.js创建了上述列表中每个对象的默认实现,你只需覆写需要修改的内容。因此,记事本应用程序并不用实现所有列出的类。
现在已定义了应用程序的每个路由,接下来还需要通知应用程序每个路由可使用哪些数据。代码清单1-4所示的程序定义了notes与notes.note路由。

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

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

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

我们在id为notes的< 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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

createNewNote动作的含义很清楚了。

  • 验证新事项的名称至少包括两个字符。
  • 确保不存在同名的重复事项。
  • 一旦新事项名称通过验证,就创建新事项并将其保存到浏览器本地存储当中去。

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

1.4.5 选择并查看单条事项
记事本应用程序都具备输入事项内容的能力,本节最后面将实现该功能。

注意
 本节完整源代码可以通过 GitHub 链接下载,相关文件为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实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

  • 创建一条事项并添加到事项列表中。
  • 查看添加到应用程序中的所有事项。
  • 选择一条事项,转换到新路由,并更新URL。
  • 查看及编辑所选事项的内容。
  • 在查看特定事项时刷新应用,将初始化应用程序,并仍会显示同一条事项。

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

  • 应用程序无法标识当前所选事项。
  • 无法保存所选事项的更改。

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

代码清单1-20 实现doCancelDelete和doConfirmDelete动作

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

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

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

  • 通知用户不能删除正在查看的事项。
  • 将用户转换到notes路由上。

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

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

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

《Ember.js实战》——1.4 第一个Ember.js应用程序:记事本应用

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

上一篇:我的第一个flex应用(leopard)


下一篇:初识MySQL数据库