2012年6月3日日曜日

ページネーションを利用する

cakePHPにはページネーションという便利な機能が搭載されてるらしい。
それを使うと、簡単にページの分割やリンクによるソートなどができるようになるそうな。というわけで、今回はそのページネーションについてまとめる。

使い方

主にコントローラー内ビュー内で使用する。

コントローラー内での操作

基本の使い方は以下の通り。

$data = $this->paginate()

ずいぶんと簡単でございますね。上記の場合、命名規約により自動的にコントローラーに対応するモデルが選ばれ、$this->Model->find('all')と同じリクエストが発行されてデータが吐き出される。
なお、デフォルトでは20件のデータが呼び出されるみたい。あと第一引数にモデル名を指定することもできる。

【下記の結果は事実上同じ】
//PostalCodesController内
$result = $this->PostalCode->find('all',array('limit'=>20));
$result = $this->paginate();
$result = $this->paginate('PostalCode');

ちなみに、最初の結果は同じとしてもpaginateを使用するとページ分割の為のツールがビューページで使用できるようになる。

検索条件などを指定する

条件は直接指定する方法と、$this->paginateにてconditionsを指定する方法とがある。

【下記の結果は事実上同じ】
//PostalCodesController内
public $paginate = array(
   'conditions' = array('zipcode'=>'郵便番号')
);
public function address(){
    $this->paginate();
}
//または
public function address($zipcode){
    $this->paginate = array(
       'conditions' = array('zipcode'=>$zipcode)
    );
    $result = $this->paginate();
}
//または
public function address($zipcode){
    $result = $this->pagenate('PostalCode' , array('zipcode'=>$zipcode));
}
Limit値やその他の設定

これは、$paginate内にて行えばいいだけ。
同様に、抽出するフィールドやソートなどもその中で設定できる。
もしも同じコントローラ内で複数のページネーションにて個別の設定が必要な場合、モデル名をキーにすれば実装できる。

//PostalCodesController内
public $paginate = array(
    //PostalCodeモデルの設定
    'PostalCode'=>array(
       'field' => array('zipcode' , 'address') ,
       'limit' => 30 ,
       'order' => array('zipcode' => 'desc')
    ),
    //Exampleモデルの設定
    'Example'=>array(
      //他の設定
    ),
    ...
);

また、モデル内でもpaginate使用時に呼び出されるトリガー関数も設定できるみたい。
ただ今のところ使うことはなさそうだから割愛(引数が多すぎて面倒だから使いたくない)。
詳細はCookBook2.x(英語版)Paginationにざっくりと、ギリギリわかる程度の解説がされてます。

ビューページでの使い方

コントローラ内でページネーションを使えば、ビューページでも同じように使えるようになるみたい。
基本はfindを使った時と同じだからデータの扱い方は変わらない。
(追記:2012/06/17 $hasManyなどのアソシエーションを使ってる場合もちゃんと対応してくれる。使用例はこちら)

また、通常のfindにプラスして$this->Paginatorヘルパーが使えるようになる。

[補足]
ページネーションは、$helpers及び$componentsなどでわざわざ指定する必要はないみたい。デフォルトで使えるようになってます。

sortメソッド
$this->Paginator->sort($キーの名前 , $タイトル , $オプション);

簡単にソートできるリンクを作成してくれる。キーはソートが可能なフィールド名を指定。タイトルを割愛すれば、キーの名前がちゃんと先頭が大文字になって表示される。
オプションには
・'escape' => boolean(true)
・'model' => モデル名
・'direction' => 'asc/desc'

が指定可。指定しなければ、タイトルなどはタグがエスケープされるし、モデルはデフォルトモデルが指定される。directionは最初のソートの方向を指定。

$this->Paginator->sortDir($モデル名,$オプション);
$this->Paginator->sortKey($モデル名,$オプション)

現在ソートされてる方向とソートに使ってるキーを取得するみたい。モデル名はわかるけど、オプションで何を指定するかはCookBookにも書いてないっぽくてよくわからない。

ソートできるカラムを限定する

