
Yahoo UIライブラリを使った
|
前パートまでに解説したAjaxおよびJavaScriptの基本知識を踏まえて、本パートではAjaxとDBを組み合わせたWebアプリケーションを開発してみる。DBからのデータ取得には、サーバーサイドのJavaメソッドを簡単に呼び出せるJSON-RPC-Javaフレームワークを利用し、ユーザー画面の作成には今話題のYahoo UIライブラリを使う。最後にクロスブラウザやセキュリティ問題など、Ajaxでアプリケーションを開発する際の注意点も述べる。
ブックマーク登録機能の実装
それでは、それぞれの機能の実装を見ていこう。最初は、ブックマーク登録機能である。
この機能は、ユーザーがブックマークしたいと思ったWebページのタイトル、URL、ユーザーのコメントをDBに格納する、という処理を行なう。ごく単純な処理ではあるが、この処理を一切のページ遷移なしに実現している部分がAjaxらしいところである。
登録機能を実現するのは、2つのJSPページ(Bookmark.jsp、RegistBookmark.jsp) と1つのJavaクラス(Bookmarkクラス)だ。全体の処理の流れは、図3のとおりである。

図3 ブックマーク登録機能の処理の流れ
Bookmark.jsp
LIST3をご覧いただきたい。このJSPページは、はてなブックマーク(注1)にならって、さまざまなWebページを手軽にブックマークするためのスクリプト(JavaScriptで記述)を、Webブラウザの「お気に入り」に登録してもらうための、ごく簡単なJSPページである。このJSPページをコンテキスト内に置き、http://localhost/Bookmark.jsp(URLはJSPページが置かれたコンテキストによって異なる)にアクセスすると、画面1のように表示される。「ブックマーク登録」というハイパーリンクがあるのでこれを右クリックし、表示されたコンテキストメニューから[お気に入りに追加]を選べば、スクリプトの登録は完了だ。
あとは、登録したいWebページを表示し、お気に入りからこのスクリプトを呼び出せば、簡単にブックマーク登録用の画面を表示することができるというわけである。
LIST3 Bookmark.jsp
<%@ page contentType="text/html; charset=shift_jis" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis">
<body>
以下のURLはブックマークを追加するためのスクリプトです。<br>
リンクを右クリックしてお気に入りに追加してください。<br>
<a href="javascript:window.open(’http://localhost/RegistBookmark.jsp?title=’ ⇒
+escape(document.title)+’&url=’+escape(location.href),%20 ⇒
’_blank’,%20’width=400,height=300’);undefined;">ブックマーク登録</a>
</body>
</head>
(※誌面の都合により、“⇒”で折り返す。以下同)

