テーマ
こんにちは!naoto555です。
前回に引き続き、Laravel+JavaScriptで非同期型のチャットを作成する方法をご紹介します。
前回、コントローラとビューの作成および送信したメッセージをデータベースに登録する処理までを作成しましたので、今回は非同期でメッセージの更新をしていく部分を作成していこうと思います。前回の記事はこちらになりますのでまだ読まれてない方は、あわせてお読みくだされば幸いです。下の動画は完成図です。(2024.2記載)
開発環境
統合開発環境 | aws cloud9 |
PHP | 8.1.17 |
Laravel | 9.52.4 |
login機能 | Laravel/jetstream(livewire) |
Database | SQlite(version 3.7.17) |
解説
①コントローラの修正(メッセージ送信部分)
前回、フォームPOST送信時にコントローラから’送信完了’というメッセージをJSONで返していましたが、今回は登録したメッセージの内容をそのままビューに返す処理を追加します。そしてビューに送られてきたJSONをJavaScriptでメッセージ追加すれば、画面遷することなく送信したメッセージがリアルタイムにチャット画面に反映される仕組みです。
app/Http/Controllers/ChatController.php
public function store(Request $request)
{
$chat = new Chat();
$chat->send = $request->input('send');
$chat->recieve = $request->input('recieve');
$chat->message = $request->input('message');
$chat->save();
$param = [
'done' => '送信完了',
'id' => $chat->id,
'message' => $chat->message,
'date' => $chat->created_at->format('Y-m-d H:i'),
];
return response()->json($param);
}
ビューとJSの編集(送信メッセージ部分)①
次に、コントローラのstoreファンクションから受け取ったJSONデータを使って、画面更新する仕組みを作っていきます。前回作成したビューの"#chat_content1"
(User1のチャット画面)末尾に、非表示のdummyメッセージのDIVを作成します。このDIV要素全体をJavaScriptでコピーしてそこに新規メッセージを書いていこうと思います。
resources/views/chats/showchat.blade.php(HTML一部抜粋)
<div class="chat_content" id="chat_content1">
<!----------------省略---------------->
<div class="my_message" id="my_message_dummy1" style="display:none">
<div class="chatting">
<p class="message"></p>
<p class="date"></p>
</div>
</div>
</div>
resources/views/chats/showchat.blade.php(JavaScript一部抜粋)
//-------------------中略-------------------
function new_message1() {
//-------------------中略-------------------
.then(response => response.json())
.then(data => {
//テキストエリアのクリア
var textarea = document.querySelector("#send_message_form1 > textarea");
textarea.value = "";
//コンソールに「送信完了」のメッセージを表示
console.log(data.done);
//送信メッセージのダミー要素をコピー
var dummy_message = document.querySelector('#my_message_dummy1');
var new_message = dummy_message.cloneNode(true);
//複製したメッセージを編集
new_message.id = "my_messag" + data.id;
//新規メッセージを貼り付け
var chat_content = document.getElementById('chat_content1');
chat_content.append(new_message);
//JSONから受け取ったデータを貼付けていく
var message_content = document.querySelector('#my_messag' + data.id);
message_content.style.display = "block";
var message_p = document.querySelector('#my_messag' + data.id + "> div > p.message");
message_p.textContent = data.message;
var date = document.querySelector('#my_messag' + data.id + "> div > p.date");
date.textContent = data.date;
})
//-------------------以下、省略-------------------
"#my_message_dummy1"
要素をコピーして、それを貼り付け、コントローラからJSONで受け取ったメッセージと日付をpタグに書き込んでいます。貼り付ける要素のIDがコピー元のIDと同じにならないようにコピー時に"new_message.id = "message" + data.id;"
でDIVのIDを書き換えています。
↓は動作確認している動画です。少し分かりにくいですが、メッセージの送信ボタンを押した際に画面遷移せずメッセージが更新されています。
②ビューとJSの編集(送信メッセージ部分)②
User2の送信メッセージに関しても同様の変更を加えていきます。
resources/views/chats/showchat.blade.php(HTML一部抜粋)
<div class="chat_content" id="chat_content2">
<h3>User2</h3>
@foreach($messages as $message)
@if($message->send == $user2->id)
<div class="my_message">
<div class="chatting">
<p class="message">{{ $message->message }}</p>
<p class="date">{{ $message->created_at->format('Y-m-d H:i') }}<p>
<span style="display:none;">{{ $message->id }}</span>
</div>
</div>
@else
<div class="others_message">
<div class="chatting">
<p class="message">{{ $message->message }}</p>
<p class="date">{{ $message->created_at->format('Y-m-d H:i') }}<p>
<input class="message_id2" style="display:none;" value={{ $message->id }}>
</div>
</div>
@endif
@endforeach
<div class="my_message" id="my_message_dummy2" style="display:none">
<div class="chatting">
<p class="message"></p>
<p class="date"></p>
</div>
</div>
</div>
resources/views/chats/showchat.blade.php(JavaScript一部抜粋)
function new_message2() {
var form = document.getElementById("send_message_form2");
var formData = new FormData(form);
formData.append("_token", document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
fetch('{{ route('chats.store') }}', {
method: 'POST',
headers: {'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')},
body: formData
})
.then(response => response.json())
.then(data => {
//テキストエリアのクリア
var textarea = document.querySelector("#send_message_form2 > textarea");
textarea.value = "";
//コンソールに「送信完了」のメッセージを表示
console.log(data.done);
//送信メッセージのダミー要素をコピー
var dummy_message = document.querySelector('#my_message_dummy2');
var new_message = dummy_message.cloneNode(true);
//複製したメッセージを編集
new_message.id = "my_messag" + data.id;
//新規メッセージを貼り付け
var chat_content2 = document.getElementById('chat_content2');
chat_content2.append(new_message);
//JSONから受け取ったデータを貼付けていく
var message_content = document.querySelector('#my_messag' + data.id);
message_content.style.display = "block";
var message_p = document.querySelector('#my_messag' + data.id + "> div > p.message");
message_p.textContent = data.message;
var date = document.querySelector('#my_messag' + data.id + "> div > p.date");
date.textContent = data.date;
})
.catch(error => {
console.error('Error:', error);
});
}
これで送信メッセージの表示更新部分の非同期処理は完成です。しかしまだ、新規受信メッセージがあっても表示が更新しません。
次は新規受信メッセージがあったときに更新する仕組みを実装していきます。
④コントローラの編集
続いて、受信メッセージをJSONで返すためのファンクションをコントローラーに追加していきます。
app/Http/Controllers/ChatController.php
public function recieve_message(Request $request)
{
//ビューで表示されている最新受信メッセージのIDを取得
$latest_message_id = $request->input('message_id');
//送信者と受信者のUser_idをビューから取得
$send = $request->input('send');
$recieve = $request->input('recieve');
//データベースに登録のある最新の受信メッセージを取得
$latest_message = Chat::where('send', $send)->where('recieve', $recieve)->latest()->first();
//データベースに登録のある最新の受信メッセージのIDを取得
if(is_Null($latest_message)){ //まだメッセージがひとつもないときは0
$latest_id = 0;
}else{
$latest_id = $latest_message->id;
}
//JSONでレスポンスを返す
if($latest_id > $latest_message_id){
$message = Chat::where('id', '>', $latest_message_id)->where('send', $send)->where('recieve', $recieve)->first();
$param = [
'new_message' => true, //表示されてないメッセージがあるか?
'id' => $message->id,
'message' => $message->message,
'date' => $message->created_at->format('Y-m-d H:i'),
];
else{
$param = [
'new_message' => false, //表示されてないメッセージがあるか?
];
}
return response()->json($param);
}
チャット画面で表示される最新の受信メッセージIDを取得し、データベースからそのID以降に登録された受信メッセージがあるかどうかを調べています。
受信メッセージがある場合は、メッセージの内容と送信日をビューに返します。
具体的には、チャット画面上の最新受信メッセージのIDと、データベースに登録されている最新受信メッセージのIDを比較します。
チャット画面上の最新の受信メッセージのIDよりもデータベースに登録されている最新の受信メッセージのIDのほうが大きければ、表示されていない受信メッセージがあると判断し最新の受信メッセージの内容と送信日をJSON形式で返します。
⑤ルーティングの設定
web.phpを編集してURIを設定していきます。
use App\Http\Controllers\ChatController;
//----------------------------------------中略----------------------------------------
Route::post('/showchat/recieve_message', [ChatController::class, 'recieve_message'])->name('chats.recieve_message');
⑥ビューとJSの編集(受信メッセージ部分)①
送信と同様に、ダミーの受信メッセージを作りそれをコピペ・編集して新規受信メッセージを作っていきます。”<div class="chat_content" id="chat_content1">
“の末尾にコピー用のダミーメッセージを追加します。
resources/views/chats/showchat.blade.php
<div class="chat_content" id="chat_content1">
<!-------------------------省略------------------------->
<div class="others_message" id="others_message_dummy1" style="display:none">
<div class="chatting">
<p class="message"></p>
<p class="date"></p>
<input class="message_id1_dummy" style="display:none;">
</div>
</div>
</div>
受信メッセージ部分の表示更新機能をJavaScriptで作成していきます。
JavaScriptにてチャット上の最新メッセージのIDと送信者、受信者のUser_idをコントローラのrecieve_message
アクションに送り、新規の受信メッセージがあればダミーの受信メッセージをコピペし、コピペしてデータをコントローラから受け取ったJSONデータをもとに編集していきます。
resources/views/chats/showchat.blade.php(JavaScriptt一部抜粋)
var timerId = NaN;
window.onload = function(){
timerId = setInterval(tick, 1500);
}
function tick(){
clearInterval(timerId);
recieve_message1();
recieve_message2();
timerId = setInterval(tick, 1500);
}
function recieve_message1(){
//最新メッセージのIDと送信者、受信者のUser_idを取得
var ids = document.querySelectorAll('.message_id1');
var length = ids.length - 1;
var latest_message_id = ((ids.length > 0)) ? ids[length].value : 0;
var formData = new FormData;
formData.set('message_id', latest_message_id);
formData.set('send', 2);
formData.set('recieve', 1);
formData.append("_token", document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
fetch('{{ route('chats.recieve_message') }}', {
method: 'POST',
headers: {'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')},
body: formData
})
.then(response => response.json())
.then(data => {
if(data.new_message){
//console.log('新規メッセージあり');
//送信メッセージのダミー要素のコピー
var dummy_message = document.querySelector('#others_message_dummy1');
var new_message = dummy_message.cloneNode(true);
//複製したメッセージを編集
new_message.id = "others_message" + data.id;
//新規メッセージを貼り付け
var chat_content = document.getElementById('chat_content1');
chat_content.append(new_message);
//JSONから受け取ったデータを貼付けていく
var message_content = document.querySelector('#others_message' + data.id);
message_content.style.display = "block";
var message_p = document.querySelector('#others_message' + data.id + "> div > p.message");
message_p.textContent = data.message;
var date = document.querySelector('#others_message' + data.id + "> div > p.date");
date.textContent = data.date;
var message_id = document.querySelector('#others_message' + data.id + "> div > input");
message_id.setAttribute('value', data.id);
message_id.className = "message_id1";
}else{
//console.log('新規メッセージなし');
}
})
.catch(error => {
console.error('Error:', error);
});
}
⑦ビューとJSの編集(受信メッセージ部分)②
先ほどと同様に、ダミーの受信メッセージを作りそれをコピペ・編集して新規受信メッセージを作っていきます。”<div class="chat_content" id="chat_content2">
“の末尾にコピー用のダミーメッセージを追加します。
resources/views/chats/showchat.blade.php
<div class="chat_content" id="chat_content2">
<!-------------------------省略------------------------->
<div class="others_message" id="others_message_dummy1" style="display:none">
<div class="chatting">
<p class="message"></p>
<p class="date"></p>
<input class="message_id1_dummy" style="display:none;">
</div>
</div>
</div>
次に、受信メッセージ部分の表示更新機能をJavaScriptで作成していきます。
JavaScriptにてチャット上の最新メッセージのIDと送信者、受信者のUser_idをコントローラのrecieve_message
アクションに送り、新規の受信メッセージがあればダミーの受信メッセージをコピペし、コピペしてデータをコントローラから受け取ったJSONデータをもとに編集していきます。
resources/views/chats/showchat.blade.php(JavaScriptt一部抜粋)
function recieve_message2(){
//最新メッセージのIDと送信者、受信者のUser_idを取得
var ids = document.querySelectorAll('.message_id2');
var length = ids.length - 1;
var latest_message_id = ((ids.length > 0)) ? ids[length].value : 0;
var formData = new FormData;
formData.set('message_id', latest_message_id);
formData.set('send', 1);
formData.set('recieve', 2);
formData.append("_token", document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
fetch('{{ route('chats.recieve_message') }}', {
method: 'POST',
headers: {'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')},
body: formData
})
.then(response => response.json())
.then(data => {
if(data.new_message){
//console.log('新規メッセージあり');
//送信メッセージのダミー要素のコピー
var dummy_message = document.querySelector('#others_message_dummy2');
var new_message = dummy_message.cloneNode(true);
//複製したメッセージを編集
new_message.id = "others_message" + data.id;
//新規メッセージを貼り付け
var chat_content = document.getElementById('chat_content2');
chat_content.append(new_message);
//JSONから受け取ったデータを貼付けていく
var message_content = document.querySelector('#others_message' + data.id);
message_content.style.display = "block";
var message_p = document.querySelector('#others_message' + data.id + "> div > p.message");
message_p.textContent = data.message;
var date = document.querySelector('#others_message' + data.id + "> div > p.date");
date.textContent = data.date;
var message_id = document.querySelector('#others_message' + data.id + "> div > input");
message_id.setAttribute('value', data.id);
message_id.className = "message_id2";
}else{
//console.log('新規メッセージなし');
}
})
.catch(error => {
console.error('Error:', error);
});
}
完成図
上記手順にて、下図のような非同期型のチャットが完成しました。