Backend Development

Implementing A Chat App Using Laravel And Pusher

implementing a chat app with laravel and pusher

In this tutorial we will implement a real time chat application using laravel framework and laravel broadcasting using the pusher library as it supports a real time events and channels.

 

Laravel broadcasting based internally on Web Sockets but the advantages of using it is that it facilitates all the hard work of web sockets because implementing web sockets from scratch require a lot of work like special server to handle connections etc, you can read more about implementing a socket server in this article.

In fact laravel handles broadcasting using the event listener pattern so before starting you should have knowledge of laravel events and listeners you can more about them here.

There are a variety of drivers that can be used inside broadcasting some of them are pusher, redis, and log. In this tutorial we will be using Pusher.

 

Preparing The App

Let’s create a new laravel project, in this tutorial i will be using laravel 5.5 so initiate this command in the terminal:

composer create-project laravel/laravel chat  "5.5.*" --prefer-dist

After create a new database and update your .env file with the database credentials like that:

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=<put your db name>
DB_USERNAME=<put your db username>
DB_PASSWORD=<put your db password>

 

Creating Tables

When thinking about the tables, we need two have a users table and another table for the messages, fortunately laravel provides the users migration out of the box, so all we need is a messages migration:

php artisan make:migration create_messages_table

 

Next open database/migrations/xxxxx_create_migrations_table.php and update it as shown below:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMessagesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('from_user');
            $table->unsignedInteger('to_user');
            $table->text('content');

            $table->timestamps();

            $table->foreign('from_user')->references('id')->on('users');
            $table->foreign('to_user')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('messages');
    }
}

Now run

php artisan migrate

After creating the tables let’s proceed to prepare the models.

 

Creating Models

As we already have a User model we need only to create a new model for the messages table so initiate this command in the terminal:

php artisan make:model Message

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    protected $table = "messages";
}

Note there is no need to create a model for users as laravel already created to us out of the box.

 

Generating Authentication

Run this command to generate authentication scaffolding:

php artisan make:auth

Now the auth routes be sure that the redirectTo in LoginController, RegisterController, and ResetPasswordController like this:

protected $redirectTo = '/';

 

Preparing Pusher

To get the pusher credentials go to https://pusher.com/ and create a new account, then create a new app and get the credentials as shown in this image and update your .env file.

implementing a chat app with laravel and pusher

 

PUSHER_APP_ID=<insert app id>
PUSHER_APP_KEY=<insert app key>
PUSHER_APP_SECRET=<insert app secret>
PUSHER_APP_CLUSTER=<insert cluster>

 

 

Preparing Chat UI

We will use bootstrap for that, a chat box contains the messages in the top and text input along with a button in the bottom area in much the same way the facebook chat looks like as shown in this image:

building a chat app

 

Project Structure

implementing a chat app with laravel and pusherimplementing a chat app with laravel and pusherimplementing a chat app with laravel and pusher

 

 

 

 

 

 

 

 

As you see in the image above this is our project file structure, so let’s first create

app/Lib/PusherFactory.php

<?php

namespace App\Lib;


use Pusher\Pusher;

class PusherFactory
{
    public static function make()
    {
        return new Pusher(
            env("PUSHER_APP_KEY"), // public key
            env("PUSHER_APP_SECRET"), // Secret
            env("PUSHER_APP_ID"), // App_id
            array(
                'cluster' => env("PUSHER_APP_CLUSTER"), // Cluster
                'encrypted' => true,
            )
        );
    }
}

This simple class just return a new instance of Pusher to be used by other classes to subscribe and fire events.

 

create a new css file public/css/chat.css and update it like this:

p {
    font-size: 13px;
    padding: 5px;
    border-radius: 3px;
}

.base_receive p {
    background: #4bdbe6;
}

.base_sent p {
    background: #e674a8;
}

time {
    font-size: 11px;
    font-style: italic;
}

#login-box {
    margin-top: 20px
}

#chat_box {
    position: fixed;
    top: 10%;
    right: 5%;
    width: 27%;
}

.close-chat {
    margin-top: -17px;
    cursor: pointer;
}

.chat_box {
    margin-right: 25px;
    width: 310px;
}

