検索から一覧、詳細まで検索条件を引き継ぎページングする方法
今回のミッション(ゴール)
GmailのようなよくありがちなページングのフローをCakePHPで実現せよ。
- 検索フォーム
- 検索結果一覧(前後に移動できる)
- 詳細表示(前後のレコードに移動できる)
- 検索結果一覧に戻る(直前の詳細レコードを含む一覧ページ*)
*1ページあたり最大5件を表示する一覧の場合、直前の詳細が5件目なら一覧の1ページ目に6件目なら一覧の2ページ目に戻る。
*検索フォームと検索結果一覧は同一ページ。
実現するためのアイディア
- CakePHPのページングの機能をできるだけ活用する。
- 検索フォームのポストをページングで使えるURL(名前付きパラメータ)に変換してリダイレクトする。
- 検索条件を詳細画面まで引き継ぐ(名前付きパラメータを持つURL)
- 詳細画面も最大1件のページングのページであると考える。
[却下されたもの]
隣接するレコードを取得するためのModeのfind('neighbors')メソッドの使用。(ページングとの併用には適切ではなさそう)
検索条件をセッションで保持する。(URL化したかったので)
Ajax(単に検討しなかった)
[サンプルで実装するアクション]
- コントローラ:Reports
- 検索フォーム:indexアクション
- 検索結果一覧:indexアクション
- 詳細表示:viewアクション
- モデル:Report
[実装の課題]
1. 検索フォームからポストされたデータをページング用URLに変換する
2. 名前付きパラメータのデータを受け取って検索条件を組み立て検索実行する
3. 詳細表示に検索条件を引き継ぎつつリンクする
4. 詳細画面から適切な一覧ページへ戻れるようにする
1. 検索フォームからポストされたデータをページング用URLに変換する
ページングで1ページ目をクリックした時と同じになるようにページング形式のURLに変換してリダイレクトします。
コントローラがフォームからのデータを受けとったら$this->dataに格納されます。逆に言うと$this->dataにデータが入っていればフォームからのデータを受け取ったという事です。データを受け取ったら、このデータをページングで使われる名前付きパラメータを使ったURL形式の文字列に変換します。
class ReportsController extends AppController { //デフォルトのページング設定 var $paginate=array('limit'=>5,'order'=>'Report.modified DESC'); function index(){ //検索フォーム、検索結果一覧 if(!empty($this->data)){ //フォームで受け取ったデータをparam1:value1/param2:value2に変換 $param = $this->_postToNamedParam($this->data['Report']); $this->redirect(array('controller'=>'reports','action'=>'index',$param); }else{ //検索実行 } } }
文字列に変換できたらurlencodeも忘れずにかけてリダイレクトしてします。これでフォームからの検索条件をページングの1ページ目に引き継いだ事になります。
2. 名前付きパラメータのデータを受け取って検索条件を組み立て検索実行する
名前付きパラメータは$this->params['named']に入ってくるのでこれを使用して検索条件を作成し、検索を実行する。検索結果一覧と検索フォームを一体化している場合は$this->params['named']のデータを$this->dataにもセットしておくと検索フォームに入力済みデータが反映されます。
class ReportsController extends AppController { var $paginate=array('limit'=>5,'order'=>'Report.modified DESC'); function index(){ //検索フォーム、検索結果一覧 if(!empty($this->data)){ //フォームで受け取ったデータをparam1:value1/param2:value2に変換 $param = $this->_postToNamedParam($this->data['Report']); $this->redirect(array('controller'=>'reports','action'=>'index',$param); }else{ //$this->params['named']のデータを使って検索条件を組み立て //安全のためホワイトリスト等使用してパラメータを精査して下さい。 $conditions = $this->_namedToConditions($this->params['named']); $this->set('reports', $this->paginate('Report',$conditions)); } } }
ビューではURLとして引き継いだ検索条件が$this->params['named']で受け取れます。ビューのページネーター・ヘルパーは何もしないとこれらを引き継いでくれませんので、ページネーターのoptionsメソッドを使ってセットしておきます。こうするとこれ以降でページネーターが生成するリンクにパラメータが反映されます。
$namedArgs = array_map('urlencode',$this->params['named']);//urlエンコードする。 $paginator->options(array('url' => $namedArgs ));
3. 詳細表示に検索条件を引き継ぎつつリンクする
詳細画面においても前後のレコードに移動できるようにページング機能を利用します。検索結果一覧と詳細画面はどちらも同一の検索条件で1ページの最大表示件数が異なるだけという考え方です。例えば検索結果一覧で最大5件ずつ表示している場合、先頭から6件目のレコードは検索結果一覧の2ページ目に表示され、詳細画面では6ページ目に表示される事になります。
この例えで見られるように検索結果一覧から詳細画面へリンクするには検索結果一覧で表示している個々のレコードが先頭から何件目のレコードであるかが調べればリンク出来る事が分かります。先頭から何件目であるかと検索条件をマージして詳細画面へのリンクを生成します。
//この一覧で何件目から表示を開始しているか調べる。 $start =($paginator->params['paging']['Report']['page']-1)*$paginator->params['paging']['Report']['options']['limit'] ; foreach ($reports as $i=>$report){ //$start+$iで何件目かを決定。次のアクションはview,これを検索条件とマージする $link = array_merge($namedArgs,array('action'=>'view','page'=>$start+$i)); echo $html->link($report['Report']['name'],$link); }
4. 詳細画面から適切な一覧ページへ戻れるようにする
詳細画面は通常のページングと同じように作成します。コントローラーはほぼ同じコード。ただし、検索結果一覧で設定している最大表示件数をビューに渡しておきます。これは詳細画面から検索結果一覧の何ページ目へ戻るかを決定するために必要です。
class ReportsController extends AppController { var $paginate=array('limit'=>5,'order'=>'Report.modified DESC'); function index(){ //省略 } function view(){ $this->set('listPageLimit',$this->paginate['limit']);//検索結果一覧の最大表示件数をビューにセット $this->paginate['limit']=1;//詳細画面では最大表示件数は1件。 //その他は検索実行と同じ。検索時に使ったアクションを呼んでもいい。この例ではindex()アクションを実行。 $this->index(); } }
ビュー。検索結果一覧へ戻るためのリンクを生成
$namedArgs = array_map('urlencode',$this->params['named']);//urlエンコードする $namedArgs['action']='index'; $namedArgs['page']=floor(($namedArgs['page']-1)/$listPageLimit)+1; echo $html->link('検索結果に戻る', $namedArgs);
検索結果一覧へ戻るためのリンク以外は通常のページングです。以下は詳細画面用にシンプルにしたものです。
<div class="paging"> <?php echo $paginator->prev('<< '.__('前へ', true), array(), null, array('class'=>'disabled'));?> | <span class="current"><?php echo $paginator->counter(array('format' => __('%end%件目(%count%件中)', true)));?> </span> | <?php echo $paginator->next(__('次へ', true).' >>', array(), null, array('class' => 'disabled'));?> </div>
[今後の課題]
コンポーネント化。
参考
URLのクエリ「?」として渡す方法
検索
最近の投稿
作ったもの
写真共有のTWINGAR
CakePHPのまとめノートCakePHP Note
CakePHPのAPIFramework API
About Me
@ZiSTA Tweets
CakePHPとかMacとか

コメント