Infinite Chat Scrollbar
Published in
2 min readOct 13, 2021
What is it, and why is it needed?
An infinite scrollbar is a common element among applications that allow you to load a small number of elements at a time while giving the impression of smooth traversal across the items to give users a seamless experience. However, unlike common infinite scrolls which add to the bottom of the current content, messaging scrollbars work in the opposite direction, i.e. bringing a small number of most recent messages and adding them to the top of the currently displayed messages.
Prerequisites
Steps
- Add the following view to views.py. We use a ListView since it takes care of pagination for us. We can just send the data as json and voila, it becomes an API endpoint!
from django.http import Http404, JsonResponse, HttpResponse
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from django.views.generic import ListView
from .models import Message, ConnectionEstablished
class MessagesView(LoginRequiredMixin, ListView):
paginate_by = 20
def get_queryset(self, *args, **kwargs):
connection_established = ConnectionEstablished.objects.get(
id=self.kwargs['connection_id']
)
queryset = Message.objects.filter(
connection_established=connection_established
).order_by('-timestamp')
return queryset
def get(self, *args, **kwargs):
# Validation and authentication
connection = get_object_or_404(
ConnectionEstablished,
pk=kwargs['connection_id']
)
if self.request.user not in [connection.user1, connection.user2]:
raise Http404("Chat not found")
response = {}
response['success'] = True
paginator = self.get_paginator(self.get_queryset(), self.paginate_by)
if int(self.request.GET.get('page', 1)) > paginator.num_pages:
response['success'] = False
return JsonResponse(response)
template_response = super().get(*args, **kwargs)
context_data = self.get_context_data()
message_list = context_data['object_list']
message_json_list = []
for message in message_list:
message_json = {}
message_json['content'] = message.content
message_json['timestamp'] = timezone.localtime(
message.timestamp).strftime('%Y-%m-%d %H:%M')
message_json['author'] = message.author.pk
message_json_list.append(message_json)
response['messages'] = message_json_list[::-1]
return JsonResponse(response)
- Add the following js snippets to your frontend (Replace all the ids and classes with your own)
Get Messages JS
<script>
function scroll_top_load(page){
let messages_page = document.querySelector(`#messages-page-${page}`);
if(messages_page){
messages_page.scrollIntoView();
}
} //This function helps you scroll to the correct position once a new batch of messages are loaded.
var page = 1
var observer = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true){
url = `
{% url 'chat:get_messages' connection_id %}?page=${page}
`
fetch(url, {
method: "GET",
headers: {
"Content-type": "application/json; charset=UTF-8",
},
})
.then((response) => {
if (response.ok != true) {
toastr.error(response.statusText);
return null
}
else {
return response.json()
}
})
.then(function (data) {
if(data.success === true){
var add_messages = ``
if(data.messages.length==0){
toastr.info('Start a conversation')
}
for(message of data.messages){
var add_message = draw_message(message,`scroll_top_load(${page})`)
add_messages += add_message
}
add_messages += `<div id='messages-page-${page}'></div>`;
let chat_box = document.querySelector('#chat-log');
chat_box.innerHTML = add_messages + chat_box.innerHTML;
scroll_top_load(page++);
}
})
}
observer.observe(document.querySelector("#message-loader"));
}, { root: null });
observer.observe(document.querySelector("#message-loader"));
</script>
- Update urls.py with your new API
urlpatterns += [
path('messages/<uuid:connection_id>/',
views.MessagesView.as_view(), name='get_messages'),
]