Creating a live shoutbox/chat using Laravel and Bootstrap

Laravel and Bootstrap shoutbox/chat example

Creating a live shoutbox/chat using Laravel and Bootstrap

Not so long ago, I was instructed to work on a shoutbox/chat feature for a client using laravel and the client also requested without the use of WebSockets. Our client didn’t have WebSockets enabled and for whatever reason, didn’t want Node.js/Socket.io installed or perhaps couldn’t. That’s fine, though, we can fix that!

 

Our client used Bootstrap with their design, which is handy because we can create a nice little chatbox using Bootstrap and Laravel. This tutorial assumes you have Gulp installed and are using Laravel Elixir, but you’re fine to not use that as we only need a few tweaks to the CSS!

 

Lets start out with me showing you how this is going to look.

Laravel and Bootstrap shoutbox/chat example

Laravel and Bootstrap shoutbox/chat example

 

Not too shabby, huh? It’s a nice clean interface, it uses Laravel Cache and it updates every few seconds. Oh, and it utilises mentions, too, awesome!

 

So, lets dive in. I imagine you’re here because you need a shoutbox for Laravel and you just want to see the code! Lets start with the CSS!

 

.shoutbox { width: 100%; bottom: -1px; margin-bottom: 0; border-radius: 0; }
.profile-avatar { position: relative; z-index: 20; background-size: cover; background-position: center center; border-radius: 100%; -webkit-transition: opacity 0.3s ease-in; transition: opacity 0.3s ease-in; }
.profile-avatar.tiny { width: 32px; height: 32px; border: none; }
.shoutbox .panel-heading { border-radius: 0; }
.shoutbox .profile-avatar { box-shadow: none; }
.shoutbox .list-group { height: 300px; overflow-y: auto; }
.shoutbox .list-group-item-heading, .shoutbox p { margin-left: 48px; padding-right: 30px; }
.shoutbox .list-group-item .message-content { position: relative; }
.shoutbox .list-group-item time { position: absolute; top: 0; right: 0; color: #777777; font-size: 12px; }
.shoutbox .form-group { width: 100%; }
.shoutbox input.form-control { width: 100%; resize: none; }

.mentioned { background-color: #C6FFFA; }
.panel-chat { border-color: #ddd; }
.panel-chat > .panel-heading { color: #fff; background-color: #373d43; border-color: #ddd; }
.panel-chat > .panel-heading + .panel-collapse > .panel-body { border-top-color: #ddd; }
.panel-chat > .panel-heading .badge { color: #373d43; background-color: #fff; }
.panel-chat > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #ddd; }
.panel-chat .close { color: #fff; position: absolute; top: 8px; right: 16px; }
.panel-heading h3 { margin-top: 15px; }

#chat-error { margin-left: 0px; margin-top: 5px; }

 

Copy this in to your app.scss or whatever Sass/CSS file you’re saving under! (I like app.scss, it’s cleaner). If you’re using Sass, don’t forget to run gulp or gulp watch from the command line!

 

This is the beginning of your new Laravel Shoutbox! Hopefully it will make your Shoutbox look nice and pretty.

Now, lets give it some structure with some simple HTML. This Laravel Blade template uses a Shoutbox model that I will go over further on in the tutorial. Of course, I have added some relationships to the models that I will also go over. I’m assuming you use the default Users model that comes with Laravel.

 

<div class="row">
  <div class="col-md-12">

   	<div class="panel panel-chat shoutbox">
      <div class="panel-heading">
        <h3 class="panel-title text-center">Say something!</h3>
      </div>
      
      <div class="chat-messages">
          <ul class="list-group">
          @foreach($shoutboxItems as $messages)
          
           	<?php
             $class = '';
    
              if(in_array(\Auth::user()->id, explode(',', $messages->mentions)))
              {
                $class = 'mentioned';
              }		
              ?>
              			
            <li class="list-group-item {{ $class }}">
              	<div class="profile-avatar tiny pull-left" style="background-image: url()"></div>
              	<h5 class="list-group-item-heading"><a href="#">{{ $messages->poster->username }}</a></h5>
                <p class="message-content"><time>{{ date("H:i", strtotime($messages->created_at)) }}</time>{{ $messages->message }}</p>
              
            </li>	
          @endforeach  					
        </ul>
      </div>
      
      <div class="panel-footer ">

          {!! csrf_field() !!}
          <div class="form-group">
            <input class="form-control" id="chat-message">
            <p id="chat-error" class="hidden text-danger"></p>
          </div>

      </div>
    </div>  
  </div>
</div>

 

The above code contains the Blade template control structures and expressions that are passed to the template from our ShoutboxController, which we will get to later. It’s worth noting that, I was going to extend the default Blade functionality to handle the @mentions code, but, lets not re-invent the wheel for this simple demonstration.

I put my code in my “home.blade.php” file. But you can put yours wherever you’d like, as long as you can add Javascript to that file too!

 

We’re nearly there now. We need to add the controller next and the model! As you know, with Laravel, you get the powerful Artisan CLI which you run from the command prompt with the command php artisan. The next commands are optional, you can of course create the file yourself, considering I’m giving you all of the code structure and you won’t need the generators. But, whatever you prefer! So either run:

php artisan make:controller ShoutboxController
php artisan make:model Shoutbox -m

 

Or, create a file in app/Http/Controllers called ShoutboxController.php and create a model just in the app directory called Shoutbox.php.

Inside of your ShoutboxController.php, add the following code:

 

(you’ll need to change this line return view(‘home’, compact(‘shoutboxItems’)); where “home” is the name of your blade template).

 

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\Shoutbox;
use Cache;

use App\User;
use Validator;

use Carbon\Carbon;

class ShoutboxController extends Controller
{

    public function index()
    {

		$shoutboxItems = Cache::remember('shoutbox_messages', 60, function()
		{
		    return Shoutbox::orderBy('created_at', 'desc')->take(25)->get();
		});		
	    
		$shoutboxItems = $shoutboxItems->reverse();
	    
        return view('home', compact('shoutboxItems'));
    }

    public function send(Request $request)
    {
      
      $checkSendRate = Shoutbox::where('user', \Auth::user()->id)->where('created_at', '>=', Carbon::now()->subSeconds(5))->first();
      
      if($checkSendRate)
      {
        return 'Please do not spam.';
      }
      
      $validator = Validator::make($request->all(), [
      	'message' => 'required|max:255',
      ]);
      
      if ($validator->fails()) {
      	return 'There was an error with your input';
      }
      
      if($request->ajax())
      {
        
        preg_match_all('/(@\w+)/', $request->message, $mentions);
      
      $mentionIDs = [];
      
        foreach($mentions[0] as $mention)
        {
          
          $findUser = User::where('username', 'LIKE', '%' . str_replace('@', '', $mention) . '%')->first();
          
          if(!empty($findUser->id))
          {
          	$mentionIDs[] = $findUser['id'];
        }	
        }
        
        $mentions = implode(',', $mentionIDs);

      if(count($mentions) > 0)
      {
        $insertMessage = Shoutbox::create(['user' => \Auth::user()->id, 'message' => $request->message, 'mentions' => $mentions]);
      } 
      else {
        	$insertMessage = Shoutbox::create(['user' => \Auth::user()->id, 'message' => $request->message]);
        }
        
      $data = '<li class="list-group-item">
            	<div class="profile-avatar tiny pull-left" style="background-image: url(http://lorempixel.com/180/100)"></div>
            	<h5 class="list-group-item-heading"><a href="#">' . \Auth::user()->username . '</a></h5>
              <p class="message-content"><time>' . date("H:i", time()) . '</time>' . e($request->message) . '</p>
            </li>';    
      
      Cache::forget('shoutbox_messages');
      
      return \Response::json(['success' => true, 'data' => $data]);	
    
    }	  
    }
    	
    public function fetch(Request $request)
    {
      if($request->ajax())
      {
        //$getData = Shoutbox::orderBy('created_at', 'desc')->take(25)->get();
      $getData = Cache::remember('shoutbox_messages', 60, function()
      {
          return Shoutbox::orderBy('created_at', 'desc')->take(25)->get();
      });		    
        
        $getData = $getData->reverse();
        
        $data = [];

        foreach($getData as $messages)
        {
       
       	$class = '';

          if(in_array(\Auth::user()->id, explode(',', $messages->mentions)))
          {
            $class = 'mentioned';
          }
          
          $data[] = '<li class="list-group-item ' . $class . '">
            	<div class="profile-avatar tiny pull-left" style="background-image: url(http://lorempixel.com/180/100)"></div>
            	<h5 class="list-group-item-heading"><a href="#">' . e($messages->poster->username) . '</a></h5>
              <p class="message-content"><time>' . date("H:i", strtotime($messages->created_at)) . '</time>' . e($messages->message) . '</p>
            </li>';    
        }
        
        return \Response::json(['success' => true, 'data' => $data]);
      }
    }
}

 

The above code has the send() and fetch() functions, which handle the database inserting and the database fetching. This controller also caches the results intelligently using Laravel’s powerful Cache facade.

 

Next, we need the Eloquent Model. If you don’t know what Eloquent is, Laravel explains it nicely and is highly recommended! On to the model code. Now, you’ve assuming you’ve done this right and followed the article correctly, you should have a Shoutbox.php Model inside of your /app directory. Brilliant! Open it up and paste this inside:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Shoutbox extends Model
{
    protected $table = 'shoutbox';
    protected $fillable = ['user', 'message', 'mentions'];
    
  public function poster()
  {
    return $this->belongsTo('App\User', 'user');
  }    
}

The above simply creates the Eloquent Model that has a relationship with the User model. Now you can access the associated user details of the Shoutbox messages using the belongsTo relationship, poster().

 

Now we need to create and run a migration. If you used Artisan to create the Controller and Model above, and you copied the commands I used, you would have already created the migration. If you didn’t, it is highly recommended that you use Artisan CLI to create your migration, so that you do not corrupt your migrations table. So, run this command in terminal if you haven’t already created the migration:

 

php artisan make:migration shoutbox_table

 

Perfect! Now, go to that migration located in database/migrations. Open it up and paste this code in:

 

<?php

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

class Shoutbox extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('shoutbox', function (Blueprint $table) {
            $table->increments('id');
      $table->integer('user')->unsigned();
      $table->foreign('user')->references('id')->on('users')->onDelete('cascade');			
      $table->string('message', 150);
      $table->string('mentions');
            $table->timestamps();
        });
    }

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

 

Now, run the migration in terminal!

 

php artisan migrate

 

Almost there now! You should have mostly everything setup. All that is left to do now is add in the Routes so that we have somewhere to send our data to and pull our data from, then add in some clever jQuery to send and get the data with, and we’re done!

 

So, on to the routes, nice and simple open up your app/Http/routes.php file and add:

Route::group(['prefix' => 'shoutbox'], function() {
  Route::get(('/', ['as' => 'shoutbox-home', 'uses' => 'ShoutboxController@index']);
  Route::post('messages', ['as' => 'shoutbox-fetch', 'uses' => 'ShoutboxController@fetch']);
  Route::post('send', ['as' => 'shoutbox-send', 'uses' => 'ShoutboxController@send']);	    
});

 

Perfect! Now, lets use jQuery to use these routes! Open up your blade template where you added our HTML in the first half of this article and add in the jQuery!

 

var scollBox    = $('.shoutbox');
//var height = $("ul .list-group")[0].scrollHeight;
var height=0;
$("ul li").each(function() {
   height += $(this).outerHeight(true); // to include margins
});

$('.chat-messages .list-group').animate({scrollTop: height});  
load_data = {'fetch':1};
$.ajaxSetup({
      headers: {
          'X-CSRF-TOKEN': $('input[name=_token]').val()
      }
  });	
window.setInterval(function(){
  $('.chat-messages .list-group').animate({scrollTop: height}); 
  $.ajax({
      url: "{{ route('shoutbox-fetch') }}",
      type: 'post',
      data: load_data,
      dataType: 'json',
      success: function (data) {
      //console.info(data.data);
      $('.chat-messages .list-group').html(data.data);    
      }
  });	
}, 3000);

$("#chat-message").keypress(function(evt) {
    if(evt.which == 13) {
          var message = $('#chat-message').val();
          post_data = {'message':message};
            
    $.ajax({
        url: "{{ route('shoutbox-send') }}",
        type: 'post',
        data: post_data,
        dataType: 'json',
        success: function (data) {
            //console.info(data.data);
            $(data.data).hide().appendTo('.chat-messages .list-group').fadeIn();
                $('#chat-error').addClass('hidden');
                $('#chat-message').removeClass('invalid');
                $('#chat-message').val('');		
                $('.chat-messages .list-group').animate({scrollTop: height}); 	        
        },
        error: function (data) {
          //console.log(data);
          $('#chat-message').addClass('invalid');
          $('#chat-error').removeClass('hidden');
          $('#chat-error').text(data.responseText);
        }
    });	

    }
});

 

This code sends and retrieves the data from our database, along with the relevant fields for validation, such as Laravel’s CSRF Token!

It’s worth noting also that you will need jQuery for this. So if you haven’t already, include jQuery into your template file or before the </body> of your document.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>

 

And that’s it! You (hopefully) should have a working Shoutbox for Laravel! Next time, we’ll learn how to make a Shoutbox with WebSockets!

 

If you need any Laravel development done, please feel free to contact us.