画面1 ブックマーク用スクリプトを登録するページ
RegistBookmark.jsp
次に紹介するのは、ブックマーク登録用のページ(画面2)を表示するためのJSPページである。先ほどお気に入りに登録したスクリプトから呼び出されて、登録するURLに対するタイトルとコメントの入力をユーザーに求める。[登録]ボタンがクリックされると、サーバー側にある登録用のメソッドを呼び出す仕組みだ。
まずは、LIST4をご覧いただきたい。ここでは、本格的にAjaxのコードが現われている。ポイントとなる部分を詳細に見ていこう。
まず①では、JSONRPCBridgeオブジェクトを、Servlet APIが用意しているHttpSessionオブジェクトに関連付けている。これは、JSON-RPC-Javaを利用する上での決まりごととして必ず行なう。
②では、JSONRPCBridgeにBookmarkのクラスオブジェクトを登録している。これにより、Bookmarkクラスが保持しているすべてのstaticメソッドがJavaScript経由で使えるようになる。
registerClassメソッドは、第1引数として指定した名前に、第2引数でサーバー側にあるメソッドを提供するJavaクラスのクラスオブジェクトを紐付けてくれる。これにより、JavaScriptからは第1引数で指定した名前を使ってサーバー側のメソッドを呼び出せるようになる。後の⑤では、ここで指定したBookmarkという名前を使って、BookmarkクラスのregistBookmarkメソッドをJava Scriptから呼び出している。
なお、第2引数に指定するのはクラスオブジェクトであって、インスタンスではない点に注意していただきたい。インスタンスメソッドを公開する場合には、registerClassメソッドではなく、registerObjectメソッドを使用する。
③では、JSON-RPC-Javaのクライアント側で利用するライブラリを読み込んでいる。
④からはJavaScriptの記述に入っている。ここでは、Webページを読み込んだときにブラウザが呼び出す関数(onLoad)の中で、JSONRpcClientオブジェクトを生成している。以降は、このオブジェクトを介してサーバー側のメソッドを呼び出せるようになる。
⑤のregistBookmarkは、登録処理を実行するJavaScriptの関数である。[登録]ボタンがクリックされると呼び出されて、フォームに入力されている内容を取得し、サーバー側のregistBookmarkメソッドを呼び出す。ここでは、その呼び出し方に注目してほしい。var result=jsonrpc.Bookmark.registBookmark(url, title, comment);と、あたかもローカルにあるメソッドを呼び出すようにサーバー側のJavaメソッドを呼び出している。XMLHttpRequestオブジェクトやJSONを意識する必要はないのである。この部分を見るだけでも、JSON-RPC-Javaが非常に強力かつ有用なフレームワークであることが分かると思う。
なお、registBookmarkメソッドは、戻り値としてboolean(真偽値)を返すようになっている。このような実装では、コードにあるとおり、JavaScriptでもそのまま真偽値として評価可能である。もう少し複雑なデータ構造を扱う場合には別の手段が必要であるが、それは後述する。
⑥以降は、ほとんどが普通のHTMLフォームである。このJSPページは、基本的にJavaScriptからパラメータ付きで呼び出されることを想定しており、パラメータの値(ブックマーク対象ページのURLやタイトル)をフォーム内に表示している。また、フォームのsubmit( 送信)イベントを拾って、登録処理を行なうJavaScript関数が呼び出されるようにしている。

画面2 ブックマーク登録用のJSPページ
Bookmark.java(registBookmarkメソッド)
さて、登録処理の最後はサーバー側のJavaクラスである。LIST5をご覧いただきたい。このクラスは2つのpublicメソッドを持っているが、ここでは、registBookmarkメソッドを見ていただきたい。このメソッドは、RegistBookmark.jspから送られてくるブックマーク対象ページのURL、タイトル、ユーザーが入力したコメントをパラメータとして受け取り、SQL文を組み立てて、JDBCを使ってDBに登録している。
テーブルbookmarkの主キーは「URL」である。もし、すでに同じURLが存在していれば更新処理を、そうでなければ新規にレコードを追加する処理を行なっている。おおまかなフローは以下のとおりである。
① パラメータで受け取ったURLを持つ行を取得するSELECT文を実行する
② ResultSet#nextメソッドがtrueを返せば、同一のURLを保持する行が存在するということになるので、UPDATE文を実行してbookmarkテーブルのcount列の値をインクリメントし、last_update列を現在の時刻で更新する
③ 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
<%@ page contentType="text/html; charset=shift_jis" %>
<jsp:useBean scope="session"
class="com.metaparadigm.jsonrpc.JSONRPCBridge" /> ・・・・①
<% 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">
<!--
function onLoad() { ・・・・④
jsonrpc = new JSONRpcClient("/JSON-RPC");
}
function registBookmark() { ・・・・⑤
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();"> ・・・・⑥
<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);
}
}
(注1)ソーシャルブックマークサービスの1 つ(http://b.hatena.ne.jp/)。ブックマーク登録に便利なスクリプトを提供しており、これをブラウザの「お気に入り」に登録しておき、ブックマークしたいWebページを開いた状態でお気に入りから選択すると、そのスクリプトが実行されてサーバーに登録される仕組みになっている。