XML 用户界面语言(XUL)开发简介

创建基于 XUL 的博客编辑器

XUL 是经过测试的真正的应用程序框架。事实上,最近发布的 Firefox 3.0 不仅仅是由 XUL 构建的,但它提供了一个 XUL 运行时环境,允许任何 Firefox 用户运行其他 XUL 应用程序。在本教程,您开始使用 XUL 进行编程,并学习一些帮助您开发 XUL 应用程序的工具。当您的 Web 开发技术得到提高时,您可以构建一个基于 XUL 的博客编辑器,并通过它使用 XUL 构建桌面应用程序。

Michael Galpin, 开发人员, eBay

Michael Galpin 的照片Michael Galpin 从 1998 年开始专业开发 Java 软件,目前为 eBay 工作,他从 California Institute of Technology 获得了数学学位。



2009 年 1 月 15 日 (最初于 2007 年 10 月 16 日)

开始之前

本教程适用于对桌面开发感兴趣又不想学习太多新技术的有经验的 Web 开发人员。XUL(发音与 cool 类似)使运用 Web 开发技能构建桌面应用程序变得很容易。它提供了丰富的 UI 部件集,这些工具使用的是所有 Web 开发人员都很熟悉的语法。使用 XUL,可以直接与 HTML 混合使用并可大量使用 JavaScript。

常用的缩写词

  • Ajax:异步 JavaScript + XML
  • API:应用编程接口
  • CSS:层叠样式表
  • DOM:文档对象模型
  • HTML:超文本标记语言
  • OS:操作系统
  • UI:用户界面
  • XML:可扩展标记语言

XUL 是一种基于 XML 的语言,因此需要对 XML(特别是 XML 名称空间)很熟悉。XUL 建立在我们熟悉和喜欢的 Web 技术之上:HTML、JavaScript 和 CSS。如果想有效地使用 XUL,需要对这些技术非常熟悉。使用 XPCOM 可以在很大程度上提高 XUL 应用程序的功能。这是一种跟分布式计算技术(例如 CORBA/IDL 和 COM)类似的技术。如果熟悉这些技术,在学习 XPCOM 时将会有所帮助,但并不做硬性要求。

关于本教程

在本教程中,您将了解以下内容:

  • 了解 XUL 的起源和它在 Mozilla 项目中的应用。
  • 了解 XUL 的主要优点及其架构设计,以及如何使用现有的 Web 应用程序技术构建桌面应用程序。
  • 发现 Firefox 3.0 向 XUL 开发人员呈现的机会。
  • 深入了解 XUL 并编写一个简单的应用程序来创建、保存和发布博客条目。这个基于 XUL 的博客编辑器提供了大量基础的文本编辑功能,并允许您在本地保存草稿以便随后重新加载并进行编辑。这个编辑器还加入了 XUL 的绘制功能,它允许用户使用博客电子签名。

先决条件

XUL 完全是开源的。为了使用 XUL 进行开发以及实践本教程的示例,您需要下载:


XUL 是什么?

XUL 表示 XML 用户界面语言(XML User Interface Language)。因为是 XML,所以 XUL 是一种声明性语言。XUL 提供了丰富的 UI 部件集合,这些部件可以加速开发进程。它是一种跨平台的语言,可以在 Linux™ 上构建自己的 XUL 应用程序,然后在 Windows® 上运行该程序。XUL 大量使用了 Web 技术,例如 JavaScript 和 Cascading StyleSheets(CSS)。甚至可以将 HTML 直接集成到 XUL 应用程序中。深入了解 XUL 以及它为何成为备受关注的开发平台。

XUL 历史回放

XUL 与 Netscape 和 Mozilla Foundation 是同义词。Netscape 浏览器最初的意图是作为一个跨平台浏览器。这需要将 UI 框架从特定于操作系统的布局和控制部件中分离出来。还需要一种方法让这些分离出的元素和本地进程(用于网络连接、文件 I/O 等)进行通信。要构建跨平台且能够和 HTML 和 Web 元素协作的应用程序,所有这些元素都非常必要。这个框架被叫做 XPFE(跨平台前端),用于构建 Netscape Communicator 以及该系列的其他产品,如它的电子邮件和聊天客户端。

您可能对 Netscape 公司的发展历程比较熟悉。该公司在 1995 年的 IPO 标志着 dot-com 辉煌的开始。直到 1998 年,公司虽然在财政上不太顺利,但是取得了一些重要的技术成就。这些成就的核心就是 Mozilla 项目。这是从 Netscape Communicator 4.0 的代码在获得开源许可之后公开发行开始的。 事实证明这个代码库难于开发和维护,但是幸运的是,在他们的计划中也有好的方面。Netscape 不但让现有的 Communicator 开放源代码,他们的下一代布局引擎代码也是开源的。这个布局引擎将会成为 Gecko。它的一个重要功能是支持声明性的、基于 XML 的 UI 语言,也就是 XUL。

XUL:XML、JavaScript 和 CSS

XUL 是为 Gecko 引擎构建的私有 UI 语言。它受到基于 Gecko 的 Web 浏览器开发人员的青睐。这是因为它是构建在标准技术(例如 XML、JavaScript 和 CSS)之上的。

