徒書

簡単に考える。


formに複数の送信ボタン

1つのformで複数ボタンの配置 - masdoiの日記より。

1つのformで複数ボタンを作り、どのボタンが押されたのかをCGI側で判定したい場合があります。

ということでJavaScriptを使用したソースが例示されていたのですが、「複数ボタンを作り、どのボタンが押されたのかをCGI側で判定」という目的に適うものならば、以下のようにHTMLだけでごく簡単に実現できます。

<form action="" method="get">
<input type="submit" name="b1" value="button1">
<input type="submit" name="b2" value="button2">
</form>

サンプルのフォームは action="" method="get" としているので、ボタンを押すとHTMLファイル自身のURLにクエリ文字がついた形 (~/form_sample.html?name=value) でアクセスされます。

(余談ですが、こうするとHTTPサーバを介さなくてもブラウザのみでフォーム送信のテストができるので、自分はよく使っています)

で、このようにすると、

となるので、フォームデータを受け取る側は「パラメタ名b1に値があるか」および「パラメタ名b2に値があるか」をチェックすることで、どちらのボタンが押されたかを判定することができることになります。

自分は以前、「submitボタン(type="submit"のinput要素)のvalue属性はラベル表示のためだけのもの」という誤解をしていたのですが、そうではなくちゃんとname属性を与えれば、送信時にはその name属性値=value属性値 というデータの組が付加されて送られることを後から知りました。さらに、submitボタンに設定されたデータはあくまでボタンを押した時のみ有効となります。そのおかげで上記のようなボタンの区別も容易にできるというわけです。

追記(2008年6月4日)

ブックマークでのコメントを拝見しました。

ysk_lucky-starさんのコメントで気付きましたが、フォームのname属性値(コントロール名)は同じものが複数存在しても構わないので、複数のボタンでname属性を統一し、値で押されたボタンを判別することも可能ですね。データを受ける側は1つのパラメタだけを見ればいいので、むしろそのほうが簡単そうです。以下にサンプルを作ってみました。

また、自分は type="submit"であるinput要素のことだけを想定していたのですが、同様にフォームを送信する働きをもつ要素(フォーム部品)としては、

  • type="image"であるinput要素
  • type="submit"であるbutton要素

もあります。ただ、これらの要素はブラウザにより、どんなデータを送信するかが異なっているため、注意が必要です。こちらもサンプルを。

  • type="image"のinput要素のサンプル
    • Firefoxは、value属性値と合わせてクリックしたポイントの座標データも送られます。
    • IE6, IE7は、座標データのみが送られます。
  • button要素のサンプル
    • Firefoxの動作は、type="submit"であるinput要素と同じ。
    • IE6は、コントロール名に対する値として、value属性値ではなく要素内容が用いられ、更に押していないボタンのデータも合わせて送られます。
    • IE7は、押したボタンのデータのみが送られますが、値にはIE6と同様value属性値ではなく要素内容が用いられます。
  • 内容に画像を持つbutton要素のサンプル
    • Firefoxの動作は、type="submit"であるinput要素と同じ。
    • IE6・IE7の動作は上記button要素の場合と同じですが、送られる要素内容はHTMLソース文字列の形に展開されます。

HTMLフォームを組む際には、HTML仕様書を十分確認しておきましょう。

(2008年6月3日11時30分)

文字エンコーディングについて、text/htmlの事情とXMLの事情

text/htmlのXHTML(1.0)文書のデフォルトの文字エンコーディングは - cnrdの日記より。

XML宣言とDOCTYPE宣言 - vantguarde - web:gをきっかけにXHTML Media Types - Second Editionを見て、XHTML Media Types(FE, SE)のtext/htmlの項とXHTML 1.0 SEやXHTML Media Types FEのHTML互換性ガイドラインはデフォルトの文字エンコーディングの点で矛盾があるのでは、と思いました。

上記を拝見して自分も

を読んでみたのですが、この2つの記述は矛盾というよりは、現時点における事実をそのまま述べているだけと思いました。

text/htmlについては、(XHTML Media Types SEでも言及されているように、)

といった異なる内容の規定があったりするので、「デフォルトのcharsetをあてにすると面倒なことになるから、charsetは明示しましょう」ということで注意をうながしているものと思います。

また、互換性ガイドラインの記述については、この箇所はもともとXML宣言の省略に関して述べているわけで、XML宣言を省略するとなるとエンコーディング宣言も書けないわけですから、その条件として、XML文書のデフォルト文字エンコーディング(UTF-8またはUTF-16)にも言及せざるを得ないところだと思います。

「デフォルトの文字エンコーディング」という観点で見ると一見矛盾のように見えますが、「text/htmlというメディア型」と「XML文書の仕様」という2つの事情があり、それらはXHTML Media Typesが書かれる以前に既に決められたことであったため、それをありのままに述べた……ということだと思います。

以上のような考えで、下記のブックマークコメントを記していました。

text/htmlではデフォルトcharsetを仮定すべきでない(RFC 2854)、という話と、XML文書ではUTF-8/UTF-16がデフォルト(XML仕様)、という話だと思う。

* * *

そうしたところ、ブックマークコメントへの言及として、以下の問いかけを元記事の追記にて頂きましたので、再度考えてみました。

それについては異論ありませんが、ではHTML互換性ガイドラインが仕様(XHTML 1.0 SE, XHTML Media Types SE (Editor's Draft, 2008-04-23))の言うように文書をHTML UAでもレンダリングしたい文書作成者のための設計ガイドラインだとして、何故HTML(text/html)には適用されないルールを何の注釈もなしに記述しているのでしょうか。

念のためcnrdさんの疑問点を確認しておくと、「互換性ガイドラインはHTMLとの互換を考慮するものなのに、なぜHTML(text/html)には適用されない『XML文書ではUTF-8, UTF-16がデフォルトの文字コード』という話を持ち出すのか?」ということであろうと解釈しています。以下その前提で述べます。

確かにcrndさんの引用箇所(Remember, however, that ...)に注目すると「UTF-8, UTF-16で書ける」という話にとれなくもないですが、そのもう少し前からの文章の流れを読むと、そもそもこの箇所は「XML処理命令・XML宣言を省略する」ということが主題となっているものと思います。見出しも Processing Instructions and the XML Declaration となっていますし。

Processing Instructions and the XML Declaration以下の文章の流れを、自分は以下のように解釈しています。

  1. いくつかのブラウザは、XML処理命令やXML宣言をうまく扱えないよ。
  2. そういうブラウザとの互換性を考えるのであれば、処理命令やXML宣言を外したいと思うかも。
  3. しかし思い出してほしい。文書がXML宣言を含まないとき、HTTPのような上位のプロトコルでエンコーディングが指定されていなければ、使用できるのはUTF-8かUTF-16だけだよ。

互換性を考慮するにあたっては、単にHTMLの側の事情のみを考えるのは不十分で、XML側の事情にも配慮してはじめて互換が達成できるわけですから、「XML宣言を外す」という命題において「XML側ではこのような事情がある」という点に言及することは、やはり必要なことだと思います。

* * *

……という風に考えたのですが、答えになっているでしょうか。

(2008年5月30日15時10分)

2008年4月12日

深夜に何となく工作的なことをしてみたり。

(2008年4月12日3時40分)

CSS裸日2008

ごぶさたしてます。今年のCSS Naked Dayは4月9日だそうで、また徒書限定でスタイルを外してみました。

The idea behind this event is to promote Web Standards. Plain and simple. This includes proper use of (x)html, semantic markup, a good hierarchy structure, and of course, a good 'ol play on words. It's time to show off your <body>.

(2008年4月9日1時3分)

subの中にsub

Perlで、サブルーチンを入れ子にして、レキシカル変数を外と内で共有するということを考えてみた。

use strict;
use warnings;

sub test1 {
  my $count = 0;
  print $count, "\n";

  sub test2 {
    print '->', ++$count;
    $count < 5 ? test2() : print "\n";
  }

  test2();
  print $count, "\n\n";
}

test1();

# Variable "$count" will not stay shared at c.pl line 9.
# 0
# ->1->2->3->4->5
# 5

実行はできているが、Variable "$count" will not stay shared のメッセージが出ている。

メッセージの意味はperldiagに記載されている。日本語訳perldiagより引用。

  • Variable "%s" will not stay shared

    (W closure) 内部の(ネストした) 名前付き サブルーチンが、 外側のサブルーチンで定義したレキシカル変数を参照しています。

    内側のサブルーチンが呼び出された時、おそらく外側のサブルーチンの値は、 最初の外側のサブルーチンへの呼び出し前および呼び出し中のものになります; この場合、外側のサブルーチンへの最初の呼び出しが終了した後、内側と 外側のサブルーチンは変数に関して同じ値を共有しなくなります。 言い換えると、変数はもはや共有されません。

    さらに、外側のサブルーチンが無名で、それ自身の外側のレキシカル変数を 参照している場合、外側と内側のサブルーチンは与えられた変数は 共有 しません

    この問題は普通、sub {} 構文を使って内側のサブルーチンを無名にすることで 解決します。 外側のサブルーチンの変数を参照している内側の無名サブルーチンが 呼び出されたり参照されたとき、これらはそのような変数の現在の値に 自動的に回復します。

共有しない、というところで少々ひっかかった。実行結果からすると、test2()の中で変更された$countの値はtest2()が終わった後も残っており、値が共有されているように見えるので。

しかしtest1()を複数回実行してみると、以下のような結果となる。

# 省略
test1();
test1();
test1();
Variable "$count" will not stay shared at c.pl line 9.
0
->1->2->3->4->5
5

0
->6
0

0
->7
0

test1()の直下では毎回値が0に設定されいてるのに対し、test2()の中では前に実行した時の値が残り続けている。確かに共有されていない。

なぜこういう動作になるのかと考えてみて、「Perlにおいては sub name {} の宣言はどこで書いてもグローバルになる」ということに気付くと納得できた。実際、test2()test1()の外でも実行可能であった。

# 省略
test1();
test2();
Variable "$count" will not stay shared at c.pl line 9.
0
->1->2->3->4->5
5

->6

test2()test1()実行のたびに作成されるのではなく、1度宣言したらそのまま残り続けている。そのため、その外で宣言されたレキシカル変数$countへの参照も、最初に宣言された時点のものが保持され続ける……ということだと思う。

警告メッセージを出さないようにするには、perldiagにあるとおり、内側のサブルーチンを無名サブルーチンにすればいい。

use strict;
use warnings;

sub test1 {
  my $count = 0;
  print $count, "\n";

  my $test2;
  $test2 = sub {
    print '->', ++$count;
    $count < 5 ? $test2->() : print "\n";
  };

  $test2->();
  print $count, "\n\n";
}

test1();
test1();

# 0
# ->1->2->3->4->5
# 5
# 
# 0
# ->1->2->3->4->5
# 5

ちなみにここで my $test2 = sub { ... } と書くと、内側のサブルーチンの中で 'Global symbol "$test2" requires explicit package name' というエラーになった。

* * *

このようなことを考えたきっかけは、再帰?は難しい - 刺身☆ブーメランのはてなダイアリーの記事を読み、「@linksを外で宣言するのではなく、さらに外側をsubで囲えばいいんじゃないか」と思ったことからでした。一応その考えをもとにブックマークファイルを読み込むスクリプトを自分なりに書いてみたところ、以下のようになりました。

use strict;
use warnings;
use Netscape::Bookmarks;
use Data::Dumper;
$Data::Dumper::Indent = 1;

sub extract_bookmark {
  my ($file) = @_;
  my @categories;
  my @links;

  my $_parse_recursively;
  $_parse_recursively = sub {
    my ($category) = @_;
    ref $category or $category = Netscape::Bookmarks->new($category);
    for my $element (@{$category->elements}) {
      if ($element->isa('Netscape::Bookmarks::Category')) {
        push @categories, $element->title;
        $_parse_recursively->($element);
      }
      elsif ($element->isa('Netscape::Bookmarks::Link')) {
        push @links, {
          bookmark => $element,
          categories => [ @categories ],
        };
      }
    }
    pop @categories;
  };
  $_parse_recursively->($file);

  return \@links;
}

my $links_ref = extract_bookmark('bookmarks.html');
print Dumper $links_ref;
* * *

ところでJavaScriptだと、関数の中に書かれた関数宣言は外側の関数内ローカルなものとなります。最近自分用のgresemonkeyのスクリプトを書き直したりしていたので、以上のようなことを考えたのもそのことが頭にあったからだと思います。

function a() {
  alert('hello!');
  function b() {
    alert('goodbye!');
  }
  b(); // goodbye!
}
a(); // hello!
b(); // (b is not defined)
(2008年2月7日14時17分)