はじめに
類似の記事はありますが、個人的に納得のいくものが無かったので書いてみます。
基本形
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>サンプル</title>
</head>
<body>
<h1>設定</h1>
<p>
年齢: <input type="text" id="age" value="24"><br>
職業: <input type="text" id="job" value="学生"><br>
<input type="button" id="execute" value="送信"><br>
</p>
<h1>結果</h1>
<p>
可否: <input type="text" id="result" value=""><br>
内容: <input type="text" id="detail" value=""><br>
</p>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="default.js"></script>
</body>
</html>
JavaScript
手抜き
// DOMを全て読み込んだあとに実行される
$(function () {
// 「#execute」をクリックしたとき
$('#execute').click(function () {
// Ajax通信を開始する
$.ajax({
url: 'api.php',
type: 'post', // getかpostを指定(デフォルトは前者)
dataType: 'json', // 「json」を指定するとresponseがJSONとしてパースされたオブジェクトになる
data: { // 送信データを指定(getの場合は自動的にurlの後ろにクエリとして付加される)
age: $('#age').val(),
job: $('#job').val(),
},
})
// ・ステータスコードは正常で、dataTypeで定義したようにパース出来たとき
.done(function (response) {
$('#result').val('成功');
$('#detail').val(response.data);
})
// ・サーバからステータスコード400以上が返ってきたとき
// ・ステータスコードは正常だが、dataTypeで定義したようにパース出来なかったとき
// ・通信に失敗したとき
.fail(function () {
$('#result').val('失敗');
$('#detail').val('');
});
});
});
失敗時の情報も丁寧に表示する
/* エラー文字列の生成 */
function errorHandler(args) {
var error;
// errorThrownはHTTP通信に成功したときだけ空文字列以外の値が定義される
if (args[2]) {
try {
// JSONとしてパースが成功し、且つ {"error":"..."} という構造であったとき
// (undefinedが代入されるのを防ぐためにtoStringメソッドを使用)
error = JSON.parse(args[0].responseText).error.toString();
} catch (e) {
// パースに失敗した、もしくは期待する構造でなかったとき
// (PHP側にエラーがあったときにもデバッグしやすいようにレスポンスをテキストとして返す)
error = 'parsererror(' + args[2] + '): ' + args[0].responseText;
}
} else {
// 通信に失敗したとき
error = args[1] + '(HTTP request failed)';
}
return error;
}
// DOMを全て読み込んだあとに実行される
$(function () {
// 「#execute」をクリックしたとき
$('#execute').click(function () {
// Ajax通信を開始する
$.ajax({
url: 'api.php',
type: 'post', // getかpostを指定(デフォルトは前者)
dataType: 'json', // 「json」を指定するとresponseがJSONとしてパースされたオブジェクトになる
data: { // 送信データを指定(getの場合は自動的にurlの後ろにクエリとして付加される)
age: $('#age').val(),
job: $('#job').val(),
},
})
// ・ステータスコードは正常で、dataTypeで定義したようにパース出来たとき
.done(function (response) {
$('#result').val('成功');
$('#detail').val(response.data);
})
// ・サーバからステータスコード400以上が返ってきたとき
// ・ステータスコードは正常だが、dataTypeで定義したようにパース出来なかったとき
// ・通信に失敗したとき
.fail(function () {
// jqXHR, textStatus, errorThrown と書くのは長いので、argumentsでまとめて渡す
// (PHPのfunc_get_args関数の返り値のようなもの)
$('#result').val('失敗');
$('#detail').val(errorHandler(arguments));
});
});
});
PHP
手抜き
<?php
// Content-TypeをJSONに指定する
header('Content-Type: application/json');
// 「200 OK」 で {"data":"24歳、学生です"} のように返す
$data = "{$_POST['age']}歳、{$_POST['job']}です";
echo json_encode(compact('data'));
エラーの発生を防ぎ、値の整合性チェックも行う
<?php
// Content-TypeをJSONに指定する
header('Content-Type: application/json');
// $_POST['age']、$_POST['job']をエラーを出さないように文字列として安全に展開する
$age = (string)filter_input(INPUT_POST, 'age');
$job = (string)filter_input(INPUT_POST, 'job');
// 整合性チェック
if ($age === '' || $job === '') {
$error = '年齢と職業を両方入力してください';
} else if (!ctype_digit($age)) {
$error = '年齢は正の整数で入力してください';
} else if (($age = (int)$age) > 100) {
$error = '生きすぎィ!';
}
if (!isset($error)) {
// 正常時は 「200 OK」 で {"data":"24歳、学生です"} のように返す
$data = "{$age}歳、{$job}です";
echo json_encode(compact('data'));
} else {
// 失敗時は 「400 Bad Request」 で {"error":"..."} のように返す
http_response_code(400);
echo json_encode(compact('error'));
}
FAQ
Q. done
とか fail
みたいに繋ぐ書き方なんなの?
私が知ってるのはこういう書き方だったはずなんだけど…
$.ajax({
url: 'api.php',
type: 'post',
dataType: 'json',
success: function (data) {
$('#result').val('成功');
$('#detail').val(response.data);
},
error: function () {
$('#result').val('失敗');
$('#detail').val(errorTextFromFailArguments(arguments));
},
});
A. Promise インタフェースを利用したものです
そのような書き方をすると、レスポンスの結果をもとに新たにAjaxリクエストを実行したいときなどに、いわゆる**「コールバックネスト地獄」に陥ります。そういったシーンのための解決策として、この書き方がjQuery1.5**以降から推奨されているようです。詳しくは以下を参照してください。
- jQuery モダンAjaxな書き方を目指して 〜Deferredを使ったAJAX〜
- jQuery.Deferredを使って楽しい非同期生活を送る方法
- 結局jQuery.Deferredの何が嬉しいのか分からない、という人向けの小話
- jQuery.ajax()
jQuery日本語リファレンス はjQuery1.4で更新が止まっているのであまり参考にしない方がいいでしょう。
なお,より標準のPromiseに近い形で利用する場合,done
fail
でバラバラに記述しているものを then
でまとめたほうがよいでしょう.
.then(
function (response) {
$('#result').val('成功');
$('#detail').val(response.data);
},
function () {
$('#result').val('失敗');
$('#detail').val('');
}
);
Q. data
のセットが面倒くさい!
数が増えてた時にどうにかならんの?
var data = {
age: $('#age').val(),
job: $('#job').val(),
};
A. FormData
オブジェクトを利用しましょう
以下のように name
属性を使って送信項目を定義しておけば、FormData
オブジェクトを渡すだけで済みます。
<form id="settings">
年齢: <input type="text" name="age" value="24"><br>
職業: <input type="text" name="job" value="学生"><br>
<input type="button" id="execute" value="送信"><br>
</p>
var data = new FormData($('#settings')[0]);
後から項目を追加したい場合は append
メソッドが使えます。
var data = new FormData($('#settings')[0]);
data.append('drink', 'アイスティー');
Q. クロスオリジンでやりたい!
このままだと
XMLHttpRequest cannot load .... No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '...' is therefore not allowed access.
みたいなエラーが出る…
A. Access-Control-Allow-Origin
か JSONP
を利用しましょう
Access-Control-Allow-Origin
を利用する場合
PHP側で以下のようにレスポンスヘッダを返します。
header('Access-Control-Allow-Origin: http://example.com');
header('Access-Control-Allow-Origin: *');
JSONP
を利用する場合
PHP側で以下のようにレスポンスボディを返します。
$data = "{$age}歳、{$job}です";
echo 'callback(' . json_encode(compact('data')) . ')';
JavaScript側では以下のように書けば、JSONのときと全く同じように処理することが可能です。内部で何やってるか分からなくて気持ち悪いという人は「jQueryはJSONPの理解の妨げになるか?」を参照してください。
$.ajax({
url: 'api.php',
type: 'post',
dataType: 'jsonp',
jsonpCallback: 'callback',
data: {
age: $('#age').val(),
job: $('#job').val(),
},
})
#セキュリティに関する注意点
XSS対策
ユーザ入力をエレメントに適用する際にはjQueryの text
メソッドや val
メソッドを通すことを徹底しましょう。html
メソッドは危険です。
CSRF対策
通常のフォーム送信と同じように実装しましょう。同一オリジンでは何もしなくてもセッションIDはCookieとして送信されるので、単にPHPから返されてきたトークンを data
の中に含めて送信するだけで十分です。