级别: 初级 Nigel McFarlane (nrm@kingtide.com.au), 分析家,程序员,作家
2003 年 3 月 01 日 要超越简单的 HTML,历史上惟一的选择一直是使用 Java 技术或插件。现在,您拥有了一种新方法——直接用 XML 编写和显示应用程序。Mozilla 平台提供了这样一种机制。在本文中,Nigel McFarlane 向我们介绍 XUL(XML User-interface Language,XML 用户界面语言)。XUL 是一组 GUI 组件,它具有广泛的跨平台支持,其设计目标是为具有传统的非-HTML GUI 的应用程序构建 GUI 元素。
Mozilla 平台是一组可自由使用的开放源代码技术,而这些技术是很多面向用户的软件应用程序的基础。这些应用程序中有些是桌面系统,有些是开发工具,但是其中最有名气的是
Web 浏览器,包括 Mozilla、Macintosh 平台上的 AOL、Linux 上的 Galeon,以及 Netscape。
尽管这些浏览器大多数用于显示 HTML,而浏览器之下的平台提供的功能却不止如此。尤其值得一提的是,Mozilla 平台对 XML 的广泛支持使其可以代替
Java 技术来创建 applet 和应用程序。在本文中,我将向您展示如何用 XML 标签代替 Java 类来创建这样的 applet。这是一种非常简单,但是十分强大的方法。
尽管 Mozilla 平台自身有共享的对象类(最近一次统计说已经超过一千个),但它最有名的地方却在于对 XML 的深入使用。对于有些 XML
相关技术,如 XHTML,该平台提供完整的呈现支持,而对其他一些技术,如 RDF,平台只提供数据处理功能。如果 XML 文档需要可视化表示,那么对呈现的支持就是必要的。该平台对之提供呈现支持的技术有
HTML/XHTML、MathML、SVG(可选项),还有它自己定义的 XUL。最后一种就是本文要讨论的技术。
为什么要使用 XUL?
XML User-interface Language,简称 XUL(如果您想酷一点儿,可以念作“zool”),它在 Mozilla Platform
中的地位就是 Swing 在 Java 环境中的地位,或者是 Gtk 在 X-Window 环境中的地位。它是一组 GUI 控件。这些控件适合用于具有传统的非-HTML
GUI 的应用程序。有很多基于 Mozilla 的浏览器,它们的菜单、工具栏、滚动条、对话框等等都是用 XUL 文档构建的。
XUL 具有广泛的跨平台支持——Macintosh 菜单看起来就是 Macintosh 的菜单,GNOME 按钮看起来也就是 GNOME
的按钮。为了达到这样的目的,XUL 紧密依赖于当前平台的固有控件。这样的策略类似于 IBM 支持的 Eclipse 开发工具。不过这种相似性也是有限的,因为
Eclipse 最终发布的 GUI 控件是 Java 类,而 Mozilla 发布的控件是 XML 的 XUL 方言中的标签。
XUL 存在的理由何在?多半是因为 HTML 适合显示超文本文档,但是却不适合显示 GUI。传统的基于 Web 的应用程序花费了无穷无尽的力量,试图强行让
HTML 具有传统表单/菜单形式的应用程序的外观。可是 HTML 当初根本就没有打算达到那样的目的。
向 HTML 中增加表单元素(即
FORM )无非就是创建了一种新的方法,以仿照古老的 3270 终端风格实现瘦客户机块模式(block-mode)应用程序。HTML
与 3270 一样,也提供了批处理方式的表单提交机制。基于字符模式的应用程序最终发展成十分高效的用户导航系统,但是当 GUI 应用程序出现的时候,这一切就都消失了。随后,GUI
应用程序也用鼠标和控件反馈的方式,实现了自己的界面导航结构。
当 HTML 表单登上历史舞台的时候,它们模仿了块模式终端的设计,但是却没有提供紧凑的导航机制,也没有用适当的 GUI 来代替它。在 HTML
之下,用户不得不自己猜测,页面上哪一个可视元素是用户可以控制的,哪一个又仅仅是装饰。因此对于 GUI 驱动的应用程序而言,HTML并不是一个很好的起点。这也正是为什么
Java applet 刚刚出现时市场上会出现一片热烈的响应,因为 Java applet 是实现真正 GUI 的一个机会。
现在 XUL 到来了。XUL 沿袭了 HTML 的极瘦客户机模式,但稍微让它变胖了一些。除了任何极瘦客户机都会提供的几种最平常的表单元素之外,XUL
还像复杂些的客户机软件一样,提供了完整的控件组。
在过去,这样的客户机软件可能是编写成 Java 应用程序,可能是用 Tcl/Tk 或 Visual Basic 编写的。现在,更胖的客户机可以实现为一个或者多个
XUL 文档,并且可以通过 Web 传递(或者仅仅进行本地处理)。应用程序的表现形式一开始是一个文档,而不再是一组对象。一个运行着的 Mozilla
平台读取那个文档,然后显示指定的 GUI,供用户使用。
这样,XUL 就将 HTML 轻量级的优势与传统 GUI 结构化的可用性结合起来了。
近观 XUL
XUL 看起来像什么?请您看看除了浏览器窗口之外的任何一个 Mozilla 窗口(比如 e-mailer,或是 Address Book)。在本文中,我将用一个简单的故障单登记(trouble-ticketing)系统说明
XUL 的用法,任何一个支持中心都会为它的用户提供这种系统。然后,用户就可以在内部网或者外部网上提供的一个文档上提供反馈信息。您可以认为这个东西是
applet、对话框或者嵌入式应用程序,看您怎么想了。随着我的不断介绍,您就会明白了。
清单 1 显示了这个系统背后的 XUL 代码。
清单 1. 故障单系统的 XUL 代码示例
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="ticket.css" type="text/css"?>
<!DOCTYPE window>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="init.js"/>
<script src="commands.js"/>
<script src="server.js"/>
<toolbox>
<menubar id="menubar" grippyhidden="true">
<menu id="menu_login" accesskey="L" label="Login">
<menupopup>
<menuitem id="m10" label="Login" accesskey="L"
oncommand="cmd_login()"/>
<menuitem id="m11" label="Logoff" accesskey="O"
oncommand="cmd_logoff()"/>
<menuitem id="m12" label="Exit" accesskey="X"
oncommand="cmd_exit()"/>
</menupopup>
</menu>
<menu id="menu_ticket" accesskey="N" label="New Ticket">
<menupopup>
<menuitem id="m20" label="User Ticket" accesskey="U"
oncommand="cmd_user()"/>
<menuitem id="m21" label="Technical Ticket" accesskey="T"
oncommand="cmd_tech()"/>
<menuitem id="m22" label="Reviewer's Ticket" accesskey="R"
oncommand="cmd_rev()"/>
</menupopup>
</menu>
<menu id="menu_action" accesskey="A" label="Action">
<menupopup>
<menuitem id="m30" label="Clear" accesskey="C"
oncommand="cmd_clear()"/>
<menuitem id="m31" label="Submit" accesskey="S"
oncommand="cmd_submit()"/>
</menupopup>
</menu>
<spacer flex="1"/>
<menu id="Help" accesskey="H" label="Help">
<menupopup>
<menuitem id="m40" label="Help Contents" accesskey="H"
oncommand="cmd_help()"/>
<menuitem id="m41" label="Contact Details" accesskey="D"
oncommand="cmd_contact()"/>
<menuitem id="m42" label="About" accesskey="A"
oncommand="cmd_about()"/>
</menupopup>
</menu>
</menubar>
</toolbox>
<hbox class="title">
<deck id="title">
<label value=""/>
<label value="User Problem"/>
<label value="Technical Problem"/>
<label value="Review Status"/>
</deck>
<spacer flex="1"/>
<label value="Ticket #"/>
<textbox id="f00" readonly="true" size="10"/>
</hbox>
<deck id="forms" flex="1">
<vbox/>
<vbox>
<checkbox id="f10" label="Information Missing"/>
<checkbox id="f11" label="Errors Discovered"/>
<checkbox id="f12" label="Please Send Author's E-mail"/>
</vbox>
<vbox>
<label>Please describe the nature of the problem.
Your client's version will be automatically reported</label>
<textbox id="f20" multiline="true" rows="6"/>
</vbox>
<vbox>
<radiogroup>
<radio id="f30" label="draft approval"/>
<radio id="f31" label="final draft"/>
<radio id="f32" label="publication approval"/>
</radiogroup>
</vbox>
</deck>
<statusbar id="sbar">
<description>Current Status:</description>
<deck id="stext" selectedIndex="0">
<description id="sb0" class="warn">Disconnected</description>
<description id="sb1" class="ok">Logged On</description>
<description id="sb2" class="ok">Delivered</description>
<description id="sb3" class="warn">Delivery Failed</description>
</deck>
<spacer flex="1"/>
</statusbar>
</window>
|
这个清单几乎不可能比一个 Java 类还要大,然而它却是一个应用程序的全部 GUI。XUL 既方便又简短。像
toolbar 、
menu 、
textbox
和
radiogroup 这些标签也十分简单。您不需要任何像
XCreatePixmapFromBitmapData()
这样的 3GL 调用,整个过程一点儿也不痛苦。
在这些标签中,很多都提供了不可见的结构:
toolbox 、
hbox
和 vbox
是容器标签,可以对它们所包含的元素的布局产生影响(比如说,“vbox”是“vertical box”的简写)。这种包容关系在 XUL 中经常可以遇到:
toolbox
包含
menubar ,而
menubar 又包含若干
menu s,其中每一个
menu s 都可以提供一个
menupopup 下拉菜单,这个
menupopup
中又包含若干个
menuitem 菜单项。至于其他标签,
spacer 用于在屏幕上的元素之间加入空隙。
label
和
description 两个标签专门用来代替 HTML 中的 <p>。恕我在这里没有足够的篇幅介绍每一个
XUL 标签了。
XUL 标签中用到的某些属性与 HTML 中是一样的,比如
id 、
class 、
value 、
accesskey 、
readonly
和
selectedIndex 。其他属性则是 XUL 专有的,比如
grippyhidden (不让用户隐藏某个菜单栏)和
oncommand (这是一个事件处理程序,当选中某个菜单项时激活)。
Mozilla 中 XML 应用程序的注册名称由最顶端的
window 标签中的 URL 给出;这是一个产品标识符,不会从任何地方加载。图
1 显示了这个 XUL 文档的一部分内容:
图 1. 起始的 XUL 页面
普通 XHTML 文档用三种方式部署这个 applet:
嵌入的 applet 显示在整个窗口的下半部。您可以看到,这个 applet 的菜单栏看起来和整个浏览器窗口的菜单栏类似。事实上,它们实现为
menubar 标签的不同实例。applet 菜单栏和主浏览器的菜单栏一样,也可以对鼠标、制表符导航、组合热键等等作出响应。这样的集成是免费的,这一点
Java applet 无法实现。Java applet 没有自己的 URL;必须通过
applet 或是
object
标签才能初始化。
支持这个 applet 的 XHTML 如清单 2 所示,里面只是 HTML 事件处理程序和
iframe 的简单应用。
清单 2. 支持 applet 的 XHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
var flags = "toolbar=no,locationbar=no,menubar=no,width=275,height=275";
function show()
{
window.open('ticket.xul', '_blank', flags);
}
</script>
</head>
<body>
<h2>Test Page</h2>
<p>
A hyperlink that starts the example
in a separate window:
<a href="about:blank"
onclick="show(); return false">Lodge Ticket</a>.
</p>
<p>
A button that starts the example
in a separate window:
<button onclick="show()">Lodge Ticket</button>.
</p>
<p>
Here is the ticket entry system displayed in
an <iframe>:
</p>
<iframe width="275" height="275" src="ticket.xul"/>
</body>
</html>
|
XUL 与 XHTML
集成 XUL 与 XHTML 之间的某些集成十分精彩:
- 第一点,因为 Mozilla 平台负责渲染这两种 XML,所以 Java applet 系统初始化时那种可怕的延迟在这里根本不存在。
- 第二点,XUL 的导航和整个浏览器的导航是完全相同的,因此用户不需要学习一种新的导航方法。
- 第三点,Mozilla 具有十分优秀的 CSS2 样式表支持,这种技术原先是用于 XHTML 的,但也适用于 XUL。这就意味着这样的
applet 通常会采用浏览器的默认外观(上例中的主题是 Modern)。
清单
1中所需的样式表是通过顶部的
?xml-stylesheet? 声明引入的。第一次声明引入当前主题的标准样式表,第二次声明增加了一个为
applet 专门定制的样式表。其代码如下所示:
清单 3. 简单的层叠样式表代码
.ok { color : green; }
.warn { color : red; }
.title { margin-bottom : 20px; }
hbox { -moz-box-align : end; }
|
除了
-moz-box-align 是 Mozilla 扩展的,用于提供 XUL 布局提示,其余的都是标准 CSS2。在本例中,那个提示的意思是将内容右对齐。
对于 Java applet 而言,如果要支持样式表的话,则需要一个独立于 Mozilla 的 CSS 渲染库。使用这样的库从根本上扩大了
applet 的规模。
图
2和图
3
是这个 applet 的两幅截屏画面,一个是嵌入式的,另一个是独立的对话框。
图 2. 嵌入式 applet
图 3. 运行于独立对话框中的 applet
XUL 忽略了 HTML 中一些让人生气的限制。您可以将表单元素一个摞一个的堆叠起来,下面的元素可以很好地隐藏掉(
deck
标签可以实现,这很类似于 hypercard 栈)。这也就是 applet 使多组控件在一个面板内切换显示的方法。在 HTML 中,我们无法通过静态定义实现这样的效果,如果要完全实现的话,得写相当多的动态
HTML 代码。您还可以改变底部状态条的颜色或内容,这在 HTML 中仍然是不可能完成的任务。
XUL 也不是靠动态 HTML 来模仿的表单;它的菜单系统可以超出
iframe 的区域,覆盖到 HTML 的周围,甚至可以像一个一般的弹出菜单那样蔓延到桌面上。简言之,XUL
是真正的 GUI。本例中没有展示将 XUL 和 HTML 混合在一个文档中的情况——这只要使用 XML 名称空间就可以实现。
对脚本的支持
XUL 比较弱的方面是脚本。尽管 XUL 比 HTML 提供更多的用户响应能力,但是却不能为您实现业务逻辑。这一角色在 HTML 中一样,都是由
JavaScript 代码来处理的。这样的脚本可以像 HTML 一样轻巧和简单,也可以与 Mozilla 平台上的众多对象实现深度交互。本文中的例子要支持
applet 从远程服务器端下载,以及建立后端连接,它只用到了轻量级的脚本。Mozilla 对于轻量级脚本的支持十分先进,所以您可以做的事情还很多。
清单
1中的
script 标签负责将这种功能加入 XUL applet。这个 applet 的工作情况如下:
- 在用户登录之前,除 Login 和 Help 之外所有的菜单都不可用。登录意味着从原先的 Web 服务器上获得了一个会话 ID。
- 登录之后,Ticket 菜单就可用了。
- 从这个菜单中选择特定的故障单,然后,相关的表单元素就会出现在控件的主体部分中,Action 菜单也变为可用。
- 服务器查询出一个可用于这个故障单的单据号。
- 如果从 Action 菜单中选择 Submit,这个单据就通过普通的 HTTP
GET 请求发送到服务器上,服务器上的处理像任何其他
HTTP 请求一样。
服务器端接收这次请求需要的代码很普通,本文没有介绍。
要实现所有这些逻辑,使用的 JavaScript 代码不会超过 200 行。为完成这项工作,我们用 XUL 版本的 HTML
script
标签包含进来三个源文件。脚本文件 init.js 执行各种各样的初始化工作,commands.js 实现所有的菜单选项,server.js
解决客户机与服务器交互过程中的问题。
清单 4 显示了 init.js 文件,您可以从中看出 XUL 脚本运行的环境。
清单 4. init.js 进行初始化
// ---- Global variables ----
var session = null; // log in identifier, or null
var ticket = {
id : null, // set to server-supplied number.
url : null // set to the page in question.
};
// ---- Perform initialisation when the page has loaded ----
window.addEventListener("load", initialise, false);
function initialise()
{
var node;
var cookie = window.parent.document.cookie;
// do we have an existing session?
if ( cookie && cookie.substr("sessionID") )
{
//chop up the cookie string
session = cookie.search(/sessionID=[^;]{1,};/);
session = session.replace(/^.*sessionID=/,"");
session = session.replace(/;.*/,"");
session = parseInt(session);
document.getElementById("stext").selectedIndex = 1;
}
else
{
document.getElementById("menu_ticket").setAttribute("disabled","true");
document.getElementById("menu_action").setAttribute("disabled","true");
}
// don't support "exit" if we're inside an <iframe>.
if ( window.parent.location.href != window.location.href )
{
document.getElementById("m12").setAttribute("disabled","true"); // "Exit"
window.focus();
}
}
|
由于 XUL 是一种特殊的 XML,很多 W3C 标准 DOM 接口都是可以用的。这些接口使 XUL 和 HTML 呈现出 Element
节点树的外观,其中还包括 DOM 0 特性,如 cookie 的支持,以及导航对象。
脚本自身的状态可以存储在 JavaScript 中,也可以通过与 DOM 交互实现。
getElementById()
方法功能强大,它是 Microsoft 的
document.all() 方法的标准版本。可以通过 DOM 标准访问的对象与
Mozilla 平台提供的成千上万的对象是完全隔离的。这里只使用了前一类对象。
在清单 5 中,DOM 用来存放一个装载事件处理程序,当 XUL 页面完全接收时,这个事件激活
initialize()
函数。
initialize() 函数在 applet 的 DOM 以及包含它的 HTML 文档中搜索前进,寻找用
cookie 形式保存的会话 ID,并禁用适当的菜单。这里的情况与 Java applet 不同,不会在 Java 和 JavaScript
对象之间进行蹩脚的转换,从 JavaScript 中可以找到任何东西。安全问题依然是通过自动检查的方法强制执行的。
第二个清单 commands.js 是一段例行程序。它为每一个菜单选项提供事件处理程序。清单 5 是从这个文件中节选出来的一段例子,说明如何用基本的
DOM 样式的操作来切换 applet 中
deck 标签定义的面板内容:
清单 5. 切换 deck 标签
function cmd_user()
{
init_ticket();
document.getElementById("title").selectedIndex = 1;
document.getElementById("forms").selectedIndex = 1;
}
|
在这里,两个
deck 的
selectedIndex 都是从零开始的(上面是一组空白面板),如果
Ticket|User Ticket 菜单选项选中,就切换到 1(上面是一组复选框)。请您查看 command.js 的完整清单,看看如何对这些表单元素进行查询,提取出用户的数据,然后构造出
HTTP 的
GET 请求所要求的参数字符串。从技术角度讲,提交按钮应该用 HTTP 的
POST
请求,因为这不是一个获取信息的操作,但是这里为了简单,使用了
GET 。
最后一个脚本 server.js 使用了特殊的
XMLHttpRequest 对象,现在这也是现代浏览器的标准特性了。这一特性允许
XUL applet(或任何 JavaScript 代码)通过 HTTP 连接回最初的服务器,而不会干扰到当前正在显示的页面。
Mozilla 也支持其他协议,如 SOAP 和 WSDL,还支持裸 socket。这种功能意味着基于 XML 的接口的网络应用能力现在和基于
Java 的接口是一样的。清单 6 显示了一段面向网络操作的代码:
清单 6. server.js 使 applet 可以连接到最初的服务器上
// fake ticket ID request for the purposes of this demo.
function init_ticket()
{
ticket.id = Math.floor(Math.random()*10000);
ticket.url = window.parent.location.href;
document.getElementById("menu_action").removeAttribute("disabled");
document.getElementById("f00").value = ticket.id;
}
// real implementation for a ticket request, using an HTTP server.
function init_ticket_real()
{
var req = new XMLHttpRequest(); // Request
var params = window.encodeURI("action=new-ticket-id");
// ---- HTTP GET request for the ticket id.
req.open("GET", "id-generator.cgi" + "?" + params);
req.send("");
// ---- Server results for HTTP GET
if ( req.status / 100 == 2 ) // HTTP 2xx Response?
{
ticket.id = req.getResponseHeader("X-Ticket-Sequence-Number")
ticket.url = window.parent.location.href;
document.getElementById("menu_action").removeAttribute("disabled");
}
else
{
alert("Failed to construct a new ticket. Contact support.");
}
}
|
第一个
init_ticket() 的实现允许在不需要任何服务器支持的前提下,对那个 applet 进行操作。第二个实现展示了如何在服务器的协助下悄无声息地处理请求(这是出于安全性的要求)。
req
对象用
open() 方法构造请求,再用
send() 方法发送请求,这时这个方法会阻塞,直到接收到响应为止。
也可以用非阻塞方式调用
send() 方法。当接收到响应的时候,错误检查机制能够保证服务器方面成功执行了任务。在本例中,我依靠服务器设置一个名为
X-Ticket-Sequence-Number 的特殊 HTTP 头部字段。这个字段中保存的是用户正在构造的故障单的唯一标识。这种方法也可以用来获取会话
ID,以及提交最终的故障单。反馈信息是通过表单的状态条消息提供给用户的:绿色代表好消息,红色代表坏消息。
对 XUL的感受
XUL applet 最有意思的地方在于它是轻量级和解释型的。XUL、JavaScript和 CSS 代码都是解释型的语言,每一种都缺乏完整的面向对象语言所具有的正规性。XUL
的表达式简单易用,这使得它成为建立 GUI 原型的优秀技术。它的灵活性也使得这样的 GUI 可以很容易地分开后重新组织。
尽管受到 Mozilla 平台的制约,还是有一些业务场境可以从这种方法中获益的,特别是在内部网环境中,客户机软件一开始就是受到控制的。另一个应用的例子是为高级用户交付分析结果或者数据录入的软件终端。本文中讲述的
XUL 特性仅仅是冰山一角而已。
参考资料
关于作者  | |  | Nigel McFarlane 是科技文章作家、分析家和程序员。他经验丰富,在 IT、软件、电信、Internet 和物理方面都有涉猎。他为 XML 和脚本技术撰写了大量的文章和若干书籍;他最近出版的一本图书为 Rapid Application Development with Mozilla。Nigel 住在澳大利亚的墨尔本市。可以通过 nrm@kingtide.com.au 与他联系。 |
对本文的评价
|