なお、実際にソートリンクを使ったらわかるけど、リンクを押すと「sort:カラム名」というnamedパラメータが渡される。

http://localhost/cakePHP/postal_codes/sort:zipcode/page:1/direction:desc/

これは、そこをいじればどんなカラムでもソートできてしまうということでもあるよね。

その「どんなカラムでもソートできる」状態を防ぐには、コントローラー内で$this->paginate()の第三引数にてソートできるフィールドを指定する。

//PostalCodesController内
//ソートはzipcodeのみ許可。
$this->paginate(null , array() , array('zipcode'));
numbersメソッド

$this->Paginator->numbers()メソッドを使うことで、「1|2|...10|11|12|13|...|25|26」というようなナンバーリンクを簡単に実装できる。
使い方は以下の通り。

print $this->Paginator->numbers($オプション);
オプションの種類
before/afterナンバーリンクの先頭/最後につける文字列
modelナンバーリンクを使うモデル。
デフォルトは命名規約でのデフォルトモデル
modulusナンバーリンクをいくつ表示するか。デフォルトは8
separator数の間に入れる文字列。デフォルトは「|」
tag文字を囲むタグ。デフォルトは<span>
classtagのクラスを指定
currentClass選択中のナンバーのクラス。
(cakePHP2.1から追加されたっぽい)
firstページナンバーが沢山ある場合modulusによって途中端折られるが、
これを指定することで先頭だけは出したりできる。
数値で指定すると何ページ分出すか指定でき、
文字列で指定するとその文字列のリンクに置き換わる。
lastfirstの最後版。
ellipsis端折られる際に出す文字列。デフォルトは「...」
【例(わかりやすく変な設定で)】
//ビュー内
print $this->Paginator->numbers(array(
    'before'=>'←←←',
    'after'=>'→→→',
    'separator'=>'~',
    'currentClass'=>'redback',
    'class'=>'block',
    'modulus'=>6,
    'first'=>5,
    'last'=>2,
    'tag'=>'s',
    'ellipsis' => '...'
));
//css内
s.block{
    border:1px solid #cccccc;
    padding:2px 5px;
}
s.redback{
    background:red;
    border:2px solid #000000;
    color:white;
}
ジャンプリンク
$this->Paginator->prev($title , $options = array() , $disabledTitle = null , $disabledOptions = array());
$this->Paginator->next($title , $options = array() , $disabledTitle = null , $disabledOptions = array());

いわゆる「前のページへ」「次のページへ」リンクを作成する。
$options及び$disabledOptionsでは「tag」「class」「model」「escape」などが指定できる。多分わかると思うから割愛。
$disabledTitleは、ページ番号が1番ないし最後だったりした場合に代替で表示する文字。nullの場合、リンクが外れたのが表示される。
(ページが1や後尾の時表示したくなければ、空白を指定したら消えてくれた。ちょっとかっこわるいけど)


$this->Paginator->first($title , $options = array());
$this->Paginator->last($title , $options = array());

「最初」「最後」のジャンプリンク作成。動作はnumbersfirstlastとほぼ同じ。
$titleを文字列で指定すればその文字列がでるし、数字を入れたらその数字分のナンバーリンクが表示される。先頭及び後尾の場合表示されない。
オプションは「tag」「after」「model」「separator」「ellipsis」が指定できる。

ページカウンター
$this->Paginator->counter($options = array())

現在のページネーションの情報を簡単に出すことができる。
オプションは
format
rangepagescustomから選べる。$optionsは文字列としても指定でき、文字列で書けば自動的にこのformatが指定される。

range
表示してるレコード番号を出力(例: 11 - 15 of 135)

pages
表示してるページ番号を出力(例: 3 of 27)

custom
専用のタグを使って自分で表示するのをカスタマイズできる。
【タグ一覧】
{:page}現在のページ
{:pages}全ページ数
{:current}表示件数
{:count}全レコード数
{:start}表示開始レコード番号
{:end}表示終了レコード番号
{:model}モデル名

separator
formatを'pages'または'range'にした場合、ofの代わりに何を表示させるか指定できる。

model
使用するモデルを指定。デフォルトは命名規約でのデフォルトモデル。

