ARCHIVES
アーカイブス
アーカイブス

Yahoo UIライブラリを使った Ajax+DBアプリケーション開発の実践

翔泳社『DBマガジン』2006年6月号 「AjaxでDBプログラミング」

中村正弘
2006年06月01日
※内容は公開当時のものです

前パートまでに解説したAjaxおよびJavaScriptの基本知識を踏まえて、本パートではAjaxとDBを組み合わせたWebアプリケーションを開発してみる。DBからのデータ取得には、サーバーサイドのJavaメソッドを簡単に呼び出せるJSON-RPC-Javaフレームワークを利用し、ユーザー画面の作成には今話題のYahoo UIライブラリを使う。最後にクロスブラウザやセキュリティ問題など、Ajaxでアプリケーションを開発する際の注意点も述べる。

Ajaxでソーシャルブックマークを作ろう

本パートでは、DBと情報をやり取りするAjaxアプリケーションを作成してみる。ただし、解説をAjax部分にフォーカスしたいため、サーバーサイドアプリケーションの作りはなるべくシンプルにすることとした。また、サーバーサイドアプリケーションはJavaで実装するが、DBアクセスにJDBCを直接使用しており、Hibernateなどの仕組みは利用していない。この点に関してはご了承いただきたい。サンプルアプリケーションは、複数のユーザーでWebページのブックマーク(お気に入りを共有する「ソーシャルブックマーク」とする。サーバーにブックマークを集めておき、どのユーザーも参照できる仕組みだ。ソーシャルブックマークと言うと大げさだが、ブックマークの登録機能と参照機能だけを備えた、ごく単純なものと考えてほしい。

サンプルアプリの機能概要

まずは、サンプルアプリケーションの機能概要について簡単に説明しておこう。ユーザーは、Webブラウザから自分の好きなWebページのブックマーク(URL)をDBに登録できる。登録されたブックマークは複数のユーザー間で共有され、だれもが自由に参照できるものとする。 このアプリケーションは、大きく分けて以下の2つの機能から構成される。

ブックマーク登録機能

自分の好きなWebページをブックマークするための機能。機能自体は、普通のJ2EEのシステムで簡単に作れる内容だが、本パートでは、この部分もあえてAjaxで実装してみる。

ブックマーク参照機能

DBに登録されているWebページを参照する機能。ページを表示している間は、Ajaxの仕組みを利用して定期的にサーバーに問い合わせ、最新の内容を常に表示するようにする。ページ全体のリロードではなく、Ajaxの仕組みで最新の情報を取得し続ける点がポイントである。

アプリケーションの実装方針

さて、Ajaxで先に挙げた2つの機能を持つアプリケーションを開発していくわけだが、Ajaxアプリケーションを作成するにあたり、大きく以下の2点をあらかじめ決定しておく必要がある。

サーバーとの通信手段をどうするか

Ajaxである以上、XMLHttpRequestオブジェクトを使用して通信を行なうのが通常であろう。ただしその場合には、サーバーとのやり取りを実現するプロトコル(メッセージのフォーマット)を決めておく必要がある。メッセージ自身にはXMLが使われることが多いとは思うが、アプリケーションの要件を満たすのであれば、プレーンテキストでも良いし、JSONでも良いわけである。また、XMLHttpRequestを直接扱わなくても、最近では通信のためのさまざまなフレームワークが存在している。そういったフレームワークを採用するかどうか、また採用するとしたらどのようなフレームワークにするのかを決定しておく必要があるだろう。

ユーザーインターフェイスの実装をどうするか

Ajaxアプリケーションの特徴の1つであるリッチなユーザーインターフェイスを実現するためには、それなりの量のコードを書かなければならない。当然、より使いやすいインターフェイスを提供しようと思うと、それに応じて記述するコード量も多くなっていく。システムの要件と照らし合わせ、どの程度のユーザーインターフェイスを実現し、それを実現するためにどのようなフレームワークを利用する(あるいは開発する)のかを決定しておく必要がある。

以上2点を勘案した結果、本パートでは最近注目されている(と筆者が考えている)2つのフレームワークを採用することにした。サーバーとの通信部分には「JSON-RPC-Java」を、クライアントのUI実装部分には「YahooUIライブラリ」をそれぞれ利用する。これらのライブラリの使い方は、アプリケーションの実装を行なう中で説明していく。

図1  JSON-RPC-Javaの概念図

図1 JSON-RPC-Javaの概念図

開発/実行環境

概要説明の最後に、本パートで利用する実行環境やライブラリをまとめておく。

  • Java 2 Standard Edition 5.0
  • Tomcat 5.5.16
  • MySQL 5.0
  • JSON-RPC-Java
  • Yahoo UIライブラリ

コラム:JSONとは

JSON(JavaScript Object Notation)は、構造化されたデータをテキストで表現するためのフォーマットである。名前からも分かるとおり、もともとJavaScriptで使用することを想定されていたが、基本的には言語から独立したデータフォーマットである。
例えば、JavaのHashMapのような、キーと値のペアを持つようなデータは、{"キー": "値"} という形式で表わすことができる。いくつか具体例を見てみよう。

例1:配列を作る

まずは配列を作ってみよう。

var data = [1, 2, 3]

このように、配列の場合はカンマで区切って値を直接記述する。上記の例の場合、data[0]、data[1]、data[2]でそれぞれの値を参照できる。ちなみに、eval関数を使う場合は次のように記述する。

var dataStr = "[1, 2, 3]";
var data = eval(dataStr);

あとは、先と同様にdata[0]、data[1]、data[2]で値の参照が可能である。

例2:オブジェクトを作る

次にオブジェクトを作ってみよう。

var data = { "prop": "value" };

オブジェクトの場合は、上記のようにキー:値のペアで指定する。このように定義した場合、data.propという記述で値(この場合は"value")を参照できる。また、data["prop"]というように連想配列として扱うことも可能である。
複数のプロパティを持つオブジェクトを作る場合は、次のように記述する。

var data = {  <br/>"prop": "value",  <br/>"foo": "bar"  <br/>};

この場合、dataはpropとfooという2つのプロパティを持つことになる。

例3:複雑なオブジェクトを作る

それではもう少し複雑なオブジェクトを作ってみよう。

var data = [  <br/>{"prop": "value1"},  <br/>{"prop": "value2"},  <br/>{"prop": "value3"}  <br/>];

この例は、propというプロパティを持つオブジェクトを格納した配列を作っている。data[0].prop、data[1].prop、data[2].prop でそれぞれの値"value1"、"value2"、"value3"を参照できる。

● 例4:JSON-RPC-Javaのデータをのぞいてみる

それでは最後に、JSON-RPC-Javaがどのようなデータをやりとりしているのかを見てみよう。本文のViewBookmark.jspの実行結果として載せているデータを、JSON形式のまま見るとLIST Aのようになる。

いかがだろうか。上記の内容とViewBookmark.jspのコードを合わせてご覧いただけば、なぜ変数listで配列を参照できるのか、なぜlist[0].mapという記述でHashMapの値を参照できるのかが分かると思う。このようなJSON形式のデータを作るのは結構面倒な作業だと思う。しかし、JSON-RPC-Javaを使えば、Javaで表現されているListオブジェクトやHashMapオブジェクトを自動的にJSON形式に変換してくれるのである。

JSON自身は非常にシンプルだが、かなり複雑なデータ構造も表現できる強力な仕組みである。JSONに関する詳細はWebサイト(http://www.json.org/js.htmlやhttp://d.hatena.ne.jp/brazil/20050915/1126717649)をご覧いただきたい。

LIST A JSON形式のままデータを見る

{ list: [
{ javaClass: "java.util.HashMap",
map: { last_update: "2006-03-28 13:59:27", title: "Yahoo! JAPAN",
url: "http://www.yahoo.co.jp/", note: "あ", count: "3" } },
{ javaClass: "java.util.HashMap",
map: { last_update: "2006-03-25 16:23:36",
title: "MySQL 5.0 Reference Manual",
url: "http://dev.mysql.com/doc/refman/5.0/en/index.html",
note: "テスト2", count: "1" } } ],
javaClass: "java.util.ArrayList" }

フレームワークの概要

アプリケーション実装の説明に入る前に、今回利用するAjax用フレームワークの概要を説明しておこう。

JSON-RPC-Javaはその名のとおり、JavaScriptプログラムからサーバー側にあるJavaクラスのメソッド呼び出しを実現するためのフレームワークである。このフレームワークを使うことにより、開発者はXMLHttpReuqestオブジェクトを一切意識することなく、サーバー側にあるJavaクラスのメソッドを呼び出すことができる。また、このフレームワークではサーバーとクライアント間のメッセージフォーマットにJSON(コラム「JSONとは」を参照)を使っているが、基本的に開発者がJSONを意識する必要はない。

図1の概念図をご覧いただきたい。クライアント側にあるJSONRPCClient、サーバー側にあるJSONRPCBridgeおよびJSONRPCServletがそれぞれJSON-RPC-Javaによって提供されているクラスである。Ajaxアプリケーション、Javaクラスとして表記している部分が、開発者がそれぞれのアプリケーションごとに記述する部分である。

注目すべきは、サーバー側で普通のJavaクラスを書くだけで良い点である。それだけで、JavaScript側からメソッドを呼び出せるようになる。リモートから呼び出されることを特に意識するような部分はない。非常に強力なフレームワークである。 JSON-RPC-Javaが持つ具体的な機能などは、アプリケーションの実装と併せて後ほど解説する。

一方のYahoo UIライブラリであるが、こちらも名前が示すとおり、ユーザーインターフェイス(以下、UI)を実現するための各種クラスで構成されたフレームワークである。しかも、UIだけではなく、サーバーとの通信を行なうための機能も提供されている。ただし、本パートでは通信には先ほどのJSON-RPC-Javaを利用することとし、Yahoo UIライブラリはUI部分のみ利用することにする。詳細は、やはりアプリケーションの実装解説とともに紹介しよう。

テーブルの作成とフレームワークのインストール

本パートのサンプルアプリケーションを実行するにはいくつかの準備が必要だが、ここではブックマークを格納するDBテーブルの作成と、JSON-RPC-JavaおよびYahoo UIライブラリのインストール方法を簡単に説明しておく。
なお、TomcatやMySQLといったサーバー環境のインストールおよび設定については説明を割愛させていただくので、ご了承願いたい。

テーブルの作成

LIST1のSQL文を実行し、あらかじめテーブルを作成しておく。各レコードには、URL(url列)、タイトル(title列)、コメント(note列)、同一URLへのブックマーク数(count列)、更新日時(last_update列)が入る。url列に主キー制約を設定しており、URLごとにタイトルやコメントなどの情報が格納される設計である。
また、MySQL用のJDBCドライバもTomcatコンテキスト内のlibディレクトリに入れておく。筆者はmysql-connector-java-5.0.0-betabin.jarを使用したが、こちらは皆さんの環境(MySQLのバージョンなど)に合ったものを入れてほしい。

JSON-RPC-Javaのインストール

JSON-RPC-Javaを入手し(http://oss.metaparadigm.com/jsonrpc/download.htmlでダウンロード可能)、配布パッケージに含まれている「jsonrpc-1.0.jar」をコンテキスト内のlibディレクトリへ、「jsonrpc.js」をコンテキスト内のJSPページを置くディレクトリへそれぞれ配置してほしい。
なお、JSON-RPC-Javaではクライアントとの通信にServletを利用する。このSerlvletを登録するために、LIST2の記述をweb.xmlに追加する必要がある。

Yahoo UIライブラリのインストール

Yahoo UIライブラリを入手し( http://developer.yahoo.com/yui/#downloadよりダウンロード可能)、配布パッケージyui.zipに含まれている以下のファイルを、コンテキスト内のJSPページを置くディレクトリに配置する。

  • treeviewbuildtreeview.js(TreeView本体)
  • treeviewbuildYAHOO.js(Yahooのnamespaceを定義するためのファイル)
  • eventbuildevent.js(イベント関連処理のためのファイル)
  • animationbuildanimation.js(アニメーション機能のためのファイル)
  • dombuilddom.js(DOM関連処理のためのファイル)
  • treeviewexamplescssscreen.css(Yahoo UIライブラリ全般の画面描画に使用するCSS)
  • treeviewexamplescssmenutree.css(TreeView専用のCSS)
  • treeviewsrcimgディレクトリの内容すべて

筆者の環境では、これらはすべてROOTコンテキストに配置したので、ディレクトリ構成は図2のようになっている。この図には、後述する本アプリケーションを構成するファイルも含まれている。
なお、本誌付録CD-ROMにJSON-RPCJavaとYahoo UIライブラリを収録しているので、こちらを利用していただいても良い。

図2  サンプルアプリケーションのディレクトリ構造(ファイルの配置)

図2 サンプルアプリケーションのディレクトリ構造(ファイルの配置)

LIST1 ブックマークを格納するためのテーブル定義

create table bookmark(
url varchar(500),
title varchar(500),
note varchar(1000) character set sjis,
count integer,
last_update timestamp,
primary key(url)
);

LIST2 web.xmlに追加する記述

<pre><code>
<servlet>
<servlet-name>JSONRPCServlet</servlet-name>
<servlet-class>com.metaparadigm.jsonrpc.JSONRPCServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>JSONRPCServlet</servlet-name>
<url-pattern>/JSON-RPC</url-pattern>
</servlet-mapping>

ブックマーク登録機能の実装

それでは、それぞれの機能の実装を見ていこう。最初は、ブックマーク登録機能である。
この機能は、ユーザーがブックマークしたいと思ったWebページのタイトル、URL、ユーザーのコメントをDBに格納する、という処理を行なう。ごく単純な処理ではあるが、この処理を一切のページ遷移なしに実現している部分がAjaxらしいところである。

登録機能を実現するのは、2つのJSPページ(Bookmark.jsp、RegistBookmark.jsp)と1つのJavaクラス(Bookmarkクラス)だ。全体の処理の流れは、図3のとおりである。

図3  ブックマーク登録機能の処理の流れ

図3 ブックマーク登録機能の処理の流れ

Bookmark.jsp

LIST3をご覧いただきたい。このJSPページは、はてなブックマーク (注1) にならって、さまざまなWebページを手軽にブックマークするためのスクリプト(JavaScriptで記述)を、Webブラウザの「お気に入り」に登録してもらうための、ごく簡単なJSPページである。

このJSPページをコンテキスト内に置き、http://localhost/Bookmark.jsp(URLはJSPページが置かれたコンテキストによって異なる)にアクセスすると、画面1のように表示される。「ブックマーク登録」というハイパーリンクがあるのでこれを右クリックし、表示されたコンテキストメニューから[お気に入りに追加]を選べば、スクリプトの登録は完了だ。

あとは、登録したいWebページを表示し、お気に入りからこのスクリプトを呼び出せば、簡単にブックマーク登録用の画面を表示することができるというわけである。

LIST3 Bookmark.jsp

画面1 ブックマーク用スクリプトを登録するページ

画面1 ブックマーク用スクリプトを登録するページ

RegistBookmark.jsp

次に紹介するのは、ブックマーク登録用のページ(画面2)を表示するためのJSPページである。先ほどお気に入りに登録したスクリプトから呼び出されて、登録するURLに対するタイトルとコメントの入力をユーザーに求める。[登録]ボタンがクリックされると、サーバー側にある登録用のメソッドを呼び出す仕組みだ。

まずは、LIST4をご覧いただきたい。ここでは、本格的にAjaxのコードが現われている。ポイントとなる部分を詳細に見ていこう。

まず(1)では、JSONRPCBridgeオブジェクトを、Servlet APIが用意しているHttpSessionオブジェクトに関連付けている。これは、JSON-RPC-Javaを利用する上での決まりごととして必ず行なう。

(2)では、JSONRPCBridgeにBookmarkのクラスオブジェクトを登録している。これにより、Bookmarkクラスが保持しているすべてのstaticメソッドがJavaScript経由で使えるようになる。

registerClassメソッドは、第1引数として指定した名前に、第2引数でサーバー側にあるメソッドを提供するJavaクラスのクラスオブジェクトを紐付けてくれる。これにより、JavaScriptからは第1引数で指定した名前を使ってサーバー側のメソッドを呼び出せるようになる。後の⑤では、ここで指定したBookmarkという名前を使って、BookmarkクラスのregistBookmarkメソッドをJava Scriptから呼び出している。

なお、第2引数に指定するのはクラスオブジェクトであって、インスタンスではない点に注意していただきたい。インスタンスメソッドを公開する場合には、registerClassメソッドではなく、registerObjectメソッドを使用する。

(3)では、JSON-RPC-Javaのクライアント側で利用するライブラリを読み込んでいる。

(4)からはJavaScriptの記述に入っている。ここでは、Webページを読み込んだときにブラウザが呼び出す関数(onLoad)の中で、JSONRpcClientオブジェクトを生成している。以降は、このオブジェクトを介してサーバー側のメソッドを呼び出せるようになる。

(5)のregistBookmarkは、登録処理を実行するJavaScriptの関数である。[登録]ボタンがクリックされると呼び出されて、フォームに入力されている内容を取得し、サーバー側のregistBookmarkメソッドを呼び出す。ここでは、その呼び出し方に注目してほしい。var result=jsonrpc.Bookmark.registBookmark(url, title, comment);と、あたかもローカルにあるメソッドを呼び出すようにサーバー側のJavaメソッドを呼び出している。XMLHttpRequestオブジェクトやJSONを意識する必要はないのである。この部分を見るだけでも、JSON-RPC-Javaが非常に強力かつ有用なフレームワークであることが分かると思う。

なお、registBookmarkメソッドは、戻り値としてboolean(真偽値)を返すようになっている。このような実装では、コードにあるとおり、JavaScriptでもそのまま真偽値として評価可能である。もう少し複雑なデータ構造を扱う場合には別の手段が必要であるが、それは後述する。

(6)以降は、ほとんどが普通のHTMLフォームである。このJSPページは、基本的にJavaScriptからパラメータ付きで呼び出されることを想定しており、パラメータの値(ブックマーク対象ページのURLやタイトル)をフォーム内に表示している。また、フォームのsubmit( 送信)イベントを拾って、登録処理を行なうJavaScript関数が呼び出されるようにしている。

画面2 ブックマーク登録用のJSPページ

画面2 ブックマーク登録用のJSPページ

Bookmark.java(registBookmarkメソッド)

さて、登録処理の最後はサーバー側のJavaクラスである。LIST5をご覧いただきたい。このクラスは2つのpublicメソッドを持っているが、ここでは、registBookmarkメソッドを見ていただきたい。このメソッドは、RegistBookmark.jspから送られてくるブックマーク対象ページのURL、タイトル、ユーザーが入力したコメントをパラメータとして受け取り、SQL文を組み立てて、JDBCを使ってDBに登録している。

テーブルbookmarkの主キーは「URL」である。もし、すでに同じURLが存在していれば更新処理を、そうでなければ新規にレコードを追加する処理を行なっている。おおまかなフローは以下のとおりである。

(1)パラメータで受け取ったURLを持つ行を取得するSELECT文を実行する

(2)ResultSet#nextメソッドがtrueを返せば、同一のURLを保持する行が存在するということになるので、UPDATE文を実行してbookmarkテーブルのcount列の値をインクリメントし、last_update列を現在の時刻で更新する

(3)ResultSet#nextメソッドがfalseを返せば、同一のURLを保持する行が存在しないということになるので、INSERT文を実行して新規にレコードを追加する

処理そのものはごく普通のJDBCを使った処理なので、特に変わった点はない。しかし、このごく普通の、ServletですらないJavaクラスのメソッドを、クライアント側のJavaScriptかDB Magazine 2006 June 087ら呼び出せるという点は注目に値する。JSON-RPC-Javaを利用すれば、極端な話、Servlet APIすらほとんど知らなくてもサーバー側のコードを書くことができるのである。

また、RegistBookmark.jspの説明でも述べたが、このregistBookmarkメソッドは戻り値としてboolean値を返している。JSON-RPCJavaでは、JavaScriptとサーバーアプリケーションとの間のメッセージのやり取りにJSONを使っているのだが、このようなJavaの型とJSON形式への変換はすべてJSON-RPCJava のフレームワークが面倒を見てくれるのである。サーバー側の開発者はJSON形式を意識する必要はない。

なお、LIST5では、MySQLのデータベース名として"user_db"を、接続ユーザーとして"root"を、rootのパスワードとして"pwd"をそれぞれ指定している。この部分は読者の環境に合わせて変更していただきたい。

LIST4 Bookmark.jsp

<pre><code>
<%@ page contentType="text/html; charset=shift_jis" %>
<jsp:useBean scope="session" class="com.metaparadigm.jsonrpc.JSONRPCBridge" /> <font color="#ff0000″ size="2″>・・・・①</font>
<% JSONRPCBridge.registerObject("Bookmark", sample.Bookmark.class); %><font color="#ff0000″ size="2″>・・・・②</font><html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis">
<title>ブックマーク登録</title> <script type="text/javascript" src="/jsonrpc.js"></script> <font color="#ff0000″ size="2″>・・・・③</font><script type="text/javascript">
<!- function onLoad() { <font color="#ff0000″ size="2″> ・・・・④</font>jsonrpc = new JSONRpcClient("/JSON-RPC");
} function registBookmark() { <font color="#ff0000″ size="2″>・・・・⑤</font>var url = document.getElementById("url").innerHTML;
var title = document.getElementById("title").value;
var comment = document.getElementById("comment").value;
var result = jsonrpc.Bookmark.registBookmark(url, title, comment);
var elem = document.getElementById("registResult");
if(result) {
elem.innerHTML = "登録が完了しました。";
elem.style.color = "black";
} else {
elem.innerHTML = "登録に失敗しました。";
elem.style.color = "red";
}
return false;

}
// ->
</script>
</head>
<body onload="onLoad()">
<p>ブックマーク登録</p> <form onSubmit="return registBookmark();"> <font color="#ff0000″ size="2″>・・・・⑥</font><table border="0″>
<tr>
<td align="right">URL:</td>
<td><div ><%=request.getParameter("url")%></div></td>
</tr>
<tr>
<td align="right">タイトル:</td>
<td><input type="text" size="50″
value="<%=request.getParameter("title")%>"></td>
</tr>
<tr>
<td align="right">コメント:</td>
<td><input type="text" size="50″></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="登録">
<div ></div></td>
</table>
</form>
</body>
</html>

LIST5 Bookmark.java

package sample;
import java.util.*;
import java.text.*;
import java.sql.*;
public class Bookmark {
private static Connection con = null;
private static Statement stmt = null;
static {
try {
Class.forName("org.gjt.mm.mysql.Driver");
con = DriverManager.getConnection("jdbc:mysql:///user_db", "root", "pwd");
stmt = con.createStatement();
} catch(Exception e) {
e.printStackTrace();
}
}
public static boolean registBookmark(String url, String title, String comment) {
StringBuffer querySQL = new StringBuffer();
querySQL.append("select * from bookmark where url = '").append(url).append("'");
boolean result = true;
ResultSet rs = null;
try {
rs = stmt.executeQuery(querySQL.toString());
if(rs.next()) {
// ブックマークカウントを増やす
int count = rs.getInt("count");
count++;
StringBuffer updateSQL = new StringBuffer();
updateSQL.append("update bookmark set count=").append(count).append(",");
updateSQL.append("last_update="); ??
updateSQL.append("'").append(getCurrentDateStr()).append("'");
updateSQL.append(" where url='").append(url).append("'");
stmt.executeUpdate(updateSQL.toString());
} else {
// 新規に追加する
StringBuffer insertSQL = new StringBuffer();
insertSQL.append("insert into bookmark values(");
insertSQL.append("'").append(url).append("',");
insertSQL.append("'").append(title).append("',");
insertSQL.append("'").append(comment).append("',");
insertSQL.append("1,");
insertSQL.append("'").append(getCurrentDateStr()).append("')");
stmt.executeUpdate(insertSQL.toString());
}
result = true;
} catch(Exception e) {
e.printStackTrace();
result = false;
} finally {
try {
rs.close();
} catch(Exception e) {
e.printStackTrace();
}
}
return result;
}
public static List getBookmarks(String startDate) {
StringBuffer querySQL = new StringBuffer();
querySQL.append("select * from bookmark");
if(startDate.equals("")) {
// すべてのレコードを返す(where 条件は書かない)
} else {
// 一部のレコードのみ取得
querySQL.append(" where last_update > ");
querySQL.append("'").append(startDate).append("'");
}
querySQL.append(" order by last_update desc");
ResultSet rs = null;
List list = new ArrayList();
try {
rs = stmt.executeQuery(querySQL.toString());
while(rs.next()) {
HashMap map = new HashMap();
map.put("url", rs.getString("url"));
map.put("title", rs.getString("title"));
map.put("note", rs.getString("note"));
map.put("count", rs.getString("count"));
java.util.Date date = rs.getTimestamp("last_update");
map.put("last_update", toDateString(date));
list.add(map);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
rs.close();
} catch(Exception e) {
e.printStackTrace();
}
}
return list;
}
private static String getCurrentDateStr() {
java.util.Date today = new java.util.Date();
SimpleDateFormat df = new SimpleDateFormat();
df.applyPattern("yyyy-MM-dd HH:mm:ss");
return df.format(today);
}
private static String toDateString(java.util.Date date) {
SimpleDateFormat df = new SimpleDateFormat();
df.applyPattern("yyyy-MM-dd HH:mm:ss");
return df.format(date);
}
}

ブックマーク参照機能の実装

次に、登録されたブックマークを参照する機能を見ていこう。この機能は、すでにDBに登録されているブックマークを取得し、最新のものから順番に表示するというものである。しかも、定期的にDBの内容をチェックし、更新されたものがあれば取得して再表示を行なう。この再表示を行なう部分をページ全体のリロードなしに実現している点が、本パートのアプリケーションの中で最もAjaxらしさが出ている点と言えるだろう。

また、ブックマークを表示する部分にはYahoo UIライブラリのTreeViewを使用して、画面3のような形で情報を表示している。内容は次のとおりだ。

  • ブックマークのタイトル
  • 登録回数、コメント、更新日時
  • 「リンク先を表示する」

最初はブックマークのタイトルだけが列挙された状態だが、タイトル左脇の三角形をクリックすると上記の内容が表示される仕組みである。「リンク先を表示する」という部分はハイパーリンクになっており、ここをクリックするとブックマークされているWebページを閲覧できる。それでは、さっそくコードを見てみよう。この機能を実現しているのは、以下の1つのJSP(ViewBookmark.jsp)と1つのJavaクラス(登録機能でも登場したBookmarkクラス)である。全体の処理の流れは、図4のとおりだ。

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

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

図4 ブックマーク参照機能の処理の流れ

図4 ブックマーク参照機能の処理の流れ

ViewBookmark.jsp

LIST6をご覧いただきたい。登録されているブックマークを参照するためのJSPページである。Ajaxの仕組みを使ってブックマーク表示を実現している。

(1)はお決まりの、JSON-RPC-Javaを利用するための準備を行なうコードである。基本的に登録機能で記述した内容と同じなので、説明は割愛する。

(2)では、Yahoo UIライブラリのJavaScriptを読み込んでいる。実際に利用するのはTreeViewのみだが、これだけのJavaScriptのインクルードが必要となっている。

(3)からは、JavaScriptのコードを記述している部分である。ここではJSONRPCClientオブジェクトの作成を行なっているが、この部分も登録機能のときと同様の処理である。

(4)が、このJSPページの主要部分である。ここは少し詳細に見ていこう。

(4)−1のinitTree関数では、"Root"という名前を持つTreeViewオブジェクトを作成している。これが、タイトル以下のデータを表示するツリーを束ねるルート(根本)となる。また、ツリーを描画する際に、TreeViewはこの引数で指定された名前と同じIDを持つドキュメント領域(div要素など)に対して描画される。したがって、HTMLドキュメント中には、必ずTreeViewに付けた名前と同じIDを持つ要素がなければならない。このJSPページの場合、⑦の部分がそれに該当する。続いて、TreeViewオブジェクトが持つsetExpandAnim、setCollapseAnimという2つのメソッドを呼び出している。これらは、ツリーを開いたり閉じたりする際にアニメーション効果を持たせる設定を行なう。Yahoo UIライブラリのTreeViewは、このように簡単にアニメーション効果を持たせられるようになっている。

(4)−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オブジェクトにセットしている。

(4)−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)−4 では、TreeViewオブジェクトのdrawメソッドを使ってツリーを描画している。TreeViewを作成しても、このメソッドを呼び出さない限りは描画されないので、注意してほしい。

説明の順序が逆になったが、(5)では、60秒間隔でinitTree関数を呼び出す指定を行なっている。これにより、ユーザーが明示的にページをリロードすることなく、常に最新のブックマークが表示されることになる。非同期通信とHTMLページの動的な書き換えが実現する、Ajaxならではの動作と言えるだろう。

(6)では、TreeViewオブジェクトが使用するCSSをインクルードしている。これらのCSSをカスタマイズすることで、ツリーの見栄えを変更することも可能だ。

図5 ブックマークを表示するためのツリー構造

図5 ブックマークを表示するためのツリー構造

LIST6 ViewBookmark.jsp

<%@ page contentType="text/html; charset=shift_jis" %><jsp:useBean  scope="session"                                <font color="#ff0000" size="2">・・・・①</font>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>                       <font color="#ff0000" size="2">・・・・②</font><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");                              <font color="#ff0000" size="2">・・・・③</font>
initTree();                                                            <font color="#ff0000" size="2">・・・・④</font>
setInterval("initTree()", 60000);                                      <font color="#ff0000" size="2">・・・・⑤</font>}    function initTree() {                                                      <font color="#ff0000" size="2">・・・・④-1</font>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);                <font color="#ff0000" size="2">・・・・④-2</font>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); <font color="#ff0000" size="2">・・・④-3</font>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();                                                         <font color="#ff0000" size="2">・・・・④-4</font>}
// -->
</script>
</head>
<body onLoad="onLoad()"><link rel="stylesheet" type="text/css" href="/tree.css">                        <font color="#ff0000" size="2">・・・・⑥</font><link rel="stylesheet" type="text/css" href="/screen.css">
<p>ブックマーク表示</p><div ></div>                                                           <font color="#ff0000" size="2">・・・・⑦</font></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/)に代表されるように、既存の概念を打ち破るアプリケーションを開発することもできるほど、大きな可能性を秘めている仕組みである。ぜひ習得し、読者諸氏の開発に役立ててほしい。

  • 注1:ソーシャルブックマークサービスの1つ(http://b.hatena.ne.jp/)。ブックマーク登録に便利なスクリプトを提供しており、これをブラウザの「お気に入り」に登録しておき、ブックマークしたいWebページを開いた状態でお気に入りから選択すると、そのスクリプトが実行されてサーバーに登録される仕組みになっている。
  • 注2:このJSPページには、ユーザーが更新日時を入力して条件指定する仕組みを用意していない。コード中でlastUpdateの値を直接設定していただきたい。
  • 注3:非同期に発生するイベントをチェックするための手法で、リモートコンピュータに対し、イベントが発生していないかどうかを聞きに行く。RSSリーダーが、購読しているRSSのアップデートがないかどうかを確認しに行く処理もポーリングと考えられる。
  • 注4:Secure Socket Layerの略。米ネットスケープが開発した、インターネット上で情報を暗号化して送受信するプロトコル。広く普及しており、Webで機密性の高い情報を送受信する際によく使われる。ECサイトが決済のためにクレジットカード番号の入力を促すような画面では、ほぼ例外なく使われている。

中村正弘(なかむらまさひろ)

ウルシステムズ株式会社に所属。シニアコンサルタント。最近は開発方法論のコンサルティング的な仕事が多く、コードを書く機会が減っていることが少し残念に思っている。Ajax は久しぶりに楽しめそうな技術なので、面白いことができないかといろいろ模索中。

dbmagazine-writers@ulsystems.co.jp

アーカイブス一覧へ