Javascript Vue

A beautiful chat rooms component made with Vue.js16 min read

vue-advanced-chat

A beautiful chat rooms component made with Vue.js – compatible with Vue, React & Angular

Vue 3 compatibility 🚀

Features

Vue, Angular & React compatibility
Customizeable realtime chat messaging
Backend agnostic
Images, videos, files, voice messages & emojis
Edit messages & reply to other messages
Tag users & emojis shortcut suggestions
UI elements for seen, new, deleted, typing and system messages
Text formatting – bold, italic, strikethrough, underline, code, multiline
Online / Offline users status
Flexible options and slots
Light and dark theme modes
Firestore example
Typescript, PWA, Web Component support

Real World Example

A Progressive Web Application showcasing all the features of vue-advanced-chat component.

Built with Firestore, Vuetify, and Push Notifications.

If you wish to get premium access to the real world example source code, please contact me by email.

You will get a fully working chat application for web and mobile:

UI and backend integration
Email, Facebook and Google authentication
Real-time messaging, browser push notifications, images optimization (Firebase Cloud Functions to compress avatars)
UI/UX components for alerts (errors, information), dialogs, etc.
Add existing users to a room using their email
Send email invitations to non-existing users
Edition of profile and rooms
Addition and deletion of room users
Optimised firestore implementation to reduce bandwidth and costs as much as possible
State management using vuex
Internationalisation (i18n)
Google Analytics
Support to help you get the chat up and running

Installation

# Using npm
npm install –save vue-advanced-chat

# Using yarn
yarn add –save vue-advanced-chat

# Using CDN
<script src=”https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-advanced-chat.min.js”></script>

If you want to send mp3 audio messages, you may need to install lamejs inside your project:

npm install lamejs –save

Installation with React & Angular

Usage

You can import it as a custom component:

<template>
<chat-window
:current-user-id=”currentUserId”
:rooms=”rooms”
:messages=”messages”
/>
</template>

<script>
import ChatWindow from ‘vue-advanced-chat’
import ‘vue-advanced-chat/dist/vue-advanced-chat.css’

export default {
components: {
ChatWindow
},
data() {
return {
rooms: [],
messages: [],
currentUserId: 1234
}
}
}
</script>

Or if you used CDN import:

<template>
<vue-advanced-chat/>
</template>

<script>
export default {
data() {
return {
rooms: [],
messages: [],
currentUserId: 1234
}
},
mounted() {
document.querySelector(‘vue-advanced-chat’).currentUserId = this.currentUserId
document.querySelector(‘vue-advanced-chat’).rooms = this.rooms
document.querySelector(‘vue-advanced-chat’).messages = this.messages
}
}
</script>

Important notes

vue-advanced-chat component is performance oriented, hence you have to follow specific rules to make it work properly.

Use array assignement instead of push method

// DO THIS
const rooms = []
for (let i = 0; i < res.length; i++) {
rooms.push(res)
}
this.rooms = rooms

// DON’T DO THIS
for (let i = 0; i < res.length; i++) {
this.rooms.push(res)
}

// DO THIS
this.rooms[i].typingUsers = […this.rooms[i].typingUsers, typingUserId]

// DON’T DO THIS
this.rooms[i].typingUsers.push(typingUserId)

To add or replace an item inside an array, use spread operator

// DO THIS
this.rooms[roomIndex] = room
this.rooms = […this.rooms]

// DON’T DO THIS
this.rooms[roomIndex] = room

// AND DON’T DO THIS
this.rooms.push(room)

Follow the UI loading pattern by updating messagesLoaded prop every time a new room is fetched

fetchMessages({ room, options }) {
this.messagesLoaded = false

// use timeout to imitate async server fetched data
setTimeout(() => {
this.messages = []
this.messagesLoaded = true
})
}

Props API

Prop

Type
Required
Default

height
String

600px

current-user-id(1)
[String, Number]
true

rooms
Array

[]

rooms-order
String

desc

loading-rooms(2)
Boolean

false

rooms-loaded(3)
Boolean

false

room-id(4)
[String, Number]

null

load-first-room(5)
Boolean

true

rooms-list-opened
Boolean

true

messages
Array

[]

room-message(6)
String

null

messages-loaded(7)
Boolean

false

room-actions(8)
Array

[]

menu-actions(9)
Array

[]

message-actions(10)
Array

(10)

show-search
Boolean

true

show-add-room
Boolean

true

show-send-icon
Boolean

