【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