npx express-generator -v ejs socket-io
npm i socket.io
Stub it up with the following:
touch public/javascripts/chat.js
// Creating a socket connection via functionality provided by a CDN link in the HTML <head>
let socket = io()
<script src="https://cdn.socket.io/socket.io-3.0.1.min.js"></script>
<script defer src="/javascripts/chat.js"></script>
This is done to keep the code organized. Rather than putting ALL of this code in our bin/www file, we're going to export it from io.js.
touch io.js
const io = require('socket.io')()
io.on('connection', (socket) => {
// This is where all of our server-side socket.io functionality will exist.
console.log("The server is running.")
})
module.exports = io
const http = require('http');
// Require the io.js module here:
const io = require("../io")
.
.
.
const server = http.createServer(app);
// Attach io to the https server here:
io.attach(server)
<button id="messageButton">Message Button<button>
Step 10: In chat.js
, add a cached element reference and an event listener for a click
on our new button
Click the button and verify that the message appears in the browser console.
const messageButton = document.getElementById("messageButton")
messageButton.addEventListener("click", () => {
console.log("message button clicked")
})
messageButton.addEventListener("click", () => {
// Emit 'message' to the server (optionally passing data)
socket.emit('message', {message: "message data"})
})
When 'message' is heard, we'll execute a callback function:
io.on('connection', (socket) => {
console.log("The server is running.")
// Listening for 'message' to be emitted by a client, then displaying the optional data passed along. Notice that the data must be passed as an argument to the callback function!
socket.on('message', (messageData) => {
console.log(messageData, 'data sent via message')
})
})
<input type="text" id="messageInput" placeholder="Type a message!">
const messageInput = document.getElementById("messageInput")
Step 15: Refactor the callback function in chat.js
to pass the <input>
's value instead as the message data
messageButton.addEventListener("click", () => {
console.log("message button clicked")
// Emit 'banana' to the server (optionally passing data)
socket.emit('message', {message: messageInput.value})
})
<input type="text" id="userName" placeholder="Enter name:"><br><br><br>
Step 17: Add a cached element reference in chat.js
for the <input>
and refactor the emit to send the user's name as well.
Let's also make it user-friendly by allowing the user to hit "Enter" instead of clicking the button. We'll also clean up the UI by resetting the message after it is sent.
const messageButton = document.getElementById("messageButton")
const messageInput = document.getElementById("messageInput")
const userName = document.getElementById("userName")
messageButton.addEventListener("click", sendMessage)
messageInput.addEventListener("keydown", sendMessage)
function sendMessage(e) {
if(e.key === "Enter" || e.type === "click") {
// Emit 'message' to the server (optionally passing data)
socket.emit('message', {message: messageInput.value, user: userName.value})
messageInput.value = ""
}
}
Don't forget to add the cached element reference to chat.js
!
<div id="messages"></div>
const messagesDiv = document.getElementById("messages")
Also, adjust the server-side listener for message
to push the incoming data into the messages
array.
messages = []
.
.
.
// Listening for 'message' to be emitted by a client
socket.on('message', (messageData) => {
messages.push(messageData)
io.sockets.emit('new-message', {messages})
})
The callback function should clear the contents of messagesDiv
then loop over each of the incoming messages and creating/appending a <p>
with the user's name and message.
socket.on('new-message', (data) => {
messagesDiv.innerHTML = ""
data.messages.reverse().forEach(m => {
const newMessage = document.createElement("div")
newMessage.innerHTML = `<p><strong>${m.user}</strong>: ${m.message}</p>`
messagesDiv.appendChild(newMessage)
});
})
Use these to push a message indicating the current number of clients connected to the server.
// Initialize the number of clients connected
const userCount = 0
.
.
.
// Client has connected (This must go OUTSIDE of the io.on('connection') function!)
io.sockets.on('connect', () => {
userCount ++
messages.push({user: "System", message: `${userCount} users are connected`})
io.sockets.emit('new-message', {messages})
})
.
.
.
io.on('connection', (socket) => {
// Client has disconnected (This must go INSIDE of the io.on('connection) function!)
socket.on('disconnect', () => {
userCount --
messages.push({user: "System", message: `${userCount} users are connected`})
io.sockets.emit('new-message', {messages})
})
.
.
.
}
Step 22: Display a message showing when a user is typing a message. Add a <div>
with an id of 'isTyping' below the message button in index.ejs
. Add a cached element reference for it in chat.js
.
<div id="isTyping"></div>
const isTyping = document.getElementById("isTyping")
Step 23: Expand the event listener for the messageInput
element to include an else statement to handle emitting an event while someone is typing in the <input>
.
function sendMessage(e) {
if(e.key === "Enter" || e.type === "click") {
// Emit 'message' to the server (optionally passing data)
socket.emit('message', {message: messageInput.value, user: userName.value})
messageInput.value = ""
// Add this code to handle '... is typing'
} else {
socket.emit('typing', {user: userName.value})
}
}
When detected, it should emit an event to all sockets except for the one that triggered the event. socket.broadcast.emit
, to the rescue!!!
socket.on('typing', (userName) => {
socket.broadcast.emit("user-typing", {user: userName.user})
})
Step 25: Add a listener to the client-side chat.js
to handle displaying a message whenever the 'user-typing' event is detected.
socket.on('user-typing', (userName) => {
isTyping.innerText = `${userName.user} is typing...`
})
socket.on('new-message', (data) => {
messagesDiv.innerHTML = ""
data.messages.reverse().forEach(m => {
const newMessage = document.createElement("div")
newMessage.innerHTML = `<p><strong>${m.user}</strong>: ${m.message}</p>`
messagesDiv.appendChild(newMessage)
});
// Add this line of code to reset the '... is typing ...' message
isTyping.innerText = ""
})
- oAuth instead of chat name
<input>
- list current client user names
- handle message for multiple users typing simultaneously
- drawing with colors
- sounds on entry/exit/message
- namespaces
- database connection (display message timestamps also)
- deployment
- styling (anything...)