XUL 是一种 XML 语言。这使得它的语法很简单,并且容易阅读(和解析)!XUL 跟 HTML 有很多相似之处,因此 Web 开发人员对它很熟悉。它甚至允许 XHTML 元素与 XUL 部件混合使用。XUL 通过许多方式证明了 XML 非常适合于创建 UI 语言,由一些类似语言的出现也能看出这一点。例如,来自 Adobe™ 的 MXML(Adobe Flex 框架中的 UI 语言)和来自 Microsoft® 的 XAML(.NET 3.0 和 Windows Presentation Foundation 中的 UI 语言)。

当然,声明性编程存在固有的局限性。它不可避免地需要一些强制性编程。XUL 直接支持 JavaScript,而不是发明一种新语言或者创建一些基于 XML 的语法。现在 JavaScript 作为一种编程语言经常受到负面的评价。JavaScript 被认为是一种适合于非编程人员的语言,并且充满了特定于浏览器的扩展和特性。然而,JavaScript 是一种强大的语言,它是 Web 应用程序开发的中坚力量。毕竟,JavaScript 是 Ajax 中的 “J”。它是一种函数性编程语言,但很容易以过程或面向对象的方式使用它。XUL 把 JavaScript 当作一种桌面编程语言,并将其放在最前面的位置。XUL 也非常依赖 JavaScript 中的 DOM 实现 — 毕竟,XUL 是基于 XML 的。

XUL 中用于 Web 开发的另一个重要方面是 CSS。CSS 已经成为向 Web 页面添加样式的事实标准。它的层叠特性具有强大的功能和灵活性,这种特性允许将样式应用于对象和子对象,同时也允许这些子对象根据需要重写样式。XUL 将这种功能和灵活性应用到桌面应用程序中。

JavaScript 和 CSS 的另一个共同之处是,其行为都会根据浏览器的不同而变化。浏览器嗅探在 JavaScript 中非常常见,因此程序员可以在基于用户使用的浏览器类型和版本的多个实现中编写相同的函数。在 CSS 中条件样式的使用也具有相同的特性。如果做过许多 Web 开发,就可能遭遇过这些浏览器怪僻。如果属于这种情况,您将会喜欢上使用 XUL 编程。为什么呢? 因为使用 XUL 时只需要考虑一个浏览器。就像在全世界都使用 Firefox 的情况下开发 Web 应用程序。

XPCOM 和 XBL

如果已经熟悉了 XUL,但可能又忘记了 XUL 的两个重要功能:XPCOM 和 XBL。别担心,现在将介绍这些技术,而且本教程稍后还显示它的功能。您将会看到如何使用这些技术来增强所开发的应用程序的功能。首先介绍 XPCOM。