.chat-area {
    height: 400px;
    overflow-y: scroll;
}

#users li {
    margin-bottom: 5px;
}

#chat-overlay {
    position: fixed;
    right: 0%;
    bottom: 0%;
}

.glyphicon-ok {
    color: #42b7dd;
}

.loader {
    -webkit-animation: spin 1000ms infinite linear;
    animation: spin 1000ms infinite linear;
}
@-webkit-keyframes spin {
    0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    }
    100% {
        -webkit-transform: rotate(359deg);
        transform: rotate(359deg);
    }
}

For the sound and avatar you can download them from here.

Updating main layout

Open resources/views/layouts/app.blade.php and update it like below:

<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>Chat App</title>

    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
    <link rel="stylesheet" href="{{ asset('css/chat.css') }}" />
    <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.0/js/bootstrap.min.js"></script>
    <script>
        var base_url = '{{ url("/") }}';
    </script>
</head>
<body>
<div class="container-fluid">
    @yield('content')
</div>

<div id="chat-overlay" class="row"></div>

    <audio id="chat-alert-sound" style="display: none">
        <source src="{{ asset('sound/facebook_chat.mp3') }}" />
    </audio>
    @yield('script')
</body>
</html>

This is a simple layout we just added a div with id=’chat-verlay‘ this div will contain the chat boxes and also added audio element that represent the chat alert sound.

 

Next create a new view resources/views/chat-box.blade.php

<div id="chat_box" class="chat_box pull-right" style="display: none">
    <div class="row">
        <div class="col-xs-12 col-md-12">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title"><span class="glyphicon glyphicon-comment"></span> Chat with <i class="chat-user"></i> </h3>
                        <span class="glyphicon glyphicon-remove pull-right close-chat"></span>
                    </div>
                    <div class="panel-body chat-area">

                    </div>
                    <div class="panel-footer">
                        <div class="input-group form-controls">
                            <textarea class="form-control input-sm chat_input" placeholder="Write your message here..."></textarea>
                            <span class="input-group-btn">
                                    <button class="btn btn-primary btn-sm btn-chat" type="button" data-to-user="" disabled>
                                        <i class="glyphicon glyphicon-send"></i>
                                        Send</button>
                                </span>
                        </div>
                    </div>
                </div>
        </div>
    </div>
    <input type="hidden" id="to_user_id" value="" />
</div>

The code above represents a blueprint of the chat box template, this template will be cloned according to the selected user.

 

Displaying home users

As we enter into a chat application we first see a list of users to chat with so let’s display them now:

open app/Http/Controllers/HomeController.php and update it like this:

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $users = User::where('id', '!=', Auth::user()->id)->get();

        return view('home', compact('users'));
    }
}

Then update the routes/web.php as follows:

Route::get('/', 'HomeController@index');

Auth::routes();

Finally update resources/views/home.blade.php with this code:

@extends('layouts.app')

@section('content')
    <div class="row">
        <div class="col-md-5">
            @if($users->count() > 0)
                <h3>Pick a user to chat with</h3>
                <ul id="users">
                    @foreach($users as $user)
                        <li><span class="label label-info">{{ $user->name }}</span> <a href="javascript:void(0);" class="chat-toggle" data-id="{{ $user->id }}" data-user="{{ $user->name }}">Open chat</a></li>
                    @endforeach
                </ul>
            @else
                <p>No users found! try to add a new user using another browser by going to <a href="{{ url('register') }}">Register page</a></p>
            @endif
        </div>
    </div>

    @include('chat-box')

    <input type="hidden" id="current_user" value="{{ \Auth::user()->id }}" />
    <input type="hidden" id="pusher_app_key" value="{{ env('PUSHER_APP_KEY') }}" />
    <input type="hidden" id="pusher_cluster" value="{{ env('PUSHER_APP_CLUSTER') }}" />
@stop

@section('script')
    <script src="https://js.pusher.com/4.1/pusher.min.js"></script>
    <script src="{{ asset('js/chat.js') }}"></script>

@stop

