2010/12/08

データ処理用のJSONアプリを作って、FlexBoxを使ってみた

jQueryプラグインのFlexBoxを使ってみた結果のまとめです。

Fairwayが提供するサンプル、デモへのリンクはたくさんあったんですが、FlexBoxを使った具体例はなかなかみつかりませんでした。

jQueryのプラグイン紹介ページからダウンロードできるバージョンは少し古いので、Fairwayのリンク先から CodePlexのFlexBoxページに飛んで、0.9.6をダウンロードして使っています。

背景

CouchDBの検証用にDTIのVPSサービスを利用して、インターネットに実サーバを借りたのでCouchDB、その他もろもろをインストールしてアプリケーションを作る準備をしていました。

ネタはOpenLDAPの時と同じで、 郵便番号の検索システムです。

CouchDBを使うのが目的なので、単純に番号から住所が分かっても、それほどおもしろくなさそう。 それに、似たようなシステムはたくさんあるから、これを元ネタにいろいろ検索できたり、新しい方法を試すのがおもしろいんじゃないかと思い機能を追加しています。

本当は全文検索システムにデータを入れて、探せばいいと思うんですけどね。 それでも整理された項目について条件を絞り込む、こういうシステムもおもしろいと思います。

今回はその中で都道府県名欄への入力をサポートする仕組みを使った時の作業メモです。

他の欄にも応用できそうですが、まずは47都道府県の漢字、カタカナに空文字を加えた95通りを検索させています。

検索リスト候補の表示

JavaScriptが動く環境でどれくらい入力をサポートできるものか試したいと思い、jQueryベースのリストボックスを探して、みつかったのがFairwayの FlexBoxでした。

サンプルをみた感じはちゃんと動きそうですが、検索をしても実例の紹介はあまりみつからず、裏側のデータを取得する部分の仕組みについてのガイドがない状態でした。

とりあえず使うだけなら、JSON的な内容のファイルを準備するだけで、動きそうだと思ったのが間違いでした…。 そんなに大変じゃなかったですけどね。

まずはテキストファイルを元に静的なリストを表示する

Fairwayのサイトにある FlexBoxのデモページの先頭には、"json.txt"というファイルを使って動かす例が載っています。

よくよく試すと分かりますが、これは検索結果を絞り込む機能はありません。 単純に入力した文字が強調されるだけです。最初はこれに気がつかず、json.txtファイルを準備しました。

静的検索用のjson.txtデータを作成する

最初は適当に都道府県データをRubyでHashに入れて、.to_jsonメソッドで変換しました。

rubyと郵便番号データを使った都道府県リストを生成するスクリプト

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-
##
require 'csv'
require 'json'
entry = Hash.new

tmp = Hash.new
id = 1
CSV.open(ARGV[0], 'r').each do |row|
  ## prepare output format
  pref = row[6]
  pref_kana = row[3]

  tmp[pref] = 0
  tmp[pref_kana] = 0
end

res = Hash.new
res["results"] = [{"id"=>"0","name"=>""}]
id = 1

tmp.keys.each do |pref|
  res["results"] << {"id" => id.to_s, "name" => pref}
  id += 1
end

print res.to_json

引数にUTF-8に変換した郵便番号のCSVファイルを指定して実行すると、画面に結果が出力されるので、適当にリダイレクトしてjson.txtファイルを作成します。 出力結果はおもしろくないので省略。

$ ./show_all_pref_list4flexbox.rb ../initdb/ken_all.utf8.csv

このjson.txtファイルを検索ページと同じディレクトリに配置して、検索ページの中にJavaScriptを埋め込みます。

これは動きますが、100近い候補が単純にリスト表示されるのは、まったく便利ではありません。うれしくもないし、楽しくもない。

最初はこれで絞り込みもできると思ったんですけどね、そんなに甘くはありませんでした。

動的に検索結果を絞り込む検索ボックスにする

Example 2:から先のデモは、/flexbox/results.aspx からJSON形式の検索結果をもらって表示している例があります。

問題は検索に使っているプロトコルが書かれていないところです。

ブラウザから/flexbox/results.aspxにアクセスしてみるとエラーになってしまいます。

とりあえず準備したjson.txtの内容を返信するCGIアプリを作成してみました。

json.txtの内容をそのまま返すCGI用bashスクリプト

#!/bin/bash

BASEDIR="$(dirname $0)"

echo "Content-type: application/json"
echo ""

cat "${BASEDIR}/pref_json.txt"

結局はこのスクリプトをポイントするログをみて、 q, p, s, contentTypeをキーにしていることが分かりました。それを元に検索をしてみると、stackoverflowのサイトに「 jQuery FlexBox: how to retrieve user's query & process submitted form?」などがひっかかります。

ここまでわかれば実際に動いているアプリケーションを観察することができるようになります。

$ curl 'http://www.fairwaytech.com/flexbox/results.aspx?q=&s=10&p=10'
{"results":[{"id":90,"name":"taxonomy"},{"id":91,"name":"tectonic"},{"id":92,"name":"tempestuous"},{"id":93,"name":"thermodynamics"},{"id":94,"name":"totalitarian"},{"id":95,"name":"unctuous"},{"id":96,"name":"usurp"},{"id":97,"name":"vacuous"},{"id":98,"name":"vehement"},{"id":99,"name":"vortex"}],"total":"105"}

いくつかパラメータを変更すると、このWebサービスの動きがわかってきます。

  • "p"は1から始まって、0だとエラーを返す
  • "p"が適切な範囲を越えた場合、全件数分を値として返す
  • "s"は1から始まって、0だと全件数分を値として返す
  • "s"が適切な範囲を越えた場合、全件数分を値として返し、特に問題はない
  • "id"は0から始まる
  • "results"と平行に"total"が設定されていて全件数を常に含めている

"p"が適切な範囲を越えた場合には値は帰らない方が適切な気はしますが、他は妥当そうな動きです。

"total"を結果に含めて、決められた件数だけを送信するようにしないと、flexboxが"s"や"p"のパラメータを変更してURLにアクセスすることはしませんでした。

細かい動きはコードを読むしかないようですが、いままでの結果を踏まえてjson.txtを読み込みFastCGI(fcgi.rb)を使って都道府県名のリストを返すようにしました。

flexboxに指定する pref.fcgi Rubyスクリプト

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-

ENV['GEM_HOME'] = "/opt/lib"
require 'rubygems'
require 'fcgi'
require	'json'

Basedir = File.dirname($0)

pref_file = File.join(Basedir, "pref_json.txt")
json = JSON.parse(open(pref_file,"r:utf-8").read)
json_total_rows = json['results'].length

FCGI.each {|request|
  out = request.out
  out.print "Content-type: application/json\r\n\r\n"

  q = CGI.parse(request.env['QUERY_STRING'])
  page = q['p'][0].to_i
  size = q['s'][0].to_i
  query = q['q'][0]
  ## check the parsed queries
  size = json_total_rows if size == 0
  size = 1 if size < 0
  page = 1 if page < 1

  ## ok, go ahead  
  start = page * size - size
  res = {'results' => [], 'total' => "0"}
  if query == nil or query == ""
    res['results'] = json['results'][start,size] if json['results'][start,size] != nil
    res['total'] = json_total_rows.to_s
  else
    json['results'].each do |item|
      if item['name'].index(query.to_s) == 0
        res['results'] << item
      end
      res['total'] = res['results'].length.to_s
    end

    res['results'] = res['results'][start,size]
  end

  out.print res.to_json + "\n"
  request.finish
}

次に一覧を表示して、">>"ボタンで最後に飛んだところ、ちゃんと10個づつ値を取得する様子が観察できました。


2001:3ex:xxx:x:xxx:xx:xx:xxxx - - [08/Dec/2010:23:07:55 +0900] "GET /postal/pref.fcgi?q=&p=1&s=10&contentType=application%2Fjson%3B+charset%3Dutf-8 HTTP/1.1" 200 348
2001:3ex:xxx:x:xxx:xx:xx:xxxx - - [08/Dec/2010:23:07:59 +0900] "GET /postal/pref.fcgi?q=&p=10&s=10&contentType=application%2Fjson%3B+charset%3Dutf-8 HTTP/1.1" 200 212

curlを使ったコマンドラインで動きを確認する事もできます。

$ curl 'http://www.yadiary.net/postal/pref.fcgi?s=1&p=98'

まとめ

とりあえずFlexBoxを使った 郵便番号の検索システムの補完機能はちゃんと動いています。

紹介されているサイトは多かったですが、少し敷居が高いのかもしれません。 でもCouchDBとは相性が良さそうな動きをしています。

このパラメータようにCouchDBに新しいViewを定義するか、他の方法を考えてみようと思っています。

みばえに頼るのは考えもの…

もっともJavaScriptで入力をサポートする仕組みは諸刃の剣で、JavaScriptが安定して動かなければ悲惨な結果になるのは目にみえています。 そのためJavaScriptをoffにしても通常のformがvisibleになるだけで、使えるようにしています。

JavaScriptをoffにしてもCSSは有効のままな場合の方が多いのかなと思っています。

反対にCSSでdisplay: noneを設定してJavaScriptでvisibleにする使い方は、JavaScriptとCSSが同時にオフになった場合を考えると想定していないformが表われて悲惨そうです。

でもJavaScriptでDOMオブジェクトをいじる様をGoogle ChromeのInspectorでみたら、よく考えるなぁ、という気持ちになりました。

0 件のコメント: