
Yahoo UIライブラリを使った
|
前パートまでに解説したAjaxおよびJavaScriptの基本知識を踏まえて、本パートではAjaxとDBを組み合わせたWebアプリケーションを開発してみる。DBからのデータ取得には、サーバーサイドのJavaメソッドを簡単に呼び出せるJSON-RPC-Javaフレームワークを利用し、ユーザー画面の作成には今話題のYahoo UIライブラリを使う。最後にクロスブラウザやセキュリティ問題など、Ajaxでアプリケーションを開発する際の注意点も述べる。
ブックマーク参照機能の実装
次に、登録されたブックマークを参照する機能を見ていこう。この機能は、すでにDBに登録されているブックマークを取得し、最新のものから順番に表示するというものである。しかも、定期的にDBの内容をチェックし、更新されたものがあれば取得して再表示を行なう。この再表示を行なう部分をページ全体のリロードなしに実現している点が、本パートのアプリケーションの中で最もAjaxらしさが出ている点と言えるだろう。
また、ブックマークを表示する部分にはYahoo UIライブラリのTreeViewを使用して、画面3のような形で情報を表示している。内容は次のとおりだ。
ブックマークのタイトル
登録回数,コメント,更新日時
「リンク先を表示する」
最初はブックマークのタイトルだけが列挙された状態だが、タイトル左脇の三角形をクリックすると上記の内容が表示される仕組みである。「リンク先を表示する」という部分はハイパーリンクになっており、ここをクリックするとブックマークされているWebページを閲覧できる。それでは、さっそくコードを見てみよう。この機能を実現しているのは、以下の1つのJSP(ViewBookmark.jsp)と1つのJavaクラス(登録機能でも登場したBookmarkクラス)である。全体の処理の流れは、図4のとおりだ。

画面3 ブックマーク参照用のJSPページ