true

show-files
Boolean

true

show-audio
Boolean

true

show-emojis
Boolean

true

show-reaction-emojis
Boolean

true

show-new-messages-divider(11)
Boolean

true

show-footer(12)
Boolean

true

text-messages(13)
Object

null

text-formatting(14)
Boolean

true

link-options(15)
Object

{ disabled: false, target: ‘_blank’ }

room-info-enabled (16)
Boolean

false

textarea-action-enabled(17)
Boolean

false

responsive-breakpoint(18)
Number

900

single-room(19)
Boolean

false

theme(20)
Sring

light

accepted-files(21)
String

*

styles(22)
Object

(19)

(1) current-user-id is required to display UI and trigger actions according to the user using the chat (ex: messages position on the right, etc.)

(2) loading-rooms can be used to show/hide a spinner icon while rooms are loading

(3) rooms-loaded must be set to true when all rooms have been loaded. Meaning the user cannot scroll to load more paginated rooms

(4) room-id can be used to load a specific room at any time

(5) load-first-room can be used to remove the default behaviour of opening the first room at initialization

(6) room-message can be used to add a default textarea value

(7) messages-loaded must be set to true when all messages of a conversation have been loaded. Meaning the user cannot scroll on top to load more paginated messages

(8) room-actions can be used to display your own buttons when clicking the dropdown icon of each room inside the rooms list.

You can then use the room-action-handler event to call your own action after clicking a button. Ex:

room-actions=”[
{
name: ‘archiveRoom’,
title: ‘Archive Room’
}
]”

(9) menu-actions can be used to display your own buttons when clicking the vertical dots icon inside a room.

You can then use the menu-action-handler event to call your own action after clicking a button. Ex:

menu-actions=”[
{
name: ‘inviteUser’,
title: ‘Invite User’
},
{
name: ‘removeUser’,
title: ‘Remove User’
},
{
name: ‘deleteRoom’,
title: ‘Delete Room’
}
]”

(10) message-actions can be used to display your own buttons when clicking the dropdown icon inside a message.

You can then use the message-action-handler event to call your own action after clicking a button. Ex:

message-actions=”[
{
name: ‘addMessageToFavorite’,
title: ‘Add To Favorite’
},
{
name: ‘shareMessage’,
title: ‘Share Message’
}
]”

You can use built-in message-actions names to trigger specific UI modifications when clicked.

Currently, replyMessage, editMessage and deleteMessage action names are available.

If messageActions is not set, it will use the default values below.

If you don’t want to display this messageActions menu, you can pass it an empty array.

messageActions=”[
{
name: ‘replyMessage’,
title: ‘Reply’
},
{
name: ‘editMessage’,
title: ‘Edit Message’,
onlyMe: true
},
{
name: ‘deleteMessage’,
title: ‘Delete Message’,
onlyMe: true
}
]”

(11) show-new-messages-divider can be used to show/hide the blue line divider between seen and unseen messages.

(12) show-footer can be used to hide the room footer. For example to prevent users to send any message or media.

(13) text-messages can be used to replace default i18n texts. Ex:

text-messages=”{
ROOMS_EMPTY: ‘Aucune conversation’,
ROOM_EMPTY: ‘Aucune conversation sélectionnée’,
NEW_MESSAGES: ‘Nouveaux messages’,
MESSAGE_DELETED: ‘Ce message a été supprimé’,
MESSAGES_EMPTY: ‘Aucun message’,
CONVERSATION_STARTED: ‘La conversation a commencée le :’,
TYPE_MESSAGE: ‘Tapez votre message’,
SEARCH: ‘Rechercher’,
IS_ONLINE: ‘est en ligne’,
LAST_SEEN: ‘dernière connexion ‘,
IS_TYPING: ‘est en train de taper…’
}”

(14) text-formatting can be used to add text formatting. Currently, bold, italic, strikethrough, underline, inline code and multiline code formatting are available and can be used in conjonction. You can disable text formatting by passing the prop as :text-formatting=”false”.

Style
Syntax
Example
Output

Bold
* *
*This is bold text*
This is bold text

Italic
_ _
_This text is italicized_
This text is italicized

Strikethrough
~ ~
~This was mistaken text~
This was mistaken text

Underline
° °
°This text is underlined°
This text is underlined

Nested formatting
* * and _ _
*This text is _extremely_ important*
This text is extremely important

Inline Code

Example: `This is inline code`

Output: This is inline code

Multiline Code

Example: “`This is multiline code“`

Output:

This is
multiline code

(15) link-options can be used to disable url links in messages, or change urls target. Ex:

:link-options=”{ disabled: true, target: ‘_self’ }”

(16) room-info-enabled can be used to trigger an event after clicking the room header component.

You can then use the room-info event to call your own action after clicking the header.

(17) textarea-action-enabled can be used to add an extra icon on the right of the textarea

You can then use the textarea-action-handler event to call your own action after clicking the icon.

(18) responsive-breakpoint can be used to collapse the rooms list on the left when then viewport size goes below the specified width.

(19) single-room can be used if you never want to show the rooms list on the left. You still need to pass the rooms prop as an array with a single element.

(20) theme can be used to change the chat theme. Currently, only light and dark are available.

(21) accepted-files can be used to set specifics file types allowed in chat. By default, all file types are allowed: “*”.

Example: set “accepted-files=”image/png, image/jpeg, application/pdf” to allow JPG PNG and PDF files only

(22) styles can be used to customize your own theme. You can find the full list here

styles=”{
general: {
color: ‘#0a0a0a’,
colorSpinner: ‘#333’,
borderStyle: ‘1px solid #e1e4e8’
},

footer: {
background: ‘#f8f9fa’,
backgroundReply: ‘rgba(0, 0, 0, 0.08)’
},

icons: {
search: ‘#9ca6af’
}
}”

Props data structure

Your props must follow a specific structure to display rooms and messages correctly:

Rooms prop

rooms=”[
{
roomId: 1,
roomName: ‘Room 1’,
avatar: ‘assets/imgs/people.png’,
unreadCount: 4,
index: 3,
lastMessage: {
content: ‘Last message received’,
senderId: 1234,
username: ‘John Doe’,
timestamp: ’10:20′,
saved: true,
distributed: false,
seen: false,
new: true
},
users: [
{
_id: 1234,
username: ‘John Doe’,
avatar: ‘assets/imgs/doe.png’,
status: {
state: ‘online’,
lastChanged: ‘today, 14:30’
}
},
{
_id: 4321,
username: ‘John Snow’,
avatar: ‘assets/imgs/snow.png’,
status: {
state: ‘offline’,
lastChanged: ’14 July, 20:00′
}
}
],
typingUsers: [ 4321 ]
}
]”

If you add the index property, your rooms will be ordered using this value.
index can be any sortable value, like a string, datetime, timestamp, etc.

For each room user, you can add the status property, which can hold the state and lastChanged properties:

state can be ‘online’ or ‘offline’
lastChanged is the date when state was last modified.

typingUsers is an array of all the users who are currently writing a message

Messages prop

Message objects are rendered differently depending on their type. Text, emoji, image, video and file types are supported.

Each message object has a senderId field which holds the id of the corresponding agent. If senderId matches the currentUserId prop, specific UI and actions will be implemented.

Notes:

username will be displayed on each message of corresponding agents if at least 3 users are in the room
system is used to show messages with a specific centered display
indexId can be used if you need to change a message ID that is already displayed in a room, this preventing an animation glitch. For example, when you don’t know in advance the message ID your backend will create.

Message states:

saved: true one checkmark
distributed: true two checkmarks
seen: true two blue checkmarks
deleted: true grey background with deleted message text

messages=”[
{
_id: 7890,
indexId: 12092,
content: ‘Message 1’,
senderId: 1234,
username: ‘John Doe’,
avatar: ‘assets/imgs/doe.png’,
date: ’13 November’,
timestamp: ’10:20′,
system: false,
saved: true,
distributed: true,
seen: true,
deleted: false,
disableActions: false,
disableReactions: false,
files: [
{
name: ‘My File’,
size: 67351,
type: ‘png’,
audio: true,
duration: 14.4,
url: ‘https://firebasestorage.googleapis.com/…’,
preview: ‘data:image/png;base64,iVBORw0KGgoAA…’
}
],
reactions: {
😁: [
1234, // USER_ID
4321
],
🥰: [
1234
]
},
replyMessage: {
content: ‘Reply Message’,
senderId: 4321,
files: [
{
name: ‘My Replied File’,
size: 67351,
type: ‘png’,
audio: true,
duration: 14.4,
url: ‘https://firebasestorage.googleapis.com/…’,
preview: ‘data:image/png;base64,iVBORw0KGgoAA…’
}
]
},
}
]”

Events API

Event

Params
Fires when a user

fetch-messages(1)
{ room, options }
Scrolled on top to load more messages

fetch-more-rooms(2)

Scrolled to load more rooms

send-message
{ roomId, content, files(9), replyMessage(10), usersTag }
Sent a message

edit-message
{ roomId, messageId, newContent, files(9), replyMessage(10) ,usersTag }
Edited a message

delete-message
{ roomId, message }
Deleted a message

open-file
{ message, file }
Clicked to view or download a file

open-user-tag(3)
{ user }
Clicked on a user tag inside a message

add-room

Clicked on the plus icon next to searchbar

room-action-handler(4)
{ roomId, action }
Clicked on the vertical dots icon inside a room

menu-action-handler(5)
{ roomId, action }
Clicked on the vertical dots icon inside a room

message-action-handler(6)
{ roomId, action, message }
Clicked on the dropdown icon inside a message

send-message-reaction
{ roomId, messageId, reaction, remove }
Clicked on the emoji icon inside a message

room-info (7)
room
Clicked the room header bar

toggle-rooms-list
{ opened }
Clicked on the toggle icon inside a room header

textarea-action-handler(8)
{ roomId, message }
Clicked on custom icon inside the footer

typing-message
{ message, roomId }
Started typing a message

(1) fetch-messages is triggered every time a room is opened. If the room is opened for the first time, the options param will hold reset: true.

(1) fetch-messages should be a method implementing a pagination system. Its purpose is to load older messages of a conversation when the user scroll on top.

(2) fetch-more-rooms is triggered when scrolling down the rooms list, and should be a method implementing a pagination system.

(3) open-user-tag is triggered when clicking a user tag inside a message. When creating a user tag by typing @ in the footer textarea and sending the message, the tag will be identified with the below pattern:

<usertag>TAGGED_USER_ID</usertag>

This will make the tag clickable inside a message. Ex: message tag content

send-message and edit-message events will handle that pattern for you and pass it in the content param.

(4) room-action-handler is the result of the room-actions prop.

When clicking a button from your room-actions array, room-action-handler will give you the name of the button that was click.
Then you can do whatever you want with it. Ex:

menuActionHandler({ roomId, action }) {
switch (action.name) {
case ‘archiveRoom’:
// call a method to archive the room
}
}

(5) menu-action-handler is the result of the menu-actions prop.

When clicking a button from your menu-actions array, menu-action-handler will give you the name of the button that was click.
Then you can do whatever you want with it. Ex:

menuActionHandler({ roomId, action }) {
switch (action.name) {
case ‘inviteUser’:
// call a method to invite a user to the room
case ‘removeUser’:
// call a method to remove a user from the room
case ‘deleteRoom’:
// call a method to delete the room
}
}

(6) message-action-handler is the result of the message-actions prop.

When clicking a button from your message-actions array, message-action-handler will give you the name of the button that was click and the corresponding message data.
Then you can do whatever you want with it. Ex:

messageActionHandler({ roomId, action, message }) {
switch (action.name) {
case ‘addMessageToFavorite’:
// call a method to add a message to the favorite list
case ‘shareMessage’:
// call a method to share the message with another user
}
}

(7) room-info is the result of the room-info-enabled prop.

(8) textarea-action-handler is the result of the textarea-action-enabled prop.

(9) Array of files where each file contain: { blob, localURL, name, size, type, extension }

(10) replyMessage object is available when the user replied to another message by clicking the corresponding icon, and contains the message information that was clicked.

Named Slots

Example:

<template #room-header=”{ room, userStatus }”>
{{ room.roomName }} – {{ userStatus }}
</template>

Slot

Action
Data
Overridden slots

custom-action-icon
Add a custom icon inside the footer

room-list-item
Replace the template of the room list items
room
room-list-avatar, room-list-options

room-list-avatar
Replace the avatar of room list items
room

room-list-options
Replace the template of the list room options
room
room-list-options-icon

rooms-header
Replace the content above the search bar

rooms-list-search
Replace the search bar

room-header
Replace the template of the room header
room, typingUsers, userStatus
room-options, menu-icon, toggle-icon

room-header-avatar
Replace the template of the room header avatar
room

room-header-info
Replace the template of the room header text
room, typingUsers, userStatus

room-options
Replace the template of the room options

menu-icon

message
Replace the template of the message box
message
deleted-icon, eye-icon, document-icon, pencil-icon, checkmark-icon, dropdown-icon, emoji-picker-icon

messages-empty
Replace the empty message template

rooms-empty
Replace the empty rooms template

no-room-selected
Replace the no room selected template

menu-icon
Replace the room menu icon

toggle-icon
Replace the toggle room list icon

scroll-icon
Replace the scroll to newest message icon

reply-close-icon
Replace the reply close icon

image-close-icon
Replace the image close icon

file-icon
Replace the file icon

file-close-icon
Replace the file close icon

edit-close-icon
Replace the edit close icon

emoji-picker-icon
Replace the emoji picker icon

emoji-picker-reaction-icon
Replace the emoji picker reaction icon (in the message box)

paperclip-icon
Replace the paperclip icon

send-icon
Replace the message send icon

eye-icon
Replace the eye icon (image message)

document-icon
Replace the document icon

pencil-icon
Replace the pencil icon

checkmark-icon
Replace the checkmark icon
message

deleted-icon
Replace the deleted icon
deleted

microphone-icon
Replace the microphone icon

dropdown-icon
Replace the dropdown icon

room-list-options-icon
Replace the room list options dropdown icon

search-icon
Replace the search icon

add-icon
Replace the add room icon

audio-pause-icon
Replace the message audio pause icon

audio-play-icon
Replace the message audio play icon

Using with Firestore

Source code

You can find the source code to implement a full featured chat app using Firebase/Firestore inside the demo folder.

To test it using your own Firebase project:

Setup Cloud Firestore (to store users and rooms) and Realtime Database (to store users online status)
Clone this repository: git clone https://github.com/antoine92190/vue-advanced-chat.git
Inside demo/src/firestore/index.js file, replace the line const config = … by your own Firebase config
Go inside demo folder and run npm run serve

Data structure

If you decide to use the same code as in the demo folder to create your chat app, you need to have a specific Firestore data structure.

To help you get started, I added in demo/src/App.vue a method addData to initialize some data on your Firestore database.

Users collection

users: {
USER_ID_1: {
_id: 1,
username: ‘User 1’
},
USER_ID_2: {
_id: 2,
username: ‘User 2’
},
USER_ID_3: {
_id: 3,
username: ‘User 2’
}
}

Rooms collection

chatRooms: {
ROOM_ID_1: {
users: [1, 3]
},
ROOM_ID_2: {
users: [1, 2, 3]
}
}

Messages collection inside a room document

messages: {
MESSAGE_ID_1: {
content: ‘My first message to <usertag>John</usertag>’,
senderId: 2,
timestamp: ‘December 11, 2019 at 4:00:00 PM’,
seen: true
}
}

Notes

You need to create a composite index to order rooms by last message received.
The easiest way to do it is to create a room, then click the error message url in the browser debugging console.

Use as a Web Component with React and Angular

Install vue-advance-chat component

Follow Installation steps

Install Vue.js

# Using npm
npm install –save vue

# Using yarn
yarn add –save vue

Angular Setup

// angular.json

“build”: {
“scripts”: [
“./node_modules/vue/dist/vue.min.js”,
“./node_modules/vue-advanced-chat/dist/vue-advanced-chat.min.js”
]
}

// page.module.ts

@NgModule({

schemas: [CUSTOM_ELEMENTS_SCHEMA],
})

<!– page.html –>

<vue-advanced-chat
height=”100vh”
[currentUserId]=”currentUserId”
[roomId]=”roomId”
[rooms]=”rooms”
[roomsLoaded]=”true”
[messages]=”messages”
[messagesLoaded]=”messagesLoaded”
[showFiles]=”true”
[showEmojis]=”true”
[textFormatting]=”true”
[showReactionEmojis]=”true”
[showFooter]=”true”
(fetch-messages)=”fetchMessages($event.detail[0])”
(send-message)=”sendMessage($event.detail[0])”

>
</vue-advanced-chat>

Use in a Vue 3 project

Install vue-advance-chat component (next branch)

# Using npm
npm install –save https://github.com/antoine92190/vue-advanced-chat/tarball/next

Install vue migration build

# Using npm
npm install –save-dev @vue/compat

Add migration build config

// vue.config.js

module.exports = {
chainWebpack: config => {
config.resolve.alias.set(‘vue’, ‘@vue/compat’)

config.module
.rule(‘vue’)
.use(‘vue-loader’)
.tap(options => {
return {
…options,
compilerOptions: {
compatConfig: {
MODE: 3
}
}
}
})
}
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Pin It on Pinterest