【JQuery】Ajax で動的に登録・更新・削除した後に DataTables にデータを再表示する

Javascript,Python,開発

おはようございます。

昨日に引き続き、DataTables を利用したページの作成をしていきます。

今回はデータを登録・更新・削除した後、データを再取得して DataTables をリロードする方法を試してみます。

プログラムは前回のものを流用します。

【JQuery】DataTables に Ajax で動的に取得したデータを表示する

スポンサーリンク

画面の修正

主な変更点

1.CSSを外出し
2.検索用コントロールのスタイル変更
3.登録、更新用のモーダルダイアログ(Bootstrap)を追加

static/css/style.css

.container {
    border: 1px solid black;
    padding: 1rem;
    margin: 1rem;
}
.form-inline .label {
    margin-top: 10px;
}
input {
    ime-mode: active;
}
input.ime-disabled {
    ime-mode: disabled;
}
input.ime-inactive {
    ime-mode: inactive
}

.table > tbody > tr.active > td,
.table > tbody > tr.active > th {
    background-color: #e1f2fe;
}

 

templates/index.html

<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta charset="UTF-8">
		<title>DataTableサンプル</title>
		<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.19/css/dataTables.bootstrap.min.css"/>
		<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
		<link rel="stylesheet" href="{{ static_url('css/style.css') }}"/>
	</head>
	<body>
		<div class="container container-fluid">
			<div>
				名前:
				<input type="text" class="input-sm ime-disabled" id="searchName" placeholder="名前" required>
				<button id="searchButton" type="button" class="btn btn-sm btn-info">検索</button>
			</div>
			<div>
				<table id="myTable" class="table table-striped tagle-bordered">
					<thead>
						<tr>
							<th>No</th>
							<th>名前</th>
							<th>性別</th>
							<th>年齢</th>
							<th>種別</th>
							<th>好物</th>
						</tr>
					</thead>
					<tbody>

					</tbody>
					<tfoot>
						<tr>
							<th>No</th>
							<th>名前</th>
							<th>性別</th>
							<th>年齢</th>
							<th>種別</th>
							<th>好物</th>
						</tr>
					</tfoot>
				</table>
			</div>
			<div>
				<hr>
				<div class="pull-left">
					<button id="registButton" type="button" class="btn btn-primary">登録</button>
					<button id="updateButton" type="button" class="btn btn-success" disabled>更新</button>
				</div>
				<div class="pull-right">
					<button id="deleteButton" type="button" class="btn btn-danger" disabled>削除</button>
				</div>
			</div>
		</div>
		<div id="form" class="modal fade" tabindex="-1">
			<div class="modal-dialog modal-dialog-centered ">
				<div class="modal-content">
					<div class="modal-header">
						<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
						<h4 id="dialogTitle" class="modal-title">登録</h4>
					</div>
					<div class="modal-body">
						<div class="container-fluid">
							<div class="row">
								<div class="col-xs-12">
								<form class="form-horizontal">
									<div class="form-group">
										<div class="form-inline">
											<div class="col-sm-4">
												<label class="control-label">No</label>
												<span class="label label-danger pull-right">必須</span>
											</div>
											<div class="input-group col-sm-2">
												<input type="text" class="form-control ime-disabled" id="inputNo" placeholder="No" required>
											</div>
										</div>
									</div>
									<div class="form-group">
										<div class="form-inline">
											<div class="col-sm-4">
												<label class="control-label">名前</label>
												<span class="label label-danger pull-right">必須</span>
											</div>
											<div class="input-group col-sm-2">
												<input type="text" class="form-control" id="inputName" placeholder="名前" required>
											</div>
										</div>
									</div>
									<div class="form-group">
										<div class="form-inline">
											<div class="col-sm-4">
												<label class="control-label">性別</label>
												<span class="label label-danger pull-right">必須</span>
											</div>
											<div class="input-group col-sm-2">
												<input type="text" class="form-control" id="inputSex" placeholder="性別">
											</div>
										</div>
									</div>
									<div class="form-group">
										<div class="form-inline">
											<div class="col-sm-4">
												<label class="control-label">年齢</label>
												<span class="label label-danger pull-right">必須</span>
											</div>
											<div class="input-group col-sm-2">
												<input type="text" class="form-control ime-disabled" id="inputAge" placeholder="年齢">
											</div>
										</div>
									</div>
									<div class="form-group">
										<div class="form-inline">
											<div class="col-sm-4">
												<label class="control-label">種別</label>
												<span class="label label-danger pull-right">必須</span>
											</div>
											<div class="input-group col-sm-7">
												<select id="inputKind" class="form-control" >
													<option value="01">キジトラ</option>
													<option value="02">長毛種(不明)</option>
													<option value="03">ミケ(っぽい)</option>
													<option value="04">サビ</option>
													<option value="09">その他</option>
												</select>
											</div>
										</div>
									</div>
									<div class="form-group">
										<div class="form-inline">
											<div class="col-sm-4">
												<label class="control-label">好物</label>
												<span class="label label-success pull-right">任意</span>
											</div>
											<div class="input-group col-sm-7">
												<input type="text" class="form-control" id="inputFavorite" placeholder="好物">
											</div>
										</div>
									</div>
								</form>
								</div>
							</div>
						</div>
					</div>
					<div class="modal-footer">
						<div id="inputError" class="pull-left" style="color:red; padding:5px;"></div>
						<button id="sendRegistButton" type="button" class="btn btn-primary"><i class="fa fa-check"></i>&nbsp;登録</button>
						<button id="sendUpdateButton" type="button" class="btn btn-primary"><i class="fa fa-check"></i>&nbsp;修正</button>
						<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-remove"></i>&nbsp;閉じる</button>
					</div>
				</div>
			</div>
		</div>
	</body>
	<script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.js"></script>
	<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
	<script type="text/javascript" src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
	<script type="text/javascript" src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap.min.js"></script>
	<script type="text/javascript" src="{{ static_url('js/script.js') }}"></script>

</html>

プログラムの修正

クライアント

主な変更点

1.テーブル行クリック時に行のスタイル(クラス)を変更する
2.各種ボタン(登録・更新・削除)クリック時の処理を追加
3.ダイアログの各種ボタン(登録・更新)クリック時の処理(Ajax)を追加

static/js/script.js

$(document).ready( function () {

    $('#myTable').DataTable({
		'paging'      : true,
		'pageLength'  : 5,
		'lengthChange': false,
		'searching'   : true,
		'ordering'    : true,
		'info'        : true,
		'autoWidth'   : true,
		"scrollCollapse": true,
		'scrollX'     : true,
		'scrollY'     : '185px',
		'tabIndex'    : -1,
		'order'       : [[ 0, 'asc' ]],
		'colReorder'  : true,
        'serverSide'  : false,
        'ajax'        : {
            'url'  : '/init',
            'type' : 'POST',
            'data' : function ( d ) {
                d.searchName = $("#searchName").val();
            }
        },
        'columns'     : [
            { 'data' : 'no', width: 40 },
            { 'data' : 'name', width: 60  },
            { 'data' : 'sex', width: 40  },
            { 'data' : 'age', width: 40  },
            { 'data' : 'kind_name', width: 100  },
            { 'data' : 'favorite', width: 120  },
        ],
        'language'   : {
            'decimal':        ".",
            'emptyTable':     "表示するデータがありません。",
            'info':           "_START_ ~ _END_ / _TOTAL_ 件中",
            'infoEmpty':      "0 ~ 0 / 0 件",
            'infoFiltered':   "(合計 _MAX_ 件からフィルタリングしています)",
            'infoPostFix':    "",
            'thousands':      ",",
            'lengthMenu':     "1ページ _MENU_ 件を表示する",
            'loadingRecords': "読み込み中...",
            'processing':     "処理中...",
            'search':         "絞り込み:",
            'zeroRecords':    "一致するデータが見つかりません。",
            'paginate': {
                'first':      "最初",
                'last':       "最後",
                'next':       "次",
                'previous':   "前"
            }
        }
    });

	// テーブル行クリックの設定
	$('#myTable tbody').on("click", "tr", function() {
		if ($(this).find('.dataTables_empty').length == 0) {
			var owner = $(this);
			$("#myTable tr").removeClass("active");
			owner.addClass("active");
			$("#updateButton").prop("disabled", false);
			$("#deleteButton").prop("disabled", false);
		}
	});

    // 検索ボタンクリック時の処理
    $("#searchButton").on("click", function() {
        $('#myTable').DataTable().ajax.url("/search").load();
        $('#myTable').DataTable().ajax.reload();
    });

    //
    $("#registButton").on("click", function() {
        // ダイアログ表示
        $('#form').on('show.bs.modal', function (event) {

            // コントロール制御
			$("#form #dialogTitle").text("新規登録");
			$("#form #sendRegistButton").show();
			$("#form #sendUpdateButton").hide();
            $("#form #inputNo").prop("disabled", true);

            // フォーカス
            setTimeout(function(){
                $("#inputNo").focus();
            }, 500);

        }).modal("show");
    });

    $("#sendRegistButton").on("click", function(){
        var param = {
            no : $("#inputNo").val()
            , name : $("#inputName").val()
            , sex : $("#inputSex").val()
            , age : $("#inputAge").val()
            , kind_cd : $("#inputKind").val()
            , favorite : $("#inputFavorite").val()
        }

        $.ajax({
            url: "http://localhost:8080/regist",
            type: "POST",
            data: JSON.stringify(param),
            success: function(jsonResponse) {
                jsonResponse = jsonResponse.replace( /\\/g , "" );
                var data = JSON.parse(jsonResponse);

                // テーブル更新
                $('#myTable').DataTable().ajax.url("/search").load();
                $('#myTable').DataTable().ajax.reload();

                // フォームを閉じる
                $("#form").modal("hide");
            },
            error: function() {
            }
        });
    });

    $("#updateButton").on("click", function() {

        var selectedRows = $('#myTable').DataTable().rows('.active').data();

        var param = {
            no : selectedRows[0].no
        }

        $.ajax({
            url: "http://localhost:8080/getRecord",
            type: "POST",
            data: JSON.stringify(param),
            success: function(jsonResponse) {
                var data = JSON.parse(jsonResponse);
                var cat = data[0];

                // ダイアログ表示
                $('#form').on('show.bs.modal', function (event) {

                    // 取得したデータのセット
                    $("#inputNo").val(cat.no);
                    $("#inputName").val(cat.name);
                    $("#inputSex").val(cat.sex);
                    $("#inputAge").val(cat.age);
                    $("#inputKind").val(cat.kind_cd);
                    $("#inputFavorite").val(cat.favorite);

                    // コントロール制御
                    $("#form #dialogTitle").text("更新");
                    $("#form #sendRegistButton").hide();
                    $("#form #sendUpdateButton").show();
                    $("#form #inputNo").prop("disabled", true);
                    setTimeout(function(){
                        $("#inputName").focus();
                    }, 500);


                }).modal("show");

            },
            error: function() {
            }
        });
    });

    $("#sendUpdateButton").on("click", function(){
        var param = {
            no : $("#inputNo").val()
            , name : $("#inputName").val()
            , sex : $("#inputSex").val()
            , age : $("#inputAge").val()
            , kind_cd : $("#inputKind").val()
            , favorite : $("#inputFavorite").val()
        }

        $.ajax({
            url: "http://localhost:8080/update",
            type: "POST",
            data: JSON.stringify(param),
            success: function(jsonResponse) {
                jsonResponse = jsonResponse.replace( /\\/g , "" );
                var data = JSON.parse(jsonResponse);

                // テーブル更新
                $('#myTable').DataTable().ajax.url("/search").load();
                $('#myTable').DataTable().ajax.reload();

                // フォームを閉じる
                $("#form").modal("hide");
            },
            error: function() {
            }
        });
    });

    $("#deleteButton").on("click", function(){
        var selectedRows = $('#myTable').DataTable().rows('.active').data();

        var param = {
            no : selectedRows[0].no
        }

        $.ajax({
            url: "http://localhost:8080/delete",
            type: "POST",
            data: JSON.stringify(param),
            success: function(jsonResponse) {
                // テーブル更新
                $('#myTable').DataTable().ajax.url("/search").load();
                $('#myTable').DataTable().ajax.reload();
            },
            error: function() {
            }
        });

    });

});

サーバー

主な変更点

1.MySQLUtilクラスを追加してデータ操作の処理をまとめる
2.登録・更新・削除に対応するメソッドの追加

MySQLUtil.py

import mysql.connector
import logging
from contextlib import closing


class MySQLUtil:
    """
    MySQL 操作用クラス
    """

    def __init__(self, host="localhost", port="3306", user="USER01", password="USER01", database="DB01"):
        self.config = {
            "host": host,
            "port": port,
            "user": user,
            "password": password,
            "database": database
        }

    def insert_data(self, data):
        """
        データを登録します
        :param data:
        :return:
        """

        with closing(mysql.connector.connect(**self.config)) as conn:

            c = conn.cursor()
            # データ登録
            sql = "INSERT INTO TBLCAT VALUES (%s,%s,%s,%s,%s,%s)"
            c.execute(sql, data)

            c.close()
            conn.commit()

    def update_data(self, data):
        """
        データを更新します
        :param data:
        :return:
        """

        with closing(mysql.connector.connect(**self.config)) as conn:

            c = conn.cursor()
            # データ登録
            sql = "UPDATE TBLCAT SET NAME = %s, " \
                  " SEX = %s, " \
                  " AGE = %s, " \
                  " KIND_CD = %s, " \
                  " FAVORITE = %s " \
                  "WHERE " \
                  " NO = %s"

            c.execute(sql, data)

            c.close()
            conn.commit()

    def delete_data(self, no):
        """
        データを削除します
        :return:
        """

        logging.info("delete_data")
        with closing(mysql.connector.connect(**self.config)) as conn:

            c = conn.cursor()

            # データクリア
            sql = "DELETE FROM TBLCAT WHERE NO = '" + no + "'"
            c.execute(sql)
            c.close()
            conn.commit()

    def get_data(self, no="", search_name=""):
        """
        データを取得します
        :return:
        """
        result = []

        with closing(mysql.connector.connect(**self.config)) as conn:

            c = conn.cursor(dictionary=True)

            # SQL組み立て
            sql = "SELECT C.NO, C.NAME, C.SEX, C.AGE, C.KIND_CD, K.KIND_NAME, C.FAVORITE FROM TBLCAT C"
            sql += " LEFT OUTER JOIN MSTKIND K ON ( C.KIND_CD = K.KIND_CD)"
            if no != "":
                sql += " WHERE NO = '" + no + "'"
            else:
                sql += " WHERE C.NAME LIKE '" + search_name + "%'"

            sql += " ORDER BY NO"

            c.execute(sql)
            for r in c.fetchall():
                result.append({
                    "no": r['NO'],
                    "name": r['NAME'],
                    "sex": r['SEX'],
                    "age": r['AGE'],
                    "kind_cd": r['KIND_CD'],
                    "kind_name": r['KIND_NAME'],
                    "favorite": r['FAVORITE'],
                })

        return result

    def get_next_no(self):
        """
        ユーザー毎にカレンダーIDの最大値+1を返します
        :return:
        """
        result = 0

        with closing(mysql.connector.connect(**self.config)) as conn:

            c = conn.cursor(dictionary=True)

            sql = "SELECT MAX(NO) + 1 AS NO FROM TBLCAT"

            c.execute(sql)

            result = c.fetchone()

        return result[r"NO"]

 

Main.py

import json
import logging
import os
import tornado.ioloop
import mysql.connector

from tornado.web import RequestHandler
from tornado.options import options
from contextlib import closing

from Utils.MySQLUtil import MySQLUtil

class MainHandler(RequestHandler):
    """
    画面表示
    """

    def get(self):
        logging.info("MainHandler [get]")

        self.render("index.html")


class InitHandler(RequestHandler):
    """
    一覧初期化用
    """

    def post(self):
        list = []
        result = {
            'data': list
        }

        self.write(json.dumps(result, ensure_ascii=False))


class SearchHandler(RequestHandler):
    """
    データ検索
    """
    def initialize(self):
        logging.info("SearchHandler [initialize]")

    def post(self):
        """
        データをJSON形式で返します
        :return:
        """
        logging.info("SearchHandler [post]")

        search_name = self.get_argument("searchName")
        mysql = MySQLUtil()
        data_list = mysql.get_data(search_name=search_name)

        result = {
            'data': data_list
        }

        self.write(json.dumps(result, ensure_ascii=False))


class GetRecordHandler(RequestHandler):
    """
    プライマリキーを指定してデータ取得
    """

    def initialize(self):
        logging.info("GetRecordHandler [initialize]")

    def post(self):
        logging.info("GetRecordHandler [post]")
        param = json.loads(self.request.body)

        no = str(param["no"])

        mysql = MySQLUtil()
        result = mysql.get_data(no=no)

        self.write(json.dumps(result, ensure_ascii=False))


class RegistHandler(RequestHandler):
    """
    データ登録
    """

    def initialize(self):
        logging.info("RegistHandler [initialize]")

    def post(self):
        logging.info("RegistHandler [post]")

        mysql = MySQLUtil()

        param = json.loads(self.request.body)
        no = mysql.get_next_no()

        data = [
            no,
            param["name"],
            param["sex"],
            param["age"],
            param["kind_cd"],
            param["favorite"],
        ]
        mysql.insert_data(data)

        result = {
            'result': "success"
        }

        self.write(json.dumps(result, ensure_ascii=False))


class UpdateHandler(RequestHandler):
    """
    更新
    """
    def initialize(self):
        logging.info("UpdateHandler [initialize]")

    def post(self):
        logging.info("UpdateHandler [post]")

        mysql = MySQLUtil()

        param = json.loads(self.request.body)

        data = [
            param["name"],
            param["sex"],
            param["age"],
            param["kind_cd"],
            param["favorite"],
            param["no"],
        ]
        mysql.update_data(data)

        result = {
            'result': "success"
        }

        self.write(json.dumps(result, ensure_ascii=False))


class DeleteHandler(RequestHandler):
    """
    削除
    """

    def initialize(self):
        logging.info("RegistHandler [initialize]")

    def post(self):

        logging.info("RegistHandler [post]")

        mysql = MySQLUtil()

        param = json.loads(self.request.body)
        no = str(param["no"])

        mysql.delete_data(no)

        result = {
            'result': "success"
        }

        self.write(json.dumps(result, ensure_ascii=False))

app = tornado.web.Application([
    (r"/", MainHandler),
    (r"/init", InitHandler),
    (r"/search", SearchHandler),
    (r"/getRecord", GetRecordHandler),
    (r"/regist", RegistHandler),
    (r"/update", UpdateHandler),
    (r"/delete", DeleteHandler),
],
    template_path=os.path.join(os.getcwd(), "templates"),
    static_path=os.path.join(os.getcwd(), "static"),
)

if __name__ == "__main__":
    options.parse_command_line()
    app.listen(8080)
    logging.info("server started")
    tornado.ioloop.IOLoop.instance().start()

操作してみる

検索

検索後の画面
検索後の画面

登録

登録ダイアログ
登録ダイアログ

親画面の「登録」ボタンをクリックで表示。

内容を入力してダイアログの「登録」ボタンをクリック。

登録後
登録後

No5にデータが追加されました。ちゃんと件数表示も更新されています。

更新

更新ダイアログ
更新ダイアログ

親画面で No5 を選択後「更新」ボタンをクリックで表示。内容は一度DBに問い合わせてフォームにセットしています。

種別を変更して「修正」ボタンをクリック。

更新後
更新後

一覧の種別も変更されました。

削除

削除
削除

No5 を選択して「削除」ボタンをクリック。

削除後
削除後

No5 が一覧から削除されました。

まとめ

ちょっと長くなってしまいましたが、一通り、DataTables と サーバーとの通信はできましたね。

ここまで出来れば、ちょっとしたシステムにも組み込むことができそうです。

DataTablesはまだまだ色々出来そうなので、また時間が出来たら試してみたいと思います。

ではでは。

 

スポンサーリンク


関連するコンテンツ

Javascript,Python,開発DataTables,JQuery

Posted by doradora