XPCOM(即跨平台组件模块)与 CORBA 和 Microsoft COM 类似。XPCOM 允许用一个 IDL 模块(就像 Java™ 或者 C# 代码中的接口或者 Web 服务的 WSDL)表示代码库。用其他语言编写的应用程序可以通过 XPConnect 解释程序来引用这个代码库。例如,Gecko 引擎的几乎所有功能在 XPCOM 中都是公开的。这个引擎是用 C++ 编写的,但是,有了 XPCOM,您就可以使用任何具备 XPCOM 支持的语言来利用库中的任何资源,例如 JavaScript、C++、Perl 和 Python。例如,Gecko 的网络库是一个 XPCOM 组件,因此可以从 JavaScript 访问它。

XPCOM 可以使您利用来自许多库的功能。这是 XUL 中重复出现的一个主题:为开发人员提供他们需要的所有构建块,并让他们专注于构建自己的应用程序。并且,XUL 提供了一个大型的 UI 部件库。它也提供了一种方法,此方法使用 XML 绑定语言(XML Binding Language,XBL)来更改这些部件的行为和功能。使用 XBL 可以为部件创建自己的行为,然后将这种行为绑定到部件。如何绑定呢?这是 XBL 一个很聪明的部分。使用一个 CSS 选择器来进行绑定。使用选择器选择一个或多个部件,然后用特定的 CSS 特性 -moz-binding 来指定到包含此行为的 XUL 文件的 URL。

广泛采用 XUL

从纯技术的立场上看,XUL 是一个用于跨平台应用程序开发的有趣框架。也许仅仅是一个有趣的技术框架。但是另一个产品证明它不仅仅是一个技术框架,那就是 Firefox。XUL 是通过重写 Netscape 发展起来的,这种重写是通过让 Netscape 更加模块化来实现的。相同的思想也应用在了 Mozilla Firefox Web 浏览器开发中。

创建 Firefox 的动机是构建使用 Gecko 引擎支持的精简浏览器。这只有使用 Gecko 的模块化结构才可能实现。结果被证明是成功的。至 2008 年 9 月,Firefox 已占到了 19% 的全球市场份额,拥有 1.4 亿用户。它还获得了主流媒体(例如,Forbes 和 PC World)的好评。

Firefox 最初的成功很大程度上来自于它的快速呈现引擎(Gecko)以及它在安全方面的优越性。Firefox 持续获得成功和采用的一个原因在于它的扩展系统。该扩展系统使开发人员能够轻松地在 Firefox 之上构建专有功能。针对 Firefox 的扩展已变得非常流行。在编写此教程时,Mozilla 的官方扩展列表上已有超过 1800 种扩展。而且,许多其他的扩展没有收录到 Mozilla 的官方列表中。

Firefox 扩展的关键在于,创建拥有强大功能的扩展非常简单。这很简单:可以用 XUL 编写 Firefox 扩展,就像 Firefox UI 一样。它们可以利用 XUL 强大的覆盖特性。有了覆盖,就可以定位一部分现有的 UI 组件,并插入自己制作的新的 UI 组件。图 1 显示安装了一些扩展的 Firefox。

图 1. 带有扩展的 Firefox
带有几个可见扩展的 Firefox 浏览器

5 个红色的矩形表示来自扩展的 UI 元素。导航工具栏内包含一个大工具栏和 3 个按钮。此外,状态栏上还有几个图标。单击这些图标将打开大的对话框,每个对话框都将用户界面和菜单、选项卡等关联起来。这表明 Firefox 扩展本身就是强大的应用程序,因为它们使用 Firefox,所以就使用了 XUL 作为开发平台。

超越 Firefox:XULRunner

Firefox 将 XUL 奉献给了数百万用户。但 XUL 并不仅仅是一个创建 Firefox 及其扩展的技术。Firefox 的用于电子邮件的姊妹应用程序是 Mozilla Thunderbird。这个程序也是用 XUL 编写的,并且拥有一个活跃的扩展库,通过 XUL 覆盖实现。尽管它没有 Firefox 那么流行,但是它拥有 5 百万活动用户。机会在于您的 ISP 提供了一些指令,如果他们为您提供了电子邮件帐户,那么这些指令可用来将 Thunderbird 设置为该帐户的一个 IMAP 或者 POP 客户机。XUL 并不局限于 Mozilla 项目。它也被设计为一个框架,用于跨平台桌面应用程序的开发。然而,像 Firefox 和 Thunderbird 这样的应用程序是围绕 Gecko 引擎构建的。它们需要 Gecko 引擎来呈现 HTML 页面和 HTML 电子邮件,但是 Gecko 引擎也呈现了它们的 UI。一般而言,大多数桌面应用程序不需要呈现 HTML,因此它们也不需要 Gecko 引擎。但是没有 Gecko,它们如何使用 XUL 呢?答案就在于 XULRunner。XULRunner 在 Gecko 引擎之外提供纯 XUL 运行时环境,从而延续了 Gecko 的模块化特性。这允许您构建应用程序代码中直接包含 XULRunner 的应用程序。

Firefox 3.0

构建运行在 XULRunner 上的应用程序时,一个不足之处是需要在应用程序中包含 XULRunner。这导致应用程序大概增加了 12MB。这对于像 Songbird 这样的媒体播放器来说不算什么。毕竟现在大多数媒体播放器都比较大。对于像 Joost 这样的流视频应用程序来说也不算什么。毕竟,流视频需要快速连接,因此对大多数 Joost 用户来说,额外的 12MB 可能很快就能下载下来。但是对于许多应用程序来说,XULRunner 运行时跟应用程序本身一样大,或者更大。这使得 XULRunner 不再那么有吸引力。

然而,您不再需要将 XUL 应用程序和 XULRunner 捆绑在一起。因为 Firefox 3.0 已经构建在 XULRunner 之上。Firefox 和 XULRunner 使用相同的核心库和 libxul,这允许任何 XUL 应用程序使用 Firefox 作为 XUL 运行时,而不是 XULRunner。到 2008 年 9 月份为止,全世界一共有 1.4 亿 Firefox 用户。在这些用户当中,有 68% 已经更新到 Firefox 3.0。这相当于超过 9500 万用户已经安装了 XUL 运行时。即您的 XUL 的潜在使用者多达 9500 万。这个事实也适用于 Firefox 开发人员。在本教程后面的详细论述中,您将看到,只需添加 -app 命令行参数,就可以将 Firefox 3 作为任何 XUL 应用程序的 XUL 运行时。


XUL 开发

我们已经了解了 XUL 的起源和发展情况。更重要的是,已经明白了可以用 XUL 和它提供给开发人员的有利时机来做什么。我希望您现在已经迫不及待想要进行 XUL 开发了。首先,您将设置一个 XUL 开发环境。

XUL 开发环境

在了解 XUL 时已经注意到,可以使用 XUL 做许多不同的事情。因此,没有绝对适合的 XUL 开发环境。一般而言,您将会基于 XUL 的不同用途配置环境。

基本原理

首先,XUL 是一种基于 XML 的 UI 语言。要创建 XUL 文件,只需要能够创建 XML 文件。您或许想编写一些脚本,以使应用程序具有交互性,因此需要编写一些 JavaScript。要创建 XML 和 JavaScript 文件,不需要特定的编译器。XUL 运行时将会解释这些文件。但是,您还需要做一些事情。

也许最重要的一点是 XUL 应用程序的目录结构。在本教程中将会创建一个称为 xulblogger 的应用程序。图 2 显示了该程序的目录结构。

图 2. XUL 应用程序的目录结构
XUL 应用程序的目录结构

图 2 展示了 3 个重要文件。首先是 application.ini。这个文件必须放在应用程序的根目录下。它最重要的用途是告诉 XUL 运行时它需要什么版本的运行时,如清单 1 所示。

清单 1. application.ini 文件
[App]
Vendor=developerworks
Name=xulblogger
Version=0.2
BuildID=20080924

[Gecko]
MinVersion=1.9

下一个重要的配置文件是 chrome.manifest。这个文件必须放在 chrome 目录中。通常需要在 chrome 目录中包含一个子目录,用于存放所有的 XUL 文件。可以根据自己的喜好为其命名。它在清单 2 中叫做 “xulblogger”,但是许多应用程序将其命名为 “content”。chrome.manifest 用于告诉 XUL 运行时如何找到您的文件。清单 2 显示了 chrome.manifest 的一个示例。

清单 2. chrome.manifest 文件
content xulblogger file:xulblogger/

可以看到,这个文件只包含一行简单的配置。最后一个重要的文件是 prefs.js。此文件必须放在 /defaults/preferences 目录下。它告诉运行时首先需要载入什么样的 XUL 文件,如清单 3 所示。

清单 3. prefs.js 文件
pref("toolkit.defaultChromeURI", "chrome://xulblogger/content/home.xul");

您或许还注意到了 extensions 和 updates 目录。不用担心这两个目录,XUL 运行时将会自动创建它们。

关于这里描述的结构,还有一个需要注意的事情是:XUL 应用程序通常是通过创建顶级目录的一个 JAR 文件来部署的。如果安装了 Java 开发工具,可以使用 Java jar 命令来创建 JAR,或者可以直接将目录压缩,然后将其扩展名由 .zip 更改为 .jar。

Eclipse 和 XUL

作为一个有经验的开发人员,您可能已经知道 Integrated Development Environment (IDE) 的价值。您可能考虑 IDE 是否可用于 XUL。这有大量的选择,有几个可用的 XUL IDE 构建在非常通用的 Eclipse 平台之上。对于 XML、JavaScript 和 CSS 编辑,XULBooster(参见 参考资料)使用流行的 Eclipse Web Tools Platform。它还使用 XULRunner 来执行应用程序,并且连接到 XULRunner 进行调试。图 3 显示了 XULBooster 的屏幕截图。

图 3. XULBooster
XULBooster 应用程序的屏幕截图

另一个选项是 Spket。这可以作为独立的 IDE 获得,也可以作为 Eclipse 插件获得(参见 参考资料)。这不是特定于 XUL 的 IDE,但它提供了几个对 XUL 开发人员非常有用的特性。Spket 提供 XUL 和 XBL 控件,以及 XUL 和 JavaScript 的详细代码。图 4 展示了 Spket 的屏幕截图。

图 4. Spket IDE
Spket IDE 屏幕截图

不管选择什么样的编辑器,您最终都需要运行代码。再声明一下,您有很多选择,其中一些还使用 Firefox。

运行 XUL 应用程序

可以选择 3 种方式来运行 XUL 应用程序:

  • 对于简单的 UI 测试(chrome 测试),只需要打开 Firefox(或者任何基于 Mozilla 的浏览器,例如 Seamonkey 或者 Mac OSX 中的 Camino)中的 .xul 文件。这种方法对于测试非常简单的应用程序很有用。Firefox 不知道 chrome.manifest,因此它也不会找到您从主要的 chrome 引用的其他 chrome 文件。
  • 下一个测试方法是使用 XULRunner。可以下载一个 XULRunner 安装工具,或者从源文件构建 XULRunner。如果从源文件构建 XULRunner,同时也会从源文件构建 Gecko SDK。一旦安装了 XULrunner,只需要将你的 application.ini 文件位置传递给它就行了。XULrunner 将会读取此文件,以及前面提到的其他两个配置文件,以初始化应用程序。
  • 最后,您可以使用 Firefox 3.0 作为 XUL 运行时。它的功能和 XULRunner 很相似。如果通过 xulrunner <path_to_app>/application.ini 在命令行使用 XULRunner 调用了您的应用程序,那么要使用 Firefox 3.0 的话,就需要使用 firefox -app <path_to_app>/application.ini 命令。

博客编辑器

XUL 开发环境准备就绪后,就可以使用 XUL 构建一个示例应用程序了。我们将会构建一个简单的博客编辑器,这个编辑器可以创建并预览博客条目。也可以在本地保存博客条目并在以后重新载入。编辑器将会使用 XUL 作为用户界面,并使用 JavaScript 来完成每件事情。开始之前,先设置用户界面。

博客编辑器的用户界面

这是最令开发人员憎恶的应用程序部分。创建用户界面非常繁琐,但是 XUL 使这变得很容易。XUL 有许多控件,用于创建部件和指定布局。请看清单 4 中定义的一个简单 UI。

清单 4. XUL(/chrome/xulblogger/home.xul)中定义的 UI
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<xul:window id="xulblogger" title="Create Blog Entry" orient="horizontal"
     align="start" xmlns="http://www.w3.org/1999/xhtml" height="1000"
     xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
     <xul:script src="blog.js"/>
     <xul:script src="json.js"/>

     <xul:vbox height="800">
          <xul:hbox>
               <xul:label value="Name of entry"/>
               <xul:textbox id="name" multiline="false" cols="70"/>
                 <xul:label value="Signature"/>
                  <canvas id="canvas" width="300" height="10" 
style="border:1px solid gray;">
                </canvas>
          </xul:hbox>          
          <xul:textbox id="entry" multiline="true" rows="10" cols="80"/>
          <xul:hbox>
               <xul:label value="Tags"/>
               <xul:textbox id="tags" cols="80" multiline="false"/>
               <xul:button id="saveBtn" class="btnClass" label="Save" />
               <xul:button id="previewBtn" label="Preview" onclick="preview()"/>
          </xul:hbox>
          <xul:hbox>
                 <xul:label value="Publish Date"/>
                 <xul:datepicker type="grid" value="{new Date()}"/>              
          </xul:hbox>

          <div id="preview"></div>
     </xul:vbox>     
     <xul:script src="canvas.js"/>
     <xul:script>read();</xul:script>
</xul:window>

这是一个很简单的 UI。XUL vbox 和 hbox 组件使布局变得很简单。按从左到右的顺序,vbox 在垂直方向上依次排列各个对象,而 hbox 在水平方向排列各个对象。UI 有 2 个标签、3 个文本框(包括一个多行文本框)和 2 个按钮。这些都是非常直观的代码;即使您以前没有见过 XUL,也能知道这些代码的用途。该 UI 还使用了几个更高级的控件。它使用了一个 datepicker 控件。这是在 Firefox 3 中引入的新控件。注意使用 JavaScript 表达式初始化 datepicker 的开始日期(值属性)的方式。此外,还需要注意使用户可以在 XUL 控件内部绘制的画布控件,该控件使他们能够对博客发布进行电子签名。仔细研究这个控件的工作原理。

画布控件

画布控件并不是一个真正的 XUL 控件。它是一个 HTML 控件。虽然 Safari 浏览器引入了画布元素,但 Firefox 仍然支持它???Web Hypertext Application Technology Working Group (WHATWG) 使画布成为将要推出的 HTML 5 规范的一部分。不过,当前所有版本的 Internet Explorer® 都没有支持它,包括 Internet Explorer 8 的 beta 版。因此,大部分 Web 开发人员不能利用这一特性,除非他们的用户不使用 Internet Explorer。然而,进行 XUL 开发时,这并不是什么问题。通过 XUL 应用程序,您可以使用任何 Firefox 支持的 HTML、CSS 和 JavaScript。它只在 XUL 应用程序的内部执行,而不是 Web 浏览器。因此,您不用担心它不能在 Internet Explorer 上使用。

画布控件允许应用程序在控件内部绘制。这种绘制通常使用 JavaScript 自动完成。同样,要使用户能够进行绘制,您可以使用 JavaScript 监听带有该控件的用户交互,然后使用画布 API 进行绘制。在这个应用程序中,canvas.js 脚本完成了所有这些任务。清单 5 展示了该文件的内容。

清单 5. JavaScript 画布控件代码
// courtesy of Mozilla's Mark Finkler
// http://starkravingfinkle.org/blog
  function  Scribbler_init() {
    Scribbler.init();
  }
 
  var Scribbler = {
    canvas : null,
    ctx : null,
    drawing : false,
 
    init : function() {
      this.canvas = document.getElementById("canvas");
      this.ctx = this.canvas.getContext("2d");
      this.drawing = false;
 
      this.canvas.addEventListener("mousedown", this.doDrawStart, false);
      addEventListener("mouseup", this.doDrawStop, false);
      this.canvas.addEventListener("mousemove", this.doDrawUpdate, false);
    },
 
    doDrawStart : function(event) {
      // Calculate the position of the mouse over an element. To do this, subtract
      // the position of the element the mouse is over from the mouse position. The
      // element's position can be determined from its boxObject.
      // We are using the <box> container as a XUL wrapper 
      // for the HTML <canvas>
      var offsetX = (event.clientX - event.target.parentNode.boxObject.x);
      var offsetY = (event.clientY - event.target.parentNode.boxObject.y);
 
      Scribbler.ctx.beginPath();
      Scribbler.ctx.moveTo(offsetX, offsetY);
      Scribbler.drawing = true;
    },
 
    doDrawStop : function(event) {
      if (Scribbler.drawing) {
        Scribbler.ctx.closePath();
        Scribbler.drawing = false;
      }
    },
 
    doDrawUpdate : function(event) {
      if (Scribbler.drawing) {
        // Calculate the position of the mouse over an element. To do this, subtract
        // the position of the element the mouse is over from the mouse position. The
        // element's position can be determined from its boxObject.
        // We are using the <box> container as a XUL wrapper 
        // for the HTML <canvas>
        var offsetX = (event.clientX - event.target.parentNode.boxObject.x);
        var offsetY = (event.clientY - event.target.parentNode.boxObject.y);
 
        Scribbler.ctx.lineTo(offsetX, offsetY);
        Scribbler.ctx.stroke();
      }
    },
 
    doDrawClear : function() {
      this.ctx.fillStyle = "#fff";
      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    }
  };

Scribbler_init();

清单 5 中的代码创建了一个 Scribbler 对象。这个对象监听画布控件内的 3 个事件:mousedownmousemovemouseup。这些事件在用户按下鼠标、移动鼠标然后松开鼠标时触发。这个代码仅在这些事件期间捕捉鼠标的位置并确定相对的位置,然后在这些点之间绘制线条。了解签名控件的工作原理之后,就可以测试这个应用程序了。

运行应用程序

您可以使用 XULRunner 或 Firefox 启动这个应用程序。图 5 是博客编辑器 UI 的屏幕截图。

图 5. 博客编辑器 UI
博客编辑器 UI 的屏幕截图

您或许在 清单 4 中注意到,有一个叫做 preview 的 HTML div。这是一个用来预览博客条目的 HTML 区域。它让用户进入正常的 HTML,然后单击 preview 按钮查看外观。但是如何将编辑器中的 HTML 转换成在 preview 区域上显示的 HTML。回头看看 XUL 代码,您将看到一个 preview() 函数,它在用户单击 Preview 按钮时被调用。清单 6 展示了在 blog.js 文件中的 preview() 函数。

清单 6. preview() 函数
function preview(){
    var preview = document.getElementById("preview");
    preview.innerHTML = document.getElementById("entry").value;
    var sigImg = document.createElement("img");
    sigImg.src = document.getElementById("canvas").toDataURL();
    preview.appendChild(sigImg);
}

这对于做过很多 HTML/JavaScript 处理的人来说,应该很熟悉,尤其是做过 Ajax 开发的人。这正是我们习惯编写的 JavaScript 类型:使用元素的 ID 来获得元素,然后使用 HTML 元素的 innerHTML 属性将其转储到 HTML 中。您还需要注意如何从用户的签名中获取数据,然后将其转换成一个数据 URL。这使您可以将签名显示为图像。该数据 URL 是一张采用 64 位编码的 PNG 格式的图像。甚至可以将这些数据保存到本地文件中。画布元素还有很多其他功能,并且可以在 XUL 应用程序中任意使用。

关于名称空间

您或许注意到,清单 4 声明了 2 个名称空间。一个是 xul 名称空间,在 XUL 文件中创建每个 UI 控件时使用。同时,还有一个指向 HTML 模式的默认名称空间。这跟大多数 XUL 文件的设置方式是相反的。通常,XUL 名称空间是默认的,并且任何 HTML 元素都需要添加前缀。但是在这个例子中,我们想让用户将 HTML 输入到博客编辑器中。也可以解析预览窗格的内容,然后添加合适的 html 前缀(或者想要使用的其他前缀)。在这些内容被转储前,将此前缀作为标记的一部分。


修饰应用程序外观

您的应用程序看起来很单调,可以很轻松地为其添加更好的外观。所需的只是一个小小的 CSS,就像在 Web 页面上使用一样。可以将类和/或 ID 添加到每个部件上。可以为这些类编写 CSS 选择器,或者用特定部件的 ID 为其编写 CSS 选择器,就像创建 Web 页面时的操作一样。

保存博客条目

现在把博客条目保存到本地文件系统中。XUL 允许通过 JavaScript 访问本地 I/O 操作。如果熟悉 JavaScript,就会知道它没有内建这些功能。这正是 XPCOM 发挥作用的地方。请看一下清单 7。

清单 7. 在 JavaScript 中启用本地 I/O
function save() {
    try {
        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
    } catch (e) {
        alert("Permission to save file was denied.");
    }
    var file = Components.classes["@mozilla.org/file/local;1"]
        .createInstance(Components.interfaces.nsILocalFile);
    file.initWithPath( savefile );
    if ( file.exists() == false ) {
        alert( "Creating file... " );
        file.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 420 );
    }
    var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
        .createInstance( Components.interfaces.nsIFileOutputStream );
    /* Open flags 
    #define PR_RDONLY       0x01
    #define PR_WRONLY       0x02
    #define PR_RDWR         0x04
    #define PR_CREATE_FILE  0x08
    #define PR_APPEND      0x10
    #define PR_TRUNCATE     0x20
    #define PR_SYNC         0x40
    #define PR_EXCL         0x80
    */
    /*
    ** File modes ....
    **
    ** CAVEAT: 'mode' is currently only applicable on UNIX platforms.
    ** The 'mode' argument may be ignored by PR_Open on other platforms.
    **
    **   00400   Read by owner.
    **   00200   Write by owner.
    **   00100   Execute (search if a directory) by owner.
    **   00040   Read by group.
    **   00020   Write by group.
    **   00010   Execute by group.
    **   00004   Read by others.
    **   00002   Write by others
    **   00001   Execute by others.
    **
    */
    outputStream.init( file, 0x04 | 0x08 | 0x20, 420, 0 );
    //var output = document.getElementById('blog').value;
    var output = serialize();
    var result = outputStream.write( output, output.length );
    outputStream.close();
}

