Продолжаем создавать чат на Laravel 5.6 и Ratchet. Сегодня мы создадим основные функции отображения нашего чата и Вьюхи. Итак.
Создадим роут для нашей страницы с чатами.
Route::get('/conversations/{conversation_id?}', 'HomeController@conversations')->name('conversations');
В контроллере создадим функцию conversations:
public function conversations($conversation_id = 0) { $current_user = Auth::user(); $page_title = 'Обсуждения'; $projects = Projects::where('status', 1)->get(); $users = User::all(); $conversations = Conversations::where('is_visible', 1)->orderBy('updated_at', 'desc')->get(); foreach ($conversations as $conversation) { $conversation = Conversations::getConversationInfo($conversation); } if ($conversation = Conversations::find($conversation_id)) { $conversation = Conversations::getConversationInfo($conversation); if ($conversation->title) { $page_title = 'Обсуждение "' . $conversation->title . '"'; } else { $page_title = 'Обсуждение без темы'; } } else { $conversation = null; } return view( 'conversations', [ 'conversation' => $conversation, 'conversations' => $conversations, 'current_user' => $current_user, 'page_title' => $page_title, 'users' => $users ] ); }
В модели Conversations создадим функцию getConversationInfo:
public static function getConversationInfo($conversation) { if ($conversation->title) { $conversation->title_class = 'with-title'; } else { $conversation->title_class = 'without-title'; $conversation->title = 'Без темы'; } $conversation->messages = Messages::where('conversation_id', $conversation->id)->get(); foreach($messages as $message){ $message->author = User::find($message->author_id)->name; $message->class = ($message->author_id == Auth::user()->id) ? 'message-item-my' : 'message-item-other'; } $conversation->followers = Followers::getFollowers($conversation->id, 'conversation'); $conversation->is_follower = Followers::isFollower(Auth::user()->id, 'conversation', $conversation->id); $conversation->users = User::all(); foreach ($conversation->users as $user) { $user->followed_conversation = Followers::isFollower($user->id, 'conversation', $conversation->id); } return $conversation; }
В модели Followers создадим функцию getFollowers, которая будет получать всех пользователей, подписанных на данный чат:
public static function getFollowers($post_id, $type) { $users = []; $followers = Followers::where('type', $type)->where('post_id', $post_id)->get(); foreach ($followers as $key=>$follower){ $users[$key] = User::find($follower->follower_id); } return $users; }
и функцию, проверяющую — подписан ли текущий пользователь на данный чат:
public static function isFollower($user_id, $type, $post_id) { if (Followers::where('follower_id', $user_id) ->where('type', $type) ->where('post_id', $post_id) ->first()) { return true; } else { return false; } }
Создадим вью conversations.blade:
@extends('layouts.app') @section('content') {{csrf_field()}} <input type="hidden" name="current_user" value="{{Auth::user()->id}}" id="current-user"> <div class="container-fluid text-center page-header"> <h1>{{$page_title}}</h1> </div> <div class="container-fluid page-body"> <div class="row"> <div class="col-md-12"> @if($conversation != null) <!--если мы просматриваем отдельный чат--> <div class="panel panel-default conversations-item" id="conversations-item-{{$conversation->id}}"> <div class="panel-heading"> <h3> <a href="/conversations/{{$conversation->id}}" class="{{$conversation->title_class}}" id="conversation-title-{{$conversation->id}}"> {{$conversation->title}} </a> <input type="text" style="display: none" id="conversation-title-input-{{$conversation->id}}" class="conversation-title-input" value="{{$conversation->title}}" data-id="{{$conversation->id}}"> <div class="float-right conversation-header-button conversation-delete-button" data-id="{{$conversation->id}}" data-single="yes"> <i class="fa fa-trash-o" aria-hidden="true"></i> </div> <div class="float-right conversation-header-button conversation-edit-title-button" data-id="{{$conversation->id}}"> <i class="fa fa-pencil" aria-hidden="true"></i> </div> </h3> </div> <div class="panel-body"> <div class="message-block" id="message-block-{{$conversation->id}}"> @foreach($conversation->messages as $message) <div class="message-item {{$message->class}}"> <div class="message-heading"> <b>{{$message->author}}</b> <small>{{$message->created_at}}</small> </div> <div class="message-body"> {!! $message->text !!} </div> </div> @endforeach </div> @include('partials.create_message') @include('partials.followers_conversation') </div> </div> @else <!--если просматриваем список чатов - отобразим форму создания нового чата и список всех чатов--> <div class="panel panel-default"> <div class="panel-heading"> <h3>Новое обсуждение</h3> </div> <div class="panel-body"> @include('partials.create_conversation') </div> </div> <div class="conversations-block"> @forelse($conversations as $conversation) <div class="panel panel-default conversations-item" id="conversations-item-{{$conversation->id}}"> <div class="panel-heading"> <h3> <a href="/conversations/{{$conversation->id}}" class="{{$conversation->title_class}}" id="conversation-title-{{$conversation->id}}"> {{$conversation->title}} </a> <input type="text" style="display: none" id="conversation-title-input-{{$conversation->id}}" class="conversation-title-input" value="{{$conversation->title}}" data-id="{{$conversation->id}}"> <div class="float-right conversation-header-button conversation-delete-button" data-id="{{$conversation->id}}"> <i class="fa fa-trash-o" aria-hidden="true"></i> </div> <div class="float-right conversation-header-button conversation-edit-title-button" data-id="{{$conversation->id}}"> <i class="fa fa-pencil" aria-hidden="true"></i> </div> </h3> </div> <div class="panel-body"> <div class="message-block" id="message-block-{{$conversation->id}}"> @foreach($conversation->messages as $message) <div class="message-item {{$message->class}}"> <div class="message-heading"> <b>{{$message->author}}</b> <small>{{$message->created_at}}</small> </div> <div class="message-body"> {!! $message->text !!} </div> </div> @endforeach </div> @include('tasks.partials.create_message') @include('tasks.partials.followers_conversation') </div> </div> @empty <!--Здесь можно написать сообщение о том, что пока не создано ни одного чата--> @endforelse </div> @endif </div> </div> </div> <script src="{{asset('js/pages_scripts/conversations.js')}}"></script> @endsection
Создадим формы создания нового чата, создания сообщения и блок подписки. /resources/views/partials/create_conversation.blade.php
<input type="text" class="form-control" placeholder="Тема обсуждения" id="new-conversation-title"> <label for="new-conversation-message" class="for-textarea"> <div id="new-conversation-editor"> <textarea id="new-conversation-message" rows="1" class="form-control"></textarea> </div> </label> <button class="btn btn-primary" id="new-conversation-submit">Отправить</button> <div class="dialog-hover-block float-right conversation-button"> <span id="new-conversation-mention"> <i class="fa fa-at" aria-hidden="true"></i> </span> <ul class="dialog new-conversation-mention-dialog"> @foreach($users as $user) <li data-name="{{$user->name}}">{{$user->name}}</li> @endforeach </ul> </div>
/resources/views/partials/create_message.blade.php
<div id="message-editor-{{$conversation->id}}"> <textarea id="new-message-{{$conversation->id}}" rows="1" class="form-control message-textarea"></textarea> </div> <button class="btn btn-primary message-submit" id="message-submit-{{$conversation->id}}" data-conversation_id="{{$conversation->id}}"> Отправить </button> <div class="dialog-hover-block float-right conversation-button"> <span class="message-mention-button" id="message-mention-button-{{$conversation->id}}" data-conversation_id="{{$conversation->id}}"> <i class="fa fa-at" aria-hidden="true"></i> </span> <ul class="dialog mention-dialog" id="mention-dialog-{{$conversation->id}}"> @foreach($users as $user) <li data-name="{{$user->name}}" data-conversation_id="{{$conversation->id}}">{{$user->name}}</li> @endforeach </ul> </div>
/resources/views/partials/followers_conversation.blade.php
<hr/> <div class="row"> <div class="col-md-8"> Подписчики: <span class="followers-list"> @forelse($conversation->followers as $follower) <a href="#" class="users-item followers-item-{{$follower->id}}"> {{$follower->name}} </a> @empty @endforelse </span> <span class="dialog-hover-block"> <span class="add-follower-button"> <i class="fa fa-user-plus" aria-hidden="true" title="Добавить подписчика"></i> </span> <ul class="dialog followers-dialog"> @foreach($conversation->users as $user) @if($user->id != Auth::user()->id) <li data-user_id="{{$user->id}}" data-post_id="{{$conversation->id}}" data-type="conversation" class="follower-list-item-{{$user->id}}"> {{$user->name}} @if($user->followed_conversation) <i class="fa fa-minus-square-o toggle-follower" aria-hidden="true"></i> @else <i class="fa fa-plus-square-o toggle-follower" aria-hidden="true"></i> @endif </li> @endif @endforeach </ul> </span> </div> <div class="col-md-4"> <span class="follow-button-block float-right @if($conversation->is_follower == true) follow-button-block-active @else @endif" data-post_id="{{$conversation->id}}" data-type="conversation"> <i class="fa fa-bell-o" aria-hidden="true"></i> <span class="follow-button-text"> @if($conversation->is_follower == true) Отписаться @else Подписаться @endif </span> </span> </div> </div>
Создадим файл скриптов для нашего чата /public/js/conversations.js и начнем по порядку добавлять функционал. Создание нового чата:
$('#new-conversation-submit').click(function () { let user_id = $('#current-user').val(); let conversation_title = $('#new-conversation-title').val(); let message_text = $('#new-conversation-message').val(); if (message_text) { createConversationAjax(conversation_title, user_id, message_text); } else { alert('Введите текст сообщения'); } });
Напишем служебные функции для нашего Аякса:
function ajaxErrorsHandling(jqxhr, status, errorMsg){ $('.loading').hide(); if(jqxhr.status == 401){ $(location).attr('href', '/login/') } } function ajaxSetup() { $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); }
Функция создания нового чата:
function createConversationAjax(conversation_title, user_id, message_text) { $('.loading').show(); ajaxSetup(); $.ajax({ url : "/createConversationAjax/", dataType: "json", data : { conversation_title: conversation_title, user_id : user_id, message_text : message_text }, success : function (data) { $('.loading').hide(); $('#new-conversation-title').val(''); $('#new-conversation-message').val(''); let users_string = ''; for (var i = 0; i < data.success.users.length; i++) { users_string = users_string + '<li data-name="' + data.success.users[i].name + '" ' + 'data-conversation_id="' + data.success.conversation.id + '">' + data.success.users[i].name + '</li>' } let followers_string = ''; for (var i = 0; i < data.success.users.length; i++) { if (data.success.users[i].id != $('#current-user').val) { followers_string = followers_string + '<li data-user_id="' + data.success.users[i].id + '" data-post_id="' + data.success.conversation.id + '" data-type="conversation"' + ' class="follower-list-item-' + data.success.users[i].id + '">' + data.success.users[i].name + ' <i class="fa fa-plus-square-o toggle-follower" aria-hidden="true"></i>' + '</li>'; } } $('.conversations-block').prepend('' + '<div class="panel panel-default conversations-item" id="conversations-item-' + data.success.conversation.id + '">\n' + ' <div class="panel-heading">\n' + ' <h3>\n' + ' <a href="/conversations/' + data.success.conversation.id + '" class="' + data.success.conversation.title_class + '"\n' + ' id="conversation-title-' + data.success.conversation.id + '">\n' + data.success.conversation.title + ' </a>\n' + ' <input type="text" style="display: none" id="conversation-title-input-' + data.success.conversation.id + '"\n' + ' class="conversation-title-input" value="' + data.success.conversation.title + '"\n' + ' data-id="' + data.success.conversation.id + '">\n' + ' <div class="float-right conversation-header-button conversation-delete-button"\n' + ' data-id="' + data.success.conversation.id + '">\n' + ' <i class="fa fa-trash-o" aria-hidden="true"></i>\n' + ' </div>\n' + ' <div class="float-right conversation-header-button conversation-edit-title-button"\n' + ' data-id="' + data.success.conversation.id + '">\n' + ' <i class="fa fa-pencil" aria-hidden="true"></i>\n' + ' </div>\n' + ' </h3>' + ' </div>\n' + ' <div class="panel-body">\n' + ' <div class="message-block" id="message-block-' + data.success.conversation.id + '">\n' + ' <div class="message-item message-item-my">\n' + ' <div class="message-heading">\n' + ' <b>' + data.success.message.author + '</b>\n' + ' <small>' + data.success.message.created_at + '</small>\n' + ' </div>\n' + ' <div class="message-body">\n' + data.success.message.text + ' </div>\n' + attachment_string + ' </div>\n' + ' </div>\n' + ' <textarea id="new-message-' + data.success.conversation.id + '" rows="1" class="form-control" style="border: solid 1px #cccccc"></textarea>\n' + ' <button class="btn btn-primary message-submit" id="message-submit-' + data.success.conversation.id + '" ' + ' data-conversation_id="' + data.success.conversation.id + '">\n' + ' Отправить\n' + ' </button>\n' + ' <div class="dialog-hover-block float-right conversation-button">\n' + ' <span class="message-mention-button" id="message-mention-button-' + data.success.conversation.id + '"\n' + ' data-conversation_id="{{$conversation->id}}">\n' + ' <i class="fa fa-at" aria-hidden="true"></i>\n' + ' </span>\n' + ' <ul class="dialog mention-dialog" id="mention-dialog-' + data.success.conversation.id + '">\n' + users_string + ' </ul>\n' + ' </div>' + ' <hr/>\n' + ' <div class="row">\n' + ' <div class="col-md-8">\n' + ' Подписчики:\n' + ' <span class="followers-list" id="followers-list-' + data.success.conversation.id + '">\n' + ' </span>\n' + ' <span class="dialog-hover-block">\n' + ' <span class="add-follower-button">\n' + ' <i class="fa fa-user-plus" aria-hidden="true" title="Добавить подписчика"></i>\n' + ' </span>\n' + ' <ul class="dialog followers-dialog">\n' + followers_string + ' </ul>\n' + ' </span>' + ' </div>\n' + ' <div class="col-md-4">\n' + ' <span class="follow-button-block float-right"\n' + ' data-post_id="' + data.success.conversation.id + '" data-type="conversation">\n' + ' <i class="fa fa-bell-o" aria-hidden="true"></i>\n' + ' <span class="follow-button-text">\n' + ' Подписаться\n' + ' </span>\n' + ' </span>\n' + ' </div>\n' + ' </div>' + ' </div>\n' + '</div>' ); }, error: function(jqxhr, status, errorMsg) { ajaxErrorsHandling(jqxhr, status, errorMsg); } }); }
Создадим роут для Аякса:
Route::get('/createConversationAjax/', 'HomeController@createConversationAjax')->name('createConversationAjax');
В контроллере пишем:
public function createConversationAjax(Request $request) { $conversation = new Conversations; $conversation->title = $request->conversation_title; $conversation->owner_id = $request->user_id; $conversation->save(); $conversation = Conversations::getConversationInfo($conversation); $message = new Messages; $message->text = $request->message_text; $message->author_id = $request->user_id; $message->conversation_id = $conversation->id; $message->save(); $message->author = User::find($message->author_id)->name; $result['conversation'] = $conversation; $result['message'] = $message; $result['users'] = User::all(); if ($result) { echo json_encode(['success' => $result]); } else { echo json_encode(['fail' => 'Error occurred']); } }
Удаление чата:
body.on('click', '.conversation-delete-button', function () { let conversation_id = $(this).attr('data-id'); if (confirm('Удалить?')) { deleteConversationAjax(conversation_id); } });
function deleteConversationAjax(conversation_id) { $('.loading').show(); ajaxSetup(); $.ajax({ url : "/deleteConversationAjax/", dataType: "json", data : { conversation_id: conversation_id }, success : function () { $('.loading').hide(); $('#conversations-item-' + conversation_id).detach(); }, error: function(jqxhr, status, errorMsg) { ajaxErrorsHandling(jqxhr, status, errorMsg); } }); }
в /routes/web.php
Route::get('/deleteConversationAjax/', 'HomeController@deleteConversationAjax')->name('deleteConversationAjax');
В контроллере:
public function deleteConversationAjax(Request $request) { $conversation = Conversations::find($request->conversation_id); $conversation->is_visible = 0; $conversation->save(); if ($conversation) { echo json_encode(['success' => $conversation]); } else { echo json_encode(['fail' => 'Error occurred']); } }
При нажатии на заголовок чата, позволим его отредактировать:
body.on('click', '.conversation-edit-title-button', function () { let conversation_id = $(this).attr('data-id'); $('#conversation-title-' + conversation_id).hide(300); $('#conversation-title-input-' + conversation_id).show(300).focus(); });
body.on('focusout', '.conversation-title-input', function () { let conversation_id = $(this).attr('data-id'); $(this).hide(); $('#conversation-title-' + conversation_id).text($(this).val()).show(300); updateConversationAjax(conversation_id, $(this).val()); });
function updateConversationAjax(conversation_id, conversation_title) { $('.loading').show(); ajaxSetup(); $.ajax({ url : "/updateConversationAjax/", dataType: "json", data : { conversation_id : conversation_id, conversation_title: conversation_title }, success : function () { $('.loading').hide(); }, error: function(jqxhr, status, errorMsg) { ajaxErrorsHandling(jqxhr, status, errorMsg); } }); }
в /routes/web.php
Route::get('/updateConversationAjax/', 'HomeController@updateConversationAjax')->name('updateConversationAjax');
В контроллере:
public function updateConversationAjax(Request $request) { $conversation = Conversations::find($request->conversation_id); $conversation->title = $request->conversation_title; $conversation->save(); if ($conversation) { echo json_encode(['success' => $conversation]); } else { echo json_encode(['fail' => 'Error occurred']); } }
В следующей статье наконец-таки будем отправлять сообщения, получать их и выводить пуш-уведомления.