AjaxでPaginateする

タイトル通り。ぐぐってもスマートな方法が見当たらなかったので、PaginatorHelperからModelまでソースを読む羽目になったが、わかってみれば至極簡単だった。

口で説明するよかサンプルのが早いと思われるので、掲示板でも作りましょうか。

まずモデル。名前はテキトーにThreadに。命名規則にしたがってテーブル名はthreadsね。

<?php
/** 
CREATE TABLE threads (
      id SERIAL NOT NULL -- Postgres以外の人は適当に読み替えて
    , handle VARCHAR(100) DEFAULT '名無しさん'
    , email VARCHAR(100)
    , comment TEXT not null
    , created TIMESTAMP
    , PRIMARY KEY (id)
)
*/
class Thread extends AppModel {
    var $name = 'Thread';
    // ほんとはちゃんとバリデータ書こうね
    var $order = 'created desc'; //最新の書き込みを先に
}
?>


コントローラをつくる。controllers/threads_controller.php

<?php
class ThreadsController extend AppController {
    // RequestHandlerを呼んでおくとAjaxかどうか自動的に判断してくれる
    var $component = array('RequestHandler'); 

  // JavascriptHelperはprototype.jsをview側で読まないなら不要(layoutで読むとか)
    var $helpers = array('Html', 'Form', 'Ajax', 'Javascript'); 

    // 2行で書ける掲示板\(^o^)/
    function index(){
        if(!empty($this->data)) $this->Thread->save($this->data); // $this->dataあったら投稿
        $this->set('threads',$this->paginate()); // 再表示
    }
}
?>

次にビュー作る。views/threads/index.ctp

<?php
    $javascript->link('prototype',false); // prototype.jsを読み込みlayoutで読むなら不要

    // 投稿後に最新の場所に移動させるためのURL。
    // order by created descでひっくり返してるのでpage:1。最後ならpage:lastとすればOK。
    $url = '/threads/index/page:1';

    // Ajax.UpdaterをかけるDOM_ID
    // optionsにarray('update'=>DOM_ID)って書くのダルいからcompact('update')ってかけるように$update
    $update = 'ThreadsIndex';  
?>
<?php if( !$ajax->isAjax() ): ?>
    <h2>掲示板だゴルァ</h2> <!-- Ajaxリクエストじゃなかったらタイトル表示 -->
<?php endif; ?>
<?php echo $ajax->div($update) ?> <!-- AJAXリクエストの時は表示されない -->
    <?php echo $paginator->prev("<<",compact('update'))?> 
    <?php echo $paginator->numbers(compact('update'))?> 
    <?php echo $paginator->next(">>",compact('update'))?>
	
    <?php if(empty($threads)):?>
		<p>書き込みはまだありませんが何か(゚Д゚)?</p>
    <?php endif;?>
    <?php foreach( $threads as $thread ):?>

    <!-- カキコ表示ループ部分 エスケープわすれてはならぬぞ -->
    <div>
        <p>
            <?php echo $html->tag('span',$thread['Thread']['handle'],array('title'=>$thread['Thread']['email']),true)?>
            <?php echo $html->tag('span',$thread['Thread']['created'],array(),true)?>
        </p>
        <p>
            <?php echo $html->tag('span',$thread['Thread']['comment'],array(),true)?>
        </p>
    </div>
    <?php endforeach; ?>

    <!-- このへんからフォーム -->
    <?php echo $ajax->form(null,'post',compact('update','url'))?>
        <!-- ほんとうは普通にform->input並べようね -->
        <?php echo $ajax->Form->inputs(array('Thread.handle','Thread.email','Thread.comment'))?>
    <?php echo $ajax->Form->end('投稿')?>

<?php echo $ajax->divEnd($update) ?> // AJAXリクエストの時は表示されない

CakePHPの流儀を知らないとかなり意味不明な感じになりますが(アクションとかマジで)
とりあえずこんな記事読む人ならCakePHPはそれなりに使ってるだろうということで要点だけ

  • $ajax->div(),divEnd()はAjaxでリクエストされた場合、何も出力しない
    • 何も考えずにAjax/通常リクエストを1つのビューで出力すると同じIDのDIVがどんどん増えちゃうのねw
  • ビューでAjaxかどうかの分岐には$ajax->isAjax()を使う
    • あんまりにも2つのビュー(AJAXとそれ以外)が違う場合はコントローラで$this->RequestHandler->isAjax()で分岐して$this->render()とかやるべき?
  • $ajax->form(),$paginator->prev(),next(),numbers()などは$options['update']='DOMエレメントのID'を投げてあげると全部AjaxリクエストとしてよしなにEvent.observe吐いてくれます。
  • Configure::write('debug',0)にしないとデバッグライトがずどーんて出るので適当に(beforeFilterとか)挿入してネ。

次は同一ページで複数モデルのPaginateネタとかやろう。