As shown above we looped over the users to display them a long with a link that will open the chat box, currently you will see no users but try to register new users using a another browser. We also included the chat-box template and pusher javascript api and some hidden fields to be used by our javascript code.

Opening the chat box

Create a new controller called MessagesController so run this command in the terminal:

php artisan make:controller MessagesController

 

Now open app/Http/Controllers/MessagesController.php and update it as shown below:

<?php

namespace App\Http\Controllers;

use App\Lib\PusherFactory;
use App\Message;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class MessagesController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }


    /**
     * getLoadLatestMessages
     *
     *
     * @param Request $request
     */
    public function getLoadLatestMessages(Request $request)
    {
        if(!$request->user_id) {
            return;
        }

        $messages = Message::where(function($query) use ($request) {
            $query->where('from_user', Auth::user()->id)->where('to_user', $request->user_id);
        })->orWhere(function ($query) use ($request) {
            $query->where('from_user', $request->user_id)->where('to_user', Auth::user()->id);
        })->orderBy('created_at', 'ASC')->limit(10)->get();

        $return = [];

        foreach ($messages as $message) {

            $return[] = view('message-line')->with('message', $message)->render();
        }


        return response()->json(['state' => 1, 'messages' => $return]);
    }
}

In the code above we first create a new action getLoadLatestMessages, this action will be called when we open the chat box for the first time to fetch the latest messages between the two users.

 

Open resources/views/message-line.blade.php and update it as follows:

@if($message->from_user == \Auth::user()->id)

    <div class="row msg_container base_sent" data-message-id="{{ $message->id }}">
        <div class="col-md-10 col-xs-10">
            <div class="messages msg_sent text-right">
                <p>{!! $message->content !!}</p>
                <time datetime="{{ date("Y-m-dTH:i", strtotime($message->created_at->toDateTimeString())) }}">{{ $message->fromUser->name }} • {{ $message->created_at->diffForHumans() }}</time>
            </div>
        </div>
        <div class="col-md-2 col-xs-2 avatar">
            <img src="{{ url('images/user-avatar.png') }}" width="50" height="50" class="img-responsive">
        </div>
    </div>

@else

    <div class="row msg_container base_receive" data-message-id="{{ $message->id }}">
        <div class="col-md-2 col-xs-2 avatar">
            <img src="{{ url('images/user-avatar.png') }}" width="50" height="50" class=" img-responsive ">
        </div>
        <div class="col-md-10 col-xs-10">
            <div class="messages msg_receive text-left">
                <p>{!! $message->content !!}</p>
                <time datetime="{{ date("Y-m-dTH:i", strtotime($message->created_at->toDateTimeString())) }}">{{ $message->fromUser->name }} • {{ $message->created_at->diffForHumans() }}</time>
            </div>
        </div>
    </div>

@endif

The above code represents a single message sent or received by the user.

 

update routes/web.php:

Route::get('/', 'HomeController@index');

Route::get('/load-latest-messages', 'MessagesController@getLoadLatestMessages');

Auth::routes();

 

Finally we need to update public/js/chat.js with this code:

$(function () {
   let pusher = new Pusher($("#pusher_app_key").val(), {
        cluster: $("#pusher_cluster").val(),
        encrypted: true
    });

    let channel = pusher.subscribe('chat');


    // on click on any chat btn render the chat box
   $(".chat-toggle").on("click", function (e) {
       e.preventDefault();

       let ele = $(this);

       let user_id = ele.attr("data-id");

       let username = ele.attr("data-user");

       cloneChatBox(user_id, username, function () {

           let chatBox = $("#chat_box_" + user_id);

           if(!chatBox.hasClass("chat-opened")) {

               chatBox.addClass("chat-opened").slideDown("fast");

               loadLatestMessages(chatBox, user_id);

               chatBox.find(".chat-area").animate({scrollTop: chatBox.find(".chat-area").offset().top + chatBox.find(".chat-area").outerHeight(true)}, 800, 'swing');
           }
       });
   });

   // on close chat close the chat box but don't remove it from the dom
   $(".close-chat").on("click", function (e) {

       $(this).parents("div.chat-opened").removeClass("chat-opened").slideUp("fast");
   });
});


/**
 * loaderHtml
 *
 * @returns {string}
 */
