任何编程问题都可以通过多种正确的方法解决。本系列共考察了四种创建一个 Asynchronous JavaScript
+ XML (Ajax) 天气预报面板(weather badge)的方法,这是一种小型可重用部件,可以轻松嵌入到任何
Web 页面。本文是第一篇文章,主要介绍一些基础内容,同时审视第一种方法 —— 遍历 DOM 树。
希腊哲学家亚里士多德曾经写到:“通往失败的道路有许多条……,但通往成功的道路仅有一条。” 遗憾的是,亚里士多德并不是一名计算机程序员。
虽然亚里士多德的第一个论断符合编程的特点 — “通往失败的道路有许多条” — 他的第二个推测却没有一点依据。
本系列文章针对同一问题运用了四种方法。每种方法都被证明是正确的 — 每种方法都有各自的优缺点。它们要解决的问题并不复杂,同样,解决方案也不复杂。尽管如此,这些方法涉及了大量
“权衡”,可以包含到非常简单的解决方案中。
要定义需要解决的问题,我列出了以下的问题说明:
构建一个 Ajax 库读取从国家气象服务(National Weather Service)获得的最新观测数据,然后提取一部分数据并转换为
HTML 格式,从而创建一个天气预报面板。
许多 Web 站点喜欢在 Web 页面上显示当地的天气状况。为此,他们需要访问最新的气象信息。那么如何获得这些数据呢?
在美国,National Weather Service (NWS) 提供大量的天气信息。其中包括美国数百个城市的当前天气观测数据。该数据可使用
RSS 或 XML 格式。
在 Ajax 中,字母 X 代表 XML,所以 NWS 数据似乎非常适合 Ajax 方法。
本系列文章共考察了四种不同的方法,用于构建一个 Ajax 天气预报面板 — 一个小的信息框,包含 NWS 监测的任何城市或城镇的气象信息。其设计目标是
我将利用这四种方法来展示各个方法内在的 “权衡” 问题。任何一种方法都不存在绝对的对与错。
本质上讲,每种方法的实现截然不同,如 表 1 所描述。
表 1. 天气预报面板库的四种版本
方法 |
描述 |
1:遍历 DOM 树 |
服务器上简单的 Web 代理从 NWS 服务器拉出数据并发送到浏览器。在浏览器内,JavaScript 解释器从返回的
responseXML DOM 树提取部分数据,添加一些 HTML 格式,然后将其插入到页面中的 DIV 标记。 |
2:服务器上的 XSLT |
一个服务器端脚本从 NWS 服务器拉出数据,使用 XSLT 将数据由 XML 转化成 HTML 格式,然后将 HTML
代码片段发回浏览器。浏览器随后将代码片段插入到一个 DIV 标记。 |
3:客户端 XSLT |
该方法使用一个简单的 Web 代理(同方法 1)将 XML 数据发送回浏览器。与方法 1 不同的是,使用客户端 XSLT
将 XML 转换为 HTML,并将其插入到一个 DIV 标记。 |
4:JSON 和动态脚本标记 |
一个外部服务(Yahoo! Pipes)将 NWS 数据从 XML 转换为 JavaScript Object Notation
(JSON)。天气预报面板库利用 JSON 的特殊能力和 JavaScript 语言将转换后的数据拉回到浏览器 — 避免了对代理的需求。 |
这四种构建可重用 Ajax 天气预报面板的方法均共享以下元素:
- 管道(pipeline)方法
- 一个简单的 Ajax 库
- weather_badge() JavaScript 函数
- National Weather Service 数据
管道方法
数据管道的概念至少可以回溯到 UNIX® 的早期。在这个模型中,数据进入管道,然后在其中经历一系列过滤器。每个过滤器以某种方法转换数据。经转换后的数据被送回到管道,也有可能进行进一步的转换,直至完成所有转换为止。管道的终点可能是一个用户终端、重定向到一个文件或另一个程序。
这种方法能够有效处理从 Web 上获得的基于 XML 的数据和服务。程序能够从 Web 获取 XML 数据,发送至管道,并且将一系列转换链接以提取数据并重新格式化。
不同于 UNIX 命令行中的管道和过滤器,这种用于 Ajax 应用程序的方法要求管道横跨网络内多台计算机。 XML 数据可以源自一台
Web 服务器,然后被传送到另一个域内的不同服务器,最终到达目的地:用户的 Web 浏览器。
一个简单的 Ajax 库
有关 Ajax 的介绍超出了本文的范围,但是可以获取优秀的初级读本(参见 参考资料)。
为使本系列尽可能涵盖更多的读者,本文的示例使用了一个小型 Ajax 库,如 清单
1 所示。这个库围绕 XMLHttpRequest 对象只提供了最少的修饰 — 刚好消除各个主要浏览器中的 XMLHttpRequest
差异。
清单 1. ajax-simple.js — 示例使用的最小的 Ajax/XMLHttpRequest
库
function Ajax (url, parms, method, callback) {
this.url = url;
this.parms = parms;
this.method = method;
this.callback = callback;
this.async = true;
this.create ();
this.req.onreadystatechange = this.dispatch (this);
}
Ajax.prototype.dispatch = function (ajax) {
function funcRef()
{
if (ajax.req.readyState == 4) {
if (ajax.callback) {
ajax.callback (ajax.req);
}
}
}
return funcRef;
}
Ajax.prototype.request = function () {
if (this.method == "POST") {
this.req.open("POST", this.url, this.async);
this.req.send (this.parms);
}
else if (this.method == "GET") {
this.req.open("GET", this.url + this.parms, this.async);
this.req.send (null);
}
}
Ajax.prototype.setAsync = function (async) {
this.async = async;
}
Ajax.prototype.create = function () {
var xmlhttp;
/*@cc_on
@if (@_jscript_version >= 5)
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (E) {
xmlhttp = false;
}
}
@else
xmlhttp = false;
@end @*/
if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
try {
xmlhttp = new XMLHttpRequest();
} catch (e) {
xmlhttp = false;
}
}
this.req = xmlhttp;
} |
weather_badge() JavaScript
函数
四种方法都具有一个在 Web 页面添加天气预报面板的接口。该接口是一个单一的 JavaScript 函数:weather_badge()。该函数需要
2 个参数:一个用于识别相关城市和城镇的 NWS 站点 ID 和一个 HTML DIV 标记的元素 ID。这个 DIV 标记是呈现天气预报面板的目标。图
1 是一个 Ajax 天气预报面板的示例。
图 1. 一个 Ajax 天气预报面板
天气预报面板是使用 HTML 呈现的,但是你可以使用级联样式表(CSS)控制它的许多外观元素,包括字体、背景颜色和边框。
清单 2 演示如何在 Web 页面中嵌入天气预报面板。这里从一个
JavaScript onload 事件处理程序中调用 weather_badge() 函数。
清单 2. 在 Web 页面嵌入天气预报面板
<html>
<head>
<title>Apache Proxy Example</title>
<link rel="stylesheet" type="text/css" href="weather.css" />
<script language="Javascript" src="ajax-simple.js"></script>
<script language="Javascript" src="weather_badge_apache_proxy.js">
</script>
<script>
function init () {
weather_badge ("KAKQ", "target1");
}
</script>
</head>
<body onload="init();">
<h3>Apache Proxy Example</h3>
<div class="wbadge" id="target1">
Loading...
</div>
</body>
</html> |
National Weather Service
数据
National Weather Service 站点使用一个站点 ID 用于识别读取天气数据的城市、城镇或其他位置。站点 ID
是一个由四个字符组成的惟一编码。
所有 NWS 当前观测数据的基 URL 是:
http://www.nws.noaa.gov/data/current_obs/ |
基 URL 加上由四个字符组成的站点 ID,提供了一个气象数据 URL。例如 Richmond, Virginia 的站点 ID
是 KRIC。Richmond 的天气数据的 URL 就是:
http://www.nws.noaa.gov/data/cuurent_obs/KRIC.xml |
一个简单的 XML 格式定义了当前观测数据,如 清单
3 所示。
清单 3. Richmond, Virginia 的 National Weather
Service XML 数据
<current_observation version="1.0"
xsi:noNamespaceSchemaLocation=
"http://www.weather.gov/data/current_obs/current_observation.xsd">
<credit>NOAA's National Weather Service</credit>
<credit_URL>http://weather.gov/</credit_URL>
<image>
<url>http://weather.gov/images/xml_logo.gif</url>
<title>NOAA's National Weather Service</title>
<link>http://weather.gov</link>
</image>
<suggested_pickup>15 minutes after the hour</suggested_pickup>
<suggested_pickup_period>60</suggested_pickup_period>
<location>Richmond International Airport, VA</location>
<station_id>KRIC</station_id>
<latitude>37.51</latitude>
<longitude>-77.31</longitude>
<observation_time>
Last Updated on Dec 11, 12:54 pm EST
</observation_time>
<observation_time_rfc822>
Tue, 11 Dec 2007 12:54:00 -0500 EST
</observation_time_rfc822>
<weather>Overcast</weather>
<temperature_string>54 F (12 C)</temperature_string>
<temp_f>54</temp_f>
<temp_c>12</temp_c>
<relative_humidity>80</relative_humidity>
<wind_string>From the South at 5 MPH</wind_string>
<wind_dir>South</wind_dir>
<wind_degrees>180</wind_degrees>
<wind_mph>4.6</wind_mph>
<wind_gust_mph>NA</wind_gust_mph>
<pressure_string>30.31" (1026.7 mb)</pressure_string>
<pressure_mb>1026.7</pressure_mb>
<pressure_in>30.31</pressure_in>
<dewpoint_string>48 F (9 C)</dewpoint_string>
<dewpoint_f>48</dewpoint_f>
<dewpoint_c>9</dewpoint_c>
<heat_index_string>NA</heat_index_string>
<heat_index_f>NA</heat_index_f>
<heat_index_c>NA</heat_index_c>
<windchill_string>53 F (12 C)</windchill_string>
<windchill_f>53</windchill_f>
<windchill_c>12</windchill_c>
<visibility_mi>7.00</visibility_mi>
<icon_url_base>
http://weather.gov/weather/images/fcicons/
</icon_url_base>
<icon_url_name>ovc.jpg</icon_url_name>
<two_day_history_url>
http://www.weather.gov/data/obhistory/KRIC.html
</two_day_history_url>
<ob_url>http://www.nws.noaa.gov/data/METAR/KRIC.1.txt</ob_url>
<disclaimer_url>http://weather.gov/disclaimer.html</disclaimer_url>
<copyright_url>http://weather.gov/disclaimer.html</copyright_url>
<privacy_policy_url>http://weather.gov/notice.html</privacy_policy_url>
</current_observation> |
天气预报面板仅需要这些数据的一小部分。我将使用的值位于 location、weather、icon_url_base、icon_url_name、temperature_string、wind_string、relative_humidity、visibility_mi
和 observation_time 元素内。
使用这种方法必须首先解决一个对 XMLHttpRequest 对象(由 Ajax 程序使用)的基本限制:域相同的问题。
出于安全考虑,一个 XMLHttpRequest 调用只能够发起传递初始 Web 页面的服务器请求。除非我为 National
Weather Service 工作,否则我的服务器一定在他们的域之外(www.nws.noaa.gov)。图
2 展示了第一种方法的数据管道。
图 2. 方法 1 中用于天气预报面板的数据管道
如果可以访问 Web 服务器的配置,有一个简单的解决方法:使用一个 Web 代理。
Web 代理 指将一个服务器请求重定向到另一台服务器。 我需要 Ajax 程序在我的服务器上请求一个资源,然后将该请求转换为在
NWS 上的请求。这样就解决了 “相同域” 的问题:Ajax 程序与它所在的服务器通信,后者将这个请求秘密地重定向到 NWS 服务器。
在 Apache Web 服务器上,代理是通过一个 ProxyPass 规则实现的。该规则的语法非常简单:
ProxyPass our_directory their_url |
第一个选项引用的是我的服务器上一个不存在的位置,而第二个选项是一个远程服务器上的 URL。当一个请求进入 our_directory,请求被
Apache 重定向到 their_url。请求者(我的 Ajax 程序)永远不会察觉到这个重定向。
这就是用于实现 National Weather Service 数据访问的代理规则:
ProxyPass /nws_currobs/ http://www.nws.noaa.gov/data/current_obs/ |
若要得到关于 Richmond,Virginia 的数据,则请求这个 URL:
Apache 将请求转换成对 NWS 的请求:
http://www.nws.noaa.gov/data/current_obs/KRIC.xml |
在浏览器中解析 XML
在 Ajax 应用程序中,如果一个请求 XML 数据的服务器请求是成功的,responseXML 属性将被初始化。这个对象属性包含检索后的
XML,被解析为一个 XMLDocument 类型的 DOM 树(如果服务器数据不是有效的 XML,或者,在某些浏览器上,如果返回的数据没有伴随一个
text/xml 或 application/xml HTTP Content-type 报头,则不会创建 responseXML
属性。在这些情况下,responseText 属性包含由服务器返回的未处理过的文本)。
利用 responseXML,我可以遍历 DOM,从返回的 XML 中提取值。清单
4 显示对返回的 XML 进行删减后的版本:
清单 4. 从 NWS 服务器返回的经过删减的 XML
<current_observation version="1.0"
xsi:noNamespaceSchemaLocation=
"http://www.weather.gov/data/current_obs/current_observation.xsd">
<location>Richmond International Airport, VA</location>
<observation_time>
Last Updated on Dec 11, 12:54 pm EST
</observation_time>
<weather>Overcast</weather>
<temperature_string>54 F (12 C)</temperature_string>
<relative_humidity>80</relative_humidity>
<wind_string>From the South at 5 MPH</wind_string>
<visibility_mi>7.00</visibility_mi>
<icon_url_base>
http://weather.gov/weather/images/fcicons/
</icon_url_base>
<icon_url_name>ovc.jpg</icon_url_name>
</current_observation> |
现在我可以从 responseXML 提取 wind_string。 responseXML 属性是一个 XMLDocument
类型。XMLDocument 的 documentElement 属性返回我的 XML DOM 树的顶层元素。要在程序中进行检验,在代码中插入一个
alert() 函数:
alert ("tagName: " + req.responseXML.documentElement.tagName); |
在执行时,alert() 弹出一个窗口,包含下面的内容:
tagName: current_observation |
若访问 current_observation 下的各个元素,使用 getElementsByTagName()。这个 Element
方法接受一个标记名参数,并返回一个包含所有子 Element 节点的数组,其名称与元素名相同。在一个 JavaScript 程序中,我可以编写以下代码
var elements = req.responseXML.documentElement.getElementsByTagName("wind_string"); |
NWS XML 数据仅包括一个 wind_string 元素,因此完全可以认为我需要的数据位于第一个元素中。wind_string
元素标记内的实际文本可以按以下方式访问:
elements[0].firstChild.data |
从一个 XML 文档中提取单一值非常困难,尤其是当文档拥有一个简单的结构时。很明显,这种从 XML 提取数据的方法很快会变得难以处理。如果我把上述提到的所有步骤组合到一个
DOM 引用中,它看起来类似下面的内容:
req.responseXML.documentElement.getElementsByTagName("wind_string")[0].firstChild.data |
对于这个应用程序,我可以定义一个 JavaScript helper 函数来提取值,使代码更具可读性,如 清单
5 所示。
清单 5. 简化 DOM 访问的函数
function get_element (doc_el, name, idx) {
var element = doc_el.getElementsByTagName (name);
return element[idx].firstChild.data;
} |
有了该函数后,weather_badge() 函数变得更易于管理,如 清单
6 所示。
清单 6. weather_badge() 函数使用一个 Apache 代理检索 XML
function weather_badge (nws_id, div_name) {
var ajax = new Ajax
("/nws_currobs/" + nws_id + ".xml",
"",
"GET",
function (req) {
var doc_el = req.responseXML.documentElement;
// Extract values from XML structure returned by
// by Ajax (XMLHttpRequest) call.
var location = get_element (doc_el, "location", 0);
var temperature_string = get_element (doc_el, "temperature_string", 0);
var weather = get_element (doc_el, "weather", 0);
var icon_url_base = get_element (doc_el, "icon_url_base", 0);
var icon_url_name = get_element (doc_el, "icon_url_name", 0);
var wind_string = get_element (doc_el, "wind_string", 0);
var relative_humidity = get_element (doc_el, "relative_humidity", 0);
var visibility_mi = get_element (doc_el, "visibility_mi", 0);
var observation_time = get_element (doc_el, "observation_time", 0);
var div = document.getElementById ("target1");
div.innerHTML =
"<center>\n"
+ "<b>" + location + "</b><br>\n"
+ weather + "<br>"
+ "<img border='0' src='"
+ icon_url_base + icon_url_name + "'/><br>\n"
+ temperature_string + "<br>\n"
+ "Wind: " + wind_string + "<br>\n"
+ "Humidity: " + relative_humidity + "<br>\n"
+ "Visibility: " + visibility_mi + "<br>\n"
+ "<br><span style='font-size: 0.8em; font-weight: bold;'>"
+ observation_time + "</span><br>\n"
+ "</center>\n";
}
);
ajax.request ();
}
|
代码创建了一个 Ajax 对象(请回忆一下,这仅是一个围绕 XMLHttpRequest 对象的小型包装器)。Ajax 构造函数采用四个参数,如
表 2 所描述。
表 2. Ajax 构造函数采用的四个参数
参数 |
描述 |
url |
用于远程资源的 URL — 在本文示例中,National Weather Service XML 文件通过我的服务器进行代理。 |
parms |
一个包含任意 URL 参数的字符串。我请求的是一个静态的 XML 文档,而不是一个服务器端脚本,所以不需要任何参数。 |
method |
该参数通知 Ajax 生成一个 HTTP GET 请求。 |
callback |
这个参数定义在 XML 文档返回到浏览器时由 Ajax 调用的 callback 函数。在本文中,我从 XML 提取值,然后使用一些
HTML 格式化标记将它们拼接到一起,创建了一个 HTML 代码片段。这个代码片段定义了我的天气预报面板。 |
我使用 innerHTML 属性将 HTML 代码片段填入到 Web 页面中。目标 DIV 标记已经被作为 div_name
参数传入 weather_badge()。现在,可以非常轻松地插入我的 HTML 代码片段:
var div = document.getElementById (div_name);
div.innerHTML = html_snippet; |
优点和缺点
解决编程问题的任何方法都需要进行权衡。表 3
列举了这种方法的优缺点。
表 3. 方法 1 的优点和缺点
优点 |
缺点 |
适用于所有主要的浏览器。
不要求附加的库或第三方工具。 |
语法不规则使得难于访问 XML 元素,即使对简单的 XML 文档也是如此。 |
在本系列的
第 2 部分 中,我将介绍直接 DOM 访问的替代方法。我将使用 XSLT 将 XML 转换成 HTML。第二个和第三个实现都使用
XSLT,不同处在于发生转换的数据管道位置 — 浏览器或服务器。
描述 |
名字 |
大小 |
下载方法 |
本系列的样例代码 |
x-xmlajax.zip |
194KB |
|
学习
获得产品和技术
-
IBM 试用版软件:使用可直接从 developerworks 下载的试用版软件构建您的下一个开发项目。
讨论 |