需要做的第一件事是启用 XPConnect。这允许使用 XPConnect 来处理 XPCOM 组件。在这个示例中,使用的是 Mozilla 的 org.file.local 类。然后能够调用这个对象上的方法,就像对象是在本地运行一样。您或许还注意到这里调用的 serialize() 方法,它将输入的数据序列化成一个 JSON 串,如清单 8 所示。

清单 8. 序列化数据
function serialize(){
    var name = document.getElementById("name").value;
    var entry = document.getElementById("entry").value;
    var tags = document.getElementById("tags").value;
    var pubDate = document.getElementById("pubDate").value;
    var sigData = 
        document.getElementById("canvas").toDataURL();
    var obj = { "name" : name, "entry" : entry, "tags" : tags, 
        "pubDate":pubDate, "sigData":sigData};
    var str = obj.toJSONString();
    return str;
}

此外,您使用了普通的 JavaScript DOM 功能来获得所创建的表单外的数据。然后创建一个 JavaScript 对象,封装保存博客条目的属性。使用来自 json.org 的 JSON 库,可以将 JavaScript 对象转换为一个字符串。然后将这个字符串写入到文件中。那么这个文件是什么呢?清单 9 显示了一些代码,这些代码决定将要保存什么文件。

清单 9. 决定保存文件的代码
var savefile = "blogentry.txt";