function loaderHtml() {
    return '<i class="glyphicon glyphicon-refresh loader"></i>';
}

/**
 * getMessageSenderHtml
 *
 * this is the message template for the sender
 *
 * @param message
 * @returns {string}
 */
function getMessageSenderHtml(message)
{
    return `
           <div class="row msg_container base_sent" data-message-id="${message.id}">
        <div class="col-md-10 col-xs-10">
            <div class="messages msg_sent text-right">
                <p>${message.content}</p>
                <time datetime="${message.dateTimeStr}"> ${message.fromUserName} • ${message.dateHumanReadable} </time>
            </div>
        </div>
        <div class="col-md-2 col-xs-2 avatar">
            <img src="` + base_url +  '/images/user-avatar.png' + `" width="50" height="50" class="img-responsive">
        </div>
    </div>
    `;
}

/**
 * getMessageReceiverHtml
 *
 * this is the message template for the receiver
 *
 * @param message
 * @returns {string}
 */
function getMessageReceiverHtml(message)
{
    return `
           <div class="row msg_container base_receive" data-message-id="${message.id}">
           <div class="col-md-2 col-xs-2 avatar">
             <img src="` + base_url +  '/images/user-avatar.png' + `" width="50" height="50" class="img-responsive">
           </div>
        <div class="col-md-10 col-xs-10">
            <div class="messages msg_receive text-left">
                <p>${message.content}</p>
                <time datetime="${message.dateTimeStr}"> ${message.fromUserName}  • ${message.dateHumanReadable} </time>
            </div>
        </div>
    </div>
    `;
}


/**
 * cloneChatBox
 *
 * this helper function make a copy of the html chat box depending on receiver user
 * then append it to 'chat-overlay' div
 *
 * @param user_id
 * @param username
 * @param callback
 */
function cloneChatBox(user_id, username, callback)
{
    if($("#chat_box_" + user_id).length == 0) {

        let cloned = $("#chat_box").clone(true);

        // change cloned box id
        cloned.attr("id", "chat_box_" + user_id);

        cloned.find(".chat-user").text(username);

        cloned.find(".btn-chat").attr("data-to-user", user_id);

        cloned.find("#to_user_id").val(user_id);

        $("#chat-overlay").append(cloned);
    }

    callback();
}

/**
 * loadLatestMessages
 *
 * this function called on load to fetch the latest messages
 *
 * @param container
 * @param user_id
 */
function loadLatestMessages(container, user_id)
{
    let chat_area = container.find(".chat-area");

    chat_area.html("");

    $.ajax({
        url: base_url + "/load-latest-messages",
        data: {user_id: user_id, _token: $("meta[name='csrf-token']").attr("content")},
        method: "GET",
        dataType: "json",
        beforeSend: function () {
            if(chat_area.find(".loader").length  == 0) {
                chat_area.html(loaderHtml());
            }
        },
        success: function (response) {
            if(response.state == 1) {
                response.messages.map(function (val, index) {
                    $(val).appendTo(chat_area);
                });
            }
        },
        complete: function () {
            chat_area.find(".loader").remove();
        }
    });
}

This code is strait forward first we created a new pusher instance and subscribed to the chat channel, we will supply this channel in the server code shortly then we added a handler for chat-toggle link. The process is to make a copy of the chatbox html template according to the selected user exactly like facebook chat when you click on any user a chat box opened, so we accomplished this by using this function:

function cloneChatBox(user_id, username, callback)
{
    if($("#chat_box_" + user_id).length == 0) {

        let cloned = $("#chat_box").clone(true);

        // change cloned box id
        cloned.attr("id", "chat_box_" + user_id);

        cloned.find(".chat-user").text(username);

        cloned.find(".btn-chat").attr("data-to-user", user_id);

        cloned.find("#to_user_id").val(user_id);

        $("#chat-overlay").append(cloned);
    }

    callback();
}

After we cloned and opened the chat box we loaded the latest messages between the currently logged in user and the selected user using loadLatestMessages function.

Sending and receiving messages:

Open app/Http/Controllers/MessagesController.php and add this method:

