LayuiとjQuery AJAXによる非同期データ更新の実装

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;
    }
}

タグ: Jakarta EE Servlet Layui jQuery AJAX

6月14日 18:12 投稿