totaltodayyesterday

フリーなCGIと自宅サーバー情報サイト

Ajaxでsuggest(入力補完)

作って理解するAjax — No.4予測・補完インタフェースを作成:ITpro」にあるcomplete.js、initcompletion.jsを入手してcomplete.jsを少し改良してもっと使いやすくしようと思います。

XMLHTTPRequestはUTF8でデータを送信するので他の文字コード環境の場合文字化けを起こします。その場合は、Escape Codec Library(ecl.js)で適切な文字コードでエンコードした後、サーバー側のスクリプトで「header(‘Content-Type: text/html; charset=EUC-JP’);」等としてヘッダーで文字コードを指定してあげると文字化けを回避できます。

*今回は補完候補リストを郵便番号と住所、補完候補リストの保存形式をテキストとして説明していきます。

■サンプル
郵便番号入力補完サンプル

■complete.jsの改良

オリジナルのcomplete.js

» 1つのテキストボックスにのみ対応
» onLoadでcomplete.jsをロードしてテキストボックスの状況を監視

というシンプルなものです。

今回アレンジする点は

» 複数のテキストボックスに対応
» 候補を選択した場合にその候補を2つのテキストボックスに反映

*「1000001:東京都千代田区」という候補を選択した場合に「テキストボックス1に1000001」「テキストボックス2に東京都千代田区」

» onLoadで常時監視するのではなくonFocus時にのみ監視
» 表示される候補一覧の入力した文字列部分赤で強調

という形にしたかったので、complete.jsを以下のように改良しました。

[complete.js]

function createXmlHttpRequest() { var xmlhttp = false; if( window.XMLHttpRequest) { xmlhttp = new XMLHttpRequest(); } else if(window.ActiveXObject) { try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } } return xmlhttp; } var oldquery = ""; var xmlhttp = 0; var input = 0; function peekQuery (type,vid,nid,src) { if (! xmlhttp) xmlhttp = createXmlHttpRequest(); if (! xmlhttp || xmlhttp.readyState == 1 || xmlhttp.readyState == 2 || xmlhttp.readyState == 3){ return; } var textbox = document.getElementById(vid); var textbox2 = document.getElementById(nid); var query = EscapeUTF8(textbox.value); if (query == "") { textbox.clearCompletionItems(); textbox2.value = ""; } else if (oldquery != query) { xmlhttp.open("GET", src + "?TYPE=" + type + "&KEY=" + query, true); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200 && xmlhttp.responseText != "") { var ary = xmlhttp.responseText.split(/¥n/); textbox.showCompletionItems( ary, function(n) { ary[n] = ary[n].replace(/<font color=red>/g,""); ary[n] = ary[n].replace(/<¥/font>/g,""); var new_ary = ary[n].split(":"); if (type == 1) { textbox2.value = new_ary[0]; textbox.value = new_ary[1]; } else { textbox2.value = new_ary[1]; textbox.value = new_ary[0]; } textbox.clearCompletionItems(); oldquery = EscapeUTF8(textbox.value); } ); } } xmlhttp.send(null) } oldquery = query; } function suggestOn(t,iid,oid,src) { var textbox = document.getElementById(iid); initCompletion(textbox); TimerID = setInterval( function () { peekQuery(t,iid,oid,src); }, 500); } function suggestOff(iid) { clearInterval(TimerID); DTimerID = setInterval( function () { var textbox = document.getElementById(iid); textbox.clearCompletionItems(); clearInterval(DTimerID); }, 200); }

■補完候補リストの作成

*補完候補リストのフォーマットは「郵便番号:住所」という形式になります。

[post.txt]

1000001:東京都千代田区 1000002:東京都千代田区皇居外苑 1000003:東京都千代田区一ツ橋 ・ ・ ・

■補完候補を出力するスクリプト

スクリプト自体は下記の2点を押さえておけばperlでもphpでもなんでもよいです。

» スクリプトにはGETで送信される
» GETで受取ったデータは「TYPE」と「KEY」に格納されている
» TYPEが0だった場合はセパレータ「:」より前の文字列を対象に検索します。
*補完候補リストが「1000001:東京都千代田区」だった場合は「1000001側を検索」