/**
     * postSendMessage
     *
     * @param Request $request
     */
    public function postSendMessage(Request $request)
    {
        if(!$request->to_user || !$request->message) {
            return;
        }

        $message = new Message();

        $message->from_user = Auth::user()->id;

        $message->to_user = $request->to_user;

        $message->content = $request->message;

        $message->save();


        // prepare some data to send with the response
        $message->dateTimeStr = date("Y-m-dTH:i", strtotime($message->created_at->toDateTimeString()));

        $message->dateHumanReadable = $message->created_at->diffForHumans();

        $message->fromUserName = $message->fromUser->name;

        $message->from_user_id = Auth::user()->id;

        $message->toUserName = $message->toUser->name;

        $message->to_user_id = $request->to_user;

        PusherFactory::make()->trigger('chat', 'send', ['data' => $message]);

        return response()->json(['state' => 1, 'data' => $message]);
    }

In the above method, the most important part is pusherFactory::make()->trigger(), the trigger fires a new event with data and it takes three parameters [channel name, event name, array of data].

Now update routes/web.php:

<?php


Route::get('/', 'HomeController@index');

Route::get('/load-latest-messages', 'MessagesController@getLoadLatestMessages');

Route::post('/send', 'MessagesController@postSendMessage');


Auth::routes();

Finally update public/js/chat.js:

$(function () {
   
    ....
    
    
    // on change chat input text toggle the chat btn disabled state
    $(".chat_input").on("change keyup", function (e) {
       if($(this).val() != "") {
           $(this).parents(".form-controls").find(".btn-chat").prop("disabled", false);
       } else {
           $(this).parents(".form-controls").find(".btn-chat").prop("disabled", true);
       }
    });


    // on click the btn send the message
   $(".btn-chat").on("click", function (e) {
       send($(this).attr('data-to-user'), $("#chat_box_" + $(this).attr('data-to-user')).find(".chat_input").val());
   });

   // listen for the send event, this event will be triggered on click the send btn
    channel.bind('send', function(data) {
        displayMessage(data.data);
    });
});

/**
 * send
 *
 * this function is the main function of chat as it send the message
 *
 * @param to_user
 * @param message
 */
function send(to_user, message)
{
    let chat_box = $("#chat_box_" + to_user);
    let chat_area = chat_box.find(".chat-area");

    $.ajax({
        url: base_url + "/send",
        data: {to_user: to_user, message: message, _token: $("meta[name='csrf-token']").attr("content")},
        method: "POST",
        dataType: "json",
        beforeSend: function () {
            if(chat_area.find(".loader").length  == 0) {
                chat_area.append(loaderHtml());
            }
        },
        success: function (response) {
        },
        complete: function () {
            chat_area.find(".loader").remove();
            chat_box.find(".btn-chat").prop("disabled", true);
            chat_box.find(".chat_input").val("");
            chat_area.animate({scrollTop: chat_area.offset().top + chat_area.outerHeight(true)}, 800, 'swing');
        }
    });
}

/**
 * This function called by the send event triggered from pusher to display the message
 *
 * @param message
 */
function displayMessage(message)
{
    let alert_sound = document.getElementById("chat-alert-sound");

    if($("#current_user").val() == message.from_user_id) {

        let messageLine = getMessageSenderHtml(message);

        $("#chat_box_" + message.to_user_id).find(".chat-area").append(messageLine);

    } else if($("#current_user").val() == message.to_user_id) {

        alert_sound.play();

        // for the receiver user check if the chat box is already opened otherwise open it
        cloneChatBox(message.from_user_id, message.fromUserName, function () {

            let chatBox = $("#chat_box_" + message.from_user_id);

            if(!chatBox.hasClass("chat-opened")) {

                chatBox.addClass("chat-opened").slideDown("fast");

                loadLatestMessages(chatBox, message.from_user_id);

                chatBox.find(".chat-area").animate({scrollTop: chatBox.find(".chat-area").offset().top + chatBox.find(".chat-area").outerHeight(true)}, 800, 'swing');
            } else {

                let messageLine = getMessageReceiverHtml(message);

                // append the message for the receiver user
                $("#chat_box_" + message.from_user_id).find(".chat-area").append(messageLine);
            }
        });
    }
}

