Чат на Laravel 5.6 + Ratchet. Часть 2. Controller, View

Продолжаем создавать чат на 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']);
        }
    }

В следующей статье наконец-таки будем отправлять сообщения, получать их и выводить пуш-уведомления.

Первая часть
Третья часть
Четвертая часть

Добавить комментарий