In my last tutorial here, we created a chatroom application using Laravel, Vue.js, and Stream Chat. Although we only covered the basics of getting our chatroom up and running, Stream Chat offers a lot of other functionality.
In this tutorial, we will explore some of those functionalities, and we’ll be adding them to our chatroom application. We’d add the following functionality:
- Add a leave channel functionality
- Add functionality to join channel manually
- Show a typing indicator when the user is typing
- Display a notification when a user enters chat
- Display a notification when a user leaves the chat
Adding the Leave Channel Functionality
Currently, in our application, users are added to the chatroom as soon as they register. But typically, in a chatroom application, users should have the ability to join or leave any channel of their choice explicitly.
To implement this functionality, we need to tell Stream Chat to remove or allow a user to join different channels as they want. Let’s see this in action.
Add the following code within the class definition of ChatController.php
:
// app/Http/Controllers/ChatController.php ... protected $client; protected $channel; public function __construct() { $this->client = new StreamClient(env("MIX_STREAM_API_KEY"), env("MIX_STREAM_API_SECRET"), null, null, 9); $this->channel = $this->client->Channel("messaging", "chatroom"); } public function leaveChannel(Request $request) { return $this->channel->removeMembers([$request->username]); } ...
We created a new leaveChannel
method to handle removing users from a particular channel. This method calls the removeMembers
function on the current channel instance, which gets set as soon as the controller is invoked. The removeMembers
function accepts an array of user IDs (username in our case) we want to remove from the channel.
Still, inside the ChatController
, update the generateToken
method as below:
// app/Http/Controllers/ChatController.php public function generateToken(Request $request) { return response()->json([ 'token' => $this->client->createToken($request->user_id) ]); }
Next, we need to create the endpoint that will trigger the leave channel functionality. Add the code below inside routes/api.php
:
// routes/api.php Route::post('/leave-channel', 'ChatController@leaveChannel');
Now that all our backend needs are set up, we need to add a button on our frontend that will be used by users when they want to leave the channel. Inside the ChatRoom
component, add the following code underneath the member's list:
// resources/js/ChatRoom.vue // inside the template section <button type="button" class="mt-4 btn btn-primary" @click="leaveChannel">Leave Channel</button>
Here, we add a simple button to allow users to leave the channel. On click of this button, the leaveChannel
method will be triggered. Let’s create the method:
// resources/js/ChatRoom.vue // inside the script section leaveChannel() { axios.post("/api/leave-channel", { username: this.username, }); window.location.href = '/dashboard'; }
This makes a POST
request to our leavel-channel
endpoint with the username of the user leaving the channel. Then redirect the user to the dashboard route.
Lastly, let’s update the channel members list by removing the user that left from the list. We’ll implement that inside the initializeChannel
method. Add the code below immediately after where we are listening for when a new member is added to the channel:
// resources/js/ChatRoom.vue this.channel.on("member.removed", event => { this.members = this.members.filter(member => { return member.user.id !== event.user.id; }); });
Here, we are listening for the member.removed
event and filter the members to not include the user that left.
Joining a Channel
Stream Chat also provides an addMembers
function (which we saw in the last tutorial) for adding users a channel. Add the following code inside ChatController.php
:
// app/Http/Controllers/ChatController.php public function joinChannel(Request $request) { $username = explode('@', $request->email)[0]; $this->channel->addMembers([$username]); return redirect('/home'); }
In this method, we create the username by extracting part of the information sent to us before calling the addMembers
function on the current channel instance. To trigger this method, open api.php
and add the code below:
// routes/api.php Route::post('/join-channel', 'ChatController@joinChannel')->name('join-channel');
Next, we need a button on the user side to trigger this action. We’ll add it to the dashboard page. This is the page the user gets redirected to when they leave a channel. Create a dashboard.blade.php
file in the resources/views
directory and add the following code to it:
// resources/views/dashboard.blade.php @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">Dashboard</div> <div class="card-body"> <p>You have left the channel</p> <form action="{{ route('join-channel') }}" method="post"> <input type="hidden" name="email" value="{{ Auth::user()->email }}"> <button type="submit" class="mt-4 btn btn-warning">Join Channel</button> </form> </div> </div> </div> </div> </div> @endsection
We created a small form and button to trigger the endpoint; we just created and passed along the necessary information to it. To round up this functionality, let’s add the required route and controller code to render this page. Open routes/web.php
and add the following code to it:
// routes/web.php Route::get('/dashboard', 'HomeController@dashboard')->name('dashboard');
Next, add the following code inside HomeController.php
:
// app/Http/Controllers/HomeController.php public function dashboard() { return view('dashboard'); }
Displaying User Joined/Left Notification
When a user joins a chatroom, a cool feature to have is one where we can see the user name of the person that just entered. This can come in handy for introductions and other formalities so users won’t get lost in the chats.
Stream Chat triggers certain events, which we can listen to and perform any necessary action accordingly. We have seen a couple of these events in the last tutorial. To know when a user joins a channel, we listen for the member.added.
event.
Inside the template
section of resources/views/js/
ChatRoom.vue`, add the following code immediately before the message form:
// resources/views/js/ChatRoom.vue <span class="help-block" v-if="status" v-text="status" style="font-style: italic;"></span>
This will display the user joins notification only if the status
property has been set.
Next, inside the script
section of resources/views/js/ChatRoom.vue
, let’s start by adding a new status
property to the component’s data
:
// resources/views/js/ChatRoom.vue data() { return { ... status: "" }; },
Then inside the initializeChannel
method, we’re already listening for the member.added
event, all we need to do is update it as below:
// resources/views/js/ChatRoom.vue this.channel.on("member.added", event => { this.members.push(event); this.status = `${event.user.name} joined the chat`; });
The status
property is initially set to an empty string. Once a new member joins the channel, we set status
property accordingly.
Similarly, to display a notification when a user leaves a channel, we’re already listening for the member.removed
event, all we need to do is update it as below:
// resources/views/js/ChatRoom.vue this.channel.on("member.removed", event => { this.members = this.members.filter(member => { return member.user.id !== event.user.id; }); this.status = `${event.user.name} just left the chat`; });
Now, whenever a user leaves the channel, we’ll set status
accordingly.
Adding a Typing Indicator
Another handy feature of chat applications is the ability to know when a user has started typing a response to a message — that way, we can anticipate better instead of having to sit and wonder.
Stream Chat provides two events regarding typing: typing.start
and typing.stop
, which we can use to check if a user is typing or not. Let’s add two additional data properties to the ChatRoom
component:
// resources/js/components/ChatRoom.vue data() { return { ... isTyping: false, typing: null }; },
isTyping
will be used to indicate whether a user is typing or not, while typing
will hold the response gotten as a result of triggering the events.
Next, let’s update the message field so we can keep track of when a user is typing or not:
// resources/js/components/ChatRoom.vue <input type="text" v-model="newMessage" @keydown="startedTyping" @keyup="stoppedTyping" class="form-control" placeholder="Type your message..." />
We add two keyboard events: @keydown
and @keyup
, which are Vue.js equivalent of the JavaScript keyboard onkeydown
and onkeyup
events, respectively. On keydown, the startedTyping
method will be triggered, and on keyup, the stoppedTyping
method will be triggered.
Next, let’s create those methods:
// resources/js/components/ChatRoom.vue ... async startedTyping() { await this.channel.keystroke(); }, stoppedTyping() { setTimeout(async () => { await this.channel.stopTyping(); }, 2000); } ...
When the user starts typing (keydown), we need to trigger a typing.start
event using the keystrokes
function on the channel instance. Similarly, when the user stops typing (keyup) for more than two seconds, we need to trigger a typing.stop
event using the stopTyping
function on the channel instance.
To wrap up, we need to listen to these events as they are triggered. We’ll do that in the initializeChannel
method:
// resources/js/components/ChatRoom.vue ... this.channel.on("typing.start", event => { this.isTyping = true; this.typing = event; }); this.channel.on("typing.stop", event => { this.isTyping = false; }); ...
Lastly, we need a way to display the typing indicator. Add the following code immediately after the message form:
// resources/js/components/ChatRoom.vue <span class="help-block mt-2" v-if="isTyping && typing.user.id !== username" style="font-style: italic;">{{ `${typing.user.name} is typing...` }}</span>
We conditionally show the text {username} is typing
when isTyping
is true
and if the user typing is not the same one viewing it.
Testing the App
Now, let’s test what we’ve been building so far. First, let’s compile our JavaScript:
npm run dev
Next, let’s start our application:
php artisan serve
Navigate to http://127.0.0.1:8000
.
Final Thoughts
In this tutorial, we’ve looked into adding various functionality we can add to our chatroom application using Stream Chat. We’ve used events and functions provided by Stream Chat to help enhance our application, and the knowledge gained can be used to create more complex chat applications. To learn more about Stream Chat, check out the docs.
The complete code for this tutorial is available on GitHub.