Open4

Ajaxでフォーム画面の値を送信+SpringBootのコントローラで受け取る方法

mIO1000mIO1000

パターン1 FormDataで送る

クライアント

table_datatables.js
let count = 0;

let _form = new FormData();	// …①

$("#mytable tbody tr").each(function(index, row) {
	// テーブルの行を走査
	if ($(row).find("input:checkbox").is(":checked")) {
		// 選択されている行のみ
		$(row).find("input, select").each(function(index, element) {
			if (element.type === "checkbox") {
				// チェックボックスは除外
				return;
			}
			// FormDataに追加
			_form.append("detail[" + count + "]." + element.name, element.value);	// …②
		});
		count++;
	}
});

$.ajax({
	type: "POST",
	data: _form,
	url: "/webappSample/table_datatables/confirm_formdata",
	processData: false,	// …③
	contentType: false,	// …③
	dataType: "json"

}).done(function(data, textStatus, jqXHR) {

	// 省略

}).fail(function(jqXHR, textStatus, errorThrown) {
	alert("失敗");
});

① FormData

<form>内のフォーム項目とそのvalueのセットを表すもの。
new FormData()することで、フォームそのものがHTML上になくても、フォームとして扱えるようにしてくれる。
(もちろん、すでにHTML上に存在するフォームからFormDataを構築することも可能)
https://developer.mozilla.org/ja/docs/Web/API/FormData

② FormDataにkeyとvalueのセットをappend

keyは、
Formクラスのフィールド名[インデックス].ネストしたJavaBeanのフィールド名
となるように構築。

③ processData: false、contentType: false

processDataについて
https://js.studio-kingdom.com/jquery/ajax/ajax

デフォルトでは、dataオプションにオブジェクトとして渡されるデータ(厳密に言えば、文字列以外のもの)は、 デフォルトのcontent-typeである"application/x-www-form-urlencoded"に合わせた形式でクエリー文字列へ変換されます。 もしDOMDocument、またはその他の形式のデータを送信したい場合は、このオプションをfalseに設定します。

要は、「key=value&key=value…」の形式で送るときはtrue。

contentTypeについて

サーバーへデータが送信される際に、ここで指定されたコンテンツタイプが指定されます。 デフォルトは、"application/x-www-form-urlencoded; charset=UTF-8"で、 ほとんどのケースは、これで問題ありません。

とのこと。ここをfalseにすると、$.ajaxにてよしなにContent-Typeを指定する。

FormDataオブジェクトは、Blobやファイルを送ることを想定しているため、「Content-Type:multipart/form-data」となるのが基本の模様。
よって、ajaxでのオプションの指定の仕方もそれに合わせるのがよいようです。
「Content-Type:multipart/form-data」では、key-valueのペア一つ一つがマルチパートとして送信されることになります。

Content-Type:multipart/form-dataの場合のリクエストの中身


リクエストヘッダ


リクエストボディ(パースあり)


リクエストボディ(パースなし)

Content-Type:application/x-www-form-urlencodedの場合のリクエストの中身

普通にフォームをsubmitすると、下記のようなパラメータの送られ方になる。

リクエストボディ(パースなし)

参考

https://www.air-h.jp/articles/emopro/ajaxでファイル送信したい(xhr-jquery-axios-fetch)/
https://qiita.com/ikamirin/items/4ab8599ec00d9ce3890d
https://www.wakuwakubank.com/posts/799-it-content-type-content-disposition/
https://tech-tokyobay.manju.tokyo/archives/782

サーバ

TableDatatablesController.java
@Controller
@RequestMapping("/table_datatables")
public class TableDatatablesController {

	@RequestMapping(path = "/confirm_formdata", method = { RequestMethod.POST })
	@ResponseBody
	public ResponseDto confirmFormData(TableDatatablesConfirmForm form, BindingResult br) {		// ①

		// 単項目チェック
		validate(form.getDetail(), br);
		
		logger.debug("エラー件数=" + Integer.toString(br.getErrorCount()));
		if (br.hasErrors()) {
			// エラーがある場合
			List<FieldErrorDto> fieldErrors = getFieldErrors(br);
			ResponseDto rd = new ResponseDto(message.getMessage("WCOM00002", null), "NG", fieldErrors);
			return rd;
		}
		
		sv.confirm(form.getDetail());

		ResponseDto rd = new ResponseDto(message.getMessage("WCOM00001", null), "OK", null);

		return rd;
	}

}
TableDatatablesConfirmForm.java
@Data
public class TableDatatablesConfirmForm {

	List<TableDatatablesRecord> detail;

}

① Formクラスをリクエストハンドラの引数に指定

明細部を表すフィールドの変数名を「detail」としているため、クライアントからのパラメータのキーも「detail」とする。

mIO1000mIO1000

パターン2 JSONで送る+リクエストハンドラでFormクラスで受ける

クライアント

table_datatables.js
// リクエスト用配列
let _data = [];	// ①

$("#mytable tbody tr").each(function(index, row) {
	// テーブルの行を走査
	if ($(row).find("input:checkbox").is(":checked")) {
		// 選択されている行のみ
		
		// 1行分のオブジェクト
		let _row = {};

		$(row).find("input, select").each(function(index, element) {
			if (element.type === "checkbox") {
				// チェックボックスは除外
				return;
			}
			_row[element.name] =  element.value;	// ①
		});
		_data.push(_row);
	}
});

$.ajax({
	type: "POST",
	data: JSON.stringify({ detail : _data }),	// ②
	url: "/webappSample/table_datatables/confirm_json2form",
	contentType: "application/json",			// ③
	dataType: "json"

}).done(function(data, textStatus, jqXHR) {

	// 省略

}).fail(function(jqXHR, textStatus, errorThrown) {
	alert("失敗");
});

① リクエスト用の配列を用意

更新対象の1明細分をオブジェクトにし、かつ明細件数分、配列に追加します。

② ①の配列をオブジェクトにセットし、JSON文字列化

配列を、「detail」をプロパティとするオブジェクトに追加したうえで、JSON文字列にします。
「detail」はFormクラスのフィールドと一致させます。

③ Content-Typeはapplication/json

サーバ

TableDatatablesController.java
	/**
	 * 確定(JsonからFormクラスにバインド)
	 * @param form フォームオブジェクト
	 * @param br BindingResult
	 * @return レスポンスDTO
	 */
	@RequestMapping(path = "/confirm_json2form", method = { RequestMethod.POST })
	@ResponseBody
	public ResponseDto confirmJson2Form(@RequestBody TableDatatablesConfirmForm form, BindingResult br) {	// ①

		// 単項目チェック
		validate(form.getDetail(), br);
		
		logger.debug("エラー件数=" + Integer.toString(br.getErrorCount()));
		if (br.hasErrors()) {
			// エラーがある場合
			List<FieldErrorDto> fieldErrors = getFieldErrors(br);
			ResponseDto rd = new ResponseDto(message.getMessage("WCOM00002", null), "NG", fieldErrors);
			return rd;
		}
		
		sv.confirm(form.getDetail());

		ResponseDto rd = new ResponseDto(message.getMessage("WCOM00001", null), "OK", null);

		return rd;
	}

① Formクラスをリクエストハンドラの引数に指定

@RequestBodyでリクエストボディのJSON文字列を取り出し、Formクラスにバインドさせます。

mIO1000mIO1000

パターン3 JSONで送る+List<ネストしたJavaBeanのクラス>で受ける

クライアント

パターン2と違う部分のみ説明。

table_datatables.js
// リクエスト用配列
let _data = [];

//console.log($(".datatable").DataTable().$('input, select').html);

$("#mytable tbody tr").each(function(index, row) {
	// テーブルの行を走査
	if ($(row).find("input:checkbox").is(":checked")) {
		// 選択されている行のみ

		// 1行分のオブジェクト
		let _row = {};

		$(row).find("input, select").each(function(index, element) {
			if (element.type === "checkbox") {
				// チェックボックスは除外
				return;
			}
			_row[element.name] =  element.value;
		});
		// 送信用データに追加
		_data.push(_row);
	}
});

// エラー表示を初期化
$("table span").remove();
$(".has-error").removeClass("has-error");

// 送信
$.ajax({
	type: "POST",
	data: JSON.stringify(_data),	// ①
	url: "/webappSample/table_datatables/confirm_json2list",
	contentType: "application/json",
	dataType: "json"

}).done(function(data, textStatus, jqXHR) {

	// 省略

}).fail(function(jqXHR, textStatus, errorThrown) {
	alert("失敗");
});

① 配列をJSON文字列化

パターン3では、配列のままJSON文字列にします。

サーバ

TableDatatablesController.java
	@RequestMapping(path = "/confirm_json2list", method = { RequestMethod.POST })
	@ResponseBody
	public ResponseDto confirmJson2List(@RequestBody List<TableDatatablesRecord> list, BindingResult br) {	// ①

		// 単項目チェック
		validate(list, br);

		logger.debug("エラー件数=" + Integer.toString(br.getErrorCount()));
		if (br.hasErrors()) {
			// エラーがある場合
			List<FieldErrorDto> fieldErrors = getFieldErrors(br);
			ResponseDto rd = new ResponseDto(message.getMessage("WCOM00002", null), "NG", fieldErrors);
			return rd;
		}
		
		sv.confirm(list);

		ResponseDto rd = new ResponseDto(message.getMessage("WCOM00001", null), "OK", null);

		return rd;
	}

① List<TableDatatablesRecord>で受ける

パターン2ではFormクラスの「detail」変数にバインドしていましたが、こちらでは、List(配列)に格納されたTableDatatablesRecordに対し、直接バインドされます。