Dynamic Sidebar

Dishank Poddar
Yodaplus
Published in
4 min readOct 15, 2021

What is it, and why is it needed?

The features of the dynamic sidebar will be such:

  • Updates the list with the chat with the newest messages first every time a message is received.
  • Shows count of unread messages in every chat
  • Has a search bar which when typed into filters the sidebar with the name of the person

Prerequisites

Steps

  • Add the following view to views.py
def get_connections_list_view(request, connection_id):
user = request.user
response = {}

searchbar_value = request.GET.get('search', '')

established_connections_as_user1 = ConnectionEstablished.objects.filter(
user1=user,
status=ConnectionEstablished.OPEN
)
established_connections_as_user2 = ConnectionEstablished.objects.filter(
user2=user,
status=ConnectionEstablished.OPEN
)
established_connections_all = []
for established_connection in established_connections_as_user1:
connection_object = {}
connection_object['connection_pk'] = established_connection.pk
connection_object['user_logo'] = established_connection.user2.logo.url
connection_object['user_name'] = established_connection.user2.user_display_name
connection_object['current_connection'] = established_connection.id == connection_id
latest_message = Message.objects.filter( connection_established=established_connection).order_by('-timestamp').first()
connection_object['latest_message_timestamp'] = timezone.localtime(
latest_message.timestamp) if latest_message else ''
connection_object['unread_messages_count'] = Message.objects.filter(
connection_established=established_connection, author=user2, read=False).count()
searchable_terms = [
f"{connection_object['user_name']}",
]
searchbar_value_in_searchable_terms = [True if searchbar_value.lower(
) in searchable_term.lower() else False for searchable_term in searchable_terms]
if True in searchbar_value_in_searchable_terms:
established_connections_all.append(connection_object)
for established_connection in established_connections_as_user2:
connection_object = {}
connection_object['connection_pk'] = established_connection.pk
connection_object['user_logo'] = established_connection.user1.logo.url
connection_object['user_name'] = established_connection.user1.user_display_name
connection_object['current_connection'] = established_connection.id == connection_id
latest_message = Message.objects.filter( connection_established=established_connection).order_by('-timestamp').first()
connection_object['latest_message_timestamp'] = timezone.localtime(
latest_message.timestamp) if latest_message else ''
connection_object['unread_messages_count'] = Message.objects.filter(
connection_established=established_connection, author=user1, read=False).count()
searchable_terms = [
f"{connection_object['user_name']}",
]
searchbar_value_in_searchable_terms = [True if searchbar_value.lower(
) in searchable_term.lower() else False for searchable_term in searchable_terms]
if True in searchbar_value_in_searchable_terms:
established_connections_all.append(connection_object)

established_connections_with_timestamp = []
established_connections_without_timestamp = []
for established_connection in established_connections_all:
if established_connection['latest_message_timestamp']:
established_connections_with_timestamp.append(
established_connection)
else:
established_connections_without_timestamp.append(
established_connection)
established_connections_all = sorted(
established_connections_with_timestamp, key=lambda i: i['latest_message_timestamp'], reverse=True)
established_connections_all.extend(
established_connections_without_timestamp)
for established_connection in established_connections_all:
timestamp = established_connection['latest_message_timestamp']
if timestamp:
established_connection['latest_message_timestamp'] = timestamp.strftime(
'%Y-%m-%d %H:%M')
response['established_connections'] = established_connections_all
return JsonResponse(response)

Create the consumer MessageRecievedConsumer in consumers.py

class MessageRecievedConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.user_id = self.scope['user'].pk
self.group_name = f'recieved_{self.user_id}'

# Join room group
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)

await self.accept()

async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.group_name,
self.channel_name
)

async def receive(self, text_data):
text_data_json = json.loads(text_data)
# Send message to room group

# Receive message from room group
async def message_recieved(self, event):
# Send message to WebSocket
await self.send(text_data=json.dumps({
}))

Add this snippet to the end of receive(self, text_data) function

@database_sync_to_async
def get_reciever_user(self):
connection_established = ConnectionEstablished.objects.get(pk=self.connection_id)

if connection_established.user1 != self.scope['user'].pk:
return connection_established.user1
return connection_established.user2

Thus we created a simple consumer which, once a user receives a message, sends them a message.

  • Ensure a similar HTML exists in your chat.html
<input type="search" class="form-control py-4 h-auto" id="searchbar" onkeyup="get_connections_list()" onsearch="get_connections_list()" placeholder="Search" value='{{searchbar_value}}'/>
  • Add the following JS to chat.html

Message Received Socket JS

<script>
var url = window.location.href
var websocket_scheme = 'ws'
if(url.includes('https')){
websocket_scheme = 'wss'
}
const messageRecievedSocket = new WebSocket(
`${websocket_scheme}://`
+ window.location.host
+ '/ws/chat/message_recieved/'
);

messageRecievedSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
get_connections_list();
};

messageRecievedSocket.onclose = function(e) {
console.error('Message Received socket closed unexpectedly with error:', e);
};
</script>

Sidebar JS

<script>    
function draw_sidebar(established_connections){
var sidebar_content = ''
for (index in established_connections){
established_connection = established_connections[index]
var connection_url = '{% url "chat:chat" connection_id %}'.replace('{{connection_id}}', established_connection.connection_pk)
var unread_marker = established_connection.unread_messages_count>0?`<span class="label label-sm label-success">${established_connection.unread_messages_count}</span>`:''
sidebar_content += `
<!--begin:User-->
<div class="d-flex align-items-center justify-content-between mb-5" ${established_connection.current_connection?'id="current_connection"':''}>
<div class="d-flex align-items-center">
<div class="symbol symbol-circle symbol-50 mr-3">
<img alt="Pic" src="${established_connection.user_logo}" />
</div>
<div class="d-flex flex-column">
<a href="${connection_url}" class="text-${established_connection.current_connection?'info':'dark-75'} text-hover-info font-weight-bold font-size-lg">
${established_connection.user_name}
</a>
</div>
</div>
<div class="d-flex flex-column align-items-end">
<span class="text-muted font-weight-bold font-size-sm">${established_connection.latest_message_timestamp}</span>
${unread_marker}
</div>
</div>
<!--end:User-->
`
}
document.querySelector('#sidebar').innerHTML = sidebar_content
}
function get_connections_list(){
let searchbar = document.getElementById('searchbar');
searchbar_value = searchbar.value?searchbar.value:''
url = `
{% url 'chat:get_connections_list' connection_id %}?search=${searchbar_value}
`
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) {
draw_sidebar(data.established_connections)
})
}
get_connections_list()
setTimeout(get_connections_list, 500);
</script>
  • Update the Chat Socket JS from Upgrading Chat to include the following
    Update the chatSocket.onmessage function to this
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
var message = draw_message(data,'scroll_bottom_chat()') //draw_message is the abstract function which converts the message data into your html
let chat_box = document.querySelector('#chat-log'); //chat-log is the id of the div where all the messages are displayed
chat_box.innerHTML += message;
scroll_bottom_chat()
let read_messages_url = "{% url 'chat:mark_as_read' connection_id %}"
fetch(read_messages_url, {
method: "GET",
headers: {
"Content-type": "application/json; charset=UTF-8",
},
})
.then((response) => {
if (response.ok != true) {
toastr.error(response.statusText);
}else { //part added
get_connections_list()
}

})
};

This way once a message is read, the sidebar is updated to reflect the correct unread count

  • Update urls.py and routing.py with the following elements
urlpatterns += [
path('mark_as_read/<uuid:connection_id>/',
views.mark_as_read, name='mark_as_read'),
]
websocket_urlpatterns += [
path('message_recieved/', consumers.MessageRecievedConsumer.as_asgi()),
]

Related Articles

--

--