As shown in the code when we click the send btn it will call the send function to make an ajax request to the server, after that comes the magic in this line:

// listen for the send event, this event will be triggered on click the send btn
    channel.bind('send', function(data) {
        displayMessage(data.data);
    });

This code listens for the send event that we already subscribed it in the server previously so every time you send a message to server the server fires an event which in turn get caught in the javascript code, and get displayed using displayMessage() function.

 

Handling old messages on scroll up:

If we need to fetch the old messages on scrolling up like facebook we can accomplish that using the first message created date so

open app/Http/Controllers/MessagesController.php  and add this method:

/**
     * getOldMessages
     *
     * we will fetch the old messages using the last sent id from the request
     * by querying the created at date
     *
     * @param Request $request
     */
    public function getOldMessages(Request $request)
    {
        if(!$request->old_message_id || !$request->to_user)
            return;

        $message = Message::find($request->old_message_id);

        $lastMessages = Message::where(function($query) use ($request, $message) {
            $query->where('from_user', Auth::user()->id)
                ->where('to_user', $request->to_user)
                ->where('created_at', '<', $message->created_at);
        })
            ->orWhere(function ($query) use ($request, $message) {
            $query->where('from_user', $request->to_user)
                ->where('to_user', Auth::user()->id)
                ->where('created_at', '<', $message->created_at);
        })
            ->orderBy('created_at', 'ASC')->limit(10)->get();

        $return = [];

        if($lastMessages->count() > 0) {

            foreach ($lastMessages as $message) {

                $return[] = view('message-line')->with('message', $message)->render();
            }

            PusherFactory::make()->trigger('chat', 'oldMsgs', ['to_user' => $request->to_user, 'data' => $return]);
        }

        return response()->json(['state' => 1, 'data' => $return]);
    }

Yet a gain we fired a new event called ‘oldMsgs‘.

Now update routes/web.php:

<?php

Route::get('/', 'HomeController@index');

Route::get('/load-latest-messages', 'MessagesController@getLoadLatestMessages');

Route::post('/send', 'MessagesController@postSendMessage');

Route::get('/fetch-old-messages', 'MessagesController@getOldMessages');

Auth::routes();

Then open public/js/chat.js and update it as follows:

$(function () {

    ....
    
    // handle the scroll top of any chat box
    // the idea is to load the last messages by date depending of last message
    // that's already loaded on the chat box
    let lastScrollTop = 0;

   $(".chat-area").on("scroll", function (e) {
       let st = $(this).scrollTop();

       if(st < lastScrollTop) {

           fetchOldMessages($(this).parents(".chat-opened").find("#to_user_id").val(), $(this).find(".msg_container:first-child").attr("data-message-id"));
       }

       lastScrollTop = st;
   });

    // listen for the oldMsgs event, this event will be triggered on scroll top
    channel.bind('oldMsgs', function(data) {
        displayOldMessages(data);
    });
});

/**
 * fetchOldMessages
 *
 * this function load the old messages if scroll up triggerd
 *
 * @param to_user
 * @param old_message_id
 */
function fetchOldMessages(to_user, old_message_id)
{
    let chat_box = $("#chat_box_" + to_user);
    let chat_area = chat_box.find(".chat-area");

    $.ajax({
        url: base_url + "/fetch-old-messages",
        data: {to_user: to_user, old_message_id: old_message_id, _token: $("meta[name='csrf-token']").attr("content")},
        method: "GET",
        dataType: "json",
        beforeSend: function () {
            if(chat_area.find(".loader").length  == 0) {
                chat_area.prepend(loaderHtml());
            }
        },
        success: function (response) {
        },
        complete: function () {
            chat_area.find(".loader").remove();
        }
    });
}

function displayOldMessages(data)
{
    if(data.data.length > 0) {

        data.data.map(function (val, index) {
            $("#chat_box_" + data.to_user).find(".chat-area").prepend(val);
        });
    }
}

As shown in the code above i listened for scroll event then i added a condition to check if we scroll in the up direction not down then we called function fetchOldMessages() which in return will fire an event to the server.

 