図4 ブックマーク参照機能の処理の流れ
ViewBookmark.jsp
LIST6をご覧いただきたい。登録されているブックマークを参照するためのJSPページである。Ajaxの仕組みを使ってブックマーク表示を実現している。
①はお決まりの、JSON-RPC-Javaを利用するための準備を行なうコードである。基本的に登録機能で記述した内容と同じなので、説明は割愛する。
②では、Yahoo UIライブラリのJavaScriptを読み込んでいる。実際に利用するのはTreeViewのみだが、これだけのJavaScriptのインクルードが必要となっている。
③からは、JavaScriptのコードを記述している部分である。ここではJSONRPCClientオブジェクトの作成を行なっているが、この部分も登録機能のときと同様の処理である。
④が、このJSPページの主要部分である。ここは少し詳細に見ていこう。
④-1のinitTree関数では、“Root”という名前を持つTreeViewオブジェクトを作成している。これが、タイトル以下のデータを表示するツリーを束ねるルート(根本)となる。また、ツリーを描画する際に、TreeViewはこの引数で指定された名前と同じIDを持つドキュメント領域(div要素など)に対して描画される。したがって、HTMLドキュメント中には、必ずTreeViewに付けた名前と同じIDを持つ要素がなければならない。このJSPページの場合、⑦の部分がそれに該当する。続いて、TreeViewオブジェクトが持つsetExpandAnim、setCollapseAnimという2つのメソッドを呼び出している。これらは、ツリーを開いたり閉じたりする際にアニメーション効果を持たせる設定を行なう。Yahoo UIライブラリのTreeViewは、このように簡単にアニメーション効果を持たせられるようになっている。
④-2のrecvBookmark関数では、サーバー側のgetBookmarksメソッドを利用してブックマークの内容を取得している。引数lastUpdateで更新日時を指定すると(注2)、その日時以降に登録/更新されたブックマークのみ返すようになっている。lastUpdateは初期値として空文字列を持っているので、最初に呼び出したときには、すべてのブックマークが返されることになる。getBookmarksメソッドの処理内容は、先のLIST5をご覧いただきたい。このメソッドは、ブックマークの情報を格納したHashMapオブジェクトをListオブジェクトに入れて返すように実装されている。このように構造を持った戻り値の場合、JSON-RPC-JavaはJSON形式でクライアントにデータを渡すようになっている。クライアント側では、これらのデータにアクセスするためにeval関数を使用して、JSON形式のデータをJavaScriptオブジェクトに変換している。evalを利用すれば、Listに含まれるデータに変数.listで参照でき、その中に格納されているHashMapの値に変数.lis[t 配列インデックス].map[キー(ここではテーブルの列名)]で参照できる。forループは、HashMapオブジェクトを1つ1つ参照するためのものだ。 このようにして、サーバー側に格納されたデータにアクセスし、それをもとにノードを作成し てTreeViewオブジェクトにセットしている。
④-3が、ブックマークを表示するためのツリーを作成している部分になる。ツリーの作成にはいくつかの種類のノードを利用しているが、ノードを作成するためのコンストラクタの引数はすべて共通で、左からノードを描画するために利用する文字列またはオブジェクト、親ノード、表示時子ノードを展開して表示するかどうか、となっている。
ツリーの定義は、次の手順で行なっている。文章だけでは分かりづらいかもしれないので、ツリー構造を図5にまとめてみた。
手順1:「var node」の定義
TreeViewの直下にブックマークのタイトルを値として持つノードを作成し、Rootに格納する。Yahoo UIライブラリでは、ノードを表現形式別に数タイプ用意しているが、ここではMenuNodeを使用している。これは、MacOSでツリー形式のデータを表示する際によく使われている表現形式である。
手順2:「var child1」の定義
MenuNodeの子ノードとして登録回数、コメント、更新日時を値として持つノードを作成し、直前に作成したnodeノードに格納する。このノードはデータを表示するだけなので、TextNodeを利用している。
手順3:「var child2」の定義
MenuNodeの子ノードとして、もう1つリンク先を表示するためのノードを作成し、やはりnodeノードに格納する。このノードはハイパーリンクとしたいので、HTMLNodeを利用している。HTMLNodeは、値としてHTMLを指定できる。したがって、ハイパーリンクの表示だけでなく、さまざまな用途で利用できるだろう。
④-4 では、TreeViewオブジェクトのdrawメソッドを使ってツリーを描画している。TreeViewを作成しても、このメソッドを呼び出さない限りは描画されないので、注意してほしい。説明の順序が逆になったが、⑤では、60秒間隔でinitTree関数を呼び出す指定を行なっている。これにより、ユーザーが明示的にページをリロードすることなく、常に最新のブックマークが表示されることになる。非同期通信とHTMLページの動的な書き換えが実現する、Ajaxならではの動作と言えるだろう。⑥では、TreeViewオブジェクトが使用するCSSをインクルードしている。これらのCSSをカスタマイズすることで、ツリーの見栄えを変更することも可能だ。

