昔にモードレスウィンドウ&POST送信で躓いた記憶が残っていて、IntelliJにAIアシスタントがついていたので、昔にやったものがどんなものかを探りながら、初めて触るAIちゃんと一緒に頑張ってもらいながら書いてみました。
ボタン押下→window.open→POST送信→結果をモードレスウィンドウに表示するためモードレスウィンドウ用にPOST送信→モデルへ詰めてモードレスウィンドウのhtmlに返す みたいな内容。
window.openした後にPOST送信の結果をうんぬんするから、二回コントローラを叩いたような感じの実装でした。
フォルダの構成は以下。
src/
└── main/
├── java/
│ └── org/
│ └── example/
│ └── frontdemo/
│ ├── FrontdemoApplication.java
│ └── index/
│ ├── controller/
│ │ └── indexController.java
│ └── form/
│ └── Reqform.java
├── main.iml
└── resources/
├── application.properties
├── static/
│ └── js/
│ └── app.js
└── templates/
├── index.html
└── modelessWindow.html
package org.example.frontdemo.index.controller;
import org.example.frontdemo.index.form.Reqform;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
@Controller
public class indexController {
// ルートアクセス("/")で templates/index.html を返す
@GetMapping("/")
public String index() {
return "index";
}
@PostMapping("/test1")
@ResponseBody
public ResponseEntity<List<Reqform>> test1(@RequestBody Reqform reqform) {
return ResponseEntity.ok ( Arrays.asList ( reqform ) );
}
@PostMapping("/test2")
public String test2(
@RequestParam("testList") String json,
Model model
) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// フロントは配列を送るため、明示的に List<Reqform> 型を指定してデシリアライズ
List<Reqform> list = objectMapper.readValue(
json,
new TypeReference<List<Reqform>>() {}
);
list.add (new Reqform("山田太郎", "male"));
model.addAttribute("testList", list);
return "modelessWindow"; // modelessWindow.html
} catch (Exception e) {
// デバッグ用に簡易エラー表示(必要に応じて削除/ログ化)
model.addAttribute("testList", java.util.Collections.emptyList());
model.addAttribute("error", e.getMessage());
return "modelessWindow";
}
}
}
package org.example.frontdemo.index.form;
public class Reqform {
private String name;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Reqform(String name, String sex) {
this.name = name;
this.sex = sex;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/js/app.js"></script>
</head>
<body>
<input id="name" name="name">
<input id="sex" name="sex">
<button onclick="modelessWindow()">
モードレスウィンドウ
</button>
</body>
</html>
// 共通ユーティリティ
const toAbsoluteUrl = (u) => new URL(u, window.location.origin).href;
// モードレスウィンドウを開いてサーバ連携
function modelessWindow() {
// ① ユーザー操作直後に window.open
const target = '_testList';
const modal = window.open('about:blank', target, 'width=1280,height=1080');
// ② Ajax用リクエスト
const requestBody = collectValuesById('name', 'sex');
// ルート相対URLに統一(/test1)
ajaxPost(
'/test1',
requestBody, function (response) {
// ③ 別ウィンドウへ POST するパラメータ
const param = {
testList: JSON.stringify(response),
};
// POST 先もルート相対に統一(/test2)
postToWindow('/test2', param, target);
},
function () {
modal.close();
alert('エラー');
}
);
}
// 指定した要素IDから value を収集してオブジェクト化
function collectValuesById(...elementIds) {
const values = {};
for (const id of elementIds) {
const element = document.getElementById(id);
values[id] = element?.value ?? '';
}
return values;
}
// JSON POST (fetch)
function ajaxPost(url, data, onSuccess, onError) {
const abs = toAbsoluteUrl(url);
fetch(abs, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
.then((res) => {
if (!res.ok) throw new Error('HTTP error');
return res.json();
})
.then(onSuccess)
.catch((err) => {
if (onError) onError(err);
});
}
// 別ウィンドウ(ターゲット)へフォームPOST
function postToWindow(url, params, targetName) {
const form = document.createElement('form');
form.method = 'post';
form.action = toAbsoluteUrl(url);
form.target = targetName;
Object.keys(params).forEach(function (name) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = params[name];
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>モードレス画面</title>
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #ccc; padding: 4px 8px; }
</style>
</head>
<body>
<h2>モードレス画面</h2>
<div th:if="${#lists.isEmpty(testList)}">
データがありません。
<div style="margin-top:8px; color:#666;" th:text="${testList}"></div>
<!-- ↑ デバッグ用: 中身の確認。不要なら削除可 -->
</div>
<table th:if="${!#lists.isEmpty(testList)}">
<thead>
<tr>
<th>name</th>
<th>sex</th>
</tr>
</thead>
<tbody>
<tr th:each="row : ${testList}">
<td th:text="${row.name}">name</td>
<td th:text="${row.sex}">sex</td>
</tr>
</tbody>
</table>
</body>
</html>

