どうやって動いてるの?
ガヴィ・ナラ (2004年12月25日)
コンセプト
テキストボックスにタイプした分だけ、イベントがJavascriptによって発生します。そして、そのイベントごとにHTTP GET リクエストがASPXページに行きます。ASPXページから生成されたレスポンスは、シンプルにテキストボックスの下のdivタグの中に 表示されます。ページはリフレッシュもしくはリロードすること無しに、一つ一つのキーストロークをJavascriptが処理していく わけです。Javascript内で使われているXMLHttpRequestオブジェクトについては アップルのデベロッパーサイトで読むことができます。 このオブジェクトはIE 5.0, Mozilla 1.0, それとアップルのSafari 1.2以上のバージョンでサポートされています。
データベース
データベースにはテーブルが一つ入ってるだけです。各辞書のデータはウェブ上で無料で利用できる物(例えば、パブリック・ドメインと なっている英英辞書は"1913 US Webster's Unabridged"や、"The Project Gutenberg Etext of Webster's Unabridged Dictionary"をベースにした物)です。ダウンロードはここから。 このテーブルの名前はWordListです。
| WordList |
| Word varchar(255) |
| Type varchar (10) |
| Meaning text |
"Word"をインデックスカラムとして、スピードアップするために作りました。全部で182696語データベースに収録されています。
ASP.NET page
ASP.NET ページは非常に素直に作ってあります。データベースが吐き出す最初の10項目を表示するだけ。
下のコードを見てみて下さい。(これ、本当はSqlDataReader クラスのほうがいいかも知れませんが)
<%@Page Language="C#"%>
<%@Import Namespace="System.Data"%>
<%@Import Namespace="System.Data.SqlClient"%>
<%@Import Namespace="System.Configuration"%>
<script runat="server">
public void Page_Load(object sender,EventArgs args)
{
string keyword=Request["k"];
if(keyword!=null && keyword.Trim()!="")
{
string sql="select top 10* from WordList where word like '"+keyword.Trim().Replace("'","''")+"%'";
SqlConnection conn=new SqlConnection(ConfigurationSettings.AppSettings["connectionString"]);
conn.Open();
DataTable dt=new DataTable();
SqlCommand command=new SqlCommand(sql,conn);
SqlDataAdapter adapter=new SqlDataAdapter(command);
adapter.Fill(dt);
conn.Close();
foreach(DataRow row in dt.Rows)
{
string meaning=row["Meaning"].ToString();
Response.Write("<strong>"+row["Word"].ToString()+"</strong> <i>");
Response.Write("row["Type"].ToString()+"</i>: "+meaning+"<br>");
}
}
}
</script>
HTMLページ内のXMLHttpRequestオブジェクト
<html>
<head>
<script>
var req;
function Initialize()
{
try
{
req=new ActiveXObject("Msxml2.XMLHTTP");
}
catch(e)
{
try
{
req=new ActiveXObject("Microsoft.XMLHTTP");
}
catch(oc)
{
req=null;
}
}
if(!req&&typeof XMLHttpRequest!="undefined")
{
req= new
XMLHttpRequest();
}
} function
SendQuery(key)
{
Initialize(); varurl="http://www.objectgraph.com/dictionary/dict.aspx?k="+key;
if(req!=null)
{
req.onreadystatechange = Process;
req.open("GET", url, true);
req.send(null);
}
}
function Process()
{
if (req.readyState == 4)
{
// only if "OK"
if (req.status == 200)
{
if(req.responseText=="")
HideDiv("autocomplete");
else
{
ShowDiv("autocomplete");
document.getElementById("autocomplete").innerHTML =req.responseText;
}
}
else
{
document.getElementById("autocomplete").innerHTML=
"There was a problem retrieving data:<br>"+req.statusText;
}
}
}
function ShowDiv(divid)
{
if (document.layers) document.layers[divid].visibility="show";
else document.getElementById(divid).style.visibility="visible";
}
function HideDiv(divid)
{
if (document.layers) document.layers[divid].visibility="hide";
else document.getElementById(divid).style.visibility="hidden";
}
function BodyLoad()
{
HideDiv("autocomplete");
document.form1.keyword.focus();
}
</script>
</head>
<body onload="BodyLoad();">
<form name="form1">
<input name="keyword" onKeyUp="SendQuery(this.value)" style="WIDTH:500px" autocomplete="off">
<div align="left" class="box" id="autocomplete" style="WIDTH:500px;BACKGROUND-COLOR:#ccccff"></div>
</form>
</body>
</html>
テキストボックス内でのkeyupのイベントはSendQueryメソッドのトリガーになっています。autocompleteを無効にしてる事に 注意してください。これをやらないと、IEのオートコンプリートと、divタグのレイヤーが被ってしまいます。 初期化のメソッドはXMLHttpRequestオブジェクトを作るだけです。 MozillaとSafariではこうするだけ
req=new XMLHttpRequest();
IEでは、ユーザーがインストールしてるMSXMLライブラリにしたがって作ります。 Processメソッドはイベントハンドラーとして活躍し、ウェブサーバーからのレスポンスが完了した跡にテキストを表示します。
オブザベーション
- データベースからのレスポンスの速度は十分に早いが、一つ一つのキーストロークに対して コネクションを開いているのは上策ではないと思われます。例えば、スラッシュ・ドッティングされちゃった時とか。
- グーグル・サジェストはたぶん単語の候補を全部メモリー上にロードしてると思う。そのデータベースサーバーに干渉されない 専用のウェブサーバーがあるはず。(ちなみにこれもC#使ってできると思うけど)
- ウェブサーバーとの往復の時間は最小に抑えるべきだと思う(最大でも2k)。そうしないとすぐ転送量を食いつぶしてしまう。
訳:武内樹一
(訳注:データベースの説明など、英英辞書の解説を基にしているため、英和のテーブル構造とは異なります)