【Python】AdminLTEとWebSocketでチャット機能を作ってみる その4(トーク切り替え)
おはようございます。
前回に引き続き、チャットの実装をしていきます。
今回は、コンタクトリストを選択してチャット相手を切り替えられるようにしたいと思います。
プログラムは前回のものを流用します。
【Python】AdminLTEとWebSocketでチャット機能を作ってみる その3(データ表示、登録)
スポンサーリンク
画面の修正
初期表示はメッセージを表示せずに、
コンタクトリストをクリックしてメッセージを表示するように修正。
画面
main.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"xml:lang="ja"lang="ja"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible"content="IE=edge"> <meta http-equiv="content-type"content="text/html; charset=UTF-8"> <title>チャットサンプル</title> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"name="viewport"> <link rel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet"href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet"href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic"> <link rel="stylesheet"href="{{ static_url('css/AdminLTE.min.css') }}"> <link rel="stylesheet"href="{{ static_url('css/style.css') }}"> <link rel="stylesheet"href="{{ static_url('css/skins/skin-blue.min.css') }}"> </head> <body class="hold-transition fixed"> <form id="logoutForm"action="/logout"method="get"> {% module xsrf_form_html() %} </form> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- ヘッダ情報 --> <div class="navbar-header"style="padding:15px;"> <input type="hidden"id="user_id"value="{{ user_id }}" /> <input type="hidden"id="user_name"value="{{ user_name }}" /> ユーザー名:{{ user_name }} </div> <!-- リストの配置 --> <ul class="nav navbar-nav"> <li class="active"><a href="#">チャット</a></li> <li><a href="#">メニュー1</a></li> <li><a href="#">メニュー2</a></li> </ul> <!-- ボタン --> <button type="button"class="pull-right btn btn-default navbar-btn"onclick="logout();"> ログアウト <span class="fa fa-sign-out"></span> </button> </div> </nav> <section class="content container-fluid"> <div class="row"> <div class="col-xs-8"> <div class="row"> <div class="col-xs-8"> <div id="chat-panel"class="box box-warning direct-chat direct-chat-warning box-solid"style="display:none;"> <input type="hidden"id="room_no" /> <div class="box-header with-border"> <h3 class="box-title">チャットメッセージ</h3></span> <div class="box-tools pull-right"> <span data-toggle="tooltip"title="new message"class="badge bg-red">1</span> <button type="button"class="btn btn-box-tool"data-widget="collapse"><i class="fa fa-minus"></i></button> <button type="button"class="btn btn-box-tool"data-toggle="tooltip"title="Contacts"data-widget="chat-pane-toggle"> <i class="fa fa-comments"></i> </button> <button type="button"class="btn btn-box-tool"data-widget="remove"><i class="fa fa-times"></i></button> </div> </div> <div class="box-body"> <div class="direct-chat-messages"> <span class="blank-msg">チャットする相手を選択してください。</span> </div> <div class="direct-chat-contacts"> <ul class="contacts-list"> {% for user in relation_users %} <li id="user_{{ user['user_id'] }}"> <a href="javascript:room_select('{{ user['room_no'] }}', '{{ user['user_id'] }}');"> <img class="contacts-list-img"src="static/img/{{ user['icon'] }}"alt="User Image"> <div class="contacts-list-info"> <span class="contacts-list-name"> {{ user['user_name'] }} <small class="contacts-list-date pull-right">{{ user['update_date'] }}</small> </span> <span class="contacts-list-msg">{{ user['message'] }}</span> </div> </a> </li> {% end %} </ul> </div> </div> <div class="box-footer"> <form action="#"method="post"> <div class="input-group"> <textarea id="message"type="text"name="message"placeholder="メッセージを入力してください"class="form-control"></textarea> <span class="input-group-btn"> <button id="sendButton"type="button"class="btn btn-disable btn-flat btn-sm"disabled>送信</button> </span> </div> </form> </div> </div> </div> </div> </div> </div> </section> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.19/jquery-ui.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.js"></script> <script src="{{ static_url('js/adminlte.min.js') }}"></script> <script src="{{ static_url('js/script.js') }}"></script> <script> $(document).ready(function(){ initialize(); }); </script> </body> </html> |
スタイル
style.css
次のクラスを追加
1 2 3 4 | .blank-msg { font-size:28px; color:#cccccc; } |
プログラム
新規追加
まだ作成していなかったテーブル用のDAOを追加
Dao/MstRoomDao.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | fromDao.SqlClient importSqlClient classMstRoomDao(SqlClient): """ MSTチャットルームDAO """ defselect_next_room_no(self): """ 次の部屋番号を返します. :return: """ sql="SELECT MAX(ROOM_NO) + 1 AS ROOM_NO FROM MST_ROOM" data=super().select_one(sql) returndata["ROOM_NO"] defcreate_new_room(self,room_no,room_name,user_id): """ 部屋を作成します :param data: :return: """ record=[ room_no ,room_name ,"" ,super().get_datetime() ,user_id ,super().get_datetime() ,None ,None ] sql="INSERT INTO MST_ROOM VALUES (%s,%s,%s,%s,%s,%s,%s,%s)" super().execute(sql,record) |
Dao/MstUserRelationDao.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | fromDao.SqlClient importSqlClient classMstUserRelationDao(SqlClient): """ MSTユーザ関係DAO """ defupdate_room_no(self,current_user,target_user,room_no): """ 部屋番号を更新します. :param current_user: :param target_user: :param room_no: :return: """ sql="UPDATE MST_USER_RELATION" sql+=" SET" sql+=" ROOM_NO = %(room_no)s" sql+=" , UPDATE_USER = %(current_user)s" sql+=" , UPDATE_DATE = '"+super().get_datetime()+"'" sql+=" WHERE (" sql+=" USER_ID = %(current_user)s AND" sql+=" USER_ID2 = %(target_user)s" sql+=" ) OR (" sql+=" USER_ID = %(target_user)s AND" sql+=" USER_ID2 = %(current_user)s" sql+=" )" super().execute(sql,{ "room_no":room_no ,"current_user":current_user ,"target_user":target_user }) |
Dao/TblChatRoomMemberDao.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | fromDao.SqlClient importSqlClient classTblChatRoomMemberDao(SqlClient): """ チャットメンバーDAO """ definsert_member(self,room_no,current_user_id,user_id): """ チャットメンバーを登録します :param room_no: :param current_user_id: :param user_id: :return: """ record=[ room_no ,user_id ,current_user_id ,super().get_datetime() ,None ,None ] sql="INSERT INTO TBL_CHATROOM_MEMBER VALUES(%s,%s,%s,%s,%s,%s)" super().execute(sql,record) |
既存クラスの修正
クエリを一部修正
Dao/MstUserDao.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | defselect_relation_user(self,user_id): """ 関係ユーザーを取得します :param user_id: :return: """ sql="SELECT" sql+=" R.ROOM_NO" sql+=" , M.ROOM_NAME" sql+=" , U.* " sql+="FROM" sql+=" MST_USER_RELATION R" sql+=" LEFT OUTER JOIN MST_ROOM M ON (" sql+=" M.ROOM_NO = R.ROOM_NO" sql+=" )" sql+=" LEFT OUTER JOIN MST_USER U ON (" sql+=" U.USER_ID = R.USER_ID OR" sql+=" U.USER_ID = R.USER_ID2" sql+=" ) " sql+="WHERE" sql+=" U.USER_ID <> %(user_id)s AND (" sql+=" R.USER_ID = %(user_id)s OR R.USER_ID2 = %(user_id)s" sql+=" )" result=[] data=super().select(sql,{ "user_id":user_id }) forrindata: result.append(self.mapping_data(r)) returnresult |
DAO利用宣言の追加
Main.py
1 2 3 | from Dao.MstRoomDao import MstRoomDao from Dao.MstUserRelationDao import MstUserRelationDao from Dao.TblChatRoomMemberDao import TblChatRoomMemberDao |
メイン処理を修正し、コンタクトリスト選択時の処理を追加
Main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | classMainHandler(AuthBaseHandler): """ 初期表示処理 """ definitialize(self): logging.info("[MainHandler] initialize") @tornado.web.authenticated defget(self): logging.info("[MainHandler] get") user_id=self.get_current_user() user_dao=MstUserDao() user=user_dao.select_user(user_id) user_name=user['user_name'] relation_users=user_dao.select_relation_user(user_id) self.render("main.html", user_id=user_id, user_name=user_name, relation_users=relation_users) classSelectHandler(AuthBaseHandler): """ チャット相手を選択した際の処理 """ definitialize(self): logging.info("SelectHandler [initialize]") defpost(self): param=json.loads(self.request.body) room_no=param["room_no"] current_user=self.get_current_user() target_user=param["user_id"] # まだ部屋が作成されていない場合 ifroom_no=="0": # 部屋の作成 room_dao=MstRoomDao() room_no=room_dao.select_next_room_no() room_dao.create_new_room(room_no,"個別",current_user) # メンバーの登録 member_dao=TblChatRoomMemberDao() member_dao.insert_member(room_no,current_user,current_user) member_dao.insert_member(room_no,current_user,target_user) # 部屋番号の更新 relation_dao=MstUserRelationDao() relation_dao.update_room_no(current_user,target_user,room_no) # メッセージの取得 message_dao=TblChatMessageDao() chat_messages=message_dao.select_message_by_room_no(room_no) data={ "room_no":room_no ,"user_id":target_user ,"messages":chat_messages } self.write(json.dumps(data,ensure_ascii=False)) application=tornado.web.Application([ (r"/login",AuthLoginHandler), (r"/logout",AuthLogoutHandler), (r"/main",MainHandler), (r"/chat",ChatHandler), (r"/select",SelectHandler), ], template_path=os.path.join(os.getcwd(), "templates"), static_path=os.path.join(os.getcwd(), "static"), login_url="/login", cookie_secret="adfaskljfwepmaldskf:as;k", xsrf_cookies=True ) |
クライアント側の処理も修正。
初期表示、ソケット処理を修正、コンタクトリスト選択時の処理を追加
script.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | /** * 初期処理. */ functioninitialize(){ // 通信ソケットオープン socket.onopen=function(data){ $("#status").css("color","#FFFFFF"); $("#status").text(" [オンライン]"); } // 通信ソケットクローズ socket.onclose=function(){ // 初期表示に戻してオフラインとする $(".direct-chat-messages").empty(); $(".direct-chat-messages").append( $("<span>",{ "class":"blank-msg" ,"text":"ネットワークが繋がっていません。" }) ); $("#room_no").val(0); } // メッセージ受信 socket.onmessage=function(e){ console.log(e.data); vardata=JSON.parse(e.data); $(".direct-chat-messages").append(createMessage(data)); $(".direct-chat-messages").animate({ scrollTop:$(".direct-chat-messages")[0].scrollHeight },500); } // ボタンにイベントを追加 $("#sendButton").click(function(){ sendMessage(); }); // チャットの表示を一番下に $("#chat-panel").show(); $(".direct-chat-messages")[0].scrollTop=$(".direct-chat-messages")[0].scrollHeight; } /** * メッセージを送信. */ functionsendMessage(){ // メッセージが未入力の場合は送信しない if(!$("#message").val()){ return; } // 送信日時 varnow=newmoment(); varsend_date=now.format("YYYY-MM-DD HH:mm:ss") // パラメータ varparam={ "user_id":$("#user_id").val() ,"room_no":$("#room_no").val() ,"message":$("#message").val() ,"send_date":send_date } socket.send(JSON.stringify(param)); } /** * チャット相手選択イベント. */ functionroom_select(room_no,user_id){ varparam={ room_no:room_no ,user_id:user_id } $.ajax({ url:"http://localhost:8888/select", type:"POST", headers:{'X-XSRFToken':$("*[name=_xsrf]")[0].value}, data:JSON.stringify(param), success:function(jsonResponse){ jsonResponse=jsonResponse.replace(/\\/g,""); vardata=JSON.parse(jsonResponse); // 部屋番号の書き換え $("#room_no").val(data.room_no); // コンタクトリストの書き換え $("#user_"+data.user_id+" a").attr("href","javascript:room_select("+data.room_no+", "+data.user_id+");"); // メッセージの表示 $(".direct-chat-messages").empty(); for(row indata.messages){ $(".direct-chat-messages").append(createMessage(data.messages[row])); } $(".direct-chat-messages").animate({ scrollTop:$(".direct-chat-messages")[0].scrollHeight },0); $("#chat-panel").removeClass("direct-chat-contacts-open"); $("#sendButton").addClass("btn-warning"); $("#sendButton").removeClass("btn-disable"); $("#sendButton").prop("disabled",false); }, error:function(){ } }); } |
起動してみる
まとめ
とりあえず、配信部分もトークルームの番号で振り分けるようなイメージで進んでいます。
あと数回でチャット編は終わりにしようと思いますが、
この後は未読の仕組みとグループトークの仕組みをいれていければと思います。
ではでは。
ディスカッション
コメント一覧
まだ、コメントがありません