レベル: 中級 Mark Pruett (mark.l.pruett@dom.com), System Architect, Dominion
2008年 3月 04日 プログラミングの問題を解決する正しい方法は 1 つだけではありません。この連載では、Ajax (Asynchronous JavaScript + XML) による天気バッジを作成する 4 つの手法を取り上げます。このバッジは、どんな Web ページにでも簡単に組み込める再利用可能な小さなウィジェットです。第 1 回目となる今回の記事では、まず基本的な背景を紹介した後、1 番目の手法である DOM ツリーの探索について詳しく検討していきます。
ギリシャの哲学者、アリストテレスはかつてこう書いています。「失敗の仕方はいろいろあるが、成功する方法はただ 1 つしかない」。そうは言うものの、アリストテレスはコンピューター・プログラマーではありません。
 |
よく使われる頭字語
- DOM: Document Object Model
- HTML: Hypertext Markup Language
- HTTP: Hypertext Transfer Protocol
- RSS: Rich Site Summary
- XML: Extensible Markup Language
- XSLT: Extensible Stylesheet Language Transformation
|
|
アリストテレスの名言の前半、「失敗の仕方はいろいろある」という部分は確かにプログラミングにも当てはまりますが、後半の部分となると、真実とまるでかけ離れています。
この一連の記事では、同じ問題に対する 4 つの異なる手法を取り上げます。この 4 つの手法のうち、明らかに誤っている手法は 1 つもなく、そのそれぞれに長所と短所があります。ここで取り上げる問題とそのソリューションはいずれも複雑なものではありませんが、そうした単純なソリューションの場合でさえも、これらの手法が持つさまざまな長所と短所が明らかになってきます。
問題: 再利用可能な、Ajax による天気バッジを作成する
この連載で解決しようとしている問題を明らかにするために、以下にその問題を規定します。
National Weather Service から最新の観測データを読み取ってデータの一部を抽出し、それを HTML に変換して天気バッジを作成する Ajax ライブラリーを作成する。
 |
