Watch the video or follow the steps below to build and run your application.
Download here: https://github.com/PubNubDevelopers/Javascript-Chat-Tutorial-App/
First, you'll need to create the basic layout for the chat application. Below is a simple webpage with the elements needed to create a chat application using the PubNub JavaScript SDK.
Create files named 'index.html' and 'styles.css' and add the following code or download these files from here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css">
<title>JavaScript Chat</title>
</head>
<body>
<div class="modal" id="editNameModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Chat Options</h5>
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
<p>Change your display name here.</p>
<input class="form-control" id="nameInput" placeholder="New Display Name">
</div>
<div class="modal-footer">
<button type="button" data-bs-dismiss="modal" class="btn btn-secondary">Cancel</button>
<button id="saveOptionsButton" type="button" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</div>
<div class="msg-group" id="messages-area">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<button data-bs-toggle="modal" data-bs-target="#editNameModal" class="btn btn-secondary input-btn" type="button">Chat Options</button>
</div>
<textarea id="input-box" class="form-control" rows="1"></textarea>
<div class="input-group-append">
<button id="send-button" class="btn btn-primary input-btn" type="button">Send Message</button>
</div>
</div>
<span class="badge bg-primary online-users-label">Online Users: <span id="online-users-count">NA</span></span>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.pubnub.com/sdk/javascript/pubnub.7.1.2.min.js"></script>
<script>
// Add chat logic here
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.card {
padding: 8px 0px 8px 0px;
margin: 8px;
overflow-y: auto;
overflow-wrap: break-word;
word-wrap: break-word;
}
.card:last-child {
margin-bottom: 130px;
}
.input-btn {
height: 90px;
}
.input-group {
position: fixed;
height: 90px;
bottom: 10px;
}
.msg-group {
height: 90%;
overflow-y: scroll;
}
.online-users-label {
position: fixed;
bottom: 0px;
}
textarea {
resize: none;
}
There are a few key items to pay attention to:
The modal for editing chat options - In this app we'll use this modal to enable a chat user to set or change their username. There's a text input for the username, 'Cancel' button, and 'Save' button.
The <div> for the messages - This is where messages will be displayed for the chat.
The input area - New messages will be composed and sent from here. There's a 'Send Message' button that will be used to publish messages to all the chat participants. There's also a 'Chat Options' button that displays the modal for editing chat options.
These three items are required for this tutorial and you can copy them into an existing application or use the template provided above.
Your application should now look like this - however, it won't work yet. Add basic chat functionality in the next step.
Replace '// Add chat logic here' with the following JavaScript code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
(function() {
function makeid(length) { // Used to create new user ids
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
var pubnub = new PubNub({ // Set your PubNub keys here
publishKey: 'YOUR-PUBNUB-PUBLISH-KEY',
subscribeKey: 'YOUR-PUBNUB-SUBSCRIBE-KEY',
uuid: 'Anon-' + makeid(5)
});
var messagesArea = document.getElementById('messages-area'),
input = document.getElementById('input-box'),
sendButton = document.getElementById('send-button'),
saveButton = document.getElementById('saveOptionsButton'),
nameInput = document.getElementById('nameInput'),
channel = 'chat-channel';
class chatControl { // Formats messages and scrolls into view.
publishMessage(name, msg) {
messagesArea.innerHTML = messagesArea.innerHTML + this.msg(name, msg, 'start', 'primary');
messagesArea.scrollIntoView(false);
}
receiveMessage(name, msg) {
messagesArea.innerHTML = messagesArea.innerHTML + this.msg(name, msg, 'end', 'secondary');
messagesArea.scrollIntoView(false);
}
msg(name, msg, side, style) {
var msgTemp = `
<div class="card text-white bg-${style}">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-${side} display-${name}">${name}</h6>
<p class="card-text float-${side}">${msg}</p>
</div>
</div>
`;
return msgTemp;
}
}
var chat = new chatControl();
pubnub.addListener({ // Get new messages.
message: function(msg) {
// console.log(msg);
if (msg.publisher == pubnub.getUUID()) { // Check who sent the message.
chat.publishMessage('You', msg.message);
} else {
chat.receiveMessage(msg.publisher, msg.message);
}
},
});
pubnub.subscribe({ // Subscribe to wait for messages
channels: [channel]
});
function publishMessage() { // Send messages with PubNub.
var msg = input.value.trim().replace(/(?:\r\n|\r|\n)/g, '<br>'); // Format message.
input.value = '';
if (msg != '') {
var publishConfig = {
channel: channel,
message: msg
};
pubnub.publish(publishConfig, function(status, response) { // Publish message to current channel.
// console.log(status, response);
});
}
};
sendButton.addEventListener("click", function(e) {
publishMessage();
});
input.addEventListener('keyup', function(e) {
if ((e.keyCode || e.charCode) === 13) {
publishMessage();
}
});
})();
Your 'index.html' file should now look like this.
Replace 'YOUR-PUBLISH-KEY-HERE' and 'YOUR-SUBSCRIBE-KEY-HERE' with your PubNub keys.
The key details are:
makeid() - this function creates a new string used for uniquely identifying each chat user.
Creating a new PubNub instance - This is where you provide your PubNub keys and a unique UUID from the makeid() function to create a connection to PubNub.
'class chatControl' - When a new message is received or published the message needs to be formatted and displayed and then added to the area for displaying messages. We alter the display format depending on if you sent the message or received it from someone else.
PubNub listener - Here is where new messages are received. The UUID of the received message is compared to the UUID of the session to see who sent the message for formatting purposes.
PubNub subscribe - Subscribe to the 'chat-channel' and wait for messages.
publishMessage() - Publishes a message when the 'Send Message' button is pressed or the enter key is pressed while editing the message input.
You can now try out your chat application by opening 'index.html' in your browser. It should look (and work) like this.
Next, add the ability to see how many other chat users are in the chat.
Adding a count of the current number of online users can be done with the Presence feature of the PubNub JavaScript SDK.
You'll need to do two things:
Update the display of the users count when it changes.
Load the current users when you join the chat.
To update the count when it changes you'll need to listen for presence events in the same listener you used in Step 2. Add the below code inside your PubNub listener and after the message listener.
1
2
3
presence: (presenceEvent) => { // Update the number of online members.
document.getElementById("online-users-count").innerHTML = presenceEvent.occupancy;
},
To load the current users when you join the chat use a Presence Here Now call when the application loads. Add the following code to your JavaScript (in the example we added it at the bottom of the file).
1
2
3
4
5
6
7
pubnub.hereNow({ // Update the number of online members.
channels: [channel],
}).then((response) => {
document.getElementById("online-users-count").innerHTML = response.totalOccupancy;
}).catch((error) => {
console.log(error)
});
Your 'index.html' file should now look like this.
Open 'index.html' in your browser and you should see the 'Online Users' count go up in the bottom left. It should look (and work) like this.
Next, load the messages previously sent so the chat isn't blank when you refresh the page.
To fetch previous messages you'll use the Message Persistence API for PubNub JavaScript SDK.
To load and show previous messages when you join the chat use a 'fetchMessages' call when the application loads. Add the following code to your JavaScript (in the example we added it at the bottom of the file).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pubnub.fetchMessages( // Get the last 10 messages sent in the chat.
{
channels: [channel],
count: 10,
},
function (status, response) {
if (response.channels[channel] && channel in response.channels) {
response.channels[channel].forEach((message) => {
//console.log(message);
if (message.uuid == pubnub.getUUID()) { // Check who sent the message.
chat.publishMessage('You', message.message);
} else {
chat.receiveMessage(message.uuid, message.message);
}
});
}
}
);
Your 'index.html' file should now look like this.
Open 'index.html' in your browser and you should see the last 10 messages sent load in the chat. Try sending a few messages and then reload the page. It should look (and work) like this.
Next, give users the ability to change their usernames in the chat.
To set a users username you'll need to use the Metadata API for PubNub JavaScript SDK.
You'll need to do three things for this feature:
Fetch usernames when new messages are received from unknown users and store it for later messages.
Re-fetch the username if a user has published a message indicating they changed it.
Set UUID Metadata when a user changes their username and publish a message to indicate to other chat users the username has changed.
Start by replacing your 'class chatControl' with this new one that gets UUID Metadata when it's missing (when a new user sends a message) as well as when a message is received indicating a username has changed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var usernames = [];
class chatControl { // Formats messages and scrolls into view.
publishMessage(uuid, msg) {
if (msg != pubnub.getUUID()+"NEWUSERNAME"){
messagesArea.innerHTML = messagesArea.innerHTML + this.msg(uuid, "You", msg, 'start', 'primary');
messagesArea.scrollIntoView(false);
}
}
receiveMessage(uuid, msg) {
if (msg == uuid+"NEWUSERNAME"){
pubnub.objects.getUUIDMetadata({ // Recheck metadata for new username and update previous messages.
uuid: uuid,
},
function (status, response) {
if (response) {
usernames[uuid] = response.data.name; // Set username from metadata.
for(var i = 0; i < document.getElementsByClassName('display-'+response.data.id).length; i++){
document.getElementsByClassName('display-'+response.data.id)[i].innerText = response.data.name;
}
}
}
);
} else {
if (!usernames[uuid]) { // Get the username if not stored.
usernames[uuid] = uuid; // If username not set then set to the UUID.
pubnub.objects.getUUIDMetadata({
uuid: uuid,
},
function (status, response) {
if (response) {
usernames[uuid] = response.data.name; // Set username from metadata.
for(var i = 0; i < document.getElementsByClassName('display-'+response.data.id).length; i++){
document.getElementsByClassName('display-'+response.data.id)[i].innerText = response.data.name;
}
}
}
);
}
messagesArea.innerHTML = messagesArea.innerHTML + this.msg(uuid, usernames[uuid], msg, 'end', 'secondary');
messagesArea.scrollIntoView(false);
}
}
msg(uuid, name, msg, side, style) {
var msgTemp = `
<div class="card text-white bg-${style}">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-${side}"><span class="display-${uuid}">${name}<span></h6>
<p class="card-text float-${side}">${msg}</p>
</div>
</div>
`;
return msgTemp;
}
}
var chat = new chatControl();
To set or change a username you'll need an event listener for the 'Save' button that gets the username input from the 'Chat Options' modal and set's the name with the Metadata API for User Metadata. This button will also publish a "NEWUSERNAME" message to update the username for other participants.
Add the following code to your JavaScript (in the example we added it at the bottom of the file).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
saveButton.addEventListener("click", function(e) { // Save new username
username = nameInput.value.replace(/[^a-zA-Z0-9 ]/g, '').substring(0, 30);
pubnub.objects.setUUIDMetadata({
data: {
name: username
}
})
.then((resp) => {
alert("Your username has been updated.");
var publishConfig = { // Send a message to force username to update on other clients.
channel: channel,
message: pubnub.getUUID() + "NEWUSERNAME"
};
pubnub.publish(publishConfig, function(status, response) { // Publish message to current channel.
// console.log(status, response);
});
// console.log(resp);
})
.catch((error) => {
alert("Your username was not updated. See console for details.");
console.log(err);
});
});
Your 'index.html' file should now look like this.
Open 'index.html' in your browser twice in two tabs, send a message or two, and change your username. It should look (and work) like this.