インクリメンタルサーチ
技術評論社のWEB+DB PRESS Vol.27にAjaxの特集を発見。なかでも「第2章 実践! Ajaxアプリケーション開発」はJSONを使っているので、真面目に読んでみた。なるほど。XMLHttpRequestはプロトコルで、それに乗せてサーバからクライアント(Webブラウザ)へJSONオブジェクトを送る、というわけか。他の選択肢についても良く整理されていてわかりやすい。なんか断片的だった情報がつながった感じがした。
じゃ実際にインクリメンタルサーチをやってみますか。XMLHttpRequestはブラウザに実装されているので(これもこの記事を読むまで知らんかった(汗)、JSONを手に入れる。といってもJSON自体はデータ構造の定義。詳しくは
http://www.crockford.com/JSON/index.html
を見るべし。実際に必要なのはJavaでこのJSONをハンドリングするライブラリ。今回は
http://www.crockford.com/JSON/java/index.html
で公開されている、以下のJavaクラスを利用した。
jarファイルでもあるかと思ったらなかった。それぞれソースをCOPYして、Eclipseでコンパイル。そしてWEB+DB PRESS Vol.27 補足情報から掲載記事のソースをプログラムを入手。Webブラウザ側のwebdbgrep.htmlはそのままでいいはずだから、サーバ側でリクエスト(インクリメンタルサーチ)を処理するwebdbgrep.phpに代わるものをJavaで作ればいいわけだ。で、作ってみたのがこれ。
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.json.JSONArray; public class MyWebdbgrep extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String dirname = "...."; // 検索対象のdir response.setContentType("text/xml; charset=UTF-8"); PrintWriter out = response.getWriter(); // keyword取得 String keyword = null; String[] tmp = request.getParameterValues("keyword"); try { byte[] tmpbyte = tmp[0].getBytes("ISO_8859_1"); keyword = new String(tmpbyte, "UTF-8"); } catch (UnsupportedEncodingException e) { System.out.println(e); } // 当該dirのfile/dir一覧を取得 File dir = new File(dirname); File[] files = dir.listFiles(); BufferedReader br; String filename; String line; int linec; int hit = 0; // 検索結果を入れるJSONオブジェクト JSONArray result = new JSONArray(); for (int i = 0; i < files.length; i++) { // ファイルだけ対象 if (files[i].isFile()) { filename = files[i].getName(); br = new BufferedReader(new FileReader(files[i].getAbsolutePath())); linec = 0; while ((line = br.readLine()) != null) { ++linec; if (line.indexOf(keyword) > 0) { ++hit; // 結果1件分を表すJSONオブジェクト JSONArray array = new JSONArray(); array.put(filename); array.put(linec); array.put(line); result.put(array); } } br.close(); } } // ブラウザへ返す out.println(result.toString()); } }
これをwebdbgrep.htmlから呼び出してみると...。ダメだ。うごかねー。ん?FireFoxでは動く?でも変だ。整理してみると、
- IE6.0では何も表示されない。
- FireFox1.0では1つ前の結果が表示される。
という感じ。MyWebdbgrepは動いているっぽいから、こうなるとwebdbgrep.htmlだろうな。いきなりJavaScriptのデバッグかよw。webdbgrep.htmlをよーく見てたら、onloadで呼ばれるstartup()の中が
function startup() { document.form1.text1.onkeyup = function() { //(a) xmlhttp.onreadystatechange = function() { //(b) : //検索結果をセットする処理(c) : } //XMLHttpRequestで検索を実行(d) } }
という構造になっているのが引っ掛る。これだと最初の文字が入力されて(a)に来たときに、(b)をコールバック関数として設定する。このときはそのまま(d)に行き、リクエストを投げて終わり。このタイミングで(b)は実行されないようだ。次の文字が入力されて(a)に来て、再び(b)がセットされると、前回の(1文字目入力で実行された)結果が返ってきているので(c)が実行される?試しに
function startup() { //(a) document.form1.text1.onkeyup = function() { : //XMLHttpRequestで検索を実行(d) xmlhttp.abort(); xmlhttp.onreadystatechange = handleHttpEvent; //(b) xmlhttp.open("GET", "http://.../mywebdbgrep?keyword=" + encodeURI(this.value), true); xmlhttp.send(null); : } } function handleHttpEvent(){ //検索結果をセットする処理(c) }
といった感じでXMLHttpRequestをハンドルする関数を独立させてみたらうまく行った。IEでも同じように動く。微妙なタイミングの違いか、それともMyWebdbgrepの作りの問題か。まぁいいや。ともかく、おもしろい。
あと良く見ると、IMEで入力したときonkeyupの発生タイミングがIEとFireFoxでは違うみたいね。
もっとも日本語もインクリメンタルサーチの対象にするなら、
String keyword = null; String[] tmp = request.getParameterValues("keyword"); try { byte[] tmpbyte = tmp[0].getBytes("ISO_8859_1"); keyword = new String(tmpbyte, "UTF-8"); } catch (UnsupportedEncodingException e) { System.out.println(e); }
見たいな感じで、リクエストパラメータを取り出す必要がある。POSTで渡せばsetCharacterEncoding()が使えるからこういうコードは不要。でもそのときは、
xmlhttp.setRequestHeader("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
でヘッダーを上書きする必要があるみたい。これ入れたらうまく行きました。
なるほどAjaxってこういう感じなんだ。結構使えるかもしれない。