バッジとは何か
バッジ (ウィジェットとも呼ばれます) とは、Web サイトに組み込んで特殊な (大抵はサード・パーティーの) コンテンツを表示するために必要なものを完備した JavaScript コードの小さな塊のことです。通常、バッジはこのようなコンテンツをページの小さな領域に表示します。バッジでは、ニュース・フィード、カレンダー、時計だけでなく、バッチの作成者が望むあらゆるコンテンツを表示することができます。
|
|
Web サイトがその Web ページに局地の天気を含めようとすることはよくありますが、それには最新の気象情報にアクセスする必要が出てきます。では、この最新データはどのように取得するのでしょうか。
米国国内では、NWS (National Weather Service) が膨大な気象情報を提供しています。このデータには数百に及ぶ都市の現在の気象観測値が含まれ、RSS または XML フォーマットのいずれかで入手することができます。
Ajax に含まれる X は XML を意味します。したがって、NWS のデータは Ajax の手法にぴったり合うはずです。
考えられる 4 つのソリューション
この連載記事では、Ajax を使って 天気バッジを作成する 4 つの異なる手法を紹介します。このバッチは、NWS が観測する都市または町の気象情報を表示する小さなボックスです。設計の目標としては、以下の 2 つがあります。
この 4 つの手法を用いて、それぞれの手法に伴う長所や短所を探っていきます。どれが正しく、どれが間違っているというわけではありません。
それぞれの実装の内容は、表 1 に説明するように大きく異なります。
表 1. 天気バッジ・ライブラリーの 4 つのバージョン
| 手法 | 内容 |
|---|
| 1: DOM ツリーの探索 | サーバー上の単純な Web プロキシーが NWS サーバーからデータをプルしてブラウザーに送信します。ブラウザー内部では、JavaScript インタープリターが、返されてきた responseXML DOM ツリーの部分を抽出して HTML フォーマット設定を追加し、これをページ内の DIV タグに挿入します。 | | 2: サーバーでの XSLT | サーバー・サイドのスクリプトが NWS サーバーからデータをプルし、XSLT を使用してその XML データを HTML に変換してから、変換後の HTML スニペットをブラウザーに送り返します。ブラウザーは単にこのスニペットを DIV タグに挿入するだけとなります。 | | 3: クライアント・サイドの XSLT | この手法では (手法その 1 とまったく同じ) 単純な Web プロキシーを使用して XML データをブラウザーに送り返します。手法その 1 と違う点は、クライアント・サイドの XSLT を使用して XML を HTML に変換し、DIV タグに挿入することです。 | | 4: JSON と動的 script タグ | 外部サービス (Yahoo! Pipes) が NWS データを XML から JSON (JavaScript Object Notation) に変換します。天気バッジ・ライブラリーは JSON 特有の性質と JavaScript 言語を利用して変換されたデータをプルしてブラウザーに返します。つまり、プロキシーは必要ありません。 |
共通の要素
以下の要素は、再利用可能な Ajax 天気バッジを作成する 4 つの手法すべてに共通します。
- パイプライン手法
- 単純な Ajax ライブラリー
-
weather_badge() JavaScript 関数
- National Weather Service のデータ
パイプライン手法
データ・パイプラインの概念が生まれたのは、初期の頃の UNIX® にまで遡ります。このモデルでは、パイプラインに入ったデータが一連のフィルターを通過していき、それぞれのフィルターが何らかの方法でデータを変換します。変換されたデータはまたパイプラインに戻され、すべての変換が完了するまで、さらに変換が重ねられます。パイプラインの終わりにあるのは、ユーザーの端末であったり、ファイルへのリダイレクトであったり、あるいは別のプログラムであったりします。
この手法は、Web 上の XML ベースのデータとサービスを処理する場合に効果があります。この場合、プログラムは Web から XML データを取得してパイプラインに送り、一連の変換チェーンによってデータの抽出と再フォーマット設定を行うことができます。
UNIX コマンドラインでのパイプとフィルターとは異なり、Ajax アプリケーションに適用するこの手法には、ネットワーク上の複数のコンピューターにまたがるパイプラインが必要になります。ある Web サーバーから送信された XML データが、別のドメインにある別のサーバーに渡されて、最終的な宛先であるユーザーの Web ブラウザーに届けられる可能性があるからです。
単純な Ajax ライブラリー
この記事では、Ajax の詳細には触れませんが、「参考文献」で優れた入門書を紹介しています。
この連載をできるだけ多くの読者に利用してもらうため、ここで紹介する例ではリスト 1 に示す小さな Ajax ライブラリーを使用します。このライブラリーが提供する 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 ページに追加するためのインターフェースは、4 つすべての手法に共通しています。このインターフェースは weather_badge() という 1 つの JavaScript 関数です。この関数は、対象の都市または町を識別する NWS ステーション ID、そして HTML DIV の要素 ID という 2 つのパラメーターを受け取ることを想定しています。この DIV タグが、天気バッジをレンダリングするターゲットとなります。図 1 は、Ajax による天気バッジの一例です。
図 1. Ajax による天気バッチ
天気バッジは HTML によってレンダリングされますが、フォント、背景色、枠などをはじめ、さまざまな外観の要素については CSS (Cascading Style Sheets) を使用して制御することができます。
リスト 2 に、天気バッジを Web ページに組み込む方法を示します。ここでは、weather_badge() 関数が JavaScript の onLoad イベント・ハンドラー内から呼び出されています。
リスト 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 は 4 文字からなる固有のコードです。
すべての NWS 最新観測データの基本 URL は、以下のとおりです。
http://www.nws.noaa.gov/data/current_obs/
|
この基本 URL に 4 文字のステーション ID が組み合わせられて特定気象データの URL になります。例えば、バージニア州リッチモンドのステーション ID は KRIC なので、リッチモンドの天気データの URL は以下のようになります。
http://www.nws.noaa.gov/data/cuurent_obs/KRIC.xml
|
最新の観測データを定義する単純な XML フォーマットをリスト 3 に示します。
リスト 3. バージニア州リッチモンドの場合の 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 要素に含まれる値です。
手法その 1: DOM ツリーの探索
この手法ではまず、Ajax プログラムが使用する XMLHttpRequest オブジェクトの基本的制約に対処しなければなりません。その制約とは、同一ドメインの問題です。
セキュリティー上の理由から、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
|
最初のオプションは私のサーバー上の存在しないロケーションを参照するもので、2 番目のオプションはリモート・サーバーの URL です。our_directory に対するリクエストを受信するたびに、Apache はそのリクエストを their_url にリダイレクトします。リクエストの送信者 (この Ajax プログラム) がこれに気付くことはありません。
National Weather Service のデータにアクセスするために実装するプロキシー規則は以下のとおりです。
ProxyPass /nws_currobs/ http://www.nws.noaa.gov/data/current_obs/
|
バージニア州リッチモンドのデータを取得するには、以下の 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 配下にある個別の要素にアクセスするには、etElementsByTagName() を使用します。この Element メソッドはタグ名をパラメーターとして取り、その要素名を持つすべての子 Element ノードの配列を返します。JavaScript プログラム内では、このメソッドを以下のように記述することができます。
var elements = req.responseXML.documentElement.getElementsByTagName("wind_string");
|
NWS XML データには 1 つの wind_string 要素しか含まれないので、必要なデータは最初の要素に含まれると見込んで間違いありません。wind_string 要素タグ内の実際のテキストには、以下の方法でアクセスします。
elements[0].firstChild.data
|
XML 文書から抽出する値がたった 1 つだけで、しかも文書の構造は単純であることを考えると、この作業はかなりの量です。このように XML からデータを抽出する方法が瞬く間に手に負えなくなってくることは目に見えています。上記のすべてのステップを単一の DOM 参照に組み合わせると、以下のようになります。
req.responseXML.documentElement.getElementsByTagName("wind_string")[0].firstChild.data
|
このアプリケーションの場合、JavaScript ヘルパー関数を定義して値を抽出すれば、コードをもう少し読みやすくすることができます (リスト 5 を参照)。
Listing 5. A function to make DOM access less cumbersome
function get_element (doc_el, name, idx) {
var element = doc_el.getElementsByTagName (name);
return element[idx].firstChild.data;
}
|
リスト 5. DOM アクセスの煩雑さを軽減するための関数
function get_element (doc_el, name, idx) {
var element = doc_el.getElementsByTagName (name);
return element[idx].firstChild.data;
}
|
リスト 6 を見るとわかるように、上記の関数を配置すると weather_badge() 関数は多少扱いやすくなります。
リスト 6. Apache プロキシーを使用して XML を取得する weather_badge() 関数
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 オブジェクトの最小限のラッパーでしかないことを思い出してください)。表 2 に、この Ajax コンストラクターが取る 4 つのパラメーターを説明します。
表 2. Ajax コンストラクターが取る 4 つのパラメーター
| パラメーター | 説明 |
|---|
| url | リモート・リソースの URL。この例でのリモート・リソースは、私のサーバーをプロキシーとして介し、その先にある National Weather Service XML ファイルです。 | | parms | すべての URL パラメーターが含まれるストリング。ここで要求するのはサーバー・サイド・スクリプトではなく静的 XML 文書なので、パラメーターは一切必要ありません。 | | method | このパラメーターが、Ajax に HTTP GET リクエストを送信するよう指示します。 | | callback | このパラメーターは、ブラウザーから XML文書が返されたときに Ajax が呼び出すコールバック関数を定義します。この例では、XML から値を抽出し、それから少数の HTML フォーマットにするためのタグによって抽出した値をつなぎ合わせて HTML のスニペットを作成します。このスニペットが天気バッジを定義します。 |
HTML スニペットを Web ページに組み込むために使用しているのは innerHTML プロパティーです。対象の 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 にフォーマット変換します。2 回目と 3 回目の記事ではいずれも XSLT を使用しますが、データ・パイプラインでの変換場所、つまりブラウザーまたはサーバーのどちらで変換が行われるかという点が異なります。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample code for this series | x-xmlajax.zip | 194KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
-
IBM 試用版ソフトウェア: developerWorks から直接ダウンロードできる試用版ソフトウェアで、次の開発プロジェクトを構築してください。
議論するために
著者について
記事の評価
|