Messaging system front-end

Overview

The frontend is written in plain JavaScript with Bootstrap. It handles:

  • Group creation via modal

  • Search and dynamic UI for group member selection

  • Chat selection and lazy-loaded message rendering

  • Auto-scrolling and polling for updates

  • Sidebar for group information

  • Form submission for sending messages

Runs on DOMContentLoaded:

document.addEventListener('DOMContentLoaded', () => { /* init */ });

๐Ÿ” Event Flow Summary

1. Setup

Selects elements and initializes state:

const messagesContainer = document.querySelector('.messages');
const chatItems = document.querySelectorAll('.chat-item');
const userId = parseInt(document.getElementById('user-data')?.dataset.userId || '0');

Flags:

let currentChatId = null;
let currentChatIsGroup = false;
let lastMessageCount = 0;
let isCurrentlyFetching = false;

๐Ÿ‘ฅ Group Chat Creation

Resets modal UI and shows modal:

document.getElementById('new-chat-btn').addEventListener('click', () => {
  groupSelected = [];
  groupUserSearch.value = '';
  new bootstrap.Modal(document.getElementById('createGroupModal')).show();
});

Query /search-global?q=term after 2+ characters:

const res = await fetch(`/search-global?q=${encodeURIComponent(value)}`);
const users = await res.json();

Results are shown with โ€œAddโ€ buttons. Selected users are pushed into groupSelected:

groupSelected.push(user);
updateGroupSelected();

Selected Users UI

Render list and generate hidden inputs for backend:

hiddenInput.name = 'user_ids';
hiddenInput.value = u.id;

Disable submit if < 2 users:

submit.disabled = groupSelected.length < 2;

๐Ÿ’ฌ Chat View Logic

Chat Selection

Every .chat-item has a click handler:

item.addEventListener('click', async () => {
  const { chatId, isGroup } = item.dataset;
  initializeChat(chatId, isGroup === 'true');
});

Avoids reloading same chat:

if (currentChatId === chatId && currentChatIsGroup === isGroup) return;

Sets form hidden fields:

chatIdField.value = chatId;
isPersonalField.value = isGroup ? 'false' : 'true';

Message Rendering

const renderMessages = (messages) => {
  messages.forEach(msg => {
    const wrapper = document.createElement('div');
    wrapper.className = msg.sender_id === userId ? 'mymessage' : 'amessage';
    wrapper.innerHTML = `<p class="message-info">${hora}</p><p class="...">${msg.message_cont}</p>`;
  });
};

Group chats show sender name and avatar if not self:

if (msg.sender_id !== userId) {
  innerHTML = `<div>${msg.sender_name}</div><div>${msg.message_cont}</div>`;
}

Adds date dividers using:

const getFriendlyDateLabel = (d) => { /* Today, Yesterday, Date */ };

๐Ÿ”„ Message Polling (Refresh)

Polling happens every 3.5s only if user is near the bottom:

const distanceFromBottom = messagesContainer.scrollHeight - messagesContainer.scrollTop - messagesContainer.clientHeight;
if (distanceFromBottom < 200) fetchAndRenderMessages(currentChatId, currentChatIsGroup);

Polling control:

let refreshInterval = null;
const startMessageRefresh = () => { refreshInterval = setInterval(...); };
const stopMessageRefresh = () => { clearInterval(refreshInterval); };

Tab visibility is respected:

document.addEventListener('visibilitychange', () => {
  if (document.hidden) stopMessageRefresh();
  else startMessageRefresh();
});

๐Ÿ“ Sending Messages

Intercept form submission:

messageForm.addEventListener('submit', async e => {
  e.preventDefault();
  const message = messageInput.value.trim();
});

Send via AJAX to /send-message:

const res = await fetch('/send-message', { method: 'POST', body: formData });

On success: refresh messages On failure: show alert and restore input

const errorMsg = document.createElement('div');
errorMsg.className = 'alert alert-danger';

๐Ÿงพ Group Sidebar Panel

Show/Hide

groupOptionsBtn.addEventListener('click', showGroupSidebar);
closeSidebarBtn.addEventListener('click', hideGroupSidebar);

Hides on outside click:

document.addEventListener('click', (e) => {
  if (!groupInfoSidebar.contains(e.target)) hideGroupSidebar();
});

Populate Content

document.querySelector('.group-name').textContent = groupData.chat_name;
document.getElementById('group-creation-date').textContent = new Date(groupData.creation_date).toLocaleDateString();

Render Members

groupData.members.forEach(member => {
  const item = document.createElement('div');
  item.innerHTML = `<img src="${member.pfp_route}"><p>${member.first_name}</p>`;
});

Navigation and removal:

memberInfo.onclick = () => window.location.href = `/user/${member.user_id}`;
removeBtn.onclick = () => confirmRemoveFromGroup(member.user_id, ...);

๐Ÿ“Œ Special Features

Chat ID in URL

Auto-selects the chat from ?currentChatId=<id>:

const urlParams = new URLSearchParams(window.location.search);
const initialChatId = urlParams.get('currentChatId');

Scroll Button

Shows or hides #scroll-to-bottom-btn:

const atBottom = messagesContainer.scrollHeight - messagesContainer.scrollTop - messagesContainer.clientHeight < 100;
scrollBtn.style.display = atBottom ? 'none' : 'block';

Scroll to bottom smoothly:

scrollBtn.addEventListener('click', () => {
  messagesContainer.scrollTo({ top: messagesContainer.scrollHeight, behavior: 'smooth' });
});

๐Ÿ“‚ DOM Elements

Selector
Function

#new-chat-btn

Opens modal for group creation

#groupUserSearch

Search input field

#groupUserResults

Search result container

#groupSelectedUsers

User list in modal

#groupHiddenInputs

Hidden inputs for form

#message-form

Message input form

.messages

Main message panel

.ChatInfo

Top chat bar

.chat-item

Left-panel chat selectors

#group-options-btn

Opens group sidebar

#group-info-sidebar

Sidebar container


โš ๏ธ Error Handling

Network/Timeout

Handled with AbortController:

const controller = new AbortController();
setTimeout(() => controller.abort(), 10000);

Error UI feedback

AJAX failures show alerts dynamically:

const errorMsg = document.createElement('div');
errorMsg.className = 'alert alert-warning';

All errors render fallback UIs, e.g.,

messagesContainer.innerHTML = '<p class="text-center text-danger">Error loading messages</p>';

Last updated