Download the source code

 

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

27 Comments

  1. Thank you for such an awesome tutorial. Really helpful!

    But I encountered some issues while using it, this might help the next user.

    1. In Chat.Js line 67.
    ${message.fromUserName} is not defined. I Solved this by passing it from the controller,
    with this code

    $message->fromUserName = $message->owner->fname;

    2. The messages appear in ascending order when displayed.

    I put the message in an array and reversed the order. Here is the code in MessageController for getLoadLatestMessages,

    $messages = Message::where(*****)->orderBy(‘created_at’, ‘DESC’)->limit(10)->with(‘owner’)->get();

    $messagereturn = [];
    foreach ($messages as $message) {
    $messagereturn[] = view(‘message-line’)->with(‘message’, $message)->render();
    }
    $newmessage = array_reverse($messagereturn); // necessary

    3. When getting previous messages, beyond 20 messages in chat area, it begins to misbehave with positioning.

    Instead of

    chat_area.animate({scrollTop: chat_area.offset().top + chat_area.outerHeight(true)}, 800, ‘swing’);

    I did,

    let message_count = $(‘.msg_container’).length + 1; //get number of messages
    let message_size = $(‘.msg_container’).outerHeight(true); //get size for one message
    let chat_size = Math.round ( message_count * message_size);// get total pixel display area

    chat_box.find(“.chat-area”).animate({scrollTop: chat_size}, 800, ‘swing’); // use are to fix scroll position.

  2. Excelente amigo busque por todo lados y no encontraba un tutorial tan claro como este muchas gracias

    1. Gracias

  3. after send message chat line not added in chat area.please help.

  4. PusherFactory::make()->trigger(‘chat’, ‘send’, [‘data’ => $message]); this is the issue . nothing triggred

    1. Be sure that you set up the pusher configuration correctly as described in the tutorial, sometimes you need to clear laravel cache and config so that laravel can read configurations from .env

  5. getting error 505 internal serval error after sending a message and can’t get sent messages

    1. What is the error saying?

      1. internal server error, that’s what is displayed at the console

      2. Rectified, did not put the files in the right directory

  6. Please Help me,
    When this application browser run then registration when submit then below the message. but not login access. why?

    No users found! try to add a new user using another browser by going to Register page

    1. It means you should have users to chat with

  7. Uncaught ReferenceError: channel is not defined
      at HTMLDocument.<anonymous> (chat.js:22)
      at c (jquery.min.js:3)
      at Object.fireWith [as resolveWith] (jquery.min.js:3)
      at Function.ready (jquery.min.js:3)
      at HTMLDocument.H (jquery.min.js:3)

    i got this error

    1. It seems that you didn’t include the pusher javascript sdk

      1. how to add pusher javascript sdk

        1. As mentioned in the article

  8. Hi, the chat is working fine at both ends. But, it is not working in real-time. It requires to close the chat panel and re-open to get the latest message. Similarly, at the sender end after sending the message, it requires to close the chat and re-open to see the sent message. Any help to resolve the issue would be appreciated.

    1. Looks like pusher is not working with you, just check the pusher configuration and turn on debug mode for pusher messages

      1. Hi Wael,
        Thank you very much for your response. Yeah, there was some issue with the Pusher configuration. Now, it is working in real-time.

  9. hi,after send the message the message line didnot add in chat box ,why ?

    1. Check your pusher configuration is correct, there is a a flag to debug the message sending
      Pusher.logToConsole = true;

  10. I followed all the steps but when I write a message it does not appear automatically in the chat window, when I close the chat and reopen it if it appears and does not show me any error, what could it be?

    1. You have something wrong with pusher config check that pusher is working properly using the pusher dashboard

  11. hello, I’m getting undefine message in chat box. messages are being set real time. both users get messages but I want to refresh the page to see the real message till then it displays “undefine

    1. undefined means something wrong in javascript code

  12. It’s work. But I can’t attach file into message.

    1. I think to attach a file you have to update the database to add another column to save the media files and you need to update the chat box to include a button to select a file to upload and send it in the same way as sending the text message

Leave a reply

Your email address will not be published. Required fields are marked *