» TYPEが1だった場合はセパレータ「:」より後ろの文字列を対象に検索します。
*補完候補リストが「1000001:東京都千代田区」だった場合は「東京都千代田区側を検索」

*「KEY」は入力した文字列が格納されています。

今回は補完候補の検索にphpスクリプト(実際の検索はUNIXシェル)を使用します。

[suggest.php]

<?php $FILE = './post.txt'; $TYPE = ""; $KEY = ""; $ARY = array(); $CNT = 0; if (isset($_GET['TYPE'])) { $TYPE = $_GET['TYPE']; } if (isset($_GET['KEY'])) { $KEY = $_GET['KEY']; } //不要な文字列の除去 $KEY = preg_replace("/[a-zA-Z¥/¥;¥&¥|]/g","",$KEY); //郵便番号で検索 if ($TYPE == 0) { //検索開始 $ARY = split("¥n",shell_exec("/bin/cat $FILE | /bin/grep '^$KEY'")); foreach ($ARY as $VAL) { if ($VAL != "") { list($NUM,$NAME) = split(":",$VAL,2); if (preg_match("/^$KEY/",$NUM)) { //文字列にマッチした部分を赤で強調 $NUM = preg_replace("/^$KEY/","<font color=red>$KEY</font>",$NUM); print $NUM . ':' . $NAME . "¥n"; $CNT++; } if ($CNT == 30) { //補完候補が30件になったら終了 break; } } } } //住所で検索 else if ($TYPE == 1) { //検索開始 $ARY = split("¥n",shell_exec("/bin/cat $FILE | /bin/grep ':$KEY'")); foreach ($ARY as $VAL) { if ($VAL != "") { list($NUM,$NAME) = split(":",$VAL,2); if (preg_match("/^$KEY/",$NAME)) { //文字列にマッチした部分を赤で強調 $NAME = preg_replace("/^$KEY/","<font color=red>$KEY</font>",$NAME); print $NUM . ':' . $NAME . "¥n"; $CNT++; } if ($CNT == 30) { //補完候補が30件になったら終了 break; } } } } ?>

■ページに設置する

[header]

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang=ja> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <script type="text/javascript" src="initcompletion.js"></script> <script type="text/javascript" src="complete.js"></script> <script type="text/javascript" src="ecl.js"></script> </head>

[body]

<input type="text" id="POST1" name="POST1" autocomplete="off" size="10" value="" onFocus="suggestOn(0,'POST1','ADDR1','suggest.php');" onBlur="suggestOff('POST1');" /> <input type="text" id="ADDR1" name="ADDR1" autocomplete="off" size="40" value="" onFocus="suggestOn(1,'ADDR1','POST1','suggest.php');" onBlur="suggestOff('ADDR1');" /> <br /> <input type="text" id="POST2" name="POST2" autocomplete="off" size="10" value="" onFocus="suggestOn(0,'POST2','ADDR2','suggest.php');" onBlur="suggestOff('POST2');" /> <input type="text" id="ADDR2" name="ADDR2" autocomplete="off" size="40" value="" onFocus="suggestOn(1,'ADDR2','POST2','suggest.php');" onBlur="suggestOff('ADDR2');" /> <br /> <input type="text" id="POST3" name="POST3" autocomplete="off" size="10" value="" onFocus="suggestOn(0,'POST3','ADDR3','suggest.php');" onBlur="suggestOff('POST3');" /> <input type="text" id="ADDR3" name="ADDR3" autocomplete="off" size="40" value="" onFocus="suggestOn(1,'ADDR3','POST3','suggest.php');" onBlur="suggestOff('ADDR3');" />

