目次

  1. はじめに
  2. 必要となる情報・知識
  3. サンプルコード
  4. まとめ

はじめに

今回、データの更新処理をAjaxを使って実装してみたのですが、結構覚えることが多く大変だったので実際にやったことをメモ。

次同じことをするときにさらっと思い出せるように。

プログラムに限らない一般的な話ですが、一見複雑なものも細分化していくと単純なものになりますね。まぁ、その単純なものをいかに有機的に組み合わせていくのかっていうのが、何においてもカギを握るわけでもありますが。なお jQuery を使用しています。そして、スクリプト系のヘルパーは今回勉強不足なこともあって一切使用していません。いずれ機会があれば Cake の機能をフル活用した書き方も実践したいところです。CakePHP のバージョンは 2.2.1

ちなみに私は CakePHP の処理の流れは一通りわかっているけど、jQuery はDOMをIDで指定して、属性を設定したり値を取得したりぐらいはできる程度で、 Ajax はいまいちというレベルの人です。なので、今回のメモはかなり初歩的な段階から記述しています。そんな方のご参考になれば幸いですが、ある程度以上 jQuery を理解されている方にはとても鬱陶しい内容だと思います(^^;)

実現する機能は次のようなものです。

  1. MyRecord というモデルで、my_field という text のフィールドを持つ
  2. ビューには送信フォーム、追加した最新の5件分のデータ、Ajaxのレスポンスメッセージを表示する
  3. フォームから MyModel のデータを1件Ajaxで追加する
  4. レスポンスメッセージを適当な場所に表示してしばらくしてから消す
  5. 最新の5件分のデータを更新する

必要となる情報・知識

0.情報の入手先など

  1. jQuery の公式サイト
  2. jQuery の日本語解説サイト

1.phpの必要な知識

  1. データをJSON形式に変換する方法
    $jsonObject = json _encode( $src )
    ※ただし(PHP 5 >= 5.2.0, PECL json >= 1.2.0) 

2.CakePHPでの必要な知識

  1. コントローラのアクション内で、リクエストが ajax であることを検知する方法
    CakeRequest::is(‘ajax’)
    を用いる
  2. 以前のエントリです:CakePHP2.0でjavascriptを使う手順

3.javascript の必要な知識

  1. 一定時間ののちに何らかの処理を行いたい場合は
    setTimeout( 処理, 時間(ミリ秒) )
    を用いる
  2. Ajax通信する場合は、リクエスト先は同じドメインでなくてはならない

4.jQuery の必要な知識

  1. DOMオブジェクトを取得したりの超基本
    $ 記号は変数名であり、jQuery オブジェクト。このオブジェクトにはさまざまな javascript の処理メソッドがカプセル化されていてとても便利というわけ。この変数 $ が jQuery ライブラリをロードしたタイミングでグローバルスコープにセットされていて、通常この “$” という変数を用いてDOMの操作などを行う。jQueryのメソッドは、メソッドチェーンというやり方で、ドット区切りで次々に追加していくことができる。
    jQuery( “#id” ) 
    の形式で、任意のIDのオブジェクトを取得できる。
  2. ふわっと表示させたり消したりする方法
    jQuery.fadeIn(), jQuery.fadeOut() を用いる
  3. あるオブジェクトウをクリックすると何かを実行させる方法
    jQuery.click(  eventObject ) 
  4. フォームのサブミットボタンを押しても通常のリクエストを行わないようにする方法
    jQuery.submit( function(){ return false; } ); 
  5. フォーム内のデータをシリアライズするには
    jQuery.serializeArray()
    を用いる
  6. JSON形式のデータをに変換する方法
    jQuery.parseJSON( jsonObject ) 
    を用いる。戻り値に対しては、次のようにドットでキーを連結するとアクセスできる。

    var jsonObject = $.parseJSON( data );
    var myField = jsonObject.MyModel.my_field;
  7. Ajax通信する方法
    jQuery.ajax()
    を用いる。非常に多くのパラメータを渡せるが、今回はこのうち、type, url, data, success, error の5つを設定する。

    • (type)…POST送信で、
    • (url)…xxxというURLに、
    • (data)…○○○のフォームのデータを渡して、
    • (success)…通信に成功したら◇◇◇を実行して、
    • (error)…通信に失敗したら△△△を実行する

    ということをこれらのパラメータで設定するわけです。

サンプルコード

コントローラ ( App/Controller/MyRecordsController.php )

<?php

class MyRecordsController extends AppController {

	/**
	 * 通常のCREATEアクションをAjax対応させたもの。通常のリクエストに対する処理は省略。
	 */
	public function add() {
		// ajax 通信だった場合に以下のブロックを処理する。
		if ($this->request->is('ajax')) {
			$this->autoRender = false;	// 自動描画をさせない

			// json response data ('succeed' と 'message'をJSON形式で返します)
			$succeed = $this->MyRecord->save($this->request->data);
			$message = $succeed ? '追加しました' : '追加に失敗しました';

			// Model::$validationErrors があれば、その先頭の一つをメッセージにセット
			if (!$succeed && $this->MyRecord->validationErrors) {
				$validationError = array_shift($this->MyRecord->validationErrors);
				$message = $validationError[0];
			}

			$data = compact('succeed', 'message');
			$this->response->type('json');
			echo json_encode($data);
			exit;
		}
		/* 通常の処理は省略 */
	}

	// ajax 用のメソッド
	// 2013/06/30 2箇所修正しました。
	// 1. exit 文を削除(不要。単体テストが困難になる)
	// 2. $this->response('json') を追加。レスポンスヘッダを正しく送出する。
	// 2013/07/02
	// 3. Configure::write('debug', 0); を追加
	public function get_last_update() {
		// ajax 専用のメソッドならこれもあり。ajax 以外はindexアクションへリダイレクトします。
		if (!$this->request->is('ajax')) {
			$this->redirect(array('action'=>'index'));
		}

		Configure::write('debug', 0); // 余分な情報の出力を抑制
		$this->autoRender = false;

		// $_POST['num'] で取得件数をリクエストされる。念のためデフォルトで5件。
		$data = array_merge(
			array(
				'num' => 5,
			),
			$this->request->data
		);

		// find するパラメータ。極力シンプルなものにしています。
		$contain = array();
		$fields = array('id', 'my_field');
		$order = 'MyRecord.created DESC';
		$limit = $data['num'];

		// json response data
		$lastUpdate = $this->MyRecord->find('all', compact('conditions', 'fields', 'contain', 'order', 'limit'));
		$succeed = $lastUpdate ? true : false;

		$this->response->type('json');
		echo json_encode(compact('succeed', 'lastUpdate'));
	}
}

ビュー ( App/View/MyRecords/add.ctp )

<?php
/* ここで作成する form の ID がのちに必要になります。
   CakePHPが自動的にに付与するIDは #MyRecordAddForm となりますが、任意に引数でセットすることも可能です。
 */
echo $this->Form->create(null, array('url'=>'#'));
echo $this->Form->input('my_field');
// 送信ボタンにはデフォルトではIDが付与されないので引数でセットします
echo $this->Form->input('送信', array('id' => 'formSubmit'));
echo $this->Form->end();
?>

<div id='messageArea'></div><!-- レスポンスメッセージの表示エリア -->
<ul id='lastUpdate'></ul><!-- 最新データのリストアップエリア -->

<?php
$this->Html->scriptStart(array('inline' => false));
?>

/* ここに javascript を記述します。
ここに書いた javascript の記述は、いったん HtmlHelper 内に退避させたあと、レイアウトファイルに吐き出されます。
次項参照 */

$(document).ready(function(){ .... });

<?php 
$this->Html->scriptEnd();

ビュー内の javascript の部分

その1: スクリプトのアウトライン

基本中の基本ですが、ドキュメントのロードが完了してからいろいろと実行させたいので、次の記述がベースになります。

$( document ).ready( function(){ /*- 無名関数内のここに色々書く -*/ } );

無名関数の中に色々書くわけですが、その概要は

  1. 送信ボタンを押した時の処理
  2. フォームの通常の送信機能を取り除く
  3. 関数定義(最新データ表示部分の更新)
  4. 関数定義(Ajaxのレスポンスメッセージを表示させる)

の4つ。

これを踏まえてスクリプトの骨格を書くとこうなります

$(document).ready(function(){

	$('#formSubmit').click(function(){ 
		/* 1:送信ボタンを押した時の処理。つまり、jQuery.ajax() の実行 */
		$.ajax( /* ここにパラメータを記述(後述) */ );
	 } );

        /* 2:ホームの通常の送信機能を取り除く */
	$('#MyRecordAddForm').submit(function(){
		return false;
	});

	/* 3:#lastUpdate 内のデータを更新する関数(後述) */
	function updateRecords() {
	}

	/* 4:#msgBox 内にメッセージを表示させて消す関数(後述) */
	function updateMessageBox( msg, status ) {
	}
});

その2: 送信ボタンを押した時の処理

jQuery.ajax( { キー:値, キー:値, キー:値, キー:値….. } ); と記述していきます。

$.ajax({
   type: 'POST',
    url: '<?php echo $this->Html->url(null, true); ?>',
   data: $( '#MyRecordAddForm' ).serializeArray(),
success: function(data, textStatus, jqXHR) {

		/* Ajax が成功したときの処理 */

		var jsonObj = $.parseJSON(data);
		updateMessageBox(jsonObj.message, jsonObj.succeed);
		if (jsonObj.succeed) {
			$('#MyRecordMyField').val(''); // 更新が成功したときのみ、インプットフィールドの値を空にする
			updateRecords();
		}

	},
  error: function(jqXHR, textStatus, errorThrown) {

		/* Ajax に失敗したときの処理 */

		updateMessageBox('失敗しました:' + errorThrown, false);
	}
});

その3: ヘルパー関数の記述

function updateRecords() {
	$.ajax( { 
		   type: 'POST',
		    url: '<?php echo $this->Html->url('/my_records/get_last_update/', true); ?>',
		   data: {"num" : 10},
		success: function(data, dataType) {
			$("#lastUpdate").empty();
			var jsonObj = $.parseJSON(data);
			var length = jsonObj.lastUpdate.length;
			for( var i = 0; i < length; i++ ) {
				var li = $('<li/>').text( jsonObj.lastUpdate[i].MyRecord.my_field ).fadeIn();
				$("#lastUpdate").append(li);
			}
		},
		error: function (HMLhttpRequest, textStatus, errorThrown) {
			alert(textStatus + " : " + errorThrown);
		}
	 } );
}

function updateMessageBox(text, succeed) {
	var color = succeed ? 'green' : 'red';
	var msg = $('<p/>').attr('id', 'resMessage').css('color', color).text(text).fadeIn();
	$('#messageArea').append(msg);
	setTimeout(function() {
		$('#resMessage').fadeOut();
	}, 2000 );
}

レイアウトファイルの記述

これは jQuery の読み込みと、add.ctp に記述したスクリプトの描画の2つを順番と場所を正しく記述すればOK。以下の2行を例えば head の中や、 body の閉じタグの直前なんかに書けば良いので、他に使用するライブラリの兼ね合いで適宜対応してください。

$this->Html->script('jquery.js'); // webroot/js ディレクトリに jquery.js がある場合を想定
echo $this->fetch('script');

まとめ

以上、いくつかの解法のうちの一つにすぎないと思いますが、もしこれから予備知識なくCakePHPでAjaxを用いたいと思われる方がいましたら参考にして下さい。

追記:このエントリを書いた後、立て続けに似たような処理を実装しました。さすがに何回もやればなれるものです。こんど機会を改めて標準のHelperを可能な限り使ってみたいと思っています。