情報メソッド
$this->Paginator->hasNext($model = null)
$this->Paginator->hasPrev($model = null)
$this->Paginator->hasPage($model = null , $page = 1)
$this->Paginator->current($model = null)

それぞれ「次のページがあるか」「前のページがあるか」「$pageがあるか」「現在のページ番号」を返す。


他にも色々とあるけど、多分基本はこれくらいかな?
他の詳しいことは、cookBook2.xのPaginationComponentPaginatorHelperに載ってます。

これらを踏まえ、郵便番号検索を作る─2.簡単なページを作成 で作ったPostalCodesController.phpaddress.ctpを書き直してみる。

//PostalCodesContoller.php
   public function address($zipcode = 0){
      $this->paginate = array(
          'conditions'=>array('zipcode'=>$zipcode),
          'limit'=>10
      );
      if(!$result = $this->paginate(null , array() , array('zipcode'))){
          $this->Session->setFlash("ヒットしませんでした");
          $this->redirect($this->referer(array('action'=>'index')));
      }else{
          $this->set(compact('result'));
      }
   }


//PostalCodes/address.ctp
<?php 
    $count = 0; 
    $cause = array(0=>'変更なし',1=>'市政・区政・町政・分区・政令指定都市施行',2=>'住居表示の実施',3=>'区画整理',4=>'郵便区調整等',5=>'訂正',6=>'廃止');
    $this->Html->addCrumb('検索',array('action'=>'index'));
    $this->Html->addCrumb('検索結果');
    $this->Html->css('mypage',null,array('inline'=>false));
?>
<?php print $this->Html->getCrumbs(">"); ?>
<hr>
<H1>検索結果</H1>
<?php
    print $this->Paginator->counter('{:count}件中{:start}-{:end}件({:pages}ページ中{:page}ページ)<br>');
    if($this->Paginator->hasPrev()) print $this->Paginator->prev('≪' , array('class'=>'block'));
    print $this->Paginator->numbers(array(
    'class'=>'block',
    'modules' => 6 ,
    'first'=>2,
    'last'=>2,
    'currentClass'=>'red',
    'separator'=>null
    ));
    if($this->Paginator->hasNext()) print $this->Paginator->next(' ≫' , array('class'=>'block'));
?>
<hr>
<?php foreach($result as $rows): ?>
【<?php print ++$count; ?>】
<table border='1' style='margin-bottom:20px; width:700px;'>
    <tr><th width='200'>郵便番号</th><td><b style="color:#292999;"><?php print $rows['PostalCode']['zipcode']; ?></b></td></tr>
    <tr><th width='200'>JISコード</th><td><?php print $rows['PostalCode']['jiscode']; ?></td></tr>
    <tr><th width='200'>都道府県</th><td><?php print $rows['PostalCode']['state']; ?>(<?php print $rows['PostalCode']['state_kana']; ?>)</td></tr>
    <tr><th width='200'>市区町村</th><td><?php print $rows['PostalCode']['city']; ?>(<?php print $rows['PostalCode']['city_kana']; ?>)</td></tr>
    <tr><th width='200'>町域名</th><td><?php print $rows['PostalCode']['street']; ?>(<?php print $rows['PostalCode']['street_kana']; ?>)</td></tr>
    <tr><th width='200'>変更</th><td><?php print $cause[$rows['PostalCode']['cause']]; ?></td></tr>
</table>
<?php endforeach; ?>
<hr>
<?php print $this->Html->link('戻る',array('action'=>'index')); ?><br/>

//webroot/css/mypage.css
.block{
    border:1px solid #cccccc;
    padding:2px 5px;
}
.redback{
    background:red;
    border:2px solid #000000;
    color:white;
}

だいぶ「郵便番号検索」からはそれちゃったけど、こういうのもあるんだということで今回まとめてみました。
次回からは作成手順どおりに都道府県リストなどを作っていこうかなって思います。

それにしても便利だよね。

【参考リンク】
cakePHP2.x(英語版)PaginationComponent
cakePHP2.x(英語版)PaginatorHelper

0 件のコメント:

コメントを投稿