» POST*は郵便番号入力フォームを指しています
» ADDR*は住所入力フォームを指しています
» autocomplete=”off”はブラウザがキャッシュしている過去に入力した候補一覧をOFFにします
» onFocus=”suggestOn(TYPE,THIS_FORM_ID,RETURN_SET_ID,SCRIPT);は入力フォームにカーソルが来た時にsuggestをONにします
» TYPEに0を指定した場合は補完候補リスト中のセパレータ「:」より前の文字列を検索します
» TYPEに1を指定した場合は補完候補リスト中のセパレータ「:」より後ろの文字列を検索します
» THIS_FORM_IDは自身のフォームIDを指定して下さい
» RETURN_SET_IDは補完候補を選択した際に自身のフォームと対になっているフォームIDを指定して下さい
» SCRIPTは補完候補を出力するスクリプトのURL(URI)を指定して下さい
» onBlur=”suggestOff(THIS_FORM_ID);”は入力フォームからカーソルが離れた時にsuggestをOFFにします
» THIS_FORM_IDは自身のフォームIDを指定して下さい

■上記で使用したスクリプト

» complete.js
» initcompletion.js
» ecl.js
» post.txt
» suggest.php
» suggest.html

6 Comments

  1. 小島
    2013年1月11日

    $ARY = split(“\n”,shell_exec(“/bin/cat $FILE | /bin/grep ‘^$KEY'”));
    の部分の説明をお願いできませんでしょうか?
    Windowsのxampp環境で動くように書き換えているのですが、
    理解不足のためかうまくできません。
    宜しくお願いします。

  2. admin
    2013年1月11日

    郵便番号のファイル($FILE)をcatコマンドで全件取得しつつ、入力値($KEY)にマッチする行をgrepを抽出しています。(郵便番号で検索する場合は前方一致、住所で検索する場合は「:住所」で部分一致)

    PHPのみで実現する場合は大雑把ですが大体↓のような感じになるかと思います。

    $ARY = array();
    $handle = fopen($FILE,”r”);
    while (!feof($handle)) {
    $buffer = fgets($handle, 1024);
    $buffer = rtrim($buffer);

    //郵便番号で検索
    if ($TYPE == 0) {
    if (strpos($buffer, $KEY, 0) == 0) {
    $ARY[] = $buffer;
    }
    }
    //住所で検索
    else if ($TYPE == 1) {
    if (strpos($buffer, “:{$KEY}”) >= 0) {
    $ARY[] = $buffer;
    }
    }
    }

    fclose($handle);

  3. 小島
    2013年1月12日

    おかげさまで動作するようになりました。ありがとうございます。

    動作はするようになったのですが、候補表示部へマウスオーバーすると、候補すべてが一括で選択された状態(青反転)となってしまう状態です。
    どの辺りを中心に探りをいれればよいか予想で結構ですので教えていただけないでしょうか?
    (ちなみに候補表示部が”\n”で改行されなかったので””と書き換えましたが、ここも関係ありますでしょうか)
    どうかよろしくお願いします。

  4. 小島
    2013年1月12日

    失礼しました。「”¥n”で改行されなかったので”<BR>”と書き換えました」と書きたかったのですが表示されませんでしたので、追記させていただきます。

  5. 小島
    2013年1月12日

    失礼しました。
    complete.js内の”¥n”を”<BR>”に書き換えることで解決しました。
    多くのコメントを残してしまい申し訳ありません。
    大変お世話になりました。

  6. リョウ
    2013年3月7日

    初めまして。
    スクリプトを使用させていただいております。

    作成しているページに組み込み、Firefoxで挙動テストを行っていた時ですが、1点バグを見つけました。
    これは、ITProの元ネタからの問題なのかもしれませんが、元ネタ記事の各ファイルのリンク先がお亡くなりになっているので詳細は分かりませんでした。

    もし改善する良い方法があればお教え願えないでしょうか?
    よろしくお願いします。

    現象:
    補完候補を表示するボックスが、候補を選択してもblurしても閉じなくなる。

    環境:
    Windows Vista / Firefox 19

    再現方法:
    1.テキストボックスに任意の文字を入力して、補完候補を表示するボックスを表示させる。(画面Aとします)
    2.新しいタブを開く。
    (タブの右端にある「+」ボタンで新しいタブを開きます。ファイル > 新しいタブではなく。)
    3.画面Aを開くと、保管候補表示ボックスが開いたままの状態になっている。
     ※ここで、文字列を変更して検索しなおすと、保管候補表示ボックスが二重に開きます。

コメントを残す

logo
RSS はてなブックマーク Twitter Facebook Google+
now loading...