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
Modal Launch
Resets modal UI and shows modal:
document.getElementById('new-chat-btn').addEventListener('click', () => {
groupSelected = [];
groupUserSearch.value = '';
new bootstrap.Modal(document.getElementById('createGroupModal')).show();
});
User Search
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
#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