Atom 协议对让网络资源(比如新闻、社区站点和博客)的提供者能够通过 Web 连锁其内容。在 Atom 的典型应用中,内容提供者一般会连锁一个文件或一个 Web 提要,并让其在 Web 上可用。提要的表示在 Atom Syndication Format 中定义,它提供了新添加的资源的一个概要。发布了的提要可随后由 Atom 客户软件使用,例如由博客阅读器使用,后者使用 Atom Publishing Protocol 发现新添加的内容并加以呈示。
本文通过展示如何开发一个基于 Ajax 和 Atom 的博客阅读器来进一步丰富您这方面的知识(在本期文章中,将会开发阅读器的视图和控制器组件)。您将使用 Dojo 工具箱来开发这个应用程序,而此应用程序使用 Atom Publishing Protocol 与后端 Atom 提要通信。此外,您还会使用 Dojo 存储包来保存提要订阅数据。
博客阅读器应用程序基于的是 Dojo 工具箱,您可能会奇怪我们为何选用了 Dojo 而不是其他的工具箱。Book of Dojo(参见 参考资料)给出了其中的几个原因,但出于本次练习的目的,我们只列出了驱使我们使用 Dojo 的两个简单原因:
- 垂直集成和完成:与其他开源 Ajax 工具箱相比,Dojo 提供了最为全面和集成化的组件库。
- 黑盒重用:Dojo 小部件机制让开发人员可以从小部件组建新的应用程序,而无需知道其 “内幕”。这就让复杂 Ajax 应用程序的创建变得比较简单。
|
Dojo 具有分层架构,其中的每一层都添加了更为先进的功能:
- Dojo 核心层包含了在很多工具箱(比如 IO 和 DnD)中都存在的 基本的 Ajax 特性、跨浏览器兼容性、基本 DOM 处理等。
- 在 Dojo 核心层之上是 DIJIT 层,提供小部件系统以及很多小部件。
- 最后一层是 DOJOX,包括各种扩展,比如离线存储和跨浏览器矢量图。
Dojo 架构的各层都提供了一种综合的集成式工具箱,可以满足大多数开发需求。
Dojo 小部件是基于 Ajax 的 UI 组件,可用一行 HTML 代码重用。比如,清单 1 给出了一个可扩展的 title 窗格小部件是如何在 HTML 页面中实例化的:
清单 1. 在 HTML 页面中 Title 窗格小部件的实例化
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <script type="text/javascript" src="../../dojo/dojo.js" djConfig="parseOnLoad: true, isDebug: true"></script> <script language="JavaScript" type="text/javascript"> dojo.require("dijit.TitlePane"); dojo.require("dojo.parser"); </script> <style type="text/css"> @import "../../dojo/resources/dojo.css"; @import "../themes/tundra/tundra.css"; </style> </head> <body class="tundra"> <div dojoType="dijit.TitlePane" title="This is the title" style="width: 300px;"> And this is the content, clicking the title will expand/collapse me. </div> </body> </html> |
正如您所见,此页面首先导入核心 Dojo 库。一旦 Dojo core 加载完毕,我们就可以使用 dojo.require()
加载 TitlePane
小部件,我们随后会在 body 标记中引用这个小部件。
图 1 显示了运行中的这个 TitlePane 小部件,即清单 1 所示代码段的结果:
图 1. 运行中的 TitlePane 小部件
InformationBox — 开发一个简单的 Dojo 小部件
小部件由可提供小部件功能的 Dojo JavaScript 类实现。对于具有复杂 UI 的小部件而言,还需要提供一个 HTML 段以充当小部件 UI 模板。要消除小部件背后的神秘性,让我们不妨来开发一个非常简单的、可显示信息消息的小部件。这个小部件将让用户可以给一个信息段加标题并提供类似于图 2 所示的输出:
图 2. 运行中的 InformationBox 小部件
清单 2 给出了我们的这个信息消息小部件 dwsample.Info
:
清单 2. InformationBox 小部件实现
if(!dojo._hasResource["dwsample.Info"]) { dojo._hasResource["dwsample.Info"] = true; dojo.provide("dwsample.Info"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.declare( "dwsample.Info", [dijit._Widget, dijit._Templated], { title: "", // The widget content template templateString: [ "<div id='${id}' class='dijitTitlePaneTitle'>", " <div>", " <span dojoAttachPoint='titleNode' style='font-size:1.1em; margin: 1em;'></span>", " </div>", " <div dojoAttachPoint='containerNode' class='dijitTitlePaneContent'></div>", "</div>" ].join('/n'), postCreate: function(){ this.setTitle(this.title); }, setTitle: function(/*String*/ title){ this.titleNode.innerHTML = title; } }); } |
如清单 2 所示,小部件实现开始于 dojo.declare()
函数。此函数接收 JSON 格式的小部件定义并初始化一个新的小部件。
这个小部件的一个重要之处是 templateString
。它提供了一个 HTML 段,可充当由小部件生成的 HTML 内容的模板。该模板由 dojoAttachPoint
属性分散,指导 Dojo 将标记过的 DOM 元素作为所生成模板的成员附加。这就让小部件可以直接访问 DOM 节点,无需进行 DOM 处理(如方法 setTitle
所示)。特殊附加点 containerNode
标记特殊的 DOM 节点,小部件主体应该在该节点中添加。
新的小部件的使用十分简单,如清单 3 所示:
清单 3. InformationBox 小部件的使用
<div dojoType="dwsample.Info" title="Do not panic, here is what you need to do" style="width: 500px;"> At first, developing a Dojo widget looks like a chore, but it is not! Here is what you should do: <ol> <li>Create a JavaScript file with the name of the widget and in a directory structure known by Dojo</li> <li>Extend the Dojo widget base classes</li> <li>...</li> </ol> </div> |
由于信息消息是一种非常简单的小部件 — 它不使用事件,也不触及新方法 — 它应该能展示出小部件是如何运作的。
有了 Dojo 的基础知识之后,就可以转而看看 Atom 及其在博客阅读器的使用了。
|
术语 Atom 指的是针对 Web 资源定义提要格式(用来表示)和协议(用来编辑)的一对标准。Web 资源包括 Weblogs、在线日志、Wikis 和类似内容:
- Atom Syndication Format (ASF) 在 RFC 4287 标准化,它定义了 Atom 资源的表示格式。
- Atom Publishing Protocol (APP) 还只有草案,定义了如何使用 REST 来管理 Atom 资源。
请注意,虽然 Atom 起初就考虑了博客功能,但它还有更通用的使用方式,原因是它还允许创建/读写/更新/删除(create/read/update/delete,缩写为 CRUD)Web 资源。
ASF 定义了资源的 XML 表示,这些资源使用连锁了的项目集合(即提要)及其所含项目(即条目)承载信息:
- 一个提要是惟一确定的、加了标题和进行了时间戳处理的连锁资源条目的集合。博客通常由一个或多个提要表示。
- 一个条目是惟一确定的、进行了时间戳处理的资源,具有元数据,比如标题、摘要和类别。提要的资源内容可以是内嵌文本,可以是二进制 base64 编码的 blob,也可以是由 URI 指定的外部内容。博客条目通常都由此博客提要内的一个条目表示。
要想让 ASF 更具体,让我们不妨来看看清单 4 中的这个简短的单条目的 Atom 提要文档:
清单 4. 示例 Atom 提要
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title>My Blog</title> <link href="http://example.org/myblog/"/> <updated>2005-11-13T16:25:05Z</updated> <author> <name>John Smith</name> </author> <id>urn:uuid:50a70c74-d399-11d9-b93C-0004545e0af6</id> <entry> <title>What is Atom?</title> <link rel="alternate" href="http://example.org/myblog/1"/> <id>urn:uuid:1234c670-cfb8-4ebb-aaaa-80da352efa6a</id> <updated>2005-11-13T16:25:05Z</updated> <summary> The term Atom refers to a pair of standards</summary> <content type="text/html"> The term Atom refers to a pair of standards which are primarily used in the context of Web content syndication. The emergence of Atom was motivated ... </content> </entry> </feed> |
如您所见,提要表示中附带了一些特定于提要的信息,后跟任意数量的条目。
Atom 提要信息包括一些强制元素以及一些建议的可选元素。强制的提要元素包括:
- Title:为提要提供可读的标题。一般地,该标题与相关网站的标题相同。
- Updated:指示提要被修改的最近时间,格式可以由发布者选定。
- ID:给出提要永久通用的惟一标识符。
Atom 条目元素也需要包括 Title、Updated 和 ID 这些子元素,这与提要元素十分类似。实际上,如果没有附加元素,条目根本无法实现其目的,比如:
- Link:到与此条目相关的资源的链接。关系类型在
rel
属性定义;例如,值alternate
表明此链接提供了这个条目元素所描述的资源的另一种表示。 - Content:包含实际资源表示或者到此表示的链接。
- Summary: 提供此条目的简短摘要。
如果没有提供任何条目内容,那么就必须至少要有指向此内容的可选链接。
Atom Publishing Protocol (APP)
APP 是基于 REST 的一种协议,用来发布和编辑 Web 资源。这意味着 APP 不仅可以应用于 Atom 资源,还可以应用于其他与 Web 相关的资源,比如图像、电子邮件内容等。随着 RESTful 协议的发展,APP 也利用了 HTTP 协议(包括方法、报头和状态码等)对资源执行 CRUD。我们这里的这个博客阅读器使用了 APP 与 ATOM 提要和条目交互。
APP 引入了集合 的概念,集合是可作为整体或部分检索的一组资源。在 Atom 连锁领域,这些集合和资源被映射到 Atom 提要和条目资源,其表示按 ASF 编码。除了集合和资源之外,APP 还定义了可用来发现集合的工作区和资源(但这超出了本文所讨论的范围)。
现在,介绍了足够的背景知识后,就可以开始构建博客阅读器了!
|
我们的博客阅读器应用程序(如图 3 所示)可管理用户感兴趣的博客提要列表(其他 URI):
图 3. 博客阅读器应用程序
提要可被添加到该列表,也可从列表中删除。在我们的应用程序中,用户可以通过在对话框中输入感兴趣的提要的标题和 URI 来订阅此提要(如图 4 所示):
图 4. 添加提要
当用户想要查看特定提要中的内容时,他或她只需在提要组合框中选择此提要。在后台,博客阅读器应用程序遵循 APP 并将 HTTP GET 请求发送给此提要 URI。阅读器之后检查返回的 HTTP 状态码,如果成功(状态码为 200),它就会从 HTTP 响应负荷中检索结果 Atom 条目集合。此时,阅读器就可以查阅所返回的提要,并将它们展示给用户。这些条目被一起列于应用程序的左侧,代表提要的提要标题也列于此列表(参见图 5):
图 5. 列出条目
此时,用户就可以通过在一个条目上单击,从此列表中选择该条目,阅读器会使用如下原则尽量显示该条目的内容:
- 如果条目中包括具实际资源表示的
content
元素,就使用它。 - 否则,如果
summary
元素可用,就使用它。 - 如果 content 和 summary 元素都没有,就显示到此内容的链接。如果初始文章链接也检测不到,就会给出到可选的条目资源表示的链接,正如在
link
元素中通过让rel
属性的值为alternate
所指定的那样。当用户跟随此链接时, 新的浏览器窗口就会因此链接而打开。
这个阅读器应用程序遵循的是 MVC(model view control)设计模式。本文后面的章节将会涵盖阅读器视图和控制组件的实现(而实现所使用的模型和相关技术则会在后续文章中介绍)。注意在我们的 MVC 实现中,采取了一种简便性重于设计的实用方式。因此,视图和控制的耦合度可能不符合最优设计。
|
博客阅读器视图通过利用了 Dojo 的单一 HTML 文件实现。此视图包括三个部分,其大致骨架如清单 5 所示:
清单 5. View 组件实现
<html> <head> <!-- Section 1, Dojo and application imports --> </head> <body class="tundra"> <!-- Section 2, blog reader UI --> <fieldset class="feed-definition-area"> Select Feed: ... </fieldset> <hr> <div class="feed-area"> <div dojoType="dijit.layout.SplitContainer" ... </div> </div> <!-- Section 3, dialog box definitions --> <div dojoType="dijit.Dialog" id="add-feed-dlg" ... </div> <!-- Progress dialog --> <div dojoType="dijit.Dialog" id="progress-dlg" title=""> ... </div> <div dojoType="dijit.Dialog" id="error-msg-dlg" ... </div> </body> </html> |
- 第一部分导入整个应用程序都会用到的各种 JavaScript、CSS 和小部件。
- 第二部分构造主要的阅读器 UI。
- 第三部分定义整个阅读器实现都会用到的各种对话框。
让我们来详细看看此应用程序的各个部分。
清单 6 所示的是此应用程序的整个导入部分:
清单 6. 导入部分
<head> <title>Blog Reader</title> <script type="text/javascript" src="./reset-styles.js"></script> <style type="text/css"> @import "../dojoAjax/dojo/resources/dojo.css"; @import "../dojoAjax/dijit/themes/tundra/tundra.css"; @import "css/blogreader.css"; </style> <script type="text/javascript" src="../dojoAjax/dojo/dojo.js" djConfig="parseOnLoad: true"></script> <script type="text/javascript"> dojo.require("dijit.Dialog"); dojo.require("dijit.util.manager"); dojo.require("dijit.form.Button"); dojo.require("dijit.form.Textbox"); dojo.require("dijit.layout.SplitContainer"); dojo.require("dijit.layout.ContentPane"); dojo.require("dojo.parser"); </script> <script type="text/javascript" src="blog-reader.js"></script> </head> |
导入部分的开始先是导入 JavaScript 文件 reset-styles.js
,它将各种标记的 CSS 属性重设为熟知的默认值。样式重设后,就会导入用来设置此应用程序观感的各种 CSS 文件。我们首先导入 Dojo CSS 文件(特别是设置各种 Dojo 小部件观感的 tundra.css
),然后向它们添加几个此博客阅读器特有的样式。
下一步是导入 Dojo 库。在本例中,Dojo 位于 dojoAjax 目录,导入的是核心 dojo.js
。各种所需的 Dojo 小部件也通过使用 dojo.require()
方法(由 Dojo core 提供)导入。
最后,导入存于 blog-reader.js
的博客阅读器逻辑(在本文稍后的部分会介绍 blog-reader.js
)。
现在,各种 CSS 和 JavaScript 文件都包括进来了,我们就可以开始使用 HTML 标记和 Dojo 小部件构建应用程序了,如清单 7 所示:
清单 7. 博客阅读器 UI 实现
<body class="tundra"> <fieldset class="feed-definition-area"> Select Feed: <select dojoType="blogreader.FeedsCombo" id="feed-combo" comboClass="feedsCombo"> </select> <button dojoType="dijit.form.Button" id="add-feed-btn" iconClass="addIcon"> Add Feed </button> <button dojoType="dijit.form.Button" id="clear-feed-btn" iconClass="cancelIcon"> Clean Feeds </button> </fieldset> <hr> <div class="feed-area"> <div dojoType="dijit.layout.SplitContainer" orientation="horizontal" sizerWidth="7" activeSizing="false" class="article-splitter"> <div dojoType="dijit.layout.ContentPane" sizeShare="30" sizeMin="20" style="overflow:auto"> <div id="articles-table-wrapper" style="visibility: hidden; width:96%"> <div dojoType="blogreader.ArticleList" id="article-list" listClass="articles-list" titleClass="articles-list-title"></div> </div> </div> </div> <div dojoType="dijit.layout.ContentPane" sizeShare="70" style="overflow:auto"> <div id="article-content-pane" class="article-content-pane"> <div id="article-title-id" class="article-title"> </div> <div id="article-content" class="article-content"> </div> </div> </div> </div> |
这一部分首先需要注意的 Dojo 依赖项是 tundra
类在主体内的使用。Dojo 使用 CSS 实现了 可视性主题。将主体类设为 tundra
就启动了对 Dojo tundra 主题的使用。
我们所创建的第一个 UI 元素是位于此应用程序的 UI 最顶部的提要选择控件。此提要选择控件通过组装 HTML 选择控件和两个 Dojo 按钮创建。此外,本部分中还有如下几个方面值得注意:
- 我们实现了一个 Dojo 小部件来包装选择控件。用我们自己的小部件来包装选择控件让我们能够将所有与此提要集合相关的表示逻辑(添加和删除提要)都放到同一个地方。
- 我们无需在 markup 中直接设置从视图到控制器的回调。在本文稍后的章节中,我们将通过编程的方式来绑定控制器 JavaScript 方法和控件事件处理程序。
- 我们用图标装饰了按钮(由 CSS 类决定)。
接下来,我们将构造主要区域,所选择的提要将在该区域呈现。提要展示区域的主要目标是:
- 如果一个提要被选中,就在左侧展示条目列表。
- 如果某个条目被单击,就在右侧展示其内容。如果用户没有选择任何条目,就不展示任何内容。
为实现上述目的,我们使用了 Dojo 分离容器(split container),通过它可以控制浏览器空间,并将其分离成两个区域,每个区域都由 ContentPane
填充。ContentPane 又由 sizer 控件加以分离,该控件让用户能够缩放 ContentPane。每个 ContentPane 包含一个能封装内容(条目列表/条目内容)的 div
HTML 元素。使用这些 div
元素,很容易显示和隐藏内容(使用显示 CSS 样式)。
条目列表也作为自建的 Dojo 小部件加以实现。
第三个也是最后一个视图部分可实例化阅读器内使用的对话框。理想情况下,每个对话框都应该作为小部件加以实现。但是,为了简化,我们将对话框嵌入到我们的主 UI 并在控制器内处理其事件。
图 6 给出了 Add Feed 对话框,它是各种对话框中最为复杂的一个:
图 6. Add Feed 对话框
清单 8 给出了 Add Feed 对话框的实现:
清单 8. Add Feed 对话框实现
<div dojoType="dijit.Dialog" id="add-feed-dlg" title="Add Feed" closeNode="cancel-add-feed-btn" style="width: 400px;"> <form οnsubmit="return false;"> <table class="table-no-border"> <tr> <td> <label for="feed-name">Title: </label> </td> <td width="100%"> <div dojoType="dijit.form.TextBox" id="feed-name" type="text" required="true" value="" trim="true" autocomplete="on" classPrefix="noColors" class="text-box"></div> </td> </tr> <tr> <td> <label for="feed-url">URL: </label> </td> <td width="100%"> <div dojoType="dijit.form.TextBox" id="feed-url" type="test" required="true" value="" trim="true" classPrefix="noColors" class="text-box"></div> </td> </tr> </table> <fieldset> <button dojoType="dijit.form.Button" id="ok-add-feed-btn"> OK </button> <button dojoType="dijit.form.Button" id="cancel-add-feed-btn"> Cancel </button> </fieldset> </form> </div> |
Add Feed 对话框一个值得注意的有趣之处是从小部件和 HTML 标记实现此对话框非常容易:
- 通过使用
dijit.Dialog
小部件,我们就能够立即在现有的浏览器窗口中打开一个简单的、轻量的对话框。 - 通过用 HTML 标记和小部件填充
dijit.Dialog
可以决定对话框主体的外观。 - 通过使用
closeNode
属性可以很容易地实现取消动作。 通过这种方式,我们就无需实现 Cancel 按钮的处理程序。 - 该实现的其余部分由 Dojo 负责,它提供了很多 API,可以让我们显示和隐藏对话框及其内容。
|
我们的博客阅读器控制器侦听来自视图组件的事件流并通过在模型上操作对其进行响应。总的来说,UI 事件是:
- 新建提要选择:控制器需要加载和展示新选中的提要。
- 新建条目选择:控制器需要展示新选中的条目。
- 添加提要按钮激活:控制器需要弹出 Add Feed 对话框。
- 清除提要按钮激活:控制器需要删除提要列表。
- 在 Add Feed 对话框按下 OK 按钮:控制器应该取得所输入的标题和 URL 并使用它们来添加一个提要。
控制器与模型间的互动对 Ajax 新手来说有点不易理解 — 这种互动是异步的。通常,在浏览器内运行的 JavaScript 代码都是单线程的。这意味着如果想要让 UI 在模型通过网络通信的同时仍能保持激活和响应性,就应该采用异步模式。出于这个原因,控制器也会侦听如下的模型事件:
feedDataArrived
:读取提要后,由模型启用。一旦收到此事件,控制器就会知道提要数据是在模型对象中缓存并可在视图中展示。feedReadFailed
:通信失败时,由模型启用。一旦收到此事件,控制器就会知道模型没能与 ATOM 提要通信,需要显示一个错误消息。
接下来的章节将会带您亲历控制器实现并向您展示如何连接到视图组件以及如何使用 Dojo 对各种事件进行响应。
清单 9 显示了控制器的总体结构,重点放在 initialize()
方法及其执行的如下两个任务:
- 通过 UI 组件的 ID 定位这些组件
- 为模型和 UI 中的各种事件分配侦听器
清单 9. 绑定到 UI 和 Model 组件
var BlogController = { mHideProgressFunction: null, mFeedsCombo: null, mAddFeedDlg: null, mFeedURLTextbox: null, mFeedTitleTextbox: null, mArticleWrapper: null, mProgressDlg: null, mProgressDlgMsg: null, mContentDiv: null, mContentTitle: null, mContentPane: null, mErrDlgContent: null, mErrDlg: null, mArticleList: null, // Initialize the page initialize: function() { this.mFeedsCombo = dijit.byId("feed-combo"); this.mArticleList = dijit.byId("article-list"); this.mAddFeedDlg = dijit.byId("add-feed-dlg"); this.mFeedURLTextbox = dijit.byId("feed-url"); this.mFeedTitleTextbox = dijit.byId("feed-name"); this.mArticleWrapper = dojo.byId("articles-table-wrapper"); this.mProgressDlg = dijit.byId("progress-dlg"); this.mProgressDlgMsg = dojo.byId("progress-msg"); this.mContentDiv = dojo.byId("article-content"); this.mContentTitle = dojo.byId("article-title-id"); this.mContentPane = dojo.byId("article-content-pane"); this.mErrDlgContent = dojo.byId("error-msg-content"); this.mErrDlg = dijit.byId("error-msg-dlg"); dojo.connect(BlogModel, "feedDataArrived", this, this.feedDataArrived); dojo.connect(BlogModel, "feedReadFailed", this, function(errMsg) { this.hideProgressDlg(); this.cleanFeedUI(); this.showErrorMsgDlg(errMsg); this.mFeedsCombo.setValue(""); this.mArticleWrapper.style.visibility = "hidden"; }); BlogModel.initialize(this); this.populateFeedsCombo(); // connect add feed/clean feeds to functions dojo.connect(dojo.byId("add-feed-btn"), "onclick", this, function() { this.mAddFeedDlg.show(); this.mFeedTitleTextbox.focus(); }); dojo.connect(dojo.byId("clear-feed-btn"), "onclick", this, this.cleanFeeds); // dialog box callbacks dojo.connect(dojo.byId("ok-add-feed-btn"), "onclick", this, this.createFeed); // Listen to combo box selection changes dojo.connect(this.mFeedsCombo, "onSelectionChanged", this, function() { this.readFeed(this.mFeedsCombo.getSelectedValue()); }); // List selection changes dojo.connect(this.mArticleList, "onSelectionChanged", this, this.updateArticleContent); }, createFeed: function(event) { // Create a new feed reference, add it to the model and // to the feeds combo }, cleanFeeds: function() { // erase all feed references from both the model and the view }, // Updates the page content according the feed xml feedDataArrived: function() { // Feed was read. Update the reader with the new information }, updateArticleContent: function() { // The selection in the articles list changed, update the article pane }, // Utility methods populateFeedsCombo: function() { // Populate the feeds combo box with the feeds registered in the model }, readFeed: function(url) { // Cleans the feed presentation UI and initiates fetching a new feed }, // Clean feed content cleanFeedUI: function() { // Zero the Feed presentation UI }, showErrorMsgDlg: function(errorMsgContent) { // Pops up an error dialog box with a specific message }, showProgressDlg: function(msg) { // Pops up the progress dialog box with a specific message } }; |
initialize()
方法显示了 Dojo 程序是如何通过使用组件 ID 和如下两个方法来定位和引用 UI 组件(HTML 元素和小部件)的:
dojo.byId(id)
:使用 HTML 元素的 ID 作为参数来查找此 HTML 元素。此方法返回 DOM 对象。dijit.byId(id)
:使用 Dojo 小部件的 ID 作为参数来查找此 Dojo 小部件。此方法返回代表此小部件的 JavaScript 对象实例。
如果您也像我们一样在控制器中使用了 byId()
方法,就会注意到我们是在 initialize()
方法中使用它们的,而且是在 member 变量中缓存返回结果的。这不仅是出于性能方面的考虑,更多的是为了可维护性。在视图中使用的 ID 可以经常在开发阶段更改,所以只查找这些 ID 一次(而且是在熟知的位置)就让维护这些代码变得比较容易。
我们的控制器使用了 dojo.connect
来侦听视图和模型中的事件。dojo.connect
允许 Dojo 应用程序向其他对象中的方法调用附加函数。例如,如下的代码段会将函数 foo
附加到对象 obj
中的方法 bar
:
清单 10. 侦听事件
dojo.connect(obj, "bar", expectedThis, foo); |
dojo.connect
的第三个参数是在被调用方法中作为 this
使用的对象引用。如果要在不同的对象上使用一个模板函数,这一点就会显得十分有用。
注意,由于 JavaScript 将函数视为数据,所以就能够将函数对象作为参数传递而无需对其进行特殊声明,正如如下的代码段所示:
清单 11. 附加函数对象
dojo.connect(this.mFeedsCombo, "onSelectionChanged", this, function() { this.readFeed(this.mFeedsCombo.getSelectedValue()); }); |
如果手头有很多简短的函数(比如只 2-3 行代码),除了能由 connect 方法使用之外别无它用,那么这个特性就会显得十分方便。在这种情况下,在使用之处声明此函数会有助于提高代码的可读性。
正如之前所看到的,Dojo 小部件驱使控制器使用事件。然而,事件处理程序是如何在 Dojo 小部件上进行回调的呢?答案很简单:Dojo 小部件是一些具有公共方法的简单 JavaScript 对象,全部所需做的就是调用这些方法。同样地,HTML 元素和其属性作为 DOM 对象公开。其结果是,在如下代码段给出的函数 showProgressDlg(msg)
可很容易地将消息设置到进程对话框并将其弹出:
清单 12. 事件处理程序在 Dojo 小部件上回调
initialize: function() { // setup references to Dojo widgets and DOM elements this.mProgressDlg = dijit.byId("progress-dlg"); this.mProgressDlgMsg = dojo.byId("progress-msg"); ... } ... showProgressDlg: function(msg) { // Use the methods exposed by DOM and the widget this.mProgressDlgMsg.innerHTML = msg; this.mProgressDlg.show(); }, ... |
有关由 Dojo 和 DOM 对象公开的方法的更多信息,请参看 参考资料 部分所列的 DOM 和 Dojo 参考。
|
当页面完成加载且 Dojo 小部件也准备好交互时,就会想要初始化控制器。在这个博客阅读器的事例中,这就意味着要调用 BlogController.initialize
。Dojo 通过 dojo.addOnLoad
提供了简单的 on-load hook 机制,借助这个机制就可以完成初始化任务。例如,在我们的控制器示例中,我们使用了如下的代码段来开始 BlogController.initialize
:
清单 13. 控制器初始化
dojo.addOnLoad(BlogController, BlogController.initialize); |
dojo.addOnLoad
可接收一个或两个参数:
- 当使用单个参数时,Dojo 就会将其视为是 Dojo 加载时所执行的那个函数。
- 当使用两个参数时,则第一个参数充当
this
,第二个参数代表 Dojo 加载时所执行的那个函数。
注意,作为一个开发人员,您也可以尝试其他方式。例如,可以使用 dojo.connect
并采用与如下代码段类似的方式将其绑定到一个 dojo 对象生命周期方法(但是,请注意我们并不推荐使用这种方式,原因是生命周期方法可能会改变):