Ajaxでフォーム画面の値を送信+SpringBootのコントローラで受け取る方法
パターン1 FormDataで送る
クライアント
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を構築することも可能)
② FormDataにkeyとvalueのセットをappend
keyは、
Formクラスのフィールド名[インデックス].ネストしたJavaBeanのフィールド名
となるように構築。
③ processData: false、contentType: false
processDataについて
デフォルトでは、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すると、下記のようなパラメータの送られ方になる。
リクエストボディ(パースなし)
参考
サーバ
@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;
}
}
@Data
public class TableDatatablesConfirmForm {
List<TableDatatablesRecord> detail;
}
① Formクラスをリクエストハンドラの引数に指定
明細部を表すフィールドの変数名を「detail」としているため、クライアントからのパラメータのキーも「detail」とする。
メモ
仮に、FormDataをx-www-form-urlencodedで送りたい場合、URLSearchParams 的なもので対応?
パターン2 JSONで送る+リクエストハンドラでFormクラスで受ける
クライアント
// リクエスト用配列
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
サーバ
/**
* 確定(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クラスにバインドさせます。
パターン3 JSONで送る+List<ネストしたJavaBeanのクラス>で受ける
クライアント
パターン2と違う部分のみ説明。
// リクエスト用配列
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文字列にします。
サーバ
@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に対し、直接バインドされます。