try {
     netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
     alert("Permission to save file was denied.");
}
// get the path to the user's home (profile) directory
const DIR_SERVICE = new Components.Constructor("@mozilla.org/file/
                                        directory_service;1","nsIProperties");
try { 
     path=(new DIR_SERVICE()).get("ProfD", Components.interfaces.nsIFile).path;
} catch (e) {
     alert("error");
}
// determine the file-separator
if (path.search(/\\/) != -1) {
     path = path + "\\";
} else {
     path = path + "/";
}
savefile = path+savefile;

所有这些代码所做的就是确定用户的主目录。因此应用程序保存的任何数据都将存储在 ~/blogentry.txt 中。另外,可以使用 XPCOM 访问一些丰富的功能,这些功能是 XUL 框架的一部分。这些代码也会做一些 OS 嗅探,以避免使用错误路径保存数据而导致的问题。

因此可以将数据写入磁盘,但是如何从磁盘读取数据呢?您或许在 图 1 注意到,在启动时调用了一个 JavaScript 函数 read()。清单 10 显示了此函数的代码。

清单 10. read() 函数
function read() {
     try {
          netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
     } catch (e) {
          alert("Permission to read file was denied.");
     }
     var file = Components.classes["@mozilla.org/file/local;1"]
          .createInstance(Components.interfaces.nsILocalFile);
     file.initWithPath( savefile );
     if ( file.exists() == false ) {
          alert("File does not exist");
     }
     var is = Components.classes["@mozilla.org/network/file-input-stream;1"]
          .createInstance( Components.interfaces.nsIFileInputStream );
     is.init( file,0x01, 00004, null);
     var sis = Components.classes["@mozilla.org/scriptableinputstream;1"]
          .createInstance( Components.interfaces.nsIScriptableInputStream );
     sis.init( is );
     var output = sis.read( sis.available() );
     deserialize(output);
}

再一次借助 XPConnect 使用通过 XPCOM 部署的本地文件库。在这个例子中,使用来自此组件的相应的读取 API,以读取写入 清单 7 的文件。这时,也会调用 deserialize() 方法,如清单 11 所示。

清单 11. deserialize() 函数
function deserialize(input){
    var obj = input.parseJSON();
    document.getElementById("name").value = obj.name;
    document.getElementById("entry").value = obj.entry;
    document.getElementById("tags").value = obj.tags;
    document.getElementById("pubDate").value = obj.pubDate;
}

此函数再一次使用了 JSON 库。此时它获取了从本地文件读取的串,并将其转换到一个 JavaScript 对象中。然后可以用这个对象的属性来设置 UI 控件中的值。惟一的例外是画布签名。您将它保存为一个数据 URL,但该格式不能在编辑时重载。您可以用图像标记显示它,就像在 Preview 中一样。

发布条目

您的应用程序可以从本地磁盘读取和写入条目,而且可以在博客条目中预览 HTML 布局。下一个逻辑步骤是将它与 Web 服务连接,以在线发布博客条目。为此,使用 XPConnect 和 XPCOM 来访问包含在 XUL 中的连网 API。如果使用 XMLHttpRequest 将所有内容写入到浏览器中时,也可以执行上面的方法。这在 XUL 中所有的 JavaScript 函数中都是可行的,就像运行在浏览器中的所有 JavaScript 文件一样。清单 12 显示了实现此功能的一些代码。

清单 12. 使用 XMLHttpRequest()
var xhr = new XMLHttpRequest();
function publish(){
     var url = "http://some.blogService.com/sendBlog"; // replace this obviously
     var serializedEntry = serialize();
     xhr.onreadystatechange = processResponse;
     xhr.open("POST", url, true);
      xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
     // set-up authentication headers and/or parameters
     // then send the request
     xhr.send(serializedEntry);
}

function processResponse(){
     // check for readyState == 'loaded'
     if (xhr.readyState == 4){
          if (xhr.status == 200){
               alert("Your blog entry was published!");
          } else {
               alert("Sorry there was an error");
          }
     }
}

清单 12 中需要注意的关键是,其中的代码跟实现从 Web 页面调用 Ajax 而编写的代码类型相同。最大的区别在于,不用担心 Internet Explorer 的不同版本,或者甚至是 Firefox(或者 Safari、Opera 和其他浏览器)的不同版本。这在很大程度上简化了 XMLHttpRequest 的使用。


更多的 XUL 开发

您仅仅使用 XUL 创建了一个简单的桌面应用程序。如果您是一个 Web 应用程序开发人员,那么 XUL 可以为您开启一个崭新的世界。您还应该了解 XUL 开发的更多具体类型。首先是为 Firefox 和其他基于 Mozilla 的浏览器编写扩展。

Firefox 扩展开发

XUL 开发的一个最流行的类型就是编写 Firefox 扩展。用 XUL 进行桌面开发和扩展开发有两个主要的区别。最重要的一个是,扩展需要安装清单文件。这个文件必须命名为 install.rdf,并且必须放在应用程序的根目录下。这个文件是包含有关扩展的重要元数据的存储库。您可能想为扩展创建一个图标。这可以通过创建图标文件并将其放到 /chrome/icons/default/ 目录中来实现。在 Windows 中,这是一个 .ico 文件,而在 Linux 中为一个 .xpm 文件。它的名称和主窗口的 ID 匹配。因此,如果主窗口的 ID 为 “xulBloggerMain”,那么就需要一个 xulBloggerMain.ico 和一个 xulBloggerMain.xpm。

XPCOM 开发

如果做过足够多的 XUL 开发,也许想创建自己的 XPCOM 组件。为此,需要 Gecko SDK。可以下载此文件并当作一个二进制文件,或者从头构建。SDK 包含大量 C++ 头文件和用于基础 Gecko XPCOM 组件的 IDL 文件,以及用于创建 XPCOM 组件的命令行工具。要开发 XBL,将 XUL 控件绑定到您开发的 XPCOM 组件上,Spket IDE 将会非常有用。


结束语

在本教程中,您了解了 XUL 的功能及其架构,以及一些开发 XUL 应用程序的工具。您还构建了一个不错的应用程序,它使用了 XUL 和 Firefox 中的一些高级组件。您了解了开发人员如何使用他们的 Web 开发技能通过 XUL 创建桌面应用程序。Firefox 3.0 的广泛采用意味着,Firefox 用户已经为 XUL 开发人员编写的桌面应用程序准备好 XUL 运行时,并且这存在巨大的市场。


下载

描述名字大小
XULBlogger 的代码xulblogger-code.zip8KB

参考资料

学习

  • 您可以参考本文在 developerWorks 全球站点上的 英文原文
  • 用 Mozilla 和 XML 创建 Web applet(Nigel McFarlane,developerWorks,2003 年 10 月):查看如何使用 XUL 创建增强的 Web 页面。
  • Technology options for Rich Internet Applications(Vaibhav V. Gadge,developerWorks,2006 年 7 月):了解能够用于创建各种富 Internet 应用程序的技术平台。
  • An introduction to XPCOM(Rick Parrish,developerWorks,2001 年 2 月):学习关于 XPCOM 各方面的知识,这是使用 XUL 进行扩展的关键。
  • Getting to know PyXPCOM(Uche Ogbuji,developerWorks,2004 年 5 月):XPCOM 的优点之一是它可以使用很多语言。查看如何在 XPCOM 中使用动态 Python 语言。
  • XML in Firefox 1.5, 第 1 部分:XML 特性概览(Uche Ogbuji,developerWorks,2006 年 3 月):在 Firefox 1.5 中首次出现的画布。阅读关于它的简介。
  • XUL 1.0 规范:更多地了解这种基于 XML 的用户界面语言,它可以构建各种富跨平台应用程序。
  • XUL planet:查找 XUL 示例和教程。
  • 构建扩展:在这篇 Mozilla 文章中创建自己的 Firefox 扩展!
  • XUL...that sounds familiar?:想知道以前在哪里听说过 XUL 吗?阅读 XUL、UI 的名称的起源。
  • developerWorks XML 专区:访问图文并茂的文章和教程,它帮助开发人员在设计高效的 Web 应用程序时利用 XML 的强大功能。
  • IBM XML 认证:了解如何成为 IBM 认证的 XML 和相关技术的开发人员。
  • XML 技术库:访问 developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。
  • developerWorks 技术活动和网络广播:随时关注 developerWorks 技术活动和网络广播。
  • 技术书店:浏览有关这些主题和其他技术主题的图书。
  • Podcasts:收听 Podcast 并了解 IBM 技术专家的最新想法。

获得产品和技术

  • Venkman,Mozilla JS 调试器:学习调试 JavaScript,这是 XUL 的重要部分。
  • XULBooster:使用这个 Eclipse 特性为 Eclipse IDE 和 WTP 添加 XUL 支持。
  • Spket:试用这个 IDE,它可以作为独立的 IDE 下载,也可以作为 Eclipse 插件下载。
  • IBM 产品评估试用软件:使用可直接从 developerWorks 下载的 IBM 试用软件构建您的下一个项目,包括来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Web development
ArticleID=363835
ArticleTitle=XML 用户界面语言(XUL)开发简介
publish-date=01152009