Following on from the previous post Create a Simple Messaging UI with Bootstrap, this article will show you how to load older messages from the conversation using the Vonage Conversation Client SDK, which is now delivered paginated from the Conversation API.
Prerequisites
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
Node & NPM
To get started, you're going to need Node and NPM installed. This guide uses Node 8 and NPM 6. Check they're installed and up-to-date.
Both Node and NPM need to be installed and at the correct version. Go to nodejs.org and install the correct version if you don't have it.
Vonage CLI
To set up your application, you'll need to install the Vonage CLI. Install it using NPM in the terminal.
You can find your API key and secret on the Dashboard which are used to set up the Vonage CLI.
The Vonage CLI has plugins that when installed, provide additional capabilities. In this tutorial, you will be working with Conversations, so here is the command to install its plugin:
Git (Optional)
You can use git to clone the demo application from GitHub.
For those uncomfortable with git commands, don't worry, I've you covered. This guide contains instructions on downloading the project as a ZIP file.
Follow this guide to install git.
Getting Started
Based on the finished app from the last tutorial, there is a new starting demo application. Clone and install it by following these steps.
Get the Demo App
For those not comfortable with git commands, you can download the demo application as a zip file and unpack it locally.
Once cloned or unpacked, change into the new demo application directory.
Install the npm dependencies.
Configure the application port using an environment file. Copy the example file:
Now, edit the environment file .env
and set the port to 3000 (or whichever port you require).
Among other packages installed by your last command, there is a package called nodemon
, that allows you to reload your application if you edit any files automatically.
To start the application in the standard way, run:
To start the application, but with nodemon instead, run:
Tip: If you're running the application with
nodemon
for the remainder of this tutorial, whenever I suggest restarting the application, you won't need to do that becausenodemon
does it for you. However, if you need to reauthenticate with the application, you will still need to do that, as the session information is stored in memory and not configured to use any other storage.
Configure The Demo App
To connect to Vonage, and send or receive messages from the service, you need to configure the demo application.
Create a Vonage Application
Firstly, create a Vonage Application with RTC (real-time communication) capabilities. The event URL will be a live log of events happening on the Vonage service, like users joining/leaving, sending messages, enabling audio (if you felt like enabling it).
Create a Vonage Conversation
Secondly, create a Vonage Conversation, which acts like a chatroom. Or, a container for messages and events.
Create Your User
Now, create a user for yourself.
Note: In this demo, you won't chat between two users. Other guides show you how to create conversations between multiple users. This guide focuses on styling your message UI in a simple, yet appealing, way.
Add the User to a Conversation
Next, add your new user to the conversation. A user can be a member of an application, but they still need to join the conversation.
Generate a User Token
Lastly, generate your new user a token. This token represents the user when accessing the application. This access token identifies them, so anyone using it will be assumed to be the correct user.
In practice, you'll configure the application with this token. In production, these should be guarded, kept secret, and very carefully exposed to the client application, if at all.
Configure the Application
Having generated all the parts you'll need, edit the views/layout.hbs
file and find the JavaScript shown here.
<script>
var userName = '';
var displayName = '';
var conversationId = '';
var clientToken = '';
</script>
Edit the config with the values you've generated in the commands above.
<script>
var userName = 'luke'; // <USER_NAME>
var displayName = 'Luke Oliff'; // <DISPLAY_NAME>
var conversationId = 'CON-123...y6346'; // <CONVERSATION_ID>
var clientToken = 'eyJhbG9.eyJzdWIiO.Sfl5c'; // this will be much much longer
</script>
Now configured, start the application and access it using the default application URL.
Note: This is only a demo and you should not be hard coding credentials into any application, especially one that exposes them to the client.
Prepare a Message History
Because you need more messages to scroll through, create some message history by sending multiple messages to the client. The default page size is 20 items, so create more than 20 messages. I recommend creating 60 test messages so you can load 2 whole pages of history.
Adding Pagination to the App
The default settings for the application only returns 20 items from the conversation's past events. Now, it's time to add pagination to the application so users can load older events.
What Is Pagination?
Pagination, or paging, is how an application divides the content into multiple pages. When implemented in an APIs design, it allows for the delivery of manageable collections of results, that can usually be navigated programmatically. SDKs like the Vonage Conversation Client SDK are no different, often extending the APIs pagination functionality into friendly methods that make pagination more straightforward.
The User Experience
Some applications offer links like 'next' or 'previous', or page numbers. But that isn't what you'll implement here. As the messages in a chat channel are a continuous stream of conversation, this app will allow users to just keep scrolling through historical messages. This is done using a concept known as infinite scrolling. As you scroll through older messages and get to the end, the app will request the next page of history and slot them in. In older channels with a lot of history, this will give the feeling of being able to scroll forever or infinite scrolling.
The Code
Now, you're going to write some code. Here, you'll make changes to detect the scroll position of your message list, and load more messages when you reach the oldest message. The oldest message will be shown at the very top of the window.
Scrolling to the Top
To detect when you scroll to the top, you need to add a new event. Edit the public/javascripts/chat.js
file and add the following code under the setupUserEvents()
method.
// public/javascripts/chat.js
// ...
setupUserEvents() {
// ...
this.messageFeed.addEventListener("scroll", () => {
alert('scrolling!');
}
}
// ...
You can test this in the browser, where you'll quickly discover why it's not very helpful. This code adds an event listener to the messageFeed
element, meaning that every time you try to scroll it triggers a pop-up. Not what you want!
So, change it slightly. Add the following code above the setupUserEvents()
method and modify your new event listener as shown.
// public/javascripts/chat.js
// ...
isFeedAtTop() {
return 0 === this.messageFeed.scrollTop;
}
setupUserEvents() {
// ...
this.messageFeed.addEventListener("scroll", () => {
if (this.isFeedAtTop()) {
alert('scrolling!');
}
}
}
// ...
This new change creates a new method that detects where the scroll position of the messageFeed
is at 0
, zero, or the very start at the top of the message history. More useful! Now, you know when someone reaches the oldest message at the top of the message list.
Who Are You
To attribute new messages to a user when they're loaded from the conversation history, you should store. Editing the public/javascripts/chat.js
file, add the following line after the line this.conversation = conversation;
.
// public/javascripts/chat.js
// ...
setupConversationEvents(conversation, user) {
// ...
this.user = user;
// ...
}
// ...
Store the Page Context
To load more messages from the message history, you need to know what page was last loaded. To do this, still editing the public/javascripts/chat.js
file, change the existing showConversationHistory
as shown below to store the most recent event page on the application.
// public/javascripts/chat.js
// ...
showConversationHistory(conversation, user) {
// ...
.then((eventsPage) => {
this.lastPage = eventsPage;
var eventsHistory = "";
// ...
}
// ...
If it's not clear how the showConversationHistory
method should look after the change, here is the entire method with the change applied.
// public/javascripts/chat.js
// ...
showConversationHistory(conversation, user) {
conversation
.getEvents({ page_size: 20, order: 'desc' })
.then((eventsPage) => {
this.lastPage = eventsPage;
var eventsHistory = "";
eventsPage.items.forEach((value, key) => {
if (conversation.members.get(value.from)) {
switch (value.type) {
case 'text':
eventsHistory = this.senderMessage(user, conversation.members.get(value.from), value) + eventsHistory;
break;
case 'member:joined':
eventsHistory = this.memberJoined(conversation.members.get(value.from), value) + eventsHistory;
break;
}
}
});
this.messageFeed.innerHTML = eventsHistory + this.messageFeed.innerHTML;
this.scrollFeedToBottom();
})
.catch(this.errorLogger);
}
// ...
The idea of this method is to store the EventsPage
returned from calling getEvents
, so that the app can use it again later on.
With this change in place, the application is now aware of the most recent page.
Avoid Unnecessary Requests
One method on the EventsPage
object is hasNext
, which returns true if there are more events to load.
With the hasNext
method, edit the scrolling event you added earlier to add this.lastPage.hasNext()
to the condition around our alert
.
// public/javascripts/chat.js
// ...
setupUserEvents() {
// ...
this.messageFeed.addEventListener("scroll", () => {
if (this.isFeedAtTop() && this.lastPage.hasNext()) {
alert('scrolling!');
}
}
}
// ...
Now, you'll only get an alert if there is another page of events to load.
Load the Next Page
To load the next page, replace the alert
in your event listener with the code shown below:
// public/javascripts/chat.js
// ...
this.lastPage
.getNext()
.then((eventsPage) => {
this.lastPage = eventsPage;
var moreEvents = "";
eventsPage.items.forEach((value, key) => {
if (this.conversation.members.get(value.from)) {
switch (value.type) {
case 'text':
moreEvents = this.senderMessage(this.user, this.conversation.members.get(value.from), value) + moreEvents;
break;
case 'member:joined':
moreEvents = this.memberJoined(this.conversation.members.get(value.from), value) + moreEvents;
break;
}
}
});
this.messageFeed.innerHTML = moreEvents + this.messageFeed.innerHTML;
})
.catch(this.errorLogger);
// ...
This code uses this.lastPage
that was stored on the application earlier in the article, and requests getNext
which returns a new EventsPage
.
The rest of the code seen here overwrites this.LastPage
with the latest page, and performs near-enough the same function of the showConversationHistory
method that renders historical messages when the page is loaded, adding them to the top of the messageFeed
.
Fix the Scroll Position
With infinite scrolling in place, you'll notice that new messages get added to the top, but you're still looking at the top of the messageFeed
, losing the position of where you were in the channel's message history. To fix this, you're going to reuse the scrollTo
method already found inside the public/javascripts/chat.js
file.
Previously, scrollTo
was used to scroll to the bottom of the messages, which is achieved by any number larger than the height of the messageFeed
. This team, you need to scroll to a specific point on the messageFeed
.
If the position was when the application loaded new messages was 0
at the top, then it would make sense to scroll to the difference between the height before and after the messageFeed
was updated.
Inside the condition that checks scroll position and hasNext
, but before the the.lastPage.getNext()
code is ran, add the code to store the scrollHeight
, as shown here:
// public/javascripts/chat.js
// ...
if (this.isFeedAtTop() && this.lastPage.hasNext()) {
this.scrollHeight = this.messageFeed.scrollHeight;
// ...
// ...
Now, in this same function, after the line that updates the messageFeed.innerHTML
with moreEvents
, add this line too:
// public/javascripts/chat.js
// ...
// ...
this.scrollTo(this.messageFeed.scrollHeight-this.scrollHeight);
// ...
If it's not clear how the "scroll"
event listener should look after the change, here is the code in its entirety:
// public/javascripts/chat.js
// ...
// ...
this.messageFeed.addEventListener("scroll", () => {
if (this.isFeedAtTop() && this.lastPage.hasNext()) {
this.scrollHeight = this.messageFeed.scrollHeight;
this.lastPage
.getNext()
.then((eventsPage) => {
this.lastPage = eventsPage;
var moreEvents = "";
eventsPage.items.forEach((value, key) => {
if (this.conversation.members.get(value.from)) {
switch (value.type) {
case 'text':
moreEvents = this.senderMessage(this.user, this.conversation.members.get(value.from), value) + moreEvents;
break;
case 'member:joined':
moreEvents = this.memberJoined(this.conversation.members.get(value.from), value) + moreEvents;
break;
}
}
});
this.messageFeed.innerHTML = moreEvents + this.messageFeed.innerHTML;
this.scrollTo(this.messageFeed.scrollHeight-this.scrollHeight);
})
.catch(this.errorLogger);
}
});
// ...
With any luck, when you try it out, you'll discover messages will seemingly load above your scroll position, allowing you to scroll 'to infinity', or the top.
The End
This article followed on from the previous post Create a Simple Messaging UI with Bootstrap, showing you how to load older messages as you scroll through the message history.
Don't forget, if you have any questions, feedback, advice, or ideas you'd like to share with the broader community, then please feel free to jump on our Community Slack workspace or pop a reply below 👇.