データベースから引き出した特定の項目を提示し、 修正が必要ならその場合のみ編集用のフォームを出すインタフェースを考える。
まずは単純なデータベースの値更新から考え、 それを実用的なものへと発展させていく。
ただ1つのカラムを持つテーブル v を、データベースファイル val.sq3 に作成する。
sqlite3 val.sq3
CREATE TABLE v(val TEXT);
ここに4つの値、'foo'、'bar'、'foo'、'baz' を格納する。
INSERT INTO v VALUES('foo');
INSERT INTO v VALUES('bar');
INSERT INTO v VALUES('foo');
INSERT INTO v VALUES('baz');
'foo' が2重登録されているが、内部的には rowid の異なる行が格納される。
SELECT rowid,* FROM v;
1|foo
2|bar
3|foo
4|baz
さて、シェルスクリプトからの操作を単純化するため REPLACE INTO ... での値挿入と既存値更新の方法を考える。 2個目の 'foo' を更新したい場合は以下のように rowid を含めて REPLACE 文を使う。
REPLACE INTO v(rowid, val) VALUES(3, 'foo2');
SELECT rowid,* FROM v;
1|foo
2|bar
3|foo2
4|baz
同じ構文で新規の値を入れるには rowid に NULL を指定すればよい。
REPLACE INTO v(rowid, val) VALUES(NULL, 'foo');
SELECT rowid,* FROM v;
1|foo
2|bar
3|foo2
4|baz
5|foo
以降ではこのデータベースの特定の行の値を更新する CGI インタラクションについて説明する。
上記のデータベース中の rowid=3 の行のカラムの、 シェルスクリプト+CGIによる表示・修正・削除を考える。 シェル変数 rowid に 3が、 シェル変数 name にカラム名、シェル変数 val にその値が代入されているとする。 なお、いずれの変数の値もHTMLエスケープされているものとする。
単純にカラム名を $name で、値を $val で出力すればよい。
$val を初期値として input 要素を提示する。たとえば テキスト入力で済む値であれば以下のように出力する。
$name: <input name="$name" type="text" value="$val">
<input name="rowid" type="hidden" value="$rowid">
     rowid はユーザに見せる必要はないので hidden 変数にしておく。
input 要素を出力すればよい。
$name: <input name="$name" type="text" value="$val">
3つの場合にそれぞれ違う出力が必要である。そうすると、データベース中の なんらかの値に対して「表示」、「編集」する場合や、 新たなレコードを「新規入力」したい場合に別々のページ出力が必要になる。 これは対話的操作の手順が増えることになる。これを避けて、単一のページで 3種の操作ができるようにしたい。
HTML文書に記述する A、B、C の3つのテキストがある。 これを条件つきでいずれか1つだけ表示するようにしたい。 このような場合に便利なのが、CSS3で利用できる :checked 擬似クラスと 一般兄弟セレクタ ~ である。まずは以下の例を見よう。 ~ (CSSセレクタ) :checked (CSS)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Displaying A, B, C</title>
<style type="text/css">
<!--
span.edit, span.confirm {		/* 標準では完全透明で不可視 */
  opacity: 0.0; visibility: hidden;}
input[value="edit"]:checked ~ span.edit {/* value="edit" のボタンチェックで */ 
  opacity: 1.0; visibility: visible;}	/* 透明解除したうえで可視化する */
input[value="rm"]:checked ~ span.confirm {
  opacity: 1.0; transition: 3s; /* value="rm" のボタンチェックで透明解除 */
  visibility: visible;}		/* 不可視解除 */
input[value="rm"]:checked ~ span.value {
  background: red;}		/* value="rm" のボタンチェックで背景赤に */
input[value="edit"]:checked ~ span.value {
  display: none;}		/* value="edit" のボタンチェックで表示なしに */
