と、ここでちょっと戻ってUU::Index(リスト2)を良く見てみると、4行目からの処理は、どうやらUU::Searchでも同じように書く必要がありそうです。同じロジックが2つのクラスに存在しているのはいまいちなので、ここは継承を使って書き換えます。UU::Appというスーパークラスを作って、UU::IndexとUU::Searchはそれを継承させましょう。UU::Appはリスト4のようになります。また、UU::Indexを書き換えて、UU::Appを使うようにします(リスト5)。ずいぶんスッキリしましたね。続けてUU::Searchを見ていきましょう(リスト6)。
package UU::App;
use strict;
use base qw (CGI::Application);
use CGI::Application::Plugin::TT;
sub cgiapp_init {
my $self = shift;
$self->tt_config(
TEMPLATE_OPTIONS => {
INCLUDE_PATH => "/path/to/template",
},
);
}
sub cgiapp_prerun {
my $self = shift;
$self->header_props( -charset => 'utf-8' );
}
1;
package UU::Index;
use strict;
use base qw (UU::App);
sub setup {
# 以前と同じ
}
sub do_index {
# 以前と同じ
}
1;
package UU::Search;
use strict;
use base qw (UU::App);
use WWW::OpenSearch;
sub setup {
my $self = shift;
$self->start_mode('search');
$self->run_modes(
search => 'do_search',
);
}
sub do_search {
my $self = shift;
my $q = $self->query;
my $osxml = $q->param('osxml') || "http://search.hatena.ne.jp/osxml";
my $engine = WWW::OpenSearch->new($osxml);
$engine->pager->current_page($q->param('page') || 1); -----(A)
$self->tt_process('search.tt', {
query => $q,
word => $q->param('word'),
osxml => $osxml,
engine => $engine,
result => $engine->search($q->param('word')),
});
}
1;
do_searchでやっていることはコマンドラインのスクリプトとほとんど同じです。入力がコマンドライン引数からCGIのクエリパラメータに変わっただけですね。異なる個所といえば、(A)の部分でしょうか。これはページングの処理で、検索結果に2ページ目以降がある場合にそれを表示できるよう、パラメータをセットしています。
一方、テンプレートのsearch.ttですが、リスト7のような感じになります。テンプレートについて少し解説しておきましょう。
1 <html>
2 <head><title>Open Search - [% word | html %]</title></head>
3 <body>
4 <h1><a href="../app.cgi">Open Search</a></h1>
5
6 [% USE FillInForm %]
7 [% FILTER fillinform fobject => query %]
8 <form action="search" method="get">
9 <input type="text" name="word">
10 <select name="osxml">
11 <option value="http://search.hatena.ne.jp/osxml">Hatena</option>
12 <option value="http://bulkfeeds.net/opensearch.xml">Bulkfeeds
13 </option>
14 </select>
15 <input type="submit" value="search">
16 </form>
17 [% END %]
18
19 [% pager = engine.pager -%]
20 <p>Search results [% pager.first %] - [% pager.last %] of [% pager.total_entries %] for <strong>[% word | html %]</strong> from [% engine.ShortName %]</p>
21 <dl>
22 [% FOREACH item IN result.items %]
23 <dt><a href="[% item.link | html %]">[% item.title %]</a></dt>
24 <dd>[% item.description %]</dd>
25 [% END %]
26 </dl>
27
28 <p>
29 [% IF pager.previous_page %]
301 <a href="https://www.itmedia.co.jp/enterprise/articles/0703/29/search?word=[% word | uri %]&page=[% pager.previous_page %]&osxml=[% osxml | uri %]">prev</a>
31 [% END %]
32
33 [% FOREACH num = [pager.first_page .. pager.last_page] %]
34 [% IF num == pager.current_page %][[% num %]]
35 [% ELSE %]<a href="https://www.itmedia.co.jp/enterprise/articles/0703/29/search?word=[% word | uri %]&page=[% num %]&osxml=[% osxml | uri %]">[[% num %]]</a>[% END %]
36 [% END %]
37
38 [% IF pager.next_page %]
39 <a href="https://www.itmedia.co.jp/enterprise/articles/0703/29/search?word=[% word | uri %]&page=[% pager.next_page %]&osxml=[% osxml | uri %]">next</a>
40 [% END %]
41 </p>
42
43 </body>
44 </html>
Googleなどの検索エンジンでは、トップページから検索して検索結果の画面に来たときも、フォームの中には使った検索語句が表示されます。細かい話なのですが、使い勝手に大きく影響する部分です。そこでGoogle同様に、指定された検索語句と検索エンジンの結果を、検索結果の画面でも保持するようにしたい、ということでTemplate::Plugin::FillInFormというTTのプラグインを使っています(6行目〜16行目)。
TTのプラグインは、CPANでインストールした後、このようにテンプレートの中で「[% USE FillInForm %]」などとすれば使うことができます。FillInFormプラグインを使うと、「[% FILTERfillinform …… %]」〜「[% END %]」で囲まれた個所にあるformの各値が、fobjectで与えられたクエリを自動で保持するようになります。
19行目〜20行目では、WWW::OpenSearchのインスタンス(engine)からpagerメソッドでインスタンスを取り出し、これに対してメソッドを使って検索結果の件数などを表示しています。WWW::OpenSearchでは、pagerメソッドでData::Page(ページング処理を管理するCPANモジュール)のインスタンスが取得でき、このインスタンスが検索結果の件数やページ数などの情報を持っています。
最後に、29行目〜40行目の部分はいわゆるページャを表示するための個所です。マクロがちょっとたくさんありますが、よく見るとやっていることは簡単で、要は前のページがあったら「prev」のリンクを表示、次のページがあったら「next」のリンクを表示、後は各ページへのリンクを表示しているだけです。
さて、このクラスとテンプレートも適宜配置して、先のトップページから適当な語句で検索してみましょう。エンジンをBulkfeedsに変えると、検索結果がはてな検索からBulkfeedsのものに変わることが確認できるかと思います。FillInFormプラグインで、クエリの内容や選んだエンジンも保持されていて、いい感じですね。スタイルシートで見た目を調整してやればもう少し格好いい表示ができるかと思います。
Copyright © ITmedia, Inc. All Rights Reserved.