図5 ブックマークを表示するためのツリー構造
LIST6 ViewBookmark.jsp
<%@ page contentType="text/html; charset=shift_jis" %>
<jsp:useBean scope="session" ・・・・①
class="com.metaparadigm.jsonrpc.JSONRPCBridge" />
class="sample.Bookmark" />
<% JSONRPCBridge.registerObject("Bookmark", sample.Bookmark.class); %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis">
<title>ブックマーク表示</title>
<script type="text/javascript" src="/jsonrpc.js"></script>
<script type="text/javascript" src="/YAHOO.js"></script> ・・・・②
<script type="text/javascript" src="/event.js"></script>
<script type="text/javascript" src="/animation.js"></script>
<script type="text/javascript" src="/dom.js"></script>
<script type="text/javascript" src="/treeview.js" ></script>
<script type="text/javascript">
<!--
var myTree;
var jsonrpc;
var lastUpdate = "";
function onLoad() {
jsonrpc = new JSONRpcClient("/JSON-RPC"); ・・・・③
initTree(); ・・・・④
setInterval("initTree()", 60000); ・・・・⑤
}
function initTree() { ・・・・④-1
myTree = new YAHOO.widget.TreeView("Root");
myTree.setExpandAnim(YAHOO.widget.TVAnim.FADE_IN);
myTree.setCollapseAnim(YAHOO.widget.TVAnim.FADE_OUT);
recvBookmark();
}
function recvBookmark() {
var result = jsonrpc.Bookmark.getBookmarks(lastUpdate); ・・・・④-2
var data = eval(result);
var listLength = data.list.length;
for(i = 0; i < listLength; i++) {
var title = data.list[i].map["title"];
var node = new YAHOO.widget.MenuNode(title, myTree.getRoot(), false); ・・・④-3
var child1 = new YAHOO.widget.TextNode(
data.list[i].map["count"] + ", " + data.list[i].map["note"] + ", " +
data.list[i].map["last_update"], node, false);
var child2 = new YAHOO.widget.HTMLNode(
"<a href=\""+ data.list[i].map["url"] + "\">リンク先を表示する</a>", node, false);
}
myTree.draw(); ・・・・④-4
}
// -->
</script>
</head>
<body onLoad="onLoad()">
<link rel="stylesheet" type="text/css" href="/tree.css"> ・・・・⑥
<link rel="stylesheet" type="text/css" href="/screen.css">
<p>ブックマーク表示</p>
<div ></div> ・・・・⑦
</body>
</html>
Bookmark.java(getBookmarksメソッド)
ブックマーク登録機能でも使用した、サーバー側のJavaプログラムである。ソースコードは前出のLIST5を参照してほしい。
getBookmarksメソッドは、ViewBookmark.jspから呼び出されるメソッドである。引数として日付/時刻型のデータを受け取り、引数で指定された値よりも新しい更新日付のデータを返す。もし、引数に空文字列が指定されたら、すべてのデータを取得し返すという処理を行なっている。データは、テーブルbookmarkの値を列名をキーとするHashMapオブジェクトに格納されている。ブックマークのデータは何件も存在するはずなので、このHashMapをListオブジェクトに格納して、JavaScript側に返している。ソートの順番は日付の降順を指定している。
また、このクラスのgetCurrentDateStrメソッド、toDateStringメソッドは、それぞれjava.util.Dateオブジェクトの値をDB上のタイムスタンプ型である“yyyy-MM-dd HH:mm:ss”形式に変換するためのメソッドである。
サンプルアプリケーションのソースコードは以上である。JSON-RPC-JavaとYahoo UIライブラリを利用することで、通信とUI部分の記述量をかなり削減できることがお分かりいただけたであろうか。
ところで、最後のViewBookmark.jspをご覧になって違和感を感じられた方もいたのではないだろうか。本来であれば、この処理は最初だけすべてのブックマークを取得し、2回目以降は差分だけを取りに行くべきである。ところが、紹介した実装では、毎回すべてのブックマークを取得するようになっている。変数lastUpdateに値を入れていないためである。
もちろん、これには理由がある。当初はそのように実装するつもりでいた。ソート順を日付の降順にしている点からもお分かりいただけるかとは思うが、最新の情報をツリーの一番上に表示するようにしたかったのである。しかし、TreeViewのメソッドなどを見てみても、動的にノードの順番を変えたり、新しいノードをツリーの先頭に持ってくるような機能が見つけられなかったのである。したがって、今回のような実装とした。
ただ、このままではページ全体をリロードするのとほとんど差がなく、Ajaxならではのメリットを活かし切れていない。ユーザーインターフェイスに限って言えば、満たすべき要件を考えると使用するフレームワークの選定を誤ってしまったということになるだろう。このように、当然ではあるが採用するべきフレームワークに関しては事前に十分な調査が必要である。ご注意願いたい。
LIST7 XMLHttpRequestオブジェクトを生成するクロスブラウザ対応コード
function createXMLHttpRequest() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
}
try {
return new ActiveXObject("MSXML2.XMLHTTP");
} catch(e) {
try {
return new ActiveXObject("Microsoft.XMLHTTP");
} catch(ex) {
return null;
}
}
}
Ajaxアプリケーション開発のポイント
さて、最後にAjaxアプリケーションを開発する際の留意点をまとめてみよう。
クロスブラウザへの対応
Ajaxアプリケーションでは、クロスブラウザ(異なるWebブラウザ間での仕様対応や動作の違い)の問題が常に付きまとう。JavaScriptの実装方法がブラウザによって多少違うからである。よく知られているのが、XMLHttpRequestオブジェクトの生成方法である。これをクロスブラウザ対応にするためには、LIST7のようなコードを書く必要がある。
ただし、今回紹介したコードではこのような記述は一切出てきていない。XMLHttpRequestオブジェクトすら登場していない。これは、JSON-RPC-JavaがXMLHttpRequestによる通信処理を隠蔽してしまっているからである。このように、クロスブラウザ対応をほとんど意識しなくて済むという点も、既存のフレームワークを利用する大きなメリットの1つである。
非同期通信が起こすサーバー負荷の軽減
サンプルアプリケーションでも紹介しているが、定期的にポーリング(注3)を行ない、ページの一部分を書き換えるようなAjaxアプリケーションを開発する場合には、サーバーにかかる負荷を十分検討しよう。ポーリング間隔は、ポーリングの延長線上で発生する処理(今回の場合だとブックマークの取得処理)とユーザーの利便性とをよく勘案して決定すべきである。
また、1ページ内の複数個所でポーリングを行なう必要がある場合、それらを別々のXMLHttpRequestオブジェクトで通信していると1ページで複数のコネクションが必要になってしまい、サーバーに大きな負荷をかけてしまう。このような場合は、本来であれば複数のコネクションで取得すべき情報を1つのリクエストにまとめてしまうといった工夫が必要になってくるであろう。
通信中のセキュリティ確保
Ajaxアプリケーションにおいても、通信路のセキュリティを確保する必要がある場面が当然出てくるであろう。このような場合、最も簡単な解決策は、SSL(注4)を導入することである。これにより、通信路上は暗号化されるので、少なくとも普通のWebアプリケーションと同等のセキュリティを確保することは可能である。 ただし、Ajaxで(XMLHttpRequestオブジェクトで)SSL通信を実現するためには、そのJavaScriptを含んでいるWebページ自身をSSL経由で取ってくる必要がある。逆に言えば、これだけでSSL通信を行なうことは可能である。
* * * * *
いささか駆け足でAjaxアプリケーションの実装方法を紹介してきたが、いかがだっただろうか。Ajax自身は決して新しい技術が採用されているわけではなく、今までの技術を利用することで成り立っている。とはいえ、JavaScriptの互換性や、Ajax関連フレームワークの未整備など解決するべき問題はある。しかし、GoogleMaps(http://maps.google.co.jp/)に代表されるように、既存の概念を打ち破るアプリケーションを開発することもできるほど、大きな可能性を秘めている仕組みである。ぜひ習得し、読者諸氏の開発に役立ててほしい。
中村正弘(なかむらまさひろ)
ウルシステムズ株式会社に所属。シニアコンサルタント。最近は開発方法論のコンサルティング的な仕事が多く、コードを書く機会が減っていることが少し残念に思っている。Ajax は久しぶりに楽しめそうな技術なので、面白いことができないかといろいろ模索中。
dbmagazine-writers@ulsystems.co.jp
(注2)このJSPページには、ユーザーが更新日時を入力して条件指定する仕組みを用意していない。コード中でlastUpdateの値を直接設定していただきたい。
(注3)非同期に発生するイベントをチェックするための手法で、リモートコンピュータに対し、イベントが発生していないかどうかを聞きに行く。RSSリーダーが、購読しているRSSのアップデートがないかどうかを確認しに行く処理もポーリングと考えられる。
(注4)Secure Socket Layerの略。米ネットスケープが開発した、インターネット上で情報を暗号化して送受信するプロトコル。広く普及しており、Webで機密性の高い情報を送受信する際によく使われる。ECサイトが決済のためにクレジットカード番号の入力を促すような画面では、ほぼ例外なく使われている。