-->
</style>
</head>
<body>
<p>
<form>
<input name="action" type="radio" value="keep">Aだけ出す
<input name="action" type="radio" value="edit">Bだけ出す
<input name="action" type="radio" value="rm">AとCを出す<br>
<span class="value">A</span>
<span class="edit">B</span>
<span class="confirm">C</span><br>
<input name="reset" type="reset" value="reset">
</form>
</p>
</body>
</html>
この HTML 文書をCSS3対応のブラウザで開いた場合の様子を示す。 ラジオボタンにチェックのない状態(または第1ラジオボタンをチェックした状態)では、 <span class="value"> で囲まれたものだけが見えている。
初期状態
続いて2つ目のラジオボタンにチェックが入った場合は、 <span class="edit"> で囲まれたものだけが見える。
第2ラジオボタンをチェックした状態
さらに、3つ目のラジオボタンにチェックが入った場合は、 <span class="value"> で囲まれたものが背景色赤になって現れ、 <span class="confirm"> で囲まれたものも現れる。
第3ラジオボタンをチェックした状態
この挙動は、出力HTMLの冒頭で定義されたCSS記述による。 この定義の要点を以下に示す。
span.edit, span.confirm {opacity: 0.0; visibility: hidden;}
opacity特性 これは、class="edit" と class="confirm" の属性定義を持つ span 要素はとくに何もなければ不透明度 0(つまり完全透明)にして、 不可視状態にする。ただし透明にするだけだと、 見えないだけで隠したものがテキストだと領域選択できたり、 ボタンだと押せたりするので、これを回避したいものは visibility 特性も hidden にして完全にアクセスできないようにしておく。
input[value="edit"]:checked ~ span.edit {opacity: 1.0; visibility: visible;}
input[value="edit"]:checked の部分は、input 要素のうち value="edit" の属性設定を持ち、 さらにそれがチェックされているものを表す。後続する ~ は、一般兄弟セレクタで、 「E1 ~ E2」 という記述で、「E1 と共通の親を持つ要素のうち、 E1 よりもあとに書かれた要素 E2」 がマッチする働きを持つ。元のHTML本体では、
<input name="action" type="radio" value="keep">Aだけ出す
<input name="action" type="radio" value="edit">Bだけ出す
<input name="action" type="radio" value="rm">AとCを出す<br>
<span class="value">A</span>
<span class="edit">B</span>
<span class="confirm">C</span>
のように input 要素3つと span 要素3つが共通の親(form 要素)に並列で配置されている。2行目の input 要素がチェックされると 「input[value="edit"]:checked」 が当てはまることになり、 それと同じ階層で後ろにある5行目の span 要素が標記のセレクタにマッチし、この span 要素のスタイルが 「{opacity: 1.0; visibility: visible}」に設定され、不透明度1.0、 かつ可視つまり完全に見える状態に設定される。
input[value="edit"]:checked ~ span.value {display: none;}
display特性 前項と同様の仕組みで2つ目の input 要素がチェックされると1つ目の span 要素がマッチするようになり、 その span 要素が「{display: none;}」によって Web ページ上にレイアウトされなくなる。opacity で完全透明にしたものや、visibility: hidden で隠したものは見えないだけでその要素を表示するだけの面積は確保されるが、 display: none の場合は面積もゼロになる。
input[value="rm"]:checked ~ span.confirm {
  opacity: 1.0; transition: 3s; visibility: visible;}
transition特性 上記2項目と同様だが、transition: 3s; によって見せ方を変えている。 標準の span.confirm は opacity: 0.0 だが、第3ラジオボタン(value="rm") をチェックすると opacity: 1.0 になる。このとき、通常は0.0から1.0に 一瞬で変わるのだが、transition プロパティに所要時間を設定するとその時間をかけてじわじわと値を変更する。 したがって、第3ラジオボタンにチェックを入れると浮かびあがるように 「C」の文字が登場する。
input 要素によるチェックボックス(type="checkbox")や ラジオボタン(type="radio") をそのまま使うとクリックすべき○や□が小さいため操作しづらい。 ボタンにラベル文字列を結び付け、 文字列クリックでも対応するボタンのチェックを可能にできる。
リスト:abc.html の input 要素の並びを以下のように変更すると操作性が向上する。
<input id="action.keep" name="action" type="radio" value="keep"><label
 for="action.keep">Aだけ出す</label>
<input id="action.edit" name="action" type="radio" value="edit"><label
 for="action.edit">Bだけ出す</label>
<input id="action.cfm" name="action" type="radio" value="rm"><label
 for="action.cfm">AとCを出す</label>
input 要素に文書中で一意に定まる id を属性指定し、label 要素の for 属性にその id を設定することで結び付けられる。
abc.html の仕組みを利用して、先述の実験用データベース val.sq3 の任意の行の値を操作できるものを作成した CGI スクリプト editv.cgi を示す。 行の指定はrowidで行なうものとする。
#!/bin/sh
myname=`basename $0`
mydir=`dirname $0`
cd $mydir				# scriptと同じディレクトリに移動
DB=db/val.sq3 . $mydir/cgilib2-sh	# 使用DBファイルを db/val.sq3 に
if [ -n "$1" ]; then	# $1 はURL直打ちできるので数字でない場合も考慮する
  r=${1%%[!0-9]*}; r=${r##*[!0-9]}	# $1 から数字以外を除去
  # ★A★ 与えられた rowid でもう一度 rowid を取り直してみる
  rowid=`query "SELECT rowid FROM v WHERE rowid=$1;"`
fi
title=${rowid:+"Edit $rowid"}		# $rowid は "" か整数になる
title=${title:-"List"}			# $rowid が空なら "List" に
m4 -D_TITLE_="$title" -D_ACTION_="$myname" editv-head.m4.html	# 【1】
if [ -z "$rowid" ]; then		# 有効なrowid指定がなければ新規入力
  echo "<p>valの新規入力: <input type=\"text\" name=\"val\"></p>"
  #★B★
  val=`getpar val | sed "s/'/''/g"`	# SQLクォートする
  rid=`getpar rowid`			# hiddenで入力された場合
  rid=${rid%%[!0-9]*}; rid=${rid##*[!0-9]}	# 数字以外を除去
  case `getpar action` in	# ★C★ラジオボタン action の値で処理切り替え
    "")	# 新規入力
      [ -n "$val" ] &&		# $val が空でなければINSERT
	  query "INSERT INTO v VALUES('$val');" &&
	  echo "<p>New record '`echo \"$val\"`' inserted."
      ;;
    edit)
      [ -n "$rid" ] &&		# hidden指定のrowidレコードを更新
	  query "REPLACE INTO v(rowid, val) VALUES($rid, '$val');" &&
	  echo "<p>Update rowid($rid)=`escape \"$val\"`.</p>"
      ;;
    rm)
      [ -n "$rid" ] &&		# hidden指定のrowidレコードを削除
	  if [ x"`getpar confirm`" = x"yes" ]; then
	    query "DELETE FROM v WHERE rowid=$rid;" &&
		echo "<p>Delete rowid($rid).</p>"
	  fi ;;
  esac
  echo "<p>既存レコード一覧(クリックして編集):<br>"
  # ★D★		.mode html でのSELECT結果1個分は以下のようになる
  query<<-EOF  |			#  <TR><TD>1</TD>
	.mode html			-- <TD>データ</TD>
	SELECT rowid, val FROM v;	-- </TR>
	EOF
  # query結果が次のsedへの入力。sed操作でHTMLタグを外し、
  # <a href="$myname?$rowid">$rowid:$val</a> に置換する
  sed -e "/^<TR>/N;			# <TR>で始まる行と次の行を連結
	s/\n//;				# 連結行にある改行を削除
	s|^<TR><TD>\([0-9]*\)</TD>|<a href=\"$myname?\1\">\1:|
	s|<TD>\(.*\)</TD>$|\1</a> |;	# valのカラムからTDタグを外す
	/^<\/TR>$/d;			# </TR> のみの行を削除"
  echo "</p>"
else
  valfile=$tmpd/val.$$			# ★E★
  escape "`query \"SELECT val FROM v WHERE rowid=$rowid;\"`" > $valfile
  m4 -D_ROWID_="$rowid" \
     -D_VAL_="syscmd(\`cat $valfile')" editv-form.m4.html	# 【2】
fi
m4 editv-foot.m4.html			# 【3】 各閉じタグをまとめて出力
このスクリプトでは、m4 に与えるソースとして3つのファイルを使用している。
| editv-head.m4.html | editv-form.m4.html | editv-foot.m4.html | ||
| ↓ | ↓ | ↓ | ||
| editv.cgi | ||||
| ↓ | ||||
| ヘッダ | ||||
| フォーム | (出力HTML) | |||
| フッタ | ||||
実際に動かした画面を示したあとで、プログラムの重要な点を解説する。
「実験用データベースの作成と更新手順」で作成した val.sq3 をこのCGIで利用する手順を示す。まず、val.sq3 を書き込み可能にする。
chmod a+w val.sq3
mkdir -m 1777 db
mv val.sq3 db
この例では val.sq3 ファイルを World Writable にしているが、httpd の動作プロセスのグループに合わせられるならば val.sq3 をそのグループに chgrp したうえで chmod g+w の方がよい。
httpd から書き込みできる状態にできたらブラウザを用い、この CGI の URL を開くと以下のような画面が現れる。
初期アクセス画面
List