Ajax通信

Ajax通信とは

Ajax(エージャックス、Asynchronous JavaScript and XML)とは、Webページ内から、外部API(サーバサイド)へ非同期通信(Asynchronous)するための実装形態のことです。
非同期通信 = ページをリロードせずにクライアントとサーバーサイドが通信を行うこと

Webページ全体を再読み込みせずに必要な部分だけを素早く更新できるので、ユーザー体験の向上に寄与できます(UX (User Experience)))。

Ajax通信の基本の書き方

XMLHttpRequest オブジェクトを用いた方法とFetchAPIを用いた方法の2パターンがあります。

XMLHttpRequest オブジェクトを用いた方法

XMLHttpRequest オブジェクトは、Ajax通信を可能にするために、ブラウザ上でサーバーとHTTP通信を行うオブジェクトです。

let xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {

	if (xhr.readyState == 4) {
		if (xhr.status == 200) {
			// 処理
		}
	}
}

xhr.open("メソッド", "URL");
xhr.responseType = "json";
xhr.send();
let 変数名 =  XMLHttpRequest();

最初に、XMLHttpRequest オブジェクトを作成し実装します。

xhr.onreadystatechange = function () {}

onreadystatechangeプロパティはイベントハンドラ(イベント発生時に実行する処理)の一つで、readyStateの値が変わると呼び出されます。

if (xhr.readyState == 4) {
	if (xhr.status == 200) {

	}
}

readyStateプロパティは XMLHttpRequest オブジェクトの状態を表し、以下の値をとります。
readyStateが4であれば、データを取得し通信が終了している状態ですので、この数値を使用します。

0:UNSET(準備段階)
1:OPENED(準備完了)
2:HEADERS_RECEIVED(通信開始)
3:LOADING(受信中)
4:DONE(通信完了)

Statusプロパティはステータス番号を返します。200は成功を表しますので、この数値を使用します。

つまり「readyStateが4で、statusが200のとき(問題なく通信が成功)、処理を実行」という意味になります。なお、「readyState」と「status」をネストではなく論理積で条件を設定しても問題ありません。

変数名.open(メソッド,URL,非同期[, ユーザー名[, パスワード]]])

XMLHttpRequest オブジェクトの open メソッドを使って HTTP リクエストの初期化を行います。

第1引数にHTTPリクエストメソッドを指定します。
サーバから指定した URL でデータを取得する場合は 「GET」 、サーバの指定した URL に対して何らかの処理を行う場合は 「POST」 を使います。

第3引数には任意で論理値を指定します。既定値はtrue(非同期通信)ですので、省略した場合は非同期通信となります。
第4・5引数は任意で認証プロセスで使用するユーザー名とパスワードを指定します。既定値はnullです。

xhr.responseType = "json";

responseTypeプロパティではレスポンスに含まれているデータの型を指定できます。
ここではjsonを指定しています。

JSON(JavaScript Object Notation)とは、JavaScriptのオブジェクトの書き方を元に作られた、軽量で汎用性の高いテキストベースのフォーマットです。
Ajax通信のようなサーバーとクライアント間のデータ連携など、Webアプリケーションでデータを転送する場合や、異なるプログラミング言語間でデータをやり取りする際によく使われます。

以下はJSONデータの一例です。連想配列のようにキーと値のペアで、階層的にデータを構成することができます。

{
  "name": "Zoro",
  "age": 21,
  "from": "East Blue",
  "weapon": ["wado-ichimonji", "sandai-kitetsu", "enma"]
}
変数名.send();

最後にsendメソッドでサーバにリクエストを送信します。POSTリクエストの場合は、引数にデータを指定します。

FetchAPIを用いた方法

FetchAPIは、ブラウザ上のJavaScriptからHTTPネットワークリクエストを行うためのインターフェースです。

fetch("URL",オブジェクト)
.then(response => {
	// レスポンスが帰ってきたときの処理
})
.catch(error => {
	// 通信失敗時の処理
});

fetch()メソッドを使用し、HTTPリクエストを送ります。
第1引数に取得したいリソースのURLを記述すると、リクエストが送信できます。
第2引数は任意で、リクエストに適応したい設定のオブジェクトを渡すことができます。

fetchはレスポンスとして、Promiseオブジェクトを返します。
Promiseは非同期通信の状態(成功or失敗)を表すオブジェクトで、その状態によって後続処理をコントロールします。
成功した場合にはthenを実行(複数繋げることが可能)し、失敗した場合catchを実行します。

基本の処理~コンソールに表示~

ajax通信を使ってDBからデータを取得してみましょう。
ボタンをクリックしたらコンソールに全メンバーデータを表示します。 ※コンソールは F12キーで確認できます。

テーブルから全件取得(XMLHttpRequest)

【/com/cmps/spring/controller/AjaxController.java】

@Controller
@RequestMapping("/ajax")
public class AjaxController {

	@Autowired
	private EmployeeRepository employeeRepository;

	/**
	 * 初期表示画面
	 * @return
	 */
	@GetMapping
	public String index() {
		return "ajax/index";
	}

	/**
	 * employeesテーブル全件取得
	 * @return List<Employee>
	 */
	@GetMapping("/all")
	@ResponseBody
	public List<Employee> getAll() {
		//全件取得
		List<Employee> Employees = employeeRepository.findAll();

		return Employees;
	}
}

【/src/main/resources/templates/ajax/index.html】

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<head>
	<meta charset="UTF-8">
	<title>Ajax通信</title>
</head>

<body>
	<h1>Ajax通信</h1>
	<div>
		<h2>全データ取得</h2>
		<button class="allBtn">クリック</button>
	</div>
	<script th:src="@{/js/ajaxmanual.js}"></script>
</body>

【/src/main/resources/static/js/ajaxmanual.js】

"use strict";
document.addEventListener("DOMContentLoaded", function () {
	const allBtn = document.querySelector(".allBtn");
	let xhr = new XMLHttpRequest();	

	allBtn.addEventListener("click", function () {

		xhr.onreadystatechange = function () {

			if (xhr.readyState == 4) { //通信完了時
				if (xhr.status == 200) { //通信の成功時
					const data = xhr.response;
					console.log(data);
				}
			}
		}
		xhr.open("GET", "/ajax/all");
		xhr.responseType = "json";
		xhr.send();
	});
});

解説していきます。

document.addEventListener("DOMContentLoaded", function () {

addEventListener() メソッドの引数にDOMContentLoadedイベントを設定しています。
DOMContentLoadedイベントは、HTML ページの読み込みが完了し HTML の解析が完了して DOM ツリー(HTML要素をツリー状に表現したもの)の構築が完了した時点で発生します。

const allBtn = document.querySelector(".allBtn");

querySelector()メソッドは引数に置いたHTML要素の取得ができます。
今回は「class=”allBtn”」の要素を取得し、変数allBtnに格納しています。

ちなみに複数のHTML要素を取得する場合(.allBtnが複数ある場合)には、querySelectorAll()メソッドを使用します。

let xhr = new XMLHttpRequest();

XMLHttpRequest オブジェクトを作成し、変数xhrへ格納しています。

if (xhr.readyState == 4) { //通信完了時
	if (xhr.status == 200) { //通信の成功時
		const data = xhr.response;
		console.log(data);
	} 
}

送信されたリクエストに応じてサーバーから受け取った値を返すresponseメソッドを記述しています。設定したresponseTypeプロパティの値に応じた型で受け取るようになっており、jsonで指定している場合はJavaScriptオブジェクトとして受け取ります。テキスト型や配列、連想配列など、それぞれ受け取り可能です。

console.log(data); でコンソールに表示されます。

xhr.open("GET", "/ajax/all");
xhr.responseType = "json";
xhr.send();

openメソッドでHTTPリクエストの初期化を行います。
通信の種類として「GET」、送信先としてgetAllメソッドを指定しています。

テーブルから全件取得(Fetch)

XMLHttpRequestと同じ「ajaxmanual.js」に記述してください(XMLHttpRequestの一連の処理はコメントアウトしてください)。

document.addEventListener("DOMContentLoaded", function () {
	const allBtns = document.querySelector(".allBtn");
	allBtns.addEventListener("click", function () {

	fetch("/ajax/all", { method: "GET" })
	.then(response => {
		return response.json();
	})
	.then(data => {
		// 通信成功
		console.log(data);
	})
	.catch(error => {
		// 通信失敗
		console.error(error);
	});
});

解説していきます。

fetch("/ajax/all", { method: "GET" })

今回は第1引数に取得したいリソースとしてgetAllメソッド、第2引数にGETメソッドを指定(デフォルトがGETなので省略可)しています。

.then(response => {
	return response.json();
})
.then(data => {
	// 通信成功
	console.log(data);
})
.catch(error => {
	// 通信失敗
	console.error(error);
});

then()メソッドで繋げているのは「前の処理を待つ」ことが目的です

返却されたResponseオブジェクトの中には、レスポンスの様々な情報が含まれています。
しかし、そのままではテーブルの中身を出力できないので、return response.json()でレスポンスの本文をJSONとして解析し、JavaScriptオブジェクトへ変換し返します。

次のthen()では、引数dataへ解析されたオブジェクトを渡し、コンソール上に出力しています。

最後のcatch()では、エラーが発生した際に飛ばされる処理を記述しています。

基本の処理~HTMLに書き込み~

上記ではコンソールに表示しただけですが、HTMLに書き込みをすることもできます。
取得したデータが複数あるときは繰り返し処理を行いデータを回してあげます。

【/src/main/resources/templates/ajax/index.html】

		<button class="allBtn">クリック</button>
		<ul class="list"></ul><!-- 追記 -->

【/src/main/resources/static/js/ajaxmanual.js】

"use strict";
document.addEventListener("DOMContentLoaded", function () {
	const allBtn = document.querySelector(".allBtn");
	const listEl = document.querySelector(".list"); // 追記 ul要素を取得
	let xhr = new XMLHttpRequest();	

	allBtn.addEventListener("click", function () {


		xhr.onreadystatechange = function () {

			if (xhr.readyState == 4) { //通信完了時
				if (xhr.status == 200) { //通信の成功時
					const data = xhr.response;
					console.log(data);
					// 以下、追記---------------------------------------
					data.forEach(emp => {
						const li = document.createElement("li");
						li.textContent = `ID: ${emp.id}, 名前: ${emp.name}, 年齢: ${emp.age}`;
						listEl.appendChild(li);
					});
					// ここまで---------------------------------------
				}
			}
		}
		xhr.open("GET", "/ajax/all");
		xhr.responseType = "json";
		xhr.send();
	});
});

クリックを押すと、データベース内のデータがリストとして画面上に出力されます。

Fetchを用いた非同期通信でも以下のように実装可能です(.then(data =>{…})内に記述)。

data.forEach(emp => {
const li = document.createElement("li");
li.textContent = `ID: ${emp.id}, 名前: ${emp.name}, 年齢: ${emp.age}`;
listEl.appendChild(li);
});

受け取ったデータを一件ずつ取り出すため、forEachメソッドでループ処理を行います。

createElement()メソッドは引数に設定されたHTML要素を生成するメソッドです。
今回はリストを作成したいので、「li」を設定しています。

次の行ではtextContentでliへ書き込みを行い、その次の行でlistEl(class=”list”)へ追加しています。

入力されたIDで検索した情報を返す

IDを入力し、検索ボタンを押すと該当データが表示する処理を示します。

XMLHttpRequestでの実装

ajaxで送信されたidに該当するEntityを取得して返却
【/com/cmps/spring/controller/AjaxController.java】に追記

	/**
	 * employeesテーブルから選択されたEntity1件を取得
	 * @param id
	 * @return
	 */
	@PostMapping("/find")
	@ResponseBody
	public Employee find(@RequestParam String id) {
		// idでEmployee取得
		Optional<Employee> optEmployee = employeeRepository.findById(Integer.parseInt(id));
		Employee Employee = optEmployee.get();
		
		return Employee;
	}

【/src/main/resources/templates/ajax/index.html】scriptタグより上に追記

	<div>
		<h2>IDで検索</h2>
		<form method="POST" th:action="@{/ajax/find}" id="find">
			<input type="text" id="id" name="id">
			<input type="submit" value="検索">
		</form>
		<div id="result"></div>
	</div>

【/src/main/resources/static/js/ajaxmanual.js】に追記

document.addEventListener("DOMContentLoaded", function () {

	const form = document.getElementById("find");
	const result = document.getElementById("result");
	
	let xhr2 = new XMLHttpRequest();
	let token = document.querySelector('input[name=_csrf]').value;

	form.addEventListener("submit", function (event) {
		// 同期処理(通常のフォーム送信)を停止
		event.preventDefault();
		const employeeId = document.getElementById("id").value;
		
		xhr2.onreadystatechange = function () {
			if (xhr2.readyState == 4) { //通信完了時
				if (xhr2.status == 200) {
					const searchId = xhr2.response;
					result.textContent = `ID: ${searchId.id}, 名前: ${searchId.name}`;
				}
			}
		}
		xhr2.open("POST", "/ajax/find");
		xhr2.responseType = "json";
		xhr2.setRequestHeader("content-type", "application/x-www-form-urlencoded;charset=UTF-8");// コンテントタイプ 
		xhr2.setRequestHeader("X-CSRF-Token", token);// CSRFトークン
		xhr2.send("id="+ encodeURIComponent(employeeId));
	});
});

今回はPOST通信の場合の記述になっています。

const form = document.getElementById("find");
const result = document.getElementById("result");
			
let xhr2 = new XMLHttpRequest;
let token = document.querySelector('input[name=_csrf]').value;

上3行はHTML要素の取得、XMLHttpRequestインスタンスの生成を行っており
4行目はCSRF Tokenの取得を行っています。CSRF対策については「ページ遷移」の単元でも触れています。

Spring Securityでは、「formタグのaciton属性にリンク式を付与する(th:action=”@{~}”)と、そのformタグ内にCSRF情報を持つinputタグ(type=hidden)が追加される」という仕様のため、CSRF情報を取得するためにPOST通信の場合はformタグが必須になります。
実際にHTMLとして生成された例が下図です。valueにCSRF Tokenが格納されています。
このvalueの値を取得するために、document.querySelector('input[name=_csrf]').valueと記述しています。

form.addEventListener("submit", function (event) {
	event.preventDefault();
	…
}

フォームのsubmitがクリックされた時が、処理の発火条件となっています。
そのままでは通常のフォーム送信処理(同期通信)が実行されてしまうので、event.preventDefault();というイベントを停止する記述で画面遷移を止め、Ajax通信を行うようにしています。
preventDefault()は、Event インターフェイスのメソッドで、今回のようにfunction(event)と記述するとそのイベント自身、すなわちここではsubmitイベントをeventという変数に取得できます(変数名は任意、短く(e)と記述することも多い)。このevent()をpreventDefault()メソッドで停止しているのです。

xhr2.open("POST", "/ajax/find");
xhr2.responseType = "json";
xhr2.setRequestHeader("content-type", "application/x-www-form-urlencoded;charset=UTF-8");// コンテントタイプ 
xhr2.setRequestHeader("X-CSRF-Token", token);// CSRFトークン
xhr2.send("id="+ encodeURIComponent(employeeId));

ここではopen()メソッドでリクエストの初期化を行い、setRequestHeader()メソッドでサーバに送る追加情報を設定、send()メソッドで実際にデータ送信しています。

setRequestHeader()メソッドは、HTTP リクエストヘッダーの値を設定します。
このメソッドはopen()メソッドの後、send()メソッドの呼び出し前に呼び出さなければなりません。

POSTリクエストなのでsend()メソッドの引数には、送信したいデータを設定しています。
encodeURIComponent()は引数に指定した文字列の特別な文字をエンコードし、安全な新しい文字列を返します。URLでは、特定の文字(例: /, ?, =, &, # など)は特別な意味を持ちますので、文字化けや意図しない動作を防ぐために設定しています。

FetchAPIでの実装

document.addEventListener("DOMContentLoaded", function () {

	const form = document.getElementById("find");
	const result = document.getElementById("result");

	let token = document.querySelector('input[name=_csrf]').value;

	form.addEventListener("submit", function (event) {
		// 同期処理(通常のフォーム送信)を停止
		event.preventDefault();
		const employeeId = document.getElementById("id").value;

		fetch("/ajax/find", {
			method: "POST",
			headers: {
				"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
				"X-CSRF-Token": token
			},
			body: "id=" + encodeURIComponent(employeeId)
		})
		.then(response => {
			return response.json();
		})
		.then(searchId => {
			result.textContent = `ID: ${searchId.id}, 名前: ${searchId.name}`;
		})
		.catch(error => {
			console.error(error);
		});
	});
});

fetch()メソッドの引数がこれまでと異なりますね。

fetch("/ajax/find", {
	method: "POST",
	headers: {
		'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
		'X-CSRF-Token': token
	},
	body: "id=" + encodeURIComponent(employeeId)
})

第1引数はこれまで通り送り先のURLですが、今回はPOSTリクエストとなるので、第2引数のオプションに様々な情報を設定しています。

まず、HTTPメソッドは既定値が「GET」ですのでそれを「POST」に設定しています。

headersはリクエストの付加情報を設定しています。
headersの記述(CSRF情報)は、GET通信の場合は不要です。Spring Securityを導入済みの場合、POST通信では”headers”にCSRF情報の記述が必須になります。
XMLHttpRequestでの実装同様、コンテントタイプとCSRFトークンを設定しています。

bodyでは実際に送るデータの本体を設定しています。
今回は文字列での送信となりますので、 encodeURIComponentでエンコードし安全に送信できるようにしています。

開発者(デベロッパー)ツールでの通信結果の確認方法

各ブラウザに用意されている開発者ツールを利用します。
※ここではGoogle Chromeを例にしています。ブラウザによって操作には差異があります。

手順としては、開発者ツールで「Network」タブ>該当の通信(URLを確認)>「Preview」タブ、の順で開きます。
Previewタブの中に表示されているのが結果です。
問題なく通信できた場合は、下記のように戻り値のjsonデータが表示されています。

通信がエラーになっている場合は、このようにコンソールと同じエラーメッセージが表示されます。

また、上図の左側Name/名前欄にも×マークで出ている通り、ここからも通信に失敗していることが分かります。
通信の成功失敗は「Headers」タブに書かれているステータスコードからも確認できます。
200はOKで、400番台500番台はエラーです。

jQueryを用いたAjaxの基本の書き方

jQueryに用意されているajaxメソッドを使用し、ajaxのオプションを設定します。
done()メソッドに通信成功時の処理、fail()メソッドに失敗時の処理を記述します。

・クライアント側から送信するデータがない場合は、”datatype”、”data”は省略可です。

CSRFトークンについては$('input[name=_csrf]').val()で値を取得しています。

$('セレクタ').イベント名(function(e){
	$.ajax({
		headers: {
			'X-CSRF-TOKEN': $('input[name=_csrf]').val(),	// CSRF情報
		},
		url: '/getdata',	// 通信したいURL
		type: 'POST',		// HTTP通信の種類
		datatype: 'json',	// 受け取りデータの種類
		data: {
			// 送信するデータ
		}
	}).done(function (data) {
		// 通信成功
	}).fail(function () {
		// 通信失敗
	});
});

jQueryを用いたAjax通信処理

テーブルから全件取得し、コンソールに表示

【/src/main/resources/templates/ajax/index.html】に適宜追記

<body>
	<script src="https://code.jquery.com/jquery-3.7.1.js" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" crossorigin="anonymous"></script>
	<script th:src="@{/js/ajaxmanual.js}">
</body>

【/src/main/resources/static/js/ajaxmanual.js】に追記

$(function() {
	// .allBtnの要素がクリックされたら発火
	$(".allBtn").on("click", function(e) {
		$.ajax({
			url: "/ajax/all",	// 通信先
			type: "GET",		// HTTP通信の種類
		}).done(function(data) {
			//// 通信成功
			console.log(data);

		}).fail(function(error) {
			// 通信失敗
			console.log("fail!");
			console.log(error);
		});
	});
});

サーバー側から返却されたList<Employee>を}).done(function(data) {dataで受け取ります。(JavaScriptのオブジェクトに変換されます)

テーブルから全件取得し、HTMLに書き込み

取得したデータが複数あるときは繰り返し処理を行いデータを回してあげます。
$.each():繰り返し処理を行う

		}).done(function(data) {
			//// 通信成功
			console.log(data);

			//以下、追記 ---------------------------------
			$.each(data, function (index, value) {
				let html =`
					<li>ID:${value.id} , name:${value.name}</li>
					`;
				$(".list").append(html);
			});
			//ここまで ---------------------------------

		}).fail(function(err) {

each() メソッドの第1引数には、繰り返しの対象となるオブジェクト(data)を指定します。
第2引数のコールバック関数が繰り返し実行する処理にあたり、引数のindexにはキーvalueにはオブジェクトの値が順番に代入されていきます。
ここでのvalueが、Java側で言うEmployeeのEntity1件分にあたり、value.idと記述することでカラムの値を取得しています。

.appendは対象要素の内側にタグを挿入します。
今回の場合だと、<ul class=”list”></ul>の中にliタグを追加しています。

入力されたIDで検索した情報を返す

【/src/main/resources/static/js/ajaxmanual.js】に適宜追記

$(function() {
	// #findがフォーム送信されたら発火
	$('#find').on('submit', function(e) {
		e.preventDefault();//同期処理(通常のフォーム送信)を停止
		$.ajax({
			headers: {
				'X-CSRF-TOKEN': $("input[name=_csrf]").val(), //CSRF情報
			},
			url: $(this).attr("action"),	// 通信先 //"/ajax/find"と書いても同じ
			type: 'POST',			// HTTP通信の種類
			data: {
				'id': $('#id').val(),	//サーバー側に送る値
			},
			datatype: 'json',
		}).done(function(data) {
			console.log(data);
			$('#result').append('ID:' + data["id"] + '<br>name:' + data["name"] + "<br>");

		}).fail(function() {
			console.log('fail!');
		});
	});
});

・JavaScriptの記述同様、何もしないと通常の同期通信でフォーム送信処理が実行されてしまうので、e.preventDefault();で停止しています。
・formタグにaction属性を指定しているので、urlは$(this).attr("action")の記述でHTMLから取得することができます。(JavaScriptでも同様の記述は可能)

$(‘#id’).val() でテキストボックスに入力された値を取得しています。
取得したデータをajaxメソッド内の data : { } に指定して、コントローラー側に送信しています。’id’という名称でパラメータを送っているので、サーバー側でもidで受け取ります。

練習問題

問1: 名前のキーワード(LIKE)検索をajax通信で行い、取得した全データをHTMLに書き込んでください。

問2:セレクトボックスを作成し、選択された人物の年齢をHTMLに書きこんでください。

練習問題のヒント
データを送信するので、POSTリクエストで実装します。

タイトルとURLをコピーしました