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 Nexmo Conversation Client SDK, which is now delivered paginated from the Conversation API.
Prerequisites
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.
node --version
npm --version
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.
Nexmo CLI
To set up your application, youâll need to install the Nexmo CLI. Install it using NPM in the terminal.
npm install -g nexmo-cli@beta
Sign up for a free Nexmo account and set up the Nexmo CLI with the API key and secret found on the dashboard.
nexmo setup <your_api_key> <your_api_secret>
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
git clone https://github.com/nexmo-community/infinite-scrolling-pagination.git
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.
cd infinite-scrolling-pagination
Install the npm dependencies.
npm install
Configure the application port using an environment file. Copy the example file:
cp .env.example .env
Now, edit the environment file .env
and set the port to 3000 (or whichever port you require).
# app config
PORT=3000
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:
npm start
To start the application, but with nodemon instead, run:
npm run dev
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 Nexmo, and send or receive messages from the service, you need to configure the demo application.
Create a Nexmo Application
Firstly, create a Nexmo Application with RTC (real-time communication) capabilities. The event URL will be a live log of events happening on the Nexmo service, like users joining/leaving, sending messages, enabling audio (if you felt like enabling it).
nexmo app:create "Nexmo RTC Chat" --capabilities=rtc --rtc-event-url=http://example.com --keyfile=private.key
# Application created: 4556dbae-bf...f6e33350d8
# Credentials written to .nexmo-app
# Private Key saved to: private.key
Create a Nexmo Conversation
Secondly, create a Nexmo Conversation, which acts like a chatroom. Or, a container for messages and events.
nexmo conversation:create display_name="Infinite Scrolling"
# Conversation created: CON-a57b0...11e57f56d
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 focusses on styling your message UI in a simple, yet appealing, way.
nexmo user:create name=<USER_NAME> display_name=<DISPLAY_NAME>
# User created: USR-6eaa4...e36b8a47f
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.
nexmo member:add <CONVERSATION_ID> action=join channel='{"type":"app"}' user_id=<USER_ID>
# Member added: MEM-df772...1ad7fa06
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.
nexmo jwt:generate ./private.key sub=<USER_NAME> exp=$(($(date +%s)+86400)) acl='{"paths":{"/*/users/**":{},"/*/conversations/**":{},"/*/sessions/**":{},"/*/devices/**":{},"/*/image/**":{},"/*/media/**":{},"/*/applications/**":{},"/*/push/**":{},"/*/knocking/**":{}}}' application_id=<APPLICATION_ID>
# eyJhbGciOi...XVCJ9.eyJpYXQiOjE1NzM5M...In0.qn7J6...efWBpemaCDC7HtqA
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 Nexmo 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
The post Chat Pagination with Infinite Scrolling appeared first on Nexmo Developer Blog.