前提
現在、独学でDjangoを用いてチャットアプリを作成しようと試みています。
まず、こちらの記事(https://www.hiramine.com/programming/chat_django_channels/index.html)を参考にチャットを作成し、これはうまく動かすことができました。これを作成するとき初めてWebSocketを扱いました。
実現したいこと
上記ページでは、チャットの参加画面とチャット画面を同じページで動作させているため、ページ分けたい(ルーム名の入力等をentrance.html、実際のチャットをchat.htmlで行うイメージ)と思い変更を加えているのですが、チャット画面で思うように結果が表示されず、質問いたしました。
発生している問題・エラーメッセージ
上記URLの内容から、entrance.htmlとchat.htmlに分け、またjsをmain.jsとし、static fileから読み込む形にしました。
しかし、Join Chat , Send等のボタンを押す度にwebsocketがリダイレクトされてしまい、うまく接続ができていないように思います。
(認識違いかもしれません。)
main.jsとして分けるものではないのかもしれませんが、entranceで入力されたユーザー名等をそのままチャット画面でも表示したいと思っているため、このようにしています。ほかに方法があればそちらで問題ありません。
該当のソースコード
https://github.com/myyyy38/chat_1
掲載すべきコードが大量になってしまうため、githubに上げました。申し訳ありません。
試したこと
自分なりにdjangoでチャットを作成されている方のページを拝見しましたが、解決には至りませんでした。
補足情報(FW/ツールのバージョンなど)
python: 3.9.5
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答1件
0
ベストアンサー
【エラーの状況】
まず、質問のソースを実行してConsoleで確認してみました。
entranceページで「Join Chat」ボタンをクリックすると、一瞬下記のエラーが出てchatページに遷移しています。
Uncaught TypeError: Cannot read properties of null (reading 'value') at onsubmitButton_Send (main.js:63:48) at HTMLElement.onsubmit ((index):41:75)
遷移後のchatページの username や roomname のinputは空白の状態で、「Send」ボタンをクリックすると上記と同様なエラーが出ています。
ここで、エラー2行目のonsubmitButton_Send (main.js:63:48)
は、main.jsの
js
1 function onsubmitButton_JoinChat(){ 2 console.log("joinが押されました") 3 //ユーザー名 4 let strInputUserName = g_elementInputUserName.value; // <<<ここ
が該当していました。すなわち、g_elementInputUserName
がnullということです。
g_elementInputUserName
の定義をさかのぼると、main.jsの4行目で
const g_elementInputUserName = document.getElementById("input_username");
となっており、エラー状況から考えると document.getElementById("input_username") がnullを返してきていることになります。
【エラーの原因】
おそらく、main.jsの冒頭1行目以降が、chat.html内要素のレンダリング前に実行されているため、g_elementInputUserName などのグローバル変数は null を保持してしまっているのでしょう。
【修正の検討】
では、window.onload =~
等に、グローバル変数(g_***)への値代入処理部分を入れてしまえばよいのでは、と思われるかもしれませんが、これでは解決しません。
なぜならば、たとえレンダリング後に document.getElementById を行い、entrance.html内の要素内の値や、webソケットオブジェクト(g_socket)を適切に取得できたとしても、chat.htmlにページが遷移したら、staticフォルダから同じ内容のmain.js が新規に読み込まれ、それまで保持していたグローバル変数にはアクセスできなくなるからです。
したがって、修正案として次のようなアプローチが考えられます。
・entrance.html で入力したusername と roomname は、フォームデータの形で サーバーに送信する。
・サーバーは、受け取った username と roomname を設定した chat.html を返す。
・クライアント側でのwebソケットの管理はchat.html内のjsだけで行う。(entranceとchatの2ベージでmain.jsを使い回さない)
注:下記は元コードからの変更を少なくするため、最適化されていません。文字数の都合上、不要なログ表示やコメントを削除していますが、修正に影響のない部分のコードはそのまま残しています。
・entrance.html
-> フォームとして username と roomname を submit します。
html
1{% extends 'chat/base.html' %} 2{% load static %} 3{% block content %} 4<div id="div_container"> 5 <div id="div_header"> 6 <h1>My Chat</h1> 7 </div> 8 9 <div id="div_main"> 10 <div id="div_join_screen"> 11 <form action="{% url 'chat:chat'%}" method="post" style="text-align: center; width:100%;"> 12 {% csrf_token %} 13 User name<br /> 14 <input type="text" name="username" placeholder="Enter User name" autofocus><br /><br /> 15 Room name<br /> 16 <input type="text" name="room" placeholder="Enter Room name" autofocus><br /><br /> 17 Room name must be a string containing only ASCII alphanumerics, hyphens, or periods.<br /><br /> 18 <button type="submit">Join Chat</button> 19 </form> 20 </div> 21 </div> 22</div> 23{% endblock %}
・chat.html
-> views.py から受け取った username、room を input に表示するとともに、
main.jsの内容の大半をこちらの中のscriptに移行。
onopen() の中でチャット開始処理を行なっています。
html
1{% extends 'chat/base.html' %} 2{% load static %} 3{% block content %} 4<div id="div_container"> 5 <div id="div_header"> 6 <h1>My Chat</h1> 7 </div> 8 9 <div id="div_main"> 10 <div id="div_chat_screen"> 11 <form action="{% url 'chat:entrance'%}" onclick="onclickButton_LeaveChat()"> 12 <button type="submit">Leave Chat</button> 13 </form><br /> 14 15 Room name : <input type="text" id="text_roomname" value="{{room}}" readonly="readonly"><br /> 16 User name : <input type="text" id="text_username" value="{{username}}" readonly="readonly"><br /> 17 18 <form action="" onsubmit="onsubmitButton_Send();return false;"> 19 Message : <input type="text" id="input_message" autocomplete="off" autofocus /><input type="submit" 20 value="Send" /> 21 </form> 22 <ul id="list_message"></ul> 23 </div> 24 </div> 25</div> 26<script> 27 28 let ws_scheme = window.location.protocol == "https:" ? "wss" : "ws"; 29 const g_socket = new WebSocket(ws_scheme + "://" + window.location.host + "/ws/chat/"); 30 31 function onclickButton_LeaveChat() { 32 //メッセージリストのクリア 33 while (document.getElementById("list_message").firstChild) { 34 document.getElementById("list_message").removeChild(document.getElementById("list_message").firstChild); 35 } 36 document.getElementById("text_username").value = ""; 37 g_socket.send(JSON.stringify({ "data_type": "leave" })); 38 } 39 40 function onsubmitButton_Send() { 41 let strMessage = document.getElementById("input_message").value; 42 if (!strMessage) { 43 return; 44 } 45 g_socket.send(JSON.stringify({ "message": strMessage })); 46 console.log("Sendが実行されました") 47 console.log(JSON.stringify({ "message": strMessage })) 48 document.getElementById("input_message").value = ""; 49 return false; 50 } 51 g_socket.onopen = (event) => { 52 console.log("websocket open") 53 g_socket.send(JSON.stringify({ "data_type": "join", "username": document.getElementById("text_username").value, "roomname": document.getElementById("text_roomname").value })); 54 } 55 56 g_socket.onmessage = (event) => { 57 if (!document.getElementById("text_username").value) { 58 return; 59 } 60 61 let data = JSON.parse(event.data); 62 let strMessage = data["datetime"] + "-[" + data["username"] + "]-" + data["message"]; 63 let elementLi = document.createElement("li"); 64 elementLi.textContent = strMessage; 65 document.getElementById("list_message").prepend(elementLi); 66 } 67 68 g_socket.onclose = (event) => { 69 console.error("Unexpected: Chat socket closed."); 70 } 71</script> 72 73{% endblock %}
・base.html
main.jsの内容はchat.html内に移したので、static ディレクトリからの読み込み部分を削除。(main.js自体も不要なので削除)
(main.jsはstaticディレクトリ内に残しておき、上記chat.html内に書いたスクリプトに修正の上、下記main.jsの読み込み行をbase.htmlからchat.htmlに移してもよいと思います)
diff
1 <body> 2 <!-- Optional JavaScript --> 3- <script type="text/javascript" src="{% static 'js/main.js' %}"></script> <===削除 4 <!-- jQuery first, then Popper.js, then Bootstrap JS --> 5 <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> 6 (略)
・consumers.py
strGroupName が空だとchannel側でエラーが出るので設定。
diff
1(略) 2 #websocket接続時の処理 3 async def connect(self): 4 #groupに参加 5+ self.strGroupName = 'chat' 6(略)
・views.py
entranceからsubmitされたデータ(username, room)を、chat.htmlの各inputに設定します。
py
1(略) 2def entrance(request): 3 print("entrance実行") 4 return render(request,'chat/entrance.html') 5 6def chat(request): 7 print("chat実行") 8 params = { 9 "username" : request.POST.get("username"), 10 "room" : request.POST.get("room"), 11 } 12 return render(request,'chat/chat.html', params)
元の記事のコードだと、1ページの中でentranceとチャットページを管理するようになっていましたが、それを無理やり2つに分けるとなると苦労しますね。
django-channelsの公式チュートリアルには、URLでルームを分けるというアプローチでのコードが丁寧に解説されているので、そちらも参照されてはいかがでしょうか。
https://channels.readthedocs.io/en/stable/tutorial/index.html
検索すると、ログイン認証まで考慮した実践的なコードも見つかりました。
https://zenn.dev/y_k/articles/e8878460fff3d5aa1d1d
投稿2022/03/26 21:00
編集2022/03/26 23:43退会済みユーザー
総合スコア0
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2022/03/30 04:57
2022/04/07 04:08