CGI::Applicationの練習として、簡単な投票CGIを作ってみることに。http://kk01234.hp.infoseek.co.jp/cgi-bin/sample01/vote.cgi
(refereあるとダメっぽいので、アドレスコピペしてください)
所要時間は2時間ほど。使い方に慣れてないことも考えると、これくらいの時間で結構きれいに作れるのはなかなかいいかも。
ファイル構成
vote.cgi
#!/usr/local/bin/perl use Post; my $post = new Post(PARAMS =>{filename => 'fate.dat'}); $post->run();
メインのCGIでは、CGI::Applicationを継承したPostというモジュールのインスタンスを生成して、run()するだけ。
Post.pm
package Post; use base 'CGI::Application'; use ReadData; use strict; sub setup{ my $self = shift; $self->start_mode('view'); $self->run_modes([qw(view post addview add)]); $self->header_props(-type=>'text/html', -charset=>'EUC-JP'); $self->param('filename', 'vote.dat') unless defined $self->param('filename'); $self->param('data', new ReadData($self->param('filename'))); } sub view{ my $self = shift; return $self->__viewhtml(); } sub post{ my $self = shift; $self->param('data')->post($self->query()->param('key')); return $self->__viewhtml(); } sub addview{ my $self = shift; return $self->__addviewhtml(); } sub add{ my $self = shift; $self->param('data')->add($self->query()->param('name')); return $self->__viewhtml(); } # private function sub __viewhtml{ my $self = shift; my $tmpl = $self->load_tmpl('view.html'); my $data = $self->param('data')->get(); my @loop = (); foreach(sort {$data->{$b}->{count} <=> $data->{$a}->{count}} keys %{$data}){ push(@loop, {key => $_, name => $data->{$_}->{name}, count => $data->{$_}->{count}}); } $tmpl->param(DATA => \@loop); return $tmpl->output(); } sub __addviewhtml{ my $self = shift; return $self->load_tmpl()->output(); } 1;
run_modes()で、CGIに使うrun modeを設定する。CGIからは、"rm=xxxx"のパラメーターで、xxxxに対応したサブルーチンを呼び出す。
今回はrun_modes()にarray referenceを渡しているので、run modeの名前と同じサブルーチンが呼び出されます。
vote.cgiでnew Post()に渡したPARAMSは、こっちのparam()で取り出すことができます。
後は何かあるかな・・・load_tmpl()は、引数を省略すると、「現在のrun mode+.html」が読み込まれます。
まあ簡単なコードですね。ループ1つ、分岐0だし。
ReadData.pm
package ReadData; use Digest::MD5; use strict; sub new { my ($class, $f) = @_; my $self = {}; $self->{filename} = (defined($f) && length($f)) ? $f : 'data.dat'; return bless($self, $class); } sub get{ my $self = shift; return $self->__read(); } sub add{ my $self = shift; my $name = shift; my $ctx = new Digest::MD5(); my $key = $ctx->add($name)->hexdigest(); my $data = $self->__read(); unless(exists($data->{$key})){ $data->{$key}->{name} = $name; $data->{$key}->{count} = 0; } $self->__write($data); } sub post{ my $self = shift; my $key = shift; my $data = $self->__read(); $data->{$key}->{count}++; $self->__write($data); } #private method sub __read{ my $self = shift; my $data = {}; open(F, $self->{filename}); while(<F>){ tr/\x0D\x0A//d; my($key, $name, $count) = split(/\t+/); $data->{$key}->{name} = $name; $data->{$key}->{count} = $count; } close(F); return $data; } sub __write{ my $self = shift; my $data = shift; open(F, ">$self->{filename}"); foreach(keys %{$data}){ print F join("\t", $_, $data->{$_}->{name}, $data->{$_}->{count}), "\n"; } close(F); } 1;
タブ区切りデータの読み書きをするモジュール。ReadDataじゃなくなってしまった。ネーミング失敗・・・。
データファイルの中身は
key name count e5f8e3bdcb8ea3aba6d7796f8b497b29 セイバー 11 5fb62c4809b0fa8f81260c065d0a1ca0 士郎 3
みたいな。
view.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=euc-jp"> </head> <body> <h1>人気投票</h1> <table border=1> <tr><th>名前</th><th>票</th><th></th></tr> <TMPL_LOOP name=DATA> <tr> <td><TMPL_VAR NAME=name></td><td><TMPL_VAR NAME=count></td> <td> <form action="vote.cgi" method=post> <input type=hidden name=rm value=post> <input type=hidden name=key value=<TMPL_VAR NAME=key>> <input type=submit value=投票></form> </td> </tr> </TMPL_LOOP> </table> <p><a href="vote.cgi?rm=addview">追加する</a></p> </body> </html>
HTML::Templateのテンプレート形式。"rm"がrun modeを設定してるというのが分かるかと思います。
addview.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=euc-jp"> </head> <body> <h1>追加</h1> <form action="vote.cgi" method=post> <input type=hidden name=rm value=add> <input type=text name=name> <input type=submit value=追加> </form> </body> </html>