AJAX XMLHttpRequest の基本的な使い方
jQuery は使わずに素の Javascript(vanilla JavaScript)で XMLHttpRequest を使って AJAX を実装する基本的な方法についての覚書です。
作成日:2023年6月13日
関連ページ:
AJAX
AJAX は Asynchronous JavaScript And XML の頭文字を取ったもので、Web サーバーとブラウザとの間で非同期通信を行う従来から利用されている方法です。
AJAX を利用すると、ページを再読み込みする(ページ全体を更新する)ことなくサーバーに要求を送ることができ、サーバーからデータを受け取って DOM に反映するなどの処理を行うことができます。
AJAX で JavaScript からサーバーに HTTP リクエストを送るために使用するのが XMLHttpRequest オブジェクト(API)です。XMLHttpRequest を使うとページを再読み込みすることなくデータを受け取ることができます。
また、XMLHttpRequest の名前に XML と入っていますが、XML だけでなくあらゆる種類のデータを受け取るために使用でき、現在では JSON がよく使われます。
XMLHttpRequest と同様の機能をもつ仕組みとしては、Fetch API があり、現在ではより簡潔に記述できる Fetch API がよく使われます。
参考ページ
XMLHttpRequest
XMLHttpRequest は JavaScript で HTTP リクエストを行うための組み込みのオブジェクトで、ブラウザと WEB サーバ間でデータの送受信を行う際に利用することができます。
AJAX を使った通信を行うには、まず、コンストラクタ XMLHttpRequest() を使って XMLHttpRequest オブジェクトのインスタンスを生成します。以下が構文です。
const xhr = new XMLHttpRequest();
- 引数:なし
- 戻り値:新しく生成された XMLHttpRequest オブジェクト
以前は古い IE に対応するために、以下のような機能検出を使った書き方をしましたが、現在の新しいブラウザを対象とする場合は、上記の書き方で大丈夫です。
//古い IE に対応するための機能検出を使った書き方 var xhr; if(window.ActiveXObject){ // ActiveXが存在するかをテスト xhr = new ActiveXObject("Microsoft.XMLHTTP"); } else if(window.XMLHttpRequest){ // XHRが定義されているかをテスト xhr = new XMLHttpRequest(); } else{ // Ajaxのサポートがなければエラーを送出 throw new Error("Ajax is not supported by this browser"); }
XMLHttpRequest オブジェクトのインスタンスを生成したら、インスタンスの open() メソッドを使って初期化し、send() メソッドを使ってリクエストをサーバーへ送信することができます。
AJAX でデータを取得
AJAX を使ってファイルのコンテンツ(データ)を取得する例です。
以下のようなファイル構成で、sample.html に AJAX のコードを記述して hello.txt に記述されているテキストを取得してコンソールに出力する例です。
. ├── sample.html // AJAX のコードを記述するファイル └── hello.txt // Hello AJAX! というテキストが記述されているファイル
以下が概要です。
- XMLHttpRequest オブジェクトのインスタンスを生成
- onreadystatechange イベントハンドラで取得操作が成功した場合の処理を定義
- open() メソッドで初期化(使用する HTTP メソッドとアクセス先の URL を指定)
- send() メソッドでリクエストを送信(GET の場合は引数なしまたは null を指定)
// XMLHttpRequest オブジェクトのインスタンスを生成 const xhr = new XMLHttpRequest(); // XMLHttpRequest オブジェクトの onreadystatechange イベントハンドラで処理を定義 xhr.onreadystatechange = function() { // 取得操作が完了していれば(readyState の値が 4 になると取得操作完了) if (this.readyState === 4) { // 取得操作が成功していれば(status の値が200番台なら成功) if (this.status >= 200 && this.status < 300) { // 取得したテキスト(responseText プロパティの値)をコンソールに出力 console.log(this.responseText); // Hello AJAX! と出力される }else{ // status の値が200番台でなければ失敗 console.log(`リクエスト失敗: ${this.status}`); } } } // HTTP のメソッド(GET)とアクセスするファイル(hello.txt)を指定 xhr.open('GET', 'hello.txt', true); // HTTP リクエストを送信 xhr.send(); // または xhr.send(null); でも同じ
onreadystatechange は生成した XMLHttpRequest インスタンスの状態(readyState)が変化する度に呼び出されるイベントハンドラです。
インスタンスの取得操作が成功した時点でサーバーからデータ(レスポンス)を取得することができます。
取得操作の完了は readyState の値が 4 かどうかで判定し、成功したかどうかは status の値が200番台かどうかで判定します。
また、 XMLHttpRequest インスタンスの状態が変化する度に readystatechange イベントが発生するので、以下のように addEventListener() を使って処理を定義することもできます。
// XMLHttpRequest オブジェクトの readystatechange イベントにリスナーを設定 xhr.addEventListener('readystatechange', () => { if(xhr.readyState === 4){ if(xhr.status >= 200 && xhr.status < 300){ console.log(xhr.responseText); }else{ console.log(`リクエスト失敗: ${this.status}`); } } });
上記では、アロー関数を使っているので this が使えません。そのためリスナーを登録した要素(変数 xhr)でプロパティにアクセスしています。イベントを e として引数に受け取れば、e.currentTarget でもプロパティにアクセスできます。
Fetch API の場合
以下は上記の sample.html のコードを Fetch API を使って書き換えた例です。
fetch('hello.txt') .then( (response) => { if(response.ok) { return response.text(); }else { return `リクエスト失敗: ${response.status}`; } }) .then( (data) => { console.log(data); // Hello AJAX! と出力される });
または Async Function を使って以下のように記述することもできます。
(async () => { const response = await fetch('hello.txt'); if(response.ok) { const data = await response.text(); console.log(data); // Hello AJAX! と出力される }else { console.log(`リクエスト失敗: ${response.status}`); } })();
AJAX でフォームを送信
AJAX でフォームを送信(POST)する例です。サーバーサイドは PHP を使っています。
以下がファイルの構成です。
. ├── sample.html // フォームと AJAX が記述されているファイル └── hello-post.php // 名前を加工して返す PHP ファイル
クライアント側でフォームに名前を入力してサーバーに送信したら、サーバー側(PHP ファイル)で名前を加工してクライアント側に返します。
クライアント側で名前を取得したら div#target に挿入して表示します。
<form name="fmx"> <label for="name">名前: </label> <input id="name" name="name" value="" size="20"> <button id="send">送信</button> </form> <div id="target"></div>
PHP では POST で送信された値($_POST['name'])をフィルタリングするので、 filter_input() の第1引数に INPUT_POST を、第2引数に name を指定し、第3引数にフィルタを指定してサニタイズしています。
そしてその値の前後にテキスト(Hello と !)を追加して出力しています。
<?php // 送信された値をサニタイズ $name = filter_input( INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // 名前を加工して出力 print 'Hello '.$name .'!';
JavaScript ではフォームの submit イベントを使って入力された名前を AJAX で送信します。
submit イベントのリスナーではフォーム送信時のデフォルトの動作によりページが再読み込みされないように、e.preventDefault() でフォーム送信時のデフォルトの動作をキャンセルします。
POST する場合は setRequestHeader() を使って HTTP リクエストヘッダーの値(Content-Type に MIME タイプを指定)を設定します。
また、POST する場合は send() メソッドの引数にリクエスト内容(リクエストボディ)を指定します。その際にフォームからサーバーに POST されるユーザー入力値は encodeURIComponent でエンコードします。
FormData や URLSearchParams を使えば setRequestHeader() によるリクエストヘッダーの設定や値のエンコードが不要になり、簡潔に記述することができます。
// フォーム要素 const fmx = document.fmx; // 出力先 div 要素 const target = document.getElementById('target'); // フォームの submit イベントのリスナー fmx.addEventListener('submit', (e) => { // デフォルトのフォームの送信をキャンセル(AJAX で送信) e.preventDefault(); // XMLHttpRequest オブジェクトのインスタンスを生成 const xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { // 取得操作が完了していれば if (xhr.status >= 200 && xhr.status < 300) { // 取得操作が成功していれば // 取得したテキストを出力 target.textContent = xhr.responseText; // 入力欄をクリア fmx.name.value = ''; } else { // 通信でエラーの場合はエラーメッセージを表示 console.warn('Request Failed.') } } else { // 取得操作が完了するまでは Connecting... と表示 target.textContent = 'Connecting...'; } }); // HTTP のメソッド(POST)と送信先ファイルを指定 xhr.open('POST', 'hello-post.php', true); // POST する場合はリクエストの MIME タイプを Content-Type に設定 xhr.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" ); // POST する場合は引数にリクエストボディを指定して送信 xhr.send('name=' + encodeURIComponent(fmx.name.value)); });
通常のフォームの送信ではページの再読み込みが発生しますが、AJAX の場合は発生しません。
関連ページ:フォーム(multipart/form-data)で送信/Fetch API fetch() の使い方
GET で送信
前述の例と同じことを GET で送信する例です。
. ├── sample.html // input 要素とボタン及び AJAX が記述されているファイル └── hello-get.php // 名前を加工して返す PHP ファイル
この例では form 要素は使わず、input 要素と button 要素、出力先の div 要素で構成しています。
<div> <label for="name">名前: </label> <input id="name" name="name" value="" size="20"> <button id="send" type="button">送信</button> </div> <div id="target"></div>
送信先の PHP ファイルでは、$_GET['name'] をフィルタリングするので、filter_input() の第1引数に INPUT_GET を指定しています。
<?php $name = filter_input( INPUT_GET, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); print 'Hello '.$name .'!';
JavaScript ではボタンのクリックイベントで入力された値を AJAX で送信します。
// input 要素 const inputName = document.getElementById('name'); // ボタン const send = document.getElementById('send'); // 出力先 div 要素 const target = document.getElementById('target'); // ボタンの click イベントのリスナー send.addEventListener('click', () => { const xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { target.textContent = xhr.responseText; inputName.value = ''; }else{ console.warn('Request Failed.') } }else{ target.textContent = 'Connecting...'; } }); // 送信先の URL の末尾に ?キー名=値の形式で情報を追加 xhr.open('GET', 'hello-get.php?name=' + encodeURIComponent(inputName.value), true); xhr.send(); });
データは URL の末尾に ?キー名=値 の形式で指定
GET の場合、open() の第1引数に GET を指定し、第2引数の送信先の URL の末尾に ?キー名=値
の形式で情報を追加し、複数ある場合は &
でつなぎます。
send() には引数を指定しないか、または null を指定します。
XMLHttpRequest のメソッド
XMLHttpRequest には以下のようなメソッドがあります。
メソッド | 説明 |
---|---|
abort() | リクエストがすでに送信されている場合、現在実行しているリクエストを中止します。 |
getAllResponseHeaders() | すべてのレスポンスヘッダーの名前と値を含む文字列(CRLF 区切り)を返します。レスポンスを何も受け取らなかった場合は null を返し、ネットワークエラーが発生した場合は空文字列が返されます。 |
getResponseHeader() | 引数に指定したヘッダーの値を含む文字列を返します。レスポンスを受信していない、またはレスポンス内に指定したヘッダーが存在しない場合は null を返します。 |
open() | 新しく作成されたリクエストを初期化します(使用する HTTP メソッドや送信先 URL の設定など)。 |
send() | リクエストをサーバーに送信します。 |
setRequestHeader() | HTTP リクエストヘッダーの値を設定します。POST の場合は このメソッドを使って Content-Type を指定します。 |
open()
open() メソッドは、新しく作成されたリクエスト(XMLHttpRequest オブジェクトのインスタンス)を初期化したり、既存のリクエストを再初期化したりします。
以下が構文です。第3引数以降(async, user, password)は省略可能です。
open(method, url, async, user, password)
引数 | 説明 | デフォルト |
---|---|---|
method | 使用する HTTP リクエストメソッド(GET, POST, PUT, DELETE, HEAD など)を指定します(必須) | |
url | リクエストの送信先 URL を指定します(必須) | |
async | 操作を非同期的に行うかどうか。デフォルトは非同期で行う。値が false の場合、 send() メソッドはレスポンスを受信するまで戻りません。 | true |
user | 認証プロセスで使用するユーザー名 | null |
password | 認証プロセスで使用するパスワード | null |
send()
send() メソッドは、リクエストをサーバーに送信します。
send() はリクエストの本文を表す引数を一つ受け取ることができます(省略可能)。リクエストメソッドが GET または HEAD の場合は、引数は無視されリクエストの本文は null に設定されます。
send(body)
- 引数(body)
- リクエストの中で送られる本文データ(リクエストボディ)。以下が使用できます。
- Document: 送信前に encodeURIComponent などを使ってシリアライズします
- XMLHttpRequestBodyInit:Blob、ArrayBuffer、TypedArray、DataView、FormData、URLSearchParams、文字列リテラル、オブジェクト
- null
POST と GET
GET の場合は send() の引数の body を持ちません(空または null を指定します)が、POST の場合は send()メソッドの引数 body に送信するデータを含めます。
また、POST の場合は setRequestHeader() メソッドを使って Content-Type を指定します(FormData や URLSearchParams を使用する場合は省略できます)。
setRequestHeader()
setRequestHeader() メソッドは、HTTP リクエストヘッダーの値を設定します。setRequestHeader() は、open() の呼び出しの後、send() の呼び出しの前に呼び出さなければなりません。
setRequestHeader(header, value)
以下の引数を受け取ります。
引数 | 説明 |
---|---|
header | 設定するヘッダーの名前 |
value | そのヘッダーの本体として設定する値 |
リクエストヘッダーに複数のヘッダーを設定する場合は、それぞれの項目について setRequestHeader() を呼び出します。
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xhr.setRequestHeader("Cache-Control", "no-cache");
同じヘッダーについて setRequestHeader() を複数回呼び出すと、複数の値が単一のリクエストヘッダーにマージされます。上書きされるわけではなく、呼び出す度に、指定されたテキストは既存のヘッダーの値の末尾に追加されます。
xhr.setRequestHeader('X-Test', 'one'); xhr.setRequestHeader('X-Test', 'two'); // 上記によりヘッダは X-Test: one, two
Content-Type
以下はコンテンツタイプ(Content-Type)の例です。
content-type の値 | 説明 |
---|---|
text/plain | テキスト文書 |
text/html | HTML 文書 |
application/json | JSON 文書 |
application/xml | XML 文書 |
multipart/form-data | 複数の種類のデータを一度に扱える形式(テキスト以外に、画像などのバイナリデータを含む形式:フォームなどで使用) |
application/x-www-form-urlencoded | 変数名=値&変数名=値... の形式のテキスト |
JSON データを POST で送信
JSON 形式のデータを POST で送信する場合、setRequestHeader() を使って Content-Type ヘッダーに application/json を指定して、send() メソッドの引数に JSON 形式のデータを指定します。
以下は JSON 形式のデータを POST で送信する例です。
この例ではサーバーから JSON 形式のデータが返されるので、JSON データを受信するように responseType プロパティに 'json' を設定しています(4行目)。
const xhr = new XMLHttpRequest(); // レスポンスを JSON で受け取る設定(受信用) xhr.responseType = 'json'; xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { const json = xhr.response; console.log(json); } } }); xhr.open('POST', 'json_handler.php'); // Content-Type に application/json を指定してリクエストを送信(charset=UTF-8は省略可能) xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); // データ const data = { name: 'foo', id: '001' }; // JSON 文字列に変換 const json_data = JSON.stringify(data) // JSON データを送信 xhr.send(json_data);
以下はサーバ側(PHP)の例です。
PHP で $_POST で POST されたデータを受け取れるのはコンテントタイプが application/x-www-form-urlencoded または multipart/form-data の場合のみなので、file_get_contents() に php://input を指定して、POST リクエストのデータを取得します。
PHP マニュアル:$_POST
取得した値を json_decode() で PHP の値に変換して何からの処理をすることができます。以下の場合は、json_decode() の第2引数に true を指定しているので連想配列の形式に変換されます。
<?php // file_get_contents() に php://input を指定して、POST リクエストのデータを取得 $json = file_get_contents("php://input"); //JSON エンコードされた文字列を受け取り PHP の値に変換 $data = json_decode($json, true); // filter_var() でサニタイズ(エスケープ処理) $name = filter_var($data["name"], FILTER_SANITIZE_FULL_SPECIAL_CHARS); $id= filter_var($data["id"], FILTER_SANITIZE_FULL_SPECIAL_CHARS); $data["name"] = $name.'さん!';; $data["id"] = $id.'番'; //JSON 形式にして返す echo json_encode($data); //テキストとして返す場合 //echo "{$name}さん ID は {$id} 番ですね。";
上記の場合、コンソールには以下のように出力されます。
{name: 'fooさん!', id: '001番'}
FormData を利用
POST 送信でフォームに複数の入力欄がある場合、例えば name と email の値を送信するには以下のように send() に キー名=値&キー名=値
のように &
で繋げて指定することができます。
// リクエストヘッダーの Content-Type の設定 xhr.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" ); // キー名=値&キー名=値 で指定 xhr.send('name=' + encodeURIComponent(fmx.name.value) + '&email=' + encodeURIComponent(fmx.email.value) );
但し、送信する値が増えると記述が大変です。
FormData オブジェクトを利用すると簡単にフォームのデータをまとめて送信することができます。
以下は FormData オブジェクトを利用して前述の例に email の入力欄を追加したフォームの値を送信する例です。
<form name="fmx" action="register.php"> <label for="name">名前: </label> <input id="name" name="name" value="" size="20"> <label for="email">Email: </label> <input id="email" name="email" value="" size="20"> <button id="send">送信</button> </form> <div id="target"></div>
send() メソッドでは、new FormData() にフォーム要素(2行目の fmx)を指定して FormData オブジェクトを生成して渡しています。
FormData を send() に指定することで入力フォームの内容がサーバに送信されます。
この場合、setRequestHeader() を使ったリクエストヘッダーの設定は必要ありません(POST される Content-Type は multipart/form-data になります)。
また、以下の open() の第2引数では form 要素の action 属性に指定されたフォームの送信先を参照して指定しています。
// フォーム const fmx = document.fmx; // 出力先 const target = document.getElementById('target'); fmx.addEventListener('submit', (e) => { e.preventDefault(); const xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { target.textContent = xhr.responseText; // フォームをリセット fmx.reset(); } else { console.warn('Request Failed.') } } else { target.textContent = 'Connecting...'; } }); // 送信先の URL はフォームの action 属性を参照 xhr.open('POST', fmx.action, true); // new FormData() にフォームを指定 xhr.send(new FormData(fmx)); });
<?php $name = filter_input( INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); $email = filter_input( INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); if(!$email) { $email = 'Not valid.'; } print 'Name: '.$name .' Email: ' .$email;
JSON データを FormData に含めて送信
JSON データを POST で送信の例では、Content-Type ヘッダーに application/json を指定して send() メソッドの引数(リクエストボディ)に JSON データを指定して送信しましたが、JSON データを FormData に含めて送信することもできます。
この場合は、setRequestHeader() を使ったリクエストヘッダーの設定は必要ありません。
また、POST される Content-Type は multipart/form-data になるので、サーバ側(PHP)では通常の $_POST でデータを受け取ることができます。
以下は、FormData のインスタンスを生成して、append() メソッドでキー(json)と値(json_data)のペアを追加して送信する例です。
const xhr = new XMLHttpRequest(); // レスポンスを JSON で受け取る設定(受信用) xhr.responseType = 'json'; xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { const json = xhr.response; console.log(json); } } }); xhr.open('POST', 'json_handler2.php'); // オブジェクト形式のデータ const data = { name: 'foo', id: '001' }; // JSON 文字列に変換 const json_data = JSON.stringify(data); // FormData のインスタンスを生成 const params = new FormData(); // FormData の append() で JSON データを追加(キー:json、値:json_data) params.append('json', json_data); // JSON データを FormData としてを送信 xhr.send(params);
URLSearchParams に含める
FormData の代わりに、URLSearchParams を使うこともできます(24〜27行目を以下に書き変)。
// URLSearchParams のインスタンスを生成 const params = new URLSearchParams(); // URLSearchParams の append() で JSON データをキーと値のペアとして追加 params.append('json', json_data);
サーバ側(PHP)
サーバ側(PHP)では $_POST['json'] を json_decode() で JSON 文字列をデコードして PHP の値に変換します。第2引数を省略するとオブジェクトが返されます(true にすると連想配列形式のオブジェクト)。
各プロパティを filter_var() でサニタイズして加工して、json_encode() で JSON 形式に変換して出力しています。
<?php // PHP の値(この場合はオブジェクト)に変換 $json = json_decode($_POST['json']); // 各プロパティの値を filter_var でサニタイズ $name = filter_var($json->name, FILTER_SANITIZE_FULL_SPECIAL_CHARS); $id = filter_var($json->id, FILTER_SANITIZE_FULL_SPECIAL_CHARS); // 値を加工してプロパティに設定 $json->name = $name . 'さん!';; $json->id = $id . '番'; // JSON 形式に変換して出力(レスポンスとする) echo json_encode($json);
URLSearchParams を利用
URLSearchParams は URL のクエリ文字列を生成するための JavaScript オブジェクトで URL のクエリ文字列の操作に利用できます。
以下は URLSearchParams オブジェクトのインスタンスを生成して、append() メソッドを使ってキーと値のペアでデータ(パラメータ)を追加する例です。
// URLSearchParams オブジェクトのインスタンスを生成 const params = new URLSearchParams(); // キー/値のペアでデータを追加 params.append('name', 'Foo'); params.append('email', 'foo@example.com'); // URLSearchParams オブジェクトをコンソールに出力 console.log(params); // 出力: URLSearchParams {size: 2} // URLSearchParams オブジェクトを文字列に変換してコンソールに出力 console.log(params.toString()); // 出力: name=Foo&email=foo%40example.com
コンストラクタ URLSearchParams() に以下のように [ ['キー1', '値1'], ['キー2', '値2']… ]
のような、キーと値のペアの配列(の配列)を渡すこともできます。
// コンストラクタに値を渡す const params = new URLSearchParams([ ['name', 'Foo'], ['email', 'foo@example.com'] ]); // URLSearchParams オブジェクト console.log(params); // 出力 URLSearchParams {size: 2} // URLSearchParams オブジェクトを文字列に変換 console.log(params.toString()); // 出力 name=Foo&email=foo%40example.com
または、{'キー': '値'}
形式のオブジェクトを渡すこともできます。
//オブジェクト(パラメータとするデータ) const data = { name: 'Foo', email: 'foo@example.com', } // オブジェクトをコンストラクタに渡す const params = new URLSearchParams(data); // URLSearchParams オブジェクト console.log(params); // 出力 URLSearchParams {size: 2} // URLSearchParams オブジェクトを文字列に変換 console.log(params.toString()); // 出力 name=Foo&email=foo%40example.com
URLSearchParams オブジェクトはそのまま XMLHTTPRequest の send() や fetch に渡して、POST などのリクエストボディに組み込むことができます。
また、URLSearchParams を使う場合、setRequestHeader() を使ったリクエストヘッダーの設定は必要ありません(Content-Type は application/x-www-form-urlencoded になります)。
以下は前述の「FormData を利用」とほぼ同じ内容ですが、フォームを使わずにボタンをクリックすると input 要素に入力された値を AJAX で送信する例です。
<div> Name: <input id="name" value="" size="20"> Email: <input id="email" value="" size="20"> <button id="send">送信</button> </div> <div id="target"></div>
<?php $name = filter_input( INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); $email = filter_input( INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); if(!$email) { $email = 'Not valid.'; } print 'Name: '.$name .' Email: ' .$email;
const btn = document.getElementById('send'); const name = document.getElementById('name'); const email = document.getElementById('email'); btn.addEventListener('click', (e) => { // URLSearchParams オブジェクトを生成(オブジェクト形式のパラメータを渡す) const params = new URLSearchParams({ name: name.value, email: email.value }); const xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { target.textContent = xhr.responseText; name.value = ''; email.value = ''; } else { console.warn('Request Failed.') } } }); xhr.open('POST', 'register.php', true); // リクエストボディに URLSearchParams オブジェクトを指定 xhr.send(params); });
以下は上記を、URLSearchParams を使わずに書き換えた例です。
パラメータを「キー名=値&キー名=値...」形式で指定し、setRequestHeader() で Content-Type を application/x-www-form-urlencoded に設定しています。
const btn = document.getElementById('send'); const name = document.getElementById('name'); const email = document.getElementById('email'); btn.addEventListener('click', (e) => { // パラメータを「キー名=値&キー名=値...」形式で指定 const params = 'name=' + encodeURIComponent(name.value) + '&email=' + encodeURIComponent(email.value); const xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { target.textContent = xhr.responseText; name.value = ''; email.value = ''; } else { console.warn('Request Failed.') } } }); xhr.open('POST', 'register.php', true); // Content-Type に application/x-www-form-urlencoded を設定 xhr.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" ); // パラメータをリクエストボディに指定 xhr.send(params); });
FormData から URLSearchParams を作成
FormData を引数に指定して URLSearchParams のインスタンスを生成することもできます。
以下はフォームの値から FormData オブジェクトを生成し、生成した FormData オブジェクトを URLSearchParams() の引数に指定して URLSearchParams オブジェクトを生成する例です。
そして URLSearchParams オブジェクトにパラメータを追加して send() に指定してリクエストボディに組み込んで送信しています。
// フォーム const fmx = document.fmx; // 出力先 const target = document.getElementById('target'); fmx.addEventListener('submit', (e) => { e.preventDefault(); const xhr = new XMLHttpRequest(); // フォームの値から FormData オブジェクトを生成 const formData = new FormData(fmx); // FormData オブジェクトから URLSearchParams オブジェクトを生成 const params = new URLSearchParams(formData); // パラメータを追加 params.append('secret', 123345); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { target.textContent = xhr.responseText; // フォームをリセット fmx.reset(); } else { console.warn('Request Failed.') } } else { target.textContent = 'Connecting...'; } }); // 送信先の URL はフォームの action 属性を参照 xhr.open('POST', fmx.action, true); // リクエストボディに URLSearchParams オブジェクトを指定 xhr.send(params); });
<form name="fmx" action="register.php"> <label for="name">名前: </label> <input id="name" name="name" value="" size="20"> <label for="email">Email: </label> <input id="email" name="email" value="" size="20"> <button id="send">送信</button> </form> <div id="target"></div>
<?php $name = filter_input( INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); $email = filter_input( INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); $secret = filter_input( INPUT_POST, 'secret', FILTER_VALIDATE_INT); if(!$email) { $email = 'Not valid.'; } print 'Name: '.$name .' Email: ' .$email.' Secret: ' .$secret;
XMLHttpRequest のプロパティ
XMLHttpRequest には以下のようなプロパティがあります。
プロパティ | 説明 |
---|---|
onreadystatechange | readyState プロパティ(インスタンスの状態)が変化したときに呼び出されるイベントハンドラ。詳細 |
readyState | 生成された XMLHttpRequest インスタンスの現在の状態を示す整数値。読取専用。詳細 |
response | サーバから受信したデータ(レスポンス)。responseType プロパティの値に応じた形式で返されます。読取専用 |
responseText | サーバから受信したテキスト形式のデータ。読取専用 |
responseType | サーバから受信するデータの形式。open() で初期化した後や、send() でリクエストをサーバーに送信する前に responseType の値を設定することで、特定の形式でデータを提供するように要求できます。詳細 |
responseURL | レスポンスのシリアライズされた URL(URL フラグメントはすべて取り除かれます)。読取専用 |
responseXML | XML または HTML 形式でサーバから受信したデータ。読取専用 |
status | XMLHttpRequest のレスポンスにおける数値の HTTP ステータスコード。200 は 成功(OK)を表します。読取専用。詳細 |
statusText | HTTP サーバーから返されるステータスメッセージ。読取専用 |
timeout | リクエストが自動的に終了するまでの時間をミリ秒で示します。既定値は 0(タイムアウトしない)。 |
readyState
readyState は生成した XMLHttpRequest オブジェクトのインスタンスの状態を返すプロパティです。その時点の状態により以下のいずれかの値を返します。
また、状態を表すキーワードは定数として利用できます(例 XMLHttpRequest.DONE は 4)。
値 | 状態 | 説明 |
---|---|---|
0 | UNSENT | インスタンスは生成済みだが、まだ open() で初期化されていない |
1 | OPENED | open() で初期化済みだが、まだ send() は呼び出されていない |
2 | HEADERS_RECEIVED | send() が呼び出し済みで、レスポンスヘッダーを受け取り済み |
3 | LOADING | サーバからデータ(レスポンス本文)を受信中 |
4 | DONE | 取得操作が完了。データ転送が成功または失敗で完了したことを意味します。この時点で status プロパティを調べることで成功か失敗かを判定できます。 |
readyState の値は、生成したインスタンスの open() や send() の呼び出しやサーバーからの受信状態により変化します。
※ readyState の取得操作の完了(DONE)の確認だけでは、サーバから指定したデータを受信したのか、何らかの理由でエラーを受信したのかは分かりません。その為、サーバとの通信が正常に行われたのかどうかを次項の status を使って確認します。
status
readyState の値が 4(DONE)であればデータの取得操作が完了していますが、操作が成功したかどうかは status プロパティが返す HTTP ステータスコードを調べる必要があります。
以下は HTTP ステータスコードの代表的な値です。
値 | 状態 | 説明 |
---|---|---|
200 | OK(成功) | 通信が成功したことを意味します |
400 | Bad Request(失敗) | エラーにより処理できないことを意味します |
403 | Forbidden(失敗) | アクセスが禁止されていることを意味します |
404 | Not Found(失敗) | 指定したリソースが存在しないことを意味します |
500 | Internal Server Error(失敗) | サーバーエラー(リクエストを処理できない)を意味します |
200から299までのステータスコードは成功で、300から399までのステータスコードはリダイレクト、400 以上はエラーを表します。
RFC 9110 | HTTP Semantics | Status Codes
以下は取得操作の完了を readyState の値(XMLHttpRequest.DONE は 4)で判定し、status が200番台なら成功と判定して処理する例です。
xhr.onreadystatechange = function() { // 取得操作が完了していれば(readyState の値が 4 であれば) if (this.readyState === XMLHttpRequest.DONE) { // status の値が 200 番台なら if (this.status >= 200 && this.status < 300) { console.log(xhr.responseText); } } }
このページの例では全て上記のように status の値が200番台であれば成功と判定していますが、単に 200 のみで判定してもほとんどの場合は問題ありません。
onreadystatechange
onreadystatechange は XMLHttpRequest インスタンスの状態(readyState プロパティ)が変化したときに呼び出されるイベントハンドラです。
また、インスタンスの状態が変化する際には、readystatechange イベントが発生するので、通常のイベント同様、 addEventListener() を使って readystatechange イベントにリスナーを登録すれば同じことができます。
以下は XMLHttpRequest インスタンスを生成し、onreadystatechange イベントハンドラを使ってインスタンスの状態と status の変化を確認する例です。
const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (this.readyState == 0) { console.log('UNSENT status: ' + xhr.status); } if (this.readyState == 1) { console.log('OPENED status: ' + xhr.status); } if (this.readyState == 2) { console.log('HEADERS_RECEIVED status: ' + xhr.status); } if (this.readyState == 3) { console.log('LOADING status: ' + xhr.status); } if (this.readyState == 4) { console.log('DONE status: ' + xhr.status); } } xhr.open('GET', 'hello.txt', true); xhr.send(); /* 以下が出力例です OPENED status: 0 HEADERS_RECEIVED status: 200 LOADING status: 200 DONE status: 200 */
上記の出力に UNSENT がないのは、readyState が 0 から 1 に変化する際に初めてイベントハンドラが呼び出されるためです。また、リクエストが完了する前は、status の値は 0 になります。
また、onreadystatechange は addEventListener() を使って以下のように書き換えることができます。
xhr.addEventListener('readystatechange', function() { if (this.readyState == 0) { console.log('UNSENT status: ' + xhr.status); } if (this.readyState == 1) { console.log('OPENED status: ' + xhr.status); } if (this.readyState == 2) { console.log('HEADERS_RECEIVED status: ' + xhr.status); } if (this.readyState == 3) { console.log('LOADING status: ' + xhr.status); } if (this.readyState == 4) { console.log('DONE status: ' + xhr.status); } })
responseType
responseType プロパティはレスポンスに含まれているデータの型を文字列で示します。
また、open() で初期化した後や、send() でリクエストをサーバーに送信する前にインスタンスの responseType プロパティに値を設定することで、特定の形式でデータを提供するように指定することができます。
※ レスポンスを responseType プロパティに設定した値に応じた形式で取得するには response プロパティを使用します。
responseType の値として以下を文字列で指定することができます。
値 | 説明 |
---|---|
"" | 空の文字列の場合は "text" と同じ(既定の型) |
"arraybuffer" | バイナリーデータを含む JavaScript の ArrayBuffer |
"blob" | バイナリーデータを含む Blob オブジェクト |
"document" | HTML の Document または XML の XMLDocument(データの MIME タイプによる) |
"json" | 受信したデータの内容を JSON として解釈して生成された JavaScript オブジェクト |
"text" | 文字列に入ったテキスト |
HTML データの受信
XMLHttpRequest を使って HTML を解析済の DOM として取得することができます。
HTML リソースを DOM として取得するには responseType プロパティに "document" を設定し、response プロパティを使ってレスポンスを DOM(ドキュメント)として取得します。
MDN の XMLHttpRequest における HTML の扱い には「open() を呼び出した後、 send() を呼び出す前に、 responseType プロパティに文字列 "document" 代入して」とありますが、send() を呼び出す前であれば大丈夫なようです。
以下は次のような構成で、hello.html を HTML ドキュメントとして取得して特定の要素を抽出して sample.html に出力する例です。
. ├── sample.html // AJAX のコードを記述して取得した要素を表示するファイル └── hello.html // 取得する HTML ファイル
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hello</title> </head> <body> <div id="hello"> <h3>Hello</h3> <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit.</p> </div> </body> </html>
<div id="target"></div>
生成したインスタンスの responseType プロパティに document を設定し(27行目)、response プロパティを使って Document(オブジェクト)を取得します(11行目)。
この例では、取得したレスポンスのドキュメントで querySelector() を使って div#hello を抽出して、その要素を appendChild() で HTML に挿入しています。
// 取得した HTML を挿入する要素 const target = document.getElementById('target'); // XMLHttpRequest インスタンスを生成 const xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { // レスポンス(HTML Document)を取得 const dom = xhr.response; // または const dom = xhr.responseXML console.log(dom); // 確認用 // レスポンスの Document オブジェクトから id が hello の要素を取得 const helloDiv = dom.querySelector('#hello'); // id が hello の要素を target に追加 target.appendChild(helloDiv); } else { console.warn('Error'); } } }); // 初期化 xhr.open('GET', 'hello.html', true); // responseType プロパティに文字列 document を設定 xhr.responseType = 'document'; // リクエストを送信 xhr.send();
上記を実行すると、sample.html は以下のように取得した要素が表示されます。
また、コンソールタブを見ると、確認用に記述した12行目の console.log(dom) により取得した Document が確認できます(body 部分はコンソールでは展開できないようです)。
DOM 操作の例
以下は取得したレスポンスのドキュメントからタイトルと複数の要素を抽出する例です。
取得したレスポンスは Document オブジェクトなので、Document のメソッドやプロパティが使えます。
. ├── sample.html // AJAX のコードを記述して取得した要素を表示するファイル └── lorem.html // 取得する HTML ファイル
sample.html では以下の lorem.html から <title>Lorem ipsum</title> と 3つの <div class="lorem">〜</div> を取得して表示します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Lorem ipsum</title> </head> <body> <div class="lorem"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p> </div> <div class="lorem"> <p>Et illum ipsum eius est hic suscipit.</p> </div> <div class="lorem"> <p>Reprehenderit tempore laborum quasi nisi tempora.</p> </div> </body> </html>
<div id="target"></div>
title 要素はドキュメント(Document)の title プロパティで取得できます。
querySelectorAll() の戻り値は NodeList になります。以下では取得した NodeList から HTML テキスト文字列を取得して innerHTML で表示しています。
const target = document.getElementById('target'); const xhr = new XMLHttpRequest(); // responseType プロパティに文字列 document を設定 xhr.responseType = 'document'; xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { // レスポンス(HTML Document)を response プロパティで取得 const dom = xhr.response; // .lorem の要素を全て取得(lorems は NodeList) const lorems = dom.querySelectorAll('.lorem'); // タイトルを取得 const titleText = dom.title; // h3 要素を生成して上記で取得したタイトルをテキストに設定 const title = document.createElement('h3'); title.textContent = titleText; // 表示する HTML を初期化 let loremsHtml = ''; // 全ての .lorem の要素が入った NodeList の各要素から HTML を取得 lorems.forEach(elem => { loremsHtml += elem.innerHTML; }); // HTML を表示 target.innerHTML = loremsHtml; // タイトルの要素を追加 target.prepend(title); } else { console.warn('Error'); } } }); xhr.open('GET', 'lorem.html', true); xhr.send();
HTML の断片
以下は、HTML の断片を返す PHP ファイルを取得して、レスポンスの HTML を表示する例です。
. ├── sample.html // AJAX のコードを記述して取得した要素を表示するファイル └── register.php // 名前とメールアドレスを加工して HTML で返す PHP ファイル
<?php $name = filter_input( INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); $email = filter_input( INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); if(!$email) { $email = 'Not valid email address.'; } // HTML を出力 print '<p class="response">Hello '.$name .'!<br>Email: '.$email .'</p>';
<form name="fmx" action="register.php"> <div> <label for="name">名前: </label> <input id="name" name="name" value="" size="20"> </div> <div> <label for="email">Email: </label> <input id="email" name="email" value="" size="30"> </div> <button id="send">送信</button> </form> <div id="target"></div>
この例の場合、register.php には p 要素しか記述されていませんが、HTML ドキュメントとして取得すると、レスポンスは p 要素を含むドキュメントオブジェクト(<html>〜</html>)になります。
そのため、この例では p 要素を取得するために、querySelector() に p 要素のクラス名を指定して取得しています(この場合は単に p 要素を指定しても同じです)。
また、この例では取得した要素を HTML に挿入するのに insertAdjacentElement() を使っていますが、appendChild() などでも同様のことが可能です。
// フォーム要素 const fmx = document.fmx; // 取得した HTML を表示する要素 const target = document.getElementById('target'); fmx.addEventListener('submit', (e) => { e.preventDefault(); // 表示する領域をクリア target.innerHTML = ''; const xhr = new XMLHttpRequest(); // responseType プロパティに "document" を設定 xhr.responseType = 'document'; xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { // レスポンスの Document オブジェクトから .response の要素を抽出 const responseHTML = xhr.response.querySelector('.response'); // 抽出した要素を挿入 target.insertAdjacentElement('afterbegin', responseHTML); // フォームをリセット fmx.reset(); } else { console.warn('Request Failed.') } } }); xhr.open('POST', fmx.action, true); // FormData を利用 xhr.send(new FormData(fmx)); });
関連項目:FormData を利用
参考
JSON データの受信
以下は受信するデータを JSON として扱う例です。
JSON としてレスポンスを取得するには responseType プロパティに "json" を設定し、response プロパティを使ってレスポンスを JSON オブジェクトとして取得します。
この例では {JSON} Placeholder というテスト用の JSON データを返してくれる API から JSON データを取得します。 例えば、https://jsonplaceholder.typicode.com/users/1 にアクセスすると以下のようなダミーのユーザーの JSON データを返してくれます。
{ "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "Sincere@april.biz", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" } }
responseType プロパティに 'json' を設定して、受信データは response プロパティを使って取得します。
const target = document.getElementById('target'); const xhr = new XMLHttpRequest(); // responseType プロパティに 'json' を設定 xhr.responseType = 'json'; xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { // JSON として解析された受信データ(JSON オブジェクト) const json = xhr.response; // JSON オブジェクトを使って HTML に出力 target.innerHTML = `<p>name: ${json.name}</p> <p>email: ${json.email}</p>`; } else { console.warn('Error'); } } }); xhr.open('GET', 'https://jsonplaceholder.typicode.com/users/1', true); xhr.send();
XMLHttpRequest のイベント
XMLHttpRequest のイベントには以下のようなものがあります。
イベント | 発生するタイミング |
---|---|
abort | リクエストが中断されたとき |
error | リクエストでエラーが発生したとき |
load | XMLHttpRequest のトランザクションが成功裏に完了したとき |
loadend | リクエストが完了したときに、成功した場合は load の後、成功しなかった場合は abort または error の後 |
loadstart | リクエストがデータを読み込み始めたとき |
progress | リクエストがデータを受信した際に定期的に発行 |
readystatechange | readyState プロパティが変化するたびに発生 |
timeout | timeout で設定された時間が経過したことで進行が終了すると発行 |
イベントプロパティ
readystatechange 以外のイベントは、通常の Event のプロパティに加えて以下のプロパティ(全て読み取り専用)を持っています。
プロパティ | 説明 |
---|---|
lengthComputable | リソースのダウンロード(取得)の進捗が計測可能かどうかを示す論理値。 |
loaded | このプロセスで既にダウンロードした量(バイト単位)。 |
total | プロセスが実行中のリソースの総量(バイト単位) |
イベントハンドラ
通常のイベント同様、イベント名の前に on を付けるとイベントハンドラになります。
例: onload や onerror、 onreadystatechange など。
const xhr = new XMLHttpRequest(); // onload イベントハンドラ xhr.onload = function(e) { if (this.status === 200) { console.log(`${this.responseText} :${e.loaded} bytes`); } }
上記の onload イベントハンドラは以下のように addEventListener() を使って記述することもできます。
xhr.addEventListener('load', (e) => { if (xhr.status === 200) { console.log(`${xhr.responseText} :${e.loaded} bytes`); } });
以下はファイルを取得する際に発生するイベントとその際の readyState 及び status プロパティの値を出力する例です。
load イベントが発生する際には、readyState の値は4になっているようです。
但し、存在しない URL や自身以外のドメインを open() に指定した場合などでは、エラーが発生し、loadend イベントは発生しますが、load イベントは発生していません。
const xhr = new XMLHttpRequest(); function handleEvent(e) { console.log(`${e.type}: readyState= ${xhr.readyState} status= ${xhr.status} `); } function addListeners(xhr) { xhr.addEventListener('loadstart', handleEvent); xhr.addEventListener('load', handleEvent); xhr.addEventListener('loadend', handleEvent); xhr.addEventListener('progress', handleEvent); xhr.addEventListener('error', handleEvent); xhr.addEventListener('abort', handleEvent); } addListeners(xhr); xhr.open('GET', 'hello.txt', true); xhr.send(); /* 指定したファイルが存在した場合の出力例(以下の順でイベントが発生) loadstart: readyState= 1 status= 0 progress: readyState= 3 status= 200 load: readyState= 4 status= 200 loadend: readyState= 4 status= 200 */ /* 指定したファイルが存在しなかった場合の出力例 loadstart: readyState= 1 status= 0 progress: readyState= 3 status= 404 progress: readyState= 4 status= 404 load: readyState= 4 status= 404 loadend: readyState= 4 status= 404 */ /* 存在しない URL(https://somewhere.org/i-dont-exist)を指定した場合の出力例 loadstart: readyState= 1 status= 0 error: readyState= 4 status= 0 loadend: readyState= 4 status= 0 GET https://somewhere.org/i-dont-exist net::ERR_CONNECTION_CLOSED */
onload でデータを取得
以下は onreadystatechange ハンドラの代わりに onload ハンドラで取得したファイルのテキストをコンソールに出力する例です。
「AJAX でデータを取得」の例とほぼ同じです。load イベントが発生したら呼び出される onload ハンドラで実行する処理を定義しています。
onload ハンドラ(load イベント)を使用した場合は、loaded などのイベントプロパティを使ってダウンロードしたデータの量も取得できます。
// XMLHttpRequest のインスタンスを生成 const xhr = new XMLHttpRequest(); // onload イベントハンドラ(load イベントが発生すると呼び出されるハンドラ) xhr.onload = function(e) { // 処理が完了(readyState が 4)して成功(status が 200)であれば if (this.readyState === 4 && this.status === 200) { // 受信した内容のテキストとデータ量をコンソールに出力 console.log(`${this.responseText} :${e.loaded} bytes`); } } // onerror イベントハンドラ xhr.onerror = function() { // エラー発生時にはエラーメッセージを表示 console.error(this.statusText); } // 初期化 xhr.open('GET', 'hello.txt', true); // リクエストを送信 xhr.send(null);
以下は対象のファイルの URL と表示先の要素を指定するとそのファイルのテキストを指定先に表示する関数の例です。
//対象のファイルと表示先の要素を指定するとそのファイルのテキストを指定先に表示する関数 function showLoadedText(url, targetElem) { const xhr = new XMLHttpRequest(); xhr.onload = function() { if (this.readyState === 4 && this.status === 200) { if (targetElem) { targetElem.textContent = this.responseText; } } } xhr.onerror = function () { console.error(this.statusText); } xhr.open('GET', url, true); xhr.send(); } // 表示先の要素 const target = document.getElementById('target'); // 対象のファイルと表示先の要素を指定して上記関数を実行 showLoadedText('hello.txt', target);