Webアプリケーションのユーザビリティ向上を目的に、データ操作時の画面遷移を廃止し、モーダルウィンドウ(ポップアップ)と非同期通信(AJAX)を組み合わせたアーキテクチャへ移行しました。具体的には、LayuiのテーブルコンポーネントとjQueryのAJAX機能を連携させ、コールバック関数を用いてテーブルデータの動的な更新を実装しています。
以下に、フロントエンドの実装例を示します。ページ遷移を行わずにlayer.openで子画面を呼び出し、操作完了時に親画面のテーブルデータを再描画する構成としています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>学生管理システム</title>
<link rel="stylesheet" href="layui/css/layui.css">
</head>
<body>
<div class="layui-container" style="padding: 20px;">
<!-- データテーブル -->
<table class="layui-hide" id="studentGrid" lay-filter="studentGrid"></table>
<!-- ヘッダーツールバーテンプレート -->
<script type="text/html" id="headerToolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="insertRecord">新規追加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="batchDelete">一括削除</button>
</div>
</script>
<!-- 行ツールバーテンプレート -->
<script type="text/html" id="rowToolbar">
<a class="layui-btn layui-btn-xs" lay-event="modify">編集</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="remove">削除</a>
</script>
</div>
<script src="layui/layui.js"></script>
<script src="jQuery/jquery-3.6.0.min.js"></script>
<script>
layui.use(['table', 'layer', 'util'], function(){
var table = layui.table;
var layer = layui.layer;
var util = layui.util;
// テーブルの初期化
var gridInstance = table.render({
elem: '#studentGrid',
url: '/api/fetchStudents',
toolbar: '#headerToolbar',
defaultToolbar: ['filter', 'exports', 'print'],
page: true,
limits: [10, 20, 50],
limit: 10,
cols: [[
{type: 'checkbox', fixed: 'left'},
{field: 'studentId', title: 'ID', width: 100, sort: true, fixed: 'left'},
{field: 'fullName', title: '氏名', width: 150},
{field: 'gender', title: '性別', width: 100},
{field: 'birthDate', title: '生年月日', width: 150},
{fixed: 'right', title: '操作', toolbar: '#rowToolbar', width: 150}
]]
});
// ヘッダーツールバーイベント処理
table.on('toolbar(studentGrid)', function(obj){
if(obj.event === 'insertRecord'){
openModal('新規追加', 'add.jsp');
}
});
// 行ツールバーイベント処理
table.on('tool(studentGrid)', function(rowObj){
var currentData = rowObj.data;
if(rowObj.event === 'modify'){
openModal('編集: ' + currentData.fullName, 'edit.jsp', currentData);
} else if(rowObj.event === 'remove'){
layer.confirm('本当に削除しますか?', function(index){
executeDelete(currentData.studentId, index);
});
}
});
// モーダルウィンドウを開く関数
function openModal(title, url, data){
layer.open({
type: 2,
title: title,
area: ['600px', '400px'],
shadeClose: false,
content: url,
btn: ['保存', 'キャンセル'],
yes: function(idx, layero){
var iframeWin = window[layero.find('iframe')[0]['name']];
var formData = iframeWin.getFormData(); // 子画面のデータ取得関数を想定
// 非同期でデータ送信
$.ajax({
url: data ? '/api/updateStudent' : '/api/addStudent',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function(response){
layer.msg('処理が完了しました', {icon: 1});
layer.close(idx);
gridInstance.reload(); // テーブル再読み込み
},
error: function(){
layer.msg('エラーが発生しました', {icon: 2});
}
});
}
});
}
// 削除実行関数
function executeDelete(id, layerIdx){
$.post('/api/removeStudent', { id: id }, function(res){
layer.msg('削除成功');
layer.close(layerIdx);
gridInstance.reload();
}).fail(function(){
layer.msg('削除失敗');
});
}
});
</script>
</body>
</html>
続いて、サーバーサイドのデータ取得処理(Servlet)です。リクエストパラメータからページ情報を取得し、データベースアクセスオブジェクト(DAO)を経由してデータを抽出、JSON形式でクライアントに返却します。
import com.google.gson.Gson;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/api/fetchStudents")
public class StudentListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ページネーションパラメータの取得
String pageParam = req.getParameter("page");
String limitParam = req.getParameter("limit");
int page = Integer.parseInt(pageParam);
int limit = Integer.parseInt(limitParam);
// データアクセス
StudentDao dao = new StudentDao();
var studentList = dao.findPaginated(page, limit);
int totalCount = dao.getTotalCount();
// レスポンスオブジェクトの構築
ApiResponse apiResponse = new ApiResponse();
apiResponse.setStatus(0);
apiResponse.setMessage("Data fetched successfully");
apiResponse.setTotal(totalCount);
apiResponse.setDataset(studentList);
// JSONへの変換とレスポンス書き込み
Gson gson = new Gson();
String jsonResponse = gson.toJson(apiResponse);
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(jsonResponse);
}
}
最後に、JSONレスポンス用のデータ転送オブジェクト(DTO)の定義です。Layuiのテーブルコンポーネントが期待する形式に合わせてプロパティを設定しています。
import java.util.List;
public class ApiResponse {
private int status;
private String message;
private int total;
private List<Student> dataset;
public ApiResponse() {}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public List<Student> getDataset() {
return dataset;
}
public void setDataset(List<Student> dataset) {